Skip to content

Comprendre Cilium (migration depuis le VPC CNI)

Ce guide explique comment on remplace le CNI d'AWS (VPC CNI) par Cilium sur le cluster EKS, et pourquoi on a fait chaque choix. Le pourquoi condensé est dans l'ADR 019 ; ici on prend le temps de comprendre.

Issue : #80. Cette migration rend la Prefix Delegation (#79, ADR 012) obsolète.


1. C'est quoi un CNI, et pourquoi en changer ?

Le CNI (Container Network Interface) est le composant qui donne une adresse IP à chaque pod et qui fait passer le trafic d'un pod à l'autre. Sans CNI, un node reste NotReady : il ne peut pas faire tourner de pods en réseau.

Sur EKS, le CNI par défaut est le VPC CNI (DaemonSet aws-node) :

  • chaque pod reçoit une vraie IP du VPC, prise sur une ENI (carte réseau) du node ;
  • conséquence : le nombre de pods par node est plafonné par le nombre d'IP que l'ENI peut porter. Sur un t3.medium, c'est ~17 pods par défaut. On a dû activer la Prefix Delegation (#79) pour monter à 110, sinon ArgoCD et l'observabilité ne tenaient pas.

Le VPC CNI marche bien, mais il a des limites qu'on veut dépasser :

