Version du cluster : pourquoi ce n'est pas une montée de version¶
Issue #186. Le cluster est passé de 1.32 à 1.35 en changeant une valeur par défaut. Cette page explique pourquoi c'est suffisant ici, ce que ça ne serait pas ailleurs, et ce qui reste à faire pour aller jusqu'à 1.36.
Le constat¶
Mail AWS du 2026-08-27 : le cluster est en 1.32 et doit monter de version.
En interrogeant l'API plutôt que le mail, le tableau est plus précis :
aws eks describe-cluster-versions \
--query 'clusterVersions[].[clusterVersion,clusterVersionStatus,endOfStandardSupportDate]' \
--output table
| Version | Fin du support standard | Situation au 2026-08-30 |
|---|---|---|
| 1.32 | 2026-03-23 | support étendu depuis 5 mois |
| 1.33 | 2026-07-29 | support étendu depuis 1 mois |
| 1.34 | 2026-12-02 | standard, 3 mois restants |
| 1.35 | 2027-03-27 | standard, 7 mois restants |
| 1.36 | 2027-08-02 | standard, 11 mois restants |
La nuance compte : le surcoût du support étendu ne va pas arriver, il court déjà dès qu'un cluster 1.32 est monté. Le seul frein aujourd'hui est que l'infrastructure est détruite, donc rien n'est facturé.
1.33 et 1.34 sont éliminées d'office : la première est déjà en support étendu, la seconde y bascule dans trois mois.
Ce qui rend ce cluster différent : il est recréé, pas mis à jour¶
L'issue #186 était rédigée pour une montée de version classique : « une version mineure à la fois », « quatre montées successives », « le plan de contrôle puis les nœuds ». Tout cela est vrai pour un cluster qu'on fait vivre.
Ici, le cluster est éphémère. Il est détruit en fin de session et recréé au montage suivant. Or à la création, on choisit directement la version.
graph TD
subgraph "Cluster que l'on fait vivre"
A["1.32"] --> B["1.33"] --> C["1.34"] --> D["1.35"]
A -.->|"interdit"| D
end
subgraph "Cluster ephemere (ce projet)"
E["terraform destroy"] --> F["eks_cluster_version = 1.35"]
F --> G["terraform apply<br/>cluster neuf en 1.35"]
end
style D fill:#27ae60,color:#fff
style G fill:#27ae60,color:#fff
Il n'y a donc ni palier, ni séquence, ni drain, ni nœuds à faire suivre. La
contrainte « une version mineure à la fois » s'applique à un UpdateClusterVersion
sur un cluster existant, pas à un CreateCluster.
C'est un effet de bord bénéfique d'une contrainte de coût : le cluster est détruit chaque soir pour ne pas payer, et cette destruction transforme une migration en choix de paramètre.
Ce qui reste vrai, et qu'il faut quand même faire¶
| Tâche | Pourquoi |
|---|---|
| Vérifier les API dépréciées | les manifests du dépôt doivent être valides dans la version cible |
| Vérifier la matrice de compatibilité des composants | Cilium, cert-manager, ArgoCD, ESO, ExternalDNS ont chacun la leur |
Aligner le client kubectl |
la politique d'écart de version ne tolère qu'une version mineure |
| Prouver le montage | tant qu'un cluster n'est pas monté dans la nouvelle version, rien n'est prouvé |
Les API dépréciées : l'absence se prouve¶
Entre deux versions mineures, Kubernetes retire des API. Un manifeste qui utilise une API retirée est rejeté par l'API server, et le déploiement échoue.
Inventaire des apiVersion du dépôt :
grep -rh '^apiVersion:' k8s/ | sort | uniq -c | sort -rn
| Famille | Verdict |
|---|---|
v1, apps/v1, batch/v1, policy/v1, autoscaling/v2, networking.k8s.io/v1 |
API natives stables, aucune retirée |
gateway.networking.k8s.io/v1 |
Gateway API, version stable |
argoproj.io/v1alpha1, external-secrets.io/v1, cert-manager.io/v1, monitoring.coreos.com/v1, gateway.envoyproxy.io/v1alpha1 |
CRD tierces |
Le point à comprendre : une CRD (Custom Resource Definition, une ressource
ajoutée au cluster par un opérateur) ne suit pas le cycle de dépréciation de
Kubernetes. Son apiVersion dépend de la version de l'opérateur qui l'installe,
pas de celle du cluster. Un v1alpha1 d'Envoy Gateway ne devient pas invalide
parce que Kubernetes passe de 1.32 à 1.35.
Aucune migration de manifeste n'était donc nécessaire.
Pourquoi 1.35 et pas 1.36 : le CNI est le facteur limitant¶
Le CNI (Container Network Interface, le composant qui donne une adresse IP
aux pods et fait circuler leurs paquets) est ici Cilium, en version 1.19.5
(ansible/bootstrap.yml).
| Cilium | Kubernetes testés de bout en bout |
|---|---|
| 1.19 | 1.32, 1.33, 1.34, 1.35 |
| 1.20 | 1.33, 1.34, 1.35, 1.36 |
Cilium 1.19 ne couvre pas 1.36. Viser 1.36 imposerait donc deux changements simultanés : la version du cluster et celle du CNI.
Et ce n'est pas un changement anodin sur ce cluster. Cilium y tourne en
kubeProxyReplacement : le bootstrap Ansible supprime aws-node et
kube-proxy au cutover (ADR 019). Si Cilium ne démarre pas, le résultat n'est
pas un cluster dégradé, c'est un cluster sans réseau du tout.
Un seul changement à la fois. Passer de 1.32 à 1.35, c'est déjà passer de « quatre versions de retard, en support étendu depuis cinq mois » à « une version de retard, en support standard ». Le bump vers Cilium 1.20 et Kubernetes 1.36 aura sa propre issue et sa propre validation en direct.
Et si on mettait vraiment le CNI à jour : qu'est-ce qui coupe ?¶
La réponse est contre-intuitive, et elle vaut d'être connue.
Cilium programme le réseau avec des programmes eBPF : du code chargé dans le
noyau Linux, qui traite les paquets sans repasser par un processus en espace
utilisateur. Ces programmes restent chargés dans le noyau même quand le pod
cilium-agent disparaît.
graph TD
A["cilium-agent redemarre"] --> B["programmes eBPF<br/>toujours chargees dans le noyau"]
B --> C["le trafic existant<br/>continue de passer"]
A --> D["plus personne ne programme<br/>de NOUVELLES regles"]
D --> E["un pod qui demarre<br/>attend son endpoint reseau"]
D --> F["une NetworkPolicy modifiee<br/>n'est pas appliquee"]
style C fill:#27ae60,color:#fff
style E fill:#e67e22,color:#fff
style F fill:#e67e22,color:#fff
Ce qui gèle pendant la fenêtre n'est donc pas le trafic, c'est le changement.
Cela vaut pour un redémarrage. Une montée de version mineure engage davantage :
Cilium fournit un cilium-pre-flight-check qui pré-télécharge l'image et valide la
configuration sur chaque nœud avant la bascule, précisément parce que le risque
n'est pas nul.
Trois stratégies de montée de version¶
| Stratégie | Comment | Quand |
|---|---|---|
| Rolling in-place | mise à jour des composants sur le cluster vivant | le standard, la plupart des équipes |
| Blue/green de node group | nouveaux nœuds à la nouvelle version, migration des pods, retrait des anciens | changement de nœuds risqué |
| Blue/green de cluster | cluster neuf, bascule du trafic (DNS ou load balancer), destruction de l'ancien | changement majeur, aucune tolérance au risque |
Ce projet fait déjà du blue/green de cluster, sans l'avoir décidé : il détruit et recrée au lieu de mettre à jour. Ce qui manque pour que ce soit un blue/green complet, c'est le recouvrement — les deux clusters vivants en même temps et une bascule DNS progressive, au lieu d'un trou entre la destruction et le montage.
Ce qu'un rolling in-place ferait ici, et pourquoi l'application ne tomberait pas¶
Même en montée in-place, l'application resterait servie, par construction :
| Garde-fou | Effet |
|---|---|
HPA minReplicas: 2 |
il y a toujours au moins deux pods |
PDB maxUnavailable: 1 |
on peut toujours évincer un pod, il en reste un |
unhealthyPodEvictionPolicy: AlwaysAllow |
un pod cassé ne bloque pas le drain |
anti-affinité preferred et non required |
les deux pods peuvent cohabiter sur un nœud |
node group core en max_size = 2 |
AWS crée le nouveau nœud avant de drainer l'ancien |
C'est le résultat de #83 / INC-054 : un minAvailable statique bloquait les drains,
il a été remplacé par maxUnavailable, et le HPA est passé à minReplicas: 2.
Le node group observability porte le taint workload=observability:NoSchedule et
ne peut donc pas accueillir les pods applicatifs. Ce n'est pas un piège : le
node group core monte à deux nœuds pendant le remplacement, la place existe.
Ne pas confondre haute disponibilité et stratégie de montée de version¶
Ce sont deux sujets distincts, et les mélanger est une erreur fréquente.
- La haute disponibilité d'un cluster se joue à l'intérieur : plusieurs nœuds, répartis sur plusieurs zones de disponibilité, avec PDB et anti-affinité.
- Un second cluster répond à « ma région AWS tombe » ou « je veux isoler des charges ». Ce n'est pas la réponse à « je mets à jour mon CNI ».
Sur ce projet, le node group core est à desired_size = 1. Il n'y a donc pas
de haute disponibilité aujourd'hui, par choix de coût assumé. La configuration la
supporte (max_size = 2, deux sous-réseaux privés, PDB et anti-affinité en place),
seul le nombre de nœuds désirés est à changer.
Le client doit suivre : le skew de kubectl¶
Le contexte, avant le détail¶
Le skew est l'écart de version toléré entre deux composants qui se parlent.
Kubernetes ne garantit qu'une seule version mineure d'écart entre le client
kubectl et l'API server.
Au-delà, la panne n'est pas franche. Le client peut sérialiser un champ que l'API server ne connaît pas, ou en ignorer un qu'elle attend. On ne voit pas une erreur, on voit un objet qui n'a pas la forme attendue.
Le constat du 2026-09-07 (#213)¶
Deux endroits installent kubectl, et un seul était tenu :
| Où | Comment | Version obtenue |
|---|---|---|
CI (.gitlab-ci-infra.yml) |
KUBECTL_VERSION: v1.35.0, épinglée |
1.35.0 |
| Devcontainer | curl $(curl -s .../stable.txt) |
1.37.0 |
Le poste de travail était donc à deux mineures du cluster, là où une seule est
garantie. La CI était tenue pendant que le poste dérivait, et rien ne le
signalait : stable.txt renvoie la dernière stable du jour, donc la valeur changeait
à chaque rebuild d'image.
Ce qui tient la règle maintenant¶
scripts/check-version-compat.sh a une deuxième section qui refuse deux écarts :
- La divergence entre
ARG KUBECTL_VERSIONdu devcontainer etKUBECTL_VERSIONde la CI. Un poste qui diverge de la CI produit des diagnostics non reproductibles : le même geste ne donne pas le même résultat des deux côtés. - Le skew de plus d'une mineure entre
kubectleteks_cluster_version.
REFUS : kubectl 1.37.0 est a plus d'une mineure de Kubernetes 1.35.
Le contrôle tourne dans le job version-compat de chaque MR et dans le
before_script d'infra-start, aux mêmes deux points que le contrôle EKS / Cilium.
Pourquoi kubectl n'est pas suivi par Renovate¶
Les autres outils du devcontainer le sont (helm, aws-vault, AWS CLI). Pas
kubectl, et c'est une décision.
Sa version est dérivée, pas amont. Elle ne doit pas suivre la dernière release de Kubernetes, elle doit suivre le cluster. Une MR Renovate qui proposerait 1.37 serait rouge par construction tant que le cluster est en 1.35, et ce bruit se répéterait à chaque release.
C'est eks_cluster_version que Renovate surveille (#204). Quand cette valeur monte,
le contrôle rappelle que kubectl doit monter avec, aux deux endroits.
Le devcontainer ne lance pas les tests backend¶
Corollaire tranché dans la même issue. python3 -m venv échoue dans le
devcontainer, en 24.04 comme en 26.04, faute du paquet python3-venv. Son python3
vient de la base Ubuntu et n'est pas épinglé.
Ce n'est pas un manque : les tests passent par docker-compose-dev.yaml, qui porte
python:3.12-slim au digest, la même image que backend/Dockerfile et que le job
run-tests. Le python3 du devcontainer ne sert qu'aux scripts du dépôt, comme
scripts/gen_dashboard.py, insensibles à la mineure.
⚠️ Figer Ubuntu ne serait pas la réponse : la base finira par bouger de toute façon.
Ce qui a changé dans le dépôt¶
| Fichier | Changement |
|---|---|
terraform/modules/eks/variables.tf |
eks_cluster_version par défaut 1.32 → 1.35, avec le raisonnement |
terraform/ephemeral/variables.tf |
idem, c'est ce défaut qui gouverne les montages |
.gitlab-ci-infra.yml |
KUBECTL_VERSION: v1.35.0, définie une seule fois |
docs/infra-eks-summary.md, docs/adr/006 |
version affichée |
.devcontainer/Dockerfile |
ARG KUBECTL_VERSION=v1.35.0 et les autres outils épinglés (#213) |
scripts/check-version-compat.sh |
refuse la divergence CI / poste et le skew de plus d'une mineure (#213) |
Le client kubectl était écrit en dur dans deux before_script distincts, et
rien n'aurait signalé qu'un bump n'en corrigeait qu'un. Il est désormais dans le
bloc variables:, sur le motif déjà utilisé pour les digests d'images.
Reste à faire¶
- [ ] Un montage complet en 1.35, application déployée et joignable. Tant que ce n'est pas fait, la version cible est un choix, pas un résultat.
- [ ] Cilium 1.20 puis Kubernetes 1.36, en issue séparée, avec validation en
direct et
cilium-pre-flight-check. - [ ] Le
curl -LOqui téléchargekubectln'a ni-fni--retry. Sur une erreur HTTP transitoire, il enregistre le corps HTML de l'erreur et l'installe comme binaire. C'est la famille d'INC-057, corrigée ailleurs mais pas ici.
Sources¶
aws eks describe-cluster-versions, relevé du 2026-08-30- Kubernetes Compatibility, documentation Cilium 1.19
- Kubernetes Compatibility, documentation Cilium stable
- ADR 019 (CNI Cilium), ADR 026 (node groups core / observability), #83 et INC-054
(PDB et drains), #158 (
maxReplicasn'est pas de la capacité)