Un peu de Cilium dans votre cluster Kubernetes ?

Un peu de Cilium dans votre cluster Kubernetes ?

Quelques mots sur Cilium...

Cilium est un projet open source de l'entreprise Isovalent qui apporte une couche réseau à votre cluster tout en permettant de garantir la sécurité et la supervision de l'ensemble des flux réseau au sein de Kubernetes.

Il se base sur une technologie présente dans les noyaux Linux (depuis la version 4.4) : eBPF (extended Berkeley Packet Filter).

Cette technologie a un avantage assez important : il n'est plus nécessaire de modifier le code source du noyau ou d'ajouter des modules complémentaires pour exécuter des programmes. Tout s'exécute dans un espace dédié, appelé sandbox, au niveau du noyau qui permet d'exécuter du bytecode eBPF.

Ce qui permet dans le cas de Cilium, d'obtenir une meilleure observabilité et sécurité lors du filtrage des paquets au niveau du réseau, car tout se joue au niveau du noyau Linux.

Enfin, Cilium est devenu depuis le 13 octobre 2021, un projet CNCF au niveau "Incubating".

L'objectif de cet article est d'installer Cilium au sein de Kubernetes et de découvrir ce qu'il apporte en matière de fonctionnalités.

Pourquoi adopter Cilium ?

Quand on utilise Kubernetes, l'ensemble des règles réseaux est géré par le composant qui s'appelle kube-proxy. C'est ce composant qui pilote iptables sur l'ensemble des noeuds du cluster.

Néanmoins, même si iptables est très répandu dans l'environnement GNU/Linux. Il dispose de quelques limitations dues à sa conception.

En effet, lorsque iptables souhaite mettre à jour une règle, il doit recréer et mettre à jour l'ensemble des règles en une seule transaction. Ce qui est un vrai problème quand un cluster dispose d'un nombre significatif de noeuds et de Pod qui s'exécutent sur celui-ci.

De plus, cet outil est capable de filtrer que ce soit sur les adresses IP ou sur les ports, mais pas sur d'éventuels chemins ou méthodes HTTP. Ce qui est un peu gênant dans un monde Kubernetes où beaucoup d'applications sont des API.

Enfin, iptables est généralement un consommateur important de CPU lorsqu'il s'exécute avec Kubernetes.

Vous l'aurez compris, la plupart des défauts d'iptables sont gommés avec Cilium. Il peut filtrer sur la couche 7 (application) du modèle OSI et permet de répondre aux problématiques de mise à l'échelle qu'iptables avait beaucoup de mal à adresser.

eBPF un peu plus en détail

Comme dit plus haut, eBPF est la technologie sur laquelle s'appuie Cilium. Il est important de comprendre comment cette technologie s'exécute au sein du noyau, c'est ce qui sera introduit dans cette partie.

En ce qui concerne le développement de programme eBPF, il existe plusieurs outils :

  • LLVM Clang qui permet de compiler le code en C en bytecode eBPF ;
  • La boite à outils BCC qui rend la création de programme eBPF plus simple avec la possibilité d'analyser les performances et contrôler le trafic réseau.

En ce qui concerne la sécurité, l'ensemble des programmes eBPF s'exécute dans une sandbox ce qui permet d'assurer l'intégrité du code du noyau Linux. De plus, avant l'exécution d'un programme eBPF, il y a une étape de vérification.

Le but du vérificateur est d'assurer que le programme eBPF n'altère pas le fonctionnement du noyau et du système ou d'empêcher l'exécution de code malveillant (boucle infinie par exemple).

Installation de Cilium dans Kubernetes

Prérequis

Avant d'installer Cilium, il est important de désactiver votre plugin réseau lors de l'installation d'un nouveau cluster ou le désinstaller pour un cluster existant. Que ce soit Calico, flannel ou un autre.

En ce qui concerne le système d'exploitation, le noyau et différentes dépendances, la liste des prérequis est disponible à cette adresse.

Installation

Dans la suite de cet article, je vais essentiellement parler de l'installation de Cilium sur un cluster déployé avec Kubeadm. Néanmoins, plusieurs guides d'installation existent en fonction du produit utilisé de GKE à k3s.

Se séparer du kube-proxy

Pour utiliser toute la puissance de Cilium, il faut notamment se passer des iptables et donc du composant kube-proxy. Il est important de désactiver leur initialisation au sein de kubeadm.

Pour cela, il suffit d'ajouter le paramètre --skip-phases=addon/kube-proxy lors de la commande kubeadm init.

Dans le cas où le composant kube-proxy est déjà initialisé, il est possible de les retirer avec cette suite d'instruction :

# Supprime le DaemonSet contenant les kube-proxy
kubectl -n kube-system delete ds kube-proxy
# Supprime la configmap de kube-proxy pour éviter que celui-ci soit réinstallé lors d'une mise à jour de kubeadm
kubectl -n kube-system delete cm kube-proxy
# A jouer sur l'ensemble de vos noeuds
# Cela réinitialise les instructions iptables définies par le kube-proxy
iptables-save | grep -v KUBE | iptables-restore

L'installation du Chart Helm

Pour installer Cilium, il est nécessaire de déployer un chart Helm comprenant l'opérateur Cilium et différents CRD (Custom Resource Definitions) dont notamment l'objet CiliumNetworkPolicy qui sera abordé plus tard dans cet article.

Si vous aimez Ansible pour déployer vos Charts Helm, voici un exemple de Playbook à adapter selon les besoins :

- hosts: master # Le nom du groupe de la machine qui dispose de Helm pour installer le chart sur votre cluster
  gather_facts: true
  become: true
  vars:
    cilium_chart_version: 1.12.1
  tasks:
  - name: Install Cilium chart
    kubernetes.core.helm:
      release_name: cilium
      chart_ref: cilium
      chart_version: "{{ cilium_chart_version }}"
      chart_repo_url: https://helm.cilium.io/
      release_namespace: kube-system
      create_namespace: false
      values:
        kubeProxyReplacement: strict
        k8sServiceHost: 10.0.0.10
        k8sServicePort: 6443
        ipam:
          mode: "cluster-pool"
          operator:
            clusterPoolIPv4PodCIDRList: 
              - "10.244.0.0/16"
    become: false

Sinon la manière classique consiste à déployer le chart Helm directement en ligne de commande :

# Ajoute le repository à Helm
helm repo add cilium https://helm.cilium.io/
# Permet de mettre à jour l'ensemble des repo
helm repo update
# Crée le fichier cilium-values.yaml
cat > cilium-values.yaml <<EOF
kubeProxyReplacement: strict
k8sServiceHost: 10.0.0.10
k8sServicePort: 6443
ipam:
  mode: "cluster-pool"
  operator:
    clusterPoolIPv4PodCIDRList: 
      - "10.244.0.0/16"
EOF
# Installe Cilium
helm install --version 1.12.1 --namespace=kube-system cilium cilium/cilium --values=./cilium-values.yaml

Un point sur les valeurs définies dans le Chart :

  • kubeProxyReplacement représente le mode choisi pour faire fonctionner Cilium avec ou sans kube-proxy. Ici, le mode strict n'utilisera pas l'iptables des kube-proxy ;
  • k8sServiceHost est l'IP de l'API du master ou controlplane ;
  • k8sServicePort est le port de l'API du master ou controlplane ;
  • ipam (IP Address Management) permet de configurer le pool d'adresses IP allouées aux points de terminaison du réseau gérés par Cilium. Différentes méthodes existent en fonction du cas d'utilisation.

En ce qui concerne le choix de la plage d'adresses IP avec le paramètre clusterPoolIPv4PodCIDRList. Il faut être vigilant de ne pas entrer en conflit avec le réseau de votre cluster.

Vérifier que tout fonctionne correctement

Cilium dispose d'un outil CLI qui permet de valider l'installation et le bon fonctionnement de tout ce qui a été installé dans votre cluster Kubernetes.

Pour Linux, quelques lignes de commande sont à exécuter :

curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-amd64.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz{,.sha256sum}

La ligne ci-dessous va vérifier l'installation et afficher différentes informations de votre cluster :

cilium status --wait

De mon côté, j'ai eu ce retour :

    /¯¯\
 /¯¯\__/¯¯\    Cilium:         OK
 \__/¯¯\__/    Operator:       OK
 /¯¯\__/¯¯\    Hubble:         disabled
 \__/¯¯\__/    ClusterMesh:    disabled
    \__/

Deployment        cilium-operator    Desired: 2, Ready: 2/2, Available: 2/2
DaemonSet         cilium             Desired: 2, Ready: 2/2, Available: 2/2
Containers:       cilium             Running: 2
                  cilium-operator    Running: 2
Cluster Pods:     2/2 managed by Cilium
Image versions    cilium             quay.io/cilium/cilium:v1.12.1@sha256:ea2db1ee21b88127b5c18a96ad155c25485d0815a667ef77c2b7c7f31cab601b: 2
                  cilium-operator    quay.io/cilium/operator-generic:v1.12.1@sha256:93d5aaeda37d59e6c4325ff05030d7b48fabde6576478e3fdbfb9bb4a68ec4a1: 2

Les composants Cilium et Operator sont au statut OK.

Pour terminer l'étape de vérification, il est possible d'analyser via le lancement d'une batterie de tests, les fonctionnalités réseaux de Cilium avec la commande :

cilium connectivity test

Cette dernière étape prend plus de temps que la première, il faut dire qu'il y a beaucoup de tests :

✅ All 23 tests (118 actions) successful, 0 tests skipped, 0 scenarios skipped.

L'installation est totalement fonctionnelle !

L'objet CiliumNetworkPolicy

Cilium permet de reprendre le concept des NetworkPolicy qui sont intégrés dans Kubernetes en ajoutant la prise en charge de la couche 7 du modèle OSI, comme expliqué dans la deuxième partie de cet article, ce qui permet de contrôler plus finement le chemin ou la méthode d'une API.

Voici un exemple simple où l'on peut souhaite limiter l'appel d'une API depuis un Pod de test à la méthode GET et au chemin /.

# Création de deux Pod de test, dont un qui va simuler une API
kubectl run test-l7 --image=nginx --labels=app=test-l7
kubectl run fake-api --image=nginx --labels=app=fake-api
kubectl expose pod fake-api --port=80
# Mise en place de la CiliumNetworkPolicy
cat <<EOF | kubectl create -f -
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "allow-get-to-fake-api"
specs:
  - endpointSelector:
      matchLabels:
        app: fake-api
    ingress:
    - fromEndpoints:
      - matchLabels:
          app: test-l7
      toPorts:
      - ports:
        - port: "80"
          protocol: "TCP"
        rules:
          http:
          - method: "GET"
            path: "/"
EOF

Dans ce cas-ci, c'est l'instruction rules associée à http qui permet de définir la méthode et le chemin que l'on souhaite autoriser.

Voici le résultat :

# Test de l'appel de fake-api
$ kubectl exec -it test-l7 -- curl fake-api
<!DOCTYPE html>
<html>
(...)
</html>

# Test de l'appel de fake-api avec un chemin en plus /test
$ kubectl exec -it test-l7 -- curl fake-api/test
Access denied

# Test de l'appel de fake-api avec la méthode POST
$ kubectl exec -it test-l7 -- curl -X POST fake-api
Access denied

De plus, il est notamment possible de filtrer par nom de domaine. Dans cet exemple, il est possible d'atteindre depuis le Pod test-dns uniquement les adresses google.fr ou les sous domaines comme www.google.fr.

# Création d'un Pod de test
kubectl run test-dns --image=nginx --labels=app=test-dns
# Mise en place de la CiliumNetworkPolicy
cat <<EOF | kubectl create -f -
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "allow-only-google-fr"
spec:
  endpointSelector:
    matchLabels:
      app: test-dns
  egress:
    - toEndpoints:
      - matchLabels:
          "k8s:io.kubernetes.pod.namespace": kube-system
          "k8s:k8s-app": kube-dns
      toPorts:
        - ports:
           - port: "53"
             protocol: ANY
          rules:
            dns:
              - matchPattern: "*"
    - toFQDNs:
        - matchPattern: "*.google.fr"
        - matchName: "google.fr"
EOF

Dans la section egress, il y a deux parties :

  • La première permet aux Pod ayant le label app: test-dns d'accéder au service kube-dns. L'instruction rules indique à Cilium d'analyser les requêtes DNS et dans ce cas-ci, de les accepter toutes ;
  • La seconde permet de définir les noms de domaine qui seront accessibles pour les Pod possédant le label app: test-dns.

Voici le résultat :

# Test avec google.fr
$ kubectl exec -it test-dns -- curl google.fr
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.fr/">here</A>.
</BODY></HTML>

# Test avec cilium.io
$ kubectl exec -it test-dns -- curl cilium.io
(Pas de réponse)

Pour les personnes qui utilisent Kafka, il faut savoir qu'il est possible de filtrer par rôle et par topic. Néanmoins, cette fonctionnalité est toujours en beta lors de l'écriture de l'article.

L'objet CiliumNetworkPolicy permet d'aller beaucoup plus loin et se révèle incontournable dans le monde Kubernetes pour venir renforcer la sécurité au niveau de la couche 7.

Quelques mots pour conclure

Comme vous l'avez au cours de l'article, Cilium permet de répondre à beaucoup de limitations d'iptables que ce soit au niveau de la mise à l'échelle ou pour filtrer la couche 7 du modèle OSI.

Depuis mon retour de la KubeCon et après m'être renseigné sur le produit, je dois avouer que je déploie Cilium à chaque fois que j'ai besoin d'un cluster Kubernetes.

Pour finir, Cilium permet d'aller beaucoup plus loin en ajoutant une vraie couche d'observabilité des flux réseaux avec Hubble sans parler de Tetragon qui permet de détecter les menaces en terme de sécurité ou même de Cilium Service Mesh, un service mesh sans sidecar. Cela fera sûrement l'objet de futurs articles !