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/16est choisi différent du VPC10.0.0.0/16pour é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.mediumdé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
Readydans un certain délai, sinon la création échoue côté Terraform. - Or un node n'est
Readyque s'il a un CNI. Si on enlevait tout CNI en attendant Cilium, les nodes resteraientNotReadyet 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 sur10260, ESO sur10261. - 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ù
10260et10261.
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 estRunning, plus aucunaws-nodenikube-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
curlvers un Service ClusterIP répond (preuve quekubeProxyReplacementroute bien). - NetworkPolicy
allow-fastapi: test positif + négatif. - Webhooks joignables (INC-058) : l'amorce passe
Install cert-manageret la création duClusterIssuersansAddress is not allowed, l'ExternalSecretse synchronise (SecretSynced). Preuve que les webhookshostNetworksont bien atteints par l'API server. - metrics-server joignable (INC-061) :
kubectl top nodes/kubectl top podsrépondent (APIServicemetrics.k8s.ioAvailable) et le HPA fastapi montre des métriques (kubectl get hpa -n fastapi→ colonnesTARGETSchiffrées, plus<unknown>). Preuve que l'APIService agrégéhostNetworkest atteint par l'API server. cilium hubble port-forwardpuis l'UI : on voit les flux.- Bout en bout :
https://api.devopsyouss.comrépond200, ArgoCDSynced/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.