Limite VPC CNI Ce que Cilium apporte
Densité de pods liée aux IP d'ENI (bricolage Prefix Delegation) IP des pods hors VPC (overlay) : plus de plafond ENI
NetworkPolicy L3/L4 seulement NetworkPolicy L3/L4 + L7 (HTTP, DNS...) via CiliumNetworkPolicy
Routage Services par kube-proxy (iptables, qui grossit avec le nombre de Services) Routage Services en eBPF (pas d'iptables, ça scale mieux)
Pas d'observabilité réseau native Hubble : qui parle à qui, en direct
Spécifique AWS Portable : même CNI sur EKS, le homelab kubeadm, la platform

eBPF, c'est la techno clé de Cilium : du code exécuté directement dans le noyau Linux pour traiter les paquets, sans repasser par les longues chaînes iptables.


2. Les choix de cette migration

Trois décisions structurantes, prises pour cette mise en place.

a. Datapath : overlay VXLAN

On fait tourner Cilium en mode overlay (routingMode: tunnel, encapsulation VXLAN). Les IP des pods viennent d'un pool privé géré par Cilium (10.42.0.0/16), distinct du CIDR du VPC (10.0.0.0/16), et le trafic inter-node est encapsulé dans des paquets VXLAN.

  • Pourquoi : on se détache complètement des IP du VPC (fini le plafond ENI et la Prefix Delegation), et l'overlay est portable sur n'importe quel cluster.
  • Le pod CIDR 10.42.0.0/16 est choisi différent du VPC 10.0.0.0/16 pour éviter toute collision d'adresses.

b. kube-proxy remplacé par eBPF (kubeProxyReplacement)

Cilium prend en charge le routage des Services lui-même, en eBPF, et on supprime kube-proxy.

  • Pourquoi : c'est le « plein potentiel » de Cilium. Plus d'iptables géantes pour router les ClusterIP, meilleure perf, et c'est un argument technique solide en entretien (eBPF concret).
  • Détail important : une fois kube-proxy parti, l'agent Cilium ne peut plus joindre l'API server via le ClusterIP kubernetes (c'était kube-proxy qui le routait). On lui donne donc l'adresse directe de l'API (k8sServiceHost = endpoint du cluster EKS, k8sServicePort: 443).

c. Observabilité réseau : Hubble + Hubble UI

On active Hubble (le plan d'observabilité de Cilium) et son interface web.

  • Pourquoi : Hubble montre les flux réseau en direct (L3/L4/L7), une service map, les paquets autorisés/refusés par les NetworkPolicy. Ça complète la stack d'observabilité du Sprint 4 (Prometheus/Grafana/Loki).
  • Comme Prometheus, l'UI Hubble reste interne (accès par port-forward), pas exposée publiquement.
  • À surveiller : Hubble consomme de la RAM, et le cluster est un mono-node t3.medium déjà chargé. Requests basses, et on coupe l'UI si le node est sous pression.

3. Le piège du démarrage : l'ordre poule / œuf

C'est le point délicat de la migration, à bien comprendre.

  • Un managed node group EKS exige que ses nodes deviennent Ready dans un certain délai, sinon la création échoue côté Terraform.
  • Or un node n'est Ready que s'il a un CNI. Si on enlevait tout CNI en attendant Cilium, les nodes resteraient NotReady et le node group échouerait.
  • Et ArgoCD ne peut pas installer Cilium : ArgoCD est lui-même un pod, il a besoin d'un CNI pour démarrer. C'est l'œuf qui a besoin de la poule.

La distinction qui résout tout : le composant ≠ l'addon managé

Il faut séparer deux choses qu'on appelle vite « le vpc-cni » :

Ce que c'est Ce qu'on en fait
Le composant vpc-cni Le DaemonSet aws-node qui distribue les IP. EKS l'installe par défaut à la création du cluster, qu'on le déclare ou non. On le garde au boot (il amène les nodes Ready), puis on le supprime après Cilium.
L'addon managé aws_eks_addon.vpc_cni La ressource Terraform qui adopte et configure ce composant (NetworkPolicy, Prefix Delegation). On retire cette gestion managée : vpc-cni revient en mode self-managed par défaut.

Pourquoi retirer la gestion managée plutôt que de garder l'addon ? Parce qu'un addon managé est réconcilié par un contrôleur EKS. Si on supprimait le DaemonSet aws-node alors qu'il est géré en addon, le contrôleur le recréerait aussitôt, et il se battrait avec Cilium. En self-managed, personne ne le recrée : le cutover est propre.

La séquence retenue

1. Le cluster démarre. vpc-cni self-managed (config par défaut, ~17 IP) amène les
   nodes Ready. Seul coredns a besoin d'IP à ce stade : 17 suffisent largement.
2. L'amorce Ansible installe Cilium EN PREMIER (avant Envoy Gateway, ESO, ArgoCD...).
3. Cutover :
   - on supprime le DaemonSet aws-node (vpc-cni)      -> Cilium devient le seul CNI
   - on supprime le DaemonSet kube-proxy              -> eBPF prend le relais
   - on redémarre coredns                             -> il reprend une IP overlay Cilium
4. Le reste de l'amorce (Envoy GW, cert-manager, ESO, ExternalDNS, ArgoCD) s'installe,
   cette fois sur Cilium.

C'est exactement le motif « Ansible amorce → ArgoCD prend le relais » déjà utilisé pour le reste de la plateforme (voir Observabilité) : Cilium est une brique de socle, posée par Ansible, pas par ArgoCD (sinon, retour au problème de l'œuf et de la poule).

Piège à câbler : kube-proxy laisse des règles iptables derrière lui. Après l'avoir supprimé, il faut purger ces règles résiduelles (sinon comportement de routage incohérent jusqu'au reboot du node).


4. Ce qui change concrètement dans le repo

Fichier Avant Après
terraform/modules/eks/main.tf addon managé vpc-cni (NetworkPolicy + Prefix Delegation) ; node group depends_on cet addon plus d'addon vpc-cni (gestion managée retirée) ; depends_on allégé
launch template maxPods: 110 justifié par la Prefix Delegation conservé, désormais servi par l'overlay Cilium
AmazonEKS_CNI_Policy (rôle node) requis par le VPC CNI conservé : le vpc-cni self-managed s'en sert encore pendant l'amorce
ansible/bootstrap.yml amorce sans CNI (VPC CNI déjà là) nouveau bloc Cilium en tête + cutover

5. NetworkPolicy sous Cilium

Le projet a déjà une NetworkPolicy standard allow-fastapi (#55). Bonne nouvelle : Cilium applique les NetworkPolicy Kubernetes standard, donc elle continue de fonctionner sans modification. On la re-teste quand même en live (un test positif : trafic autorisé qui passe ; un test négatif : trafic interdit qui est bloqué), parce qu'on ne suppose jamais qu'une règle de sécurité marche sans l'avoir vue marcher.

En bonus, Cilium ajoute les CiliumNetworkPolicy, qui savent filtrer au niveau L7 (par exemple : autoriser GET /healthz mais pas POST /admin). On ne s'en sert pas encore, mais c'est une porte ouverte.


5bis. Le piège des webhooks d'admission (vécu en live, INC-058)

C'est le piège le plus subtil de la migration, et il ne se voit qu'en live.

Sur EKS, le control plane (l'API server) est managé par AWS : il tourne dans une infra qu'on ne contrôle pas, et ses cartes réseau (ENI) vivent dans le VPC (10.0.x). Surtout, il n'est pas un node Cilium : il ne participe pas à l'overlay VXLAN. Conséquence directe : il ne sait pas router vers une IP de pod overlay (10.42.x).

Or certains composants installent un webhook d'admission : un petit serveur HTTPS, hébergé dans un pod, que l'API server appelle à chaque création/modification de ressource pour valider ou muter (cert-manager valide ses CRDs, ESO valide ses ExternalSecret/SecretStore). C'est un appel dans le sens inverse de d'habitude : ce n'est pas le pod qui appelle l'API, c'est l'API qui appelle le pod.

Sous overlay, ce pod a une IP 10.42.x → l'API server managé ne sait pas l'atteindre → l'appel échoue avec Address is not allowed. Et comme ces webhooks sont en failurePolicy: Fail (« en cas d'échec, je refuse l'opération »), ça bloque tout : l'amorce plante sur l'installation de cert-manager (son startupapicheck n'atteint pas le webhook) puis sur la création du ClusterIssuer.

Le fix : hostNetwork: true sur les webhooks concernés. En hostNetwork, le pod ne prend plus une IP overlay mais l'IP du node (10.0.x, dans le VPC) — que le control plane sait router. Le webhook redevient joignable.

Deux subtilités câblées dans bootstrap.yml :

  • Le port. En hostNetwork, le webhook écoute directement sur le réseau du node. Son port par défaut (10250) est déjà pris par le kubelet → on le décale. cert-manager passe sur 10260, ESO sur 10261.
  • Mono-node = deux ports distincts. Notre cluster a un seul node, donc les deux webhooks (cert-manager et ESO) bindent le même réseau. S'ils partageaient un port, l'un des deux ne démarrerait pas. D'où 10260 et 10261.

Tous les webhooks ne sont pas touchés : celui d'Envoy Gateway est en failurePolicy: Ignore (« en cas d'échec, je laisse passer »), donc s'il est injoignable c'est un no-op, rien à corriger. Et les webhooks managés par EKS (vpc-resource-*, pod-identity-webhook) ne sont pas concernés (hors overlay).

À retenir : dès qu'un composant doit être joint par l'API server (webhook d'admission, de conversion, agrégation d'API), il faut qu'il soit joignable depuis le VPC sous overlay → hostNetwork. Ce n'est pas lié à la version de Kubernetes, c'est du routage réseau pur.

Deuxième cas concret : metrics-server (INC-061). Ce n'est pas un webhook mais un APIService agrégé (v1beta1.metrics.k8s.io) : l'API server délègue les requêtes /apis/metrics.k8s.io au pod metrics-server, donc là encore c'est l'API server qui appelle le pod. Même racine, même symptôme : sous overlay le pod a une IP 10.42.x, l'API server managé ne sait pas la router → Address is not allowed, l'APIService passe Unavailable. Conséquence vicieuse : kubectl top ET le HPA fastapi (#82) tombent en silence (le HPA n'a plus de métriques), et ça a duré depuis la migration #80 sans erreur visible (le HPA n'alerte pas, il arrête juste de scaler). Même fix : hostNetwork: true + port décalé sur 10262 (10250 kubelet, 10260 cert-manager, 10261 ESO, donc 10262 pour metrics-server). La règle « joint par l'API server → hostNetwork » couvre donc webhooks et APIService agrégés.


6. Comment vérifier que tout marche (en live)

  • cilium status --wait : l'agent et l'opérateur sont sains.
  • kubectl get pods -A : tout est Running, plus aucun aws-node ni kube-proxy.
  • coredns tourne avec une IP 10.42.x (donc bien sur l'overlay Cilium).
  • La résolution DNS interne marche (preuve que coredns est joignable sans kube-proxy).
  • Un curl vers un Service ClusterIP répond (preuve que kubeProxyReplacement route bien).
  • NetworkPolicy allow-fastapi : test positif + négatif.
  • Webhooks joignables (INC-058) : l'amorce passe Install cert-manager et la création du ClusterIssuer sans Address is not allowed, l'ExternalSecret se synchronise (SecretSynced). Preuve que les webhooks hostNetwork sont bien atteints par l'API server.
  • metrics-server joignable (INC-061) : kubectl top nodes/kubectl top pods répondent (APIService metrics.k8s.io Available) et le HPA fastapi montre des métriques (kubectl get hpa -n fastapi → colonnes TARGETS chiffrées, plus <unknown>). Preuve que l'APIService agrégé hostNetwork est atteint par l'API server.
  • cilium hubble port-forward puis l'UI : on voit les flux.
  • Bout en bout : https://api.devopsyouss.com répond 200, ArgoCD Synced/Healthy, la stack d'observabilité est debout.

En résumé

On garde le composant vpc-cni le temps d'amorcer le cluster, on installe Cilium en overlay dès le début de l'amorce Ansible, on bascule (suppression d'aws-node et de kube-proxy), puis tout le reste tourne sur Cilium. On gagne en densité, en perf (eBPF), en observabilité (Hubble) et en portabilité, et on se débarrasse du bricolage Prefix Delegation.

Voir l'ADR 019 pour la décision et les alternatives écartées.