Sommaire
À propos de Cilium
Cet article est le premier d’un triptyque dont l’objectif est de progressivement vous faire découvrir l’outil Cilium. Aujourd’hui, nous allons nous contenter d’introduire le sujet :
- Ce qu’est Cilium et ce que sont ses cas d’utilisation
- Comment installer et configurer l’outil au travers d’un exemple local, ce qui nous permettra d’introduire un certain nombre de concepts liés à eBPF
- Visualiser les flux à l’aide d'Hubble, l’outil d’observabilité lié à Cilium
Les deux prochains articles auront pour objectifs de suivre différents scénarios avancés de deep dive, c’est-à-dire que nous allons aborder concrètement :
- Les aspects fondamentaux de la configuration de Cilium : Les politiques réseau
- L’utilisation de ces politiques réseaux dans des usages plus avancés comme le maillage de services, ou encore la sécurisation du trafic
- Les sujets d’observabilité avancée offerte par des briques comme Prometheus et Grafana
Qu’est-ce que Cilium ?
Cilium est un outil adhérant à la spécification CNI qui repose sur une technologie novatrice : eBPF.
Définissons ces deux termes :
- CNI (Container Network Interface) : Une spécification décrivant les caractéristiques que doit avoir un plugin Kubernetes afin de garantir un ensemble de fonctionnalités réseau d’un cluster.
- eBPF (extended Berkeley Packet Filter) : Une méthode permettant à du code applicatif d’être intégré de manière isolée et protégée dans le noyau Linux. C’est donc un moyen efficace pour visualiser, modifier, et agir sur la couche réseau d’un système Linux.
Grâce à l’eBPF Cilium est capable de gérer les flux réseau directement dans le noyau, et permet ainsi d’adresser un certain nombre de problématiques telles :
- L’équilibrage de charge (Load Balancing)
- Le pilotage des flux réseau (Network Policies) dans les différentes couches OSI (L3, L4, et L7)
- Le maillage de services, voire de clusters
Installation de Cilium
Nous allons nous intéresser à Cilium dans sa version 1.13. Naturellement, les choses seront amenées à changer par la suite, donc n’hésitez pas à vous référer à l’excellente et très complète documentation de Cilium.
Prérequis
Afin de pouvoir facilement créer et configurer un cluster k8s avec Cilium nous vous recommandons d’utiliser une distribution Linux qui prend en charge par défaut les cgroups v2. C’est couramment déjà le cas sur les distributions LTS récentes (Ubuntu 22, par exemple).
Création d’un cluster Kubernetes
Commençons par le commencement avec l’installation de Cilium car rien que cette étape va nous révéler des caractéristiques déterminantes de l’outil, notamment sa relation avec les cgroups v2 et la manière de gérer la mise en place de l’eBPF.
Pour ce cas exemple, nous allons partir d’une installation Kubernetes locale avec k3d, qui a l’avantage de simuler simplement un cluster ayant plusieurs nœuds et de permettre une configuration assez bas-niveau de k3s (la couche sous-jacente qui gère concrètement les nœuds). D’autres options sont possibles, comme avec kind par exemple, ou bien, vous pouvez utiliser des VM et en faire un cluster à l’aide de k3s ou Rancher Kubernetes Engine.
Comme nous voulons changer le CNI de k3d (par défaut flannel) pour Cilium, il faut le préciser à la création :
$ k3d cluster create \
local-cilium \
--agents 3 \
# Ici on indique qu'on va désactiver le load-balancer par défaut de k3s (kube-proxy)
--k3s-arg "--disable=servicelb@server:*" \
--k3s-arg "--disable=traefik@server:*" --no-lb \
# Là on indique qu'on va désactiver le CNI par défaut de k3s
--k3s-arg "--flannel-backend=none@server:*" \
--k3s-arg "--disable-network-policy@server:*" \
--wait
Normalement, à cette étape-là, le cluster devrait se monter, mais dans un état instable : en effet, notre cluster n’a pas de CNI ! Difficile pour les pods systèmes (coredns, metrics, etc.) de s’y retrouver… Tant que nous n’aurons pas installé Cilium, ils ne pourront pas progresser dans leur initialisation.
Installation
Afin de pouvoir installer Cilium sous k3d, nous allons avoir besoin d’une étape intermédiaire, la configuration des points de montage eBPF et cgroups v2. Cette étape n’est apparemment pas nécessaire pour kind donc n’hésitez pas à vous référer à la documentation de votre orchestrateur ou de la documentation de la distribution sous-jacente de vos conteneurs/VM pour déterminer ce dont vous avez besoin.
Dans le cas de notre cluster k3d, il va donc falloir, pour chaque nœud, du cluster, effectuer les opérations suivantes :
# CLUSTER_NAME contient le nom du cluster
$ for node in $(k3d node list --no-headers | grep ${CLUSTER_NAME} | cut -d " " -f1); do
docker exec -it $node mount bpffs /sys/fs/bpf -t bpf
docker exec -it $node mount --make-shared /sys/fs/bpf
docker exec -it $node mkdir -p /run/cilium/cgroupv2
docker exec -it $node mount -t cgroup2 none /run/cilium/cgroupv2
docker exec -it $node mount --make-shared /run/cilium/cgroupv2/
done
Décryptons un peu tout ça :
- Les deux premiers mount font en sorte de monter BPF de manière partagée dans chaque nœud
- Les deux suivants font en sorte de créer un autre point de montage partagé, de la même manière, pour les cgroups v2.
Étant donné les montages manuels, il faut garder en tête que le redémarrage des conteneurs ne les prendra pas en compte. Si vous comptez utiliser un cluster qui survivra à un redémarrage de système, il vaudra mieux plutôt s’orienter vers un cluster avec des VM, tel k3s + multipass par exemple.
Reste donc maintenant à installer Cilium lui-même, et plutôt que d’utiliser une ligne de commande fournie (ce qui nous imposerait de l’installer en local), nous allons le faire à l’aide du Chart Helm consacré :
$ helm repo add cilium https://helm.cilium.io/
$ helm upgrade cilium cilium/cilium --version 1.13.0 \
--install \
--namespace kube-system \
--set ipam.mode=kubernetes
Rien de bien particulier là-dedans : on se base sur le Chart Helm fourni par Cilium, auquel on précise qu’on est sur une gestion IPAM (IP Address Management : tout simplement la façon dont une application va attribuer des adresses IP à des entités (pods, services, etc.) en mode « Kubernetes ».
Maintenant que la configuration est effectuée, le cluster paraît déjà plus sain : les nœuds sont passés en Ready car leur configuration réseau est terminée. Les autres pods système (coredns, metrics, etc.) devraient se planifier correctement cette fois.
La CLI Cilium
Cilium est pourvu d’une commande (adéquatement nommée cilium), exécutable depuis les pods des agents Cilium, et qui permet d’interagir avec eux, ce qui est bien pratique pour le débogage :
$ kubectl -n kube-system exec ds/cilium -- cilium status
Defaulted container "cilium-agent" out of: cilium-agent, config (init), mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init)
KVStore: Ok Disabled
Kubernetes: Ok 1.25 (v1.25.6+k3s1) [linux/amd64]
Kubernetes APIs: ["cilium/v2::CiliumClusterwideNetworkPolicy", "cilium/v2::CiliumEndpoint", "cilium/v2::CiliumNetworkPolicy", "cilium/v2::CiliumNode", "core/v1::Namespace", "core/v1::Node", "core/v1::Pods", "core/v1::Service", "discovery/v1::EndpointSlice", "networking.k8s.io/v1::NetworkPolicy"]
KubeProxyReplacement: Strict [ens3 198.20.0.4]
Host firewall: Disabled
CNI Chaining: none
CNI Config file: CNI configuration file management disabled
Cilium: Ok 1.13.0 (v1.13.0-c9723a8d)
NodeMonitor: Listening for events on 1 CPUs with 64x4096 of shared memory
Cilium health daemon: Ok
IPAM: IPv4: 8/254 allocated from 10.42.0.0/24,
IPv6 BIG TCP: Disabled
BandwidthManager: Disabled
Host Routing: Legacy
Masquerading: IPTables [IPv4: Enabled, IPv6: Disabled]
Controller Status: 49/49 healthy
Proxy Status: OK, ip 10.42.0.88, 1 redirects active on ports 10000-20000
Global Identity Range: min 256, max 65535
Hubble: Ok Current/Max Flows: 4095/4095 (100.00%), Flows/s: 13.61 Metrics: Disabled
Encryption: Disabled
Cluster health: 3/3 reachable (2023-03-01T15:57:05Z)
Le fait d’exécuter la commande kubectl en passant par le DaemonSet (ds/cilium) est très pratique quand on veut des informations générales, et évite d’avoir à se souvenir du nom précis d’un pod.
La CLI de Cilium permet également de voir les logs de fonctionnement de Cilium, notamment les communications entre «endpoints».
Cette notion d'endpoint est suffisamment au cœur de Cilium pour qu’on la détaille un peu :
$ kubectl -n kube-system exec ds/cilium -- cilium endpoint list
ENDPOINT POLICY (ingress) POLICY (egress) IDENTITY LABELS (source:key[=value]) IPv6 IPv4 STATUS
ENFORCEMENT ENFORCEMENT
191 Disabled Disabled 57155 k8s:app=yelb-appserver 10.42.0.36 ready
1155 Disabled Disabled 2358 k8s:app=redis-server 10.42.0.29 ready
2950 Enabled Disabled 37504 k8s:app.kubernetes.io/name=deathstar 10.42.0.158 ready
Les endpoints sont une des entités fondamentales de Cilium: ce sont eux qui s’envoient (ou pas) du trafic selon les règles établies dans Cilium, et c’est à travers ceux-ci que se fait la surveillance du trafic.
Par exemple, si j’ai un service nommé deathstar, la commande cilium endpoint list va me montrer qu’elle a un ID. Cet ID peut ensuite servir à d’autres commandes comme cilium monitor qui permet d’afficher les logs relatives à cet endpoint (ici l’ID d’endpoint 2950 correspond au service deathstar) :
$ kubectl -n kube-system exec ds/cilium -- cilium monitor --related-to 2950
Listening for events on 1 CPUs with 64x4096 of shared memory
Press Ctrl-C to quit
level=info msg="Initializing dissection cache..." subsys=monitor
Policy verdict log: flow 0x0 local EP ID 2950, remote ID 600, proto 6, ingress, action redirect, match L3-L4, 10.42.2.199:55910 -> 10.42.0.158:80 tcp SYN
Policy verdict log: flow 0x0 local EP ID 2950, remote ID 59358, proto 6, ingress, action deny, match none, 10.42.1.32:54814 -> 10.42.0.158:80 tcp SYN
xx drop (Policy denied) flow 0x0 to endpoint 2950, ifindex 23, file 2:1903, , identity 59358->37504: 10.42.1.32:54814 -> 10.42.0.158:80 tcp SYN
Policy verdict log: flow 0x0 local EP ID 2950, remote ID 59358, proto 6, ingress, action deny, match none, 10.42.1.32:54814 -> 10.42.0.158:80 tcp SYN
xx drop (Policy denied) flow 0x0 to endpoint 2950, ifindex 23, file 2:1903, , identity 59358->37504: 10.42.1.32:54814 -> 10.42.0.158:80 tcp SYN
Vous pouvez voir dans le résultat ci-dessus d’abord une requête qui a été acceptée (action redirect) ainsi qu’une requête bloquée (action deny).
Cilium offre une grande variété d’options et de niveaux de trace pour ses différentes commandes afin de permettre une analyse très fine des configurations et de leurs conséquences.
Pour une vision plus « haut niveau », un outil faisant partie de la suite Cilium apporte une synthèse de ces informations en temps quasi réel : Hubble.
Hubble - l’observabilité selon Cilium
Hubble est une application associée à Cilium et qui a la caractéristique de pouvoir utiliser, elle aussi, l’eBPF pour ses objectifs d’observabilité. Elle se présente comme une application web qui matérialise ce que Cilium voit des échanges entre les différents endpoints. C’est ce qu’on appelle le Service Map. Mais l’une des grandes forces de Cilium étant la gestion et le contrôle du trafic, Hubble permet également de visualiser le trafic qui n’a pas été routé, et permet ainsi de diagnostiquer les éventuels problèmes de configuration ou de sécurité.
Hubble s’active très simplement si on a déjà déployé le Chart Helm de Cilium:
$ helm upgrade cilium cilium/cilium --version 1.13.0 \
--namespace kube-system \
--reuse-values \
--set hubble.listenAddress=":4244" \
--set hubble.relay.enabled=true \
--set hubble.ui.enabled=true
Il suffit ensuite d’atteindre le service (soit en port forwarding, soit en exposant le service à l’extérieur du cluster, mais nous reviendrons là-dessus plus tard) avec un navigateur Web pour accéder à l’IHM d'Hubble :
Dans la configuration actuellement déployée (qu’on retrouve dans la documentation de Cilium), un service deathstar peut être interrogé par deux pods: xwing et tiefighter.
Vous pouvez constater sur l’image précédente que le trafic venant de tiefighter est accepté par deathstar là où celui de xwing est «dropped» (non acheminé). C’est dû à une règle de routage que nous verrons plus tard.
Ceci étant dit, Hubble est également accessible en CLI avec des options qui permettent de filtrer les logs, de la même manière que sur l’IHM :
$ kubectl -n kube-system exec ds/cilium -- hubble observe -t trace --since 10s
Defaulted container "cilium-agent" out of: cilium-agent, config (init), mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init)
Mar 1 15:47:22.933: 198.20.0.4:58110 (host) -> kube-system/metrics-server-5f9f776df5-886rz:10250 (ID:30296) to-endpoint FORWARDED (TCP Flags: SYN)
Mar 1 15:47:22.933: 198.20.0.4:58110 (host) <- kube-system/metrics-server-5f9f776df5-886rz:10250 (ID:30296) to-stack FORWARDED (TCP Flags: SYN, ACK)
Mar 1 15:47:22.933: 198.20.0.4:58110 (host) -> kube-system/metrics-server-5f9f776df5-886rz:10250 (ID:30296) to-endpoint FORWARDED (TCP Flags: ACK)
Mar 1 15:47:22.934: 198.20.0.4:58110 (host) -> kube-system/metrics-server-5f9f776df5-886rz:10250 (ID:30296) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Conclusion
Maintenant que nous disposons d’un Cilium et d’un Hubble fonctionnels, nous pouvons rentrer dans le vif du sujet et aborder quelques cas d’utilisation d’un CNI Kubernetes.
C’est ce que nous aborderons dans les deux prochains articles, alors restez à l’écoute en vous abonnant à notre newsletter ou en nous suivant sur les divers réseaux sociaux 😉