Tout cela n’est pas nouveau en 1.37.
Kubernetes v1.37 est sorti le 26 août 2026. La plupart des changements qu’on lui attribue ont atterri en v1.34 et v1.35, l’un d’eux n’arrivera qu’en v1.38, et celui qui peut réellement laisser des pods bloqués en ContainerCreating est à peine évoqué. Voici le bilan honnête, l’audit à mener avant la mise à jour, et le runbook.
- Kubernetes
- SELinux
- Mises à jour
- Plateforme
Kubernetes v1.37 est sorti le 26 août 2026, et la couverture qui l’entoure a une forme qui mérite d’être remarquée. Plusieurs changements sont attribués à cette version sans lui appartenir : le kubelet qui refuse de démarrer sur cgroup v1 (cette valeur par défaut a basculé en v1.35), les pods statiques qui perdent leurs références aux Secrets et ConfigMaps (actif par défaut depuis la v1.34) et le mode ipvs de kube-proxy qui serait supprimé (il porte un avis d’obsolescence depuis la v1.35, et la suppression vise la v1.43). Un changement est mal rapporté dans l’autre sens : containerd 1.x tourne toujours face à un kubelet v1.37, et la falaise est en v1.38. Pendant ce temps, le seul changement de cette version qui peut réellement laisser des pods bloqués en ContainerCreating le jour de la mise à jour — SELinuxMount qui passe en GA et s’active par défaut — a droit à un paragraphe, parce qu’il est invisible pour quiconque ne fait pas tourner SELinux sur ses clusters.

La distinction n’est pas de la pédanterie, car chaque colonne implique un travail différent. Si le changement cgroup vous surprend en 1.37, vous aviez déjà deux versions de retard et le correctif est un redémarrage par nœud plutôt qu’un retour arrière. Si vous reconstruisez votre runtime de conteneurs dans la panique parce qu’on vous a dit que containerd 1.x avait cessé de fonctionner, vous aurez dépensé une fenêtre de maintenance sur une échéance encore à une version de distance — réelle, mais pas pour aujourd’hui. Et si le changement SELinux vous surprend en 1.37, vous avez des pods qui ne démarrent pas et l’audit qui les aurait trouvés devient nettement plus coûteux une fois la mise à jour passée. Cet article fait donc deux choses : il sépare le bilan honnêtement, puis il entre dans le détail des parties qui demandent du travail — l’audit SELinux et son échappatoire au complet, les vrais calendriers d’ipvs et de containerd, et un runbook qui place les étapes irréversibles dans le bon ordre.
Les modes de panne, et à quelle version chacun appartient
Commencez par ce que vous verriez réellement, car aucune de ces pannes n’annonce sa propre cause. Un pod reste indéfiniment en ContainerCreating sur un nœud alors qu’un pod identique tourne très bien sur un autre — c’est un conflit d’étiquettes SELinux, et le facteur décisif est de savoir quel pod est arrivé le premier sur le volume. Un nœud revient d’une mise à jour et son kubelet ne démarre pas du tout — c’est presque certainement cgroup v1, et c’est vrai depuis la v1.35. Un agent de supervision qui tourne en pod statique depuis 2021 échoue sur exactement un nœud, celui que vous venez de mettre à jour — c’est la référence à un Secret qu’il n’aurait jamais dû pouvoir utiliser, et le défaut qui la lui a retirée remonte à la v1.34.[sneak]
| Ce que vous voyez | Ce que cela signifie en général | Où c’est traité |
|---|---|---|
Un pod bloqué en ContainerCreating sur un nœud ; un pod identique tourne ailleurs | Un conflit d’étiquettes SELinux sur un volume partagé. Le pod arrivé le premier sur le volume tient l’unique contexte du montage | SELinuxMount |
| Le kubelet ne démarre pas du tout après une mise à jour | cgroup v1 sans le contournement failCgroupV1: false. C’est vrai depuis la v1.35 | cgroup v1 |
| Un agent de supervision qui est un pod statique depuis des années échoue sur le seul nœud mis à jour | Un secretRef ou un configMapRef dans un manifeste de pod statique. Interdit par défaut depuis la v1.34, ce qui mord les clusters ayant sauté des versions | Pods statiques |
| kube-proxy consigne un avertissement d’obsolescence et tout continue de fonctionner | Le mode ipvs, obsolète depuis la v1.35. La v1.37 ajoute la gate KubeProxyIPVS ; la suppression vise la v1.43 | ipvs |
Les pods tournent, et la métrique cri_losing_support est non nulle sur certains nœuds | containerd 1.x derrière le repli CRI sur le pilote de cgroups. Toujours pris en charge en v1.37 ; le repli disparaît en v1.38 | containerd |
L’API server émet un avertissement à chaque application d’un Service avec externalIPs | Pas la v1.37 du tout — les ExternalIPs de Service ont été déclarés obsolètes en v1.36. Rien n’est encore supprimé, et rien ne cesse de fonctionner | Le bilan |
kubectl top et le HPA continuent de fonctionner après la mise à jour | Normal. metrics.k8s.io passe en v1 et les deux versions restent servies | metrics.k8s.io |
Le schéma à intégrer une fois pour toutes, c’est qu’une montée de version fait remonter tous les changements survenus depuis la version dont vous étiez sûr, pas seulement ceux de la version vers laquelle vous allez. En pratique, les clusters n’avancent pas d’une mineure à la fois : ils restent un an en 1.34, puis bougent. Les ExternalIPs de Service en sont l’exemple du moment : ils ont été déclarés obsolètes en v1.36, où l’API server s’est mis à émettre un avertissement à chaque usage, et les équipes qui arrivent en v1.37 depuis plus loin croisent cet avertissement pour la première fois. Rien n’en est encore supprimé — la prise en charge côté kube-proxy devrait passer à désactivée par défaut pas avant la v1.40, avec une suppression pas avant la v1.43 — et c’est exactement ainsi qu’un changement situé trois versions plus loin devient une panne : l’avertissement arrive dans une version dont personne n’a lu les notes. C’est la raison pour laquelle la deuxième section de cet article est un bilan et non un résumé de notes de version.[extip]
Le bilan : nouveau en 1.37, ou déjà atterri
Voici la comptabilité honnête. La colonne de gauche reprend ce que l’avant-goût publié par l’équipe de release annonce pour la v1.37 ; les colonnes de droite indiquent d’où le changement provient réellement, ou vers quoi il se dirige encore. Tout ce qui figure dans les lignes « plus tôt » est quelque chose que vous auriez déjà dû traiter, et si ce n’est pas fait, la fenêtre de mise à jour est le moment où vous l’apprendrez ; tout ce qui figure dans les lignes « plus tard » est une date à mettre dans un agenda, pas du travail pour cette semaine.[sneak]
| Changement | Couramment attribué à | Se produit réellement en | Effet le jour de la mise à jour vers 1.37 |
|---|---|---|---|
SELinuxMount en GA, activé par défaut | 1.37 | 1.37 | Peut laisser des pods en ContainerCreating là où deux pods d’étiquettes différentes partagent un volume et où le pilote CSI a opté pour le montage à contexte. C’est celui qui demande du travail |
Feature gate KubeProxyIPVS ajoutée, marquée obsolète | 1.37 | 1.37 | Rien. La gate existe pour pouvoir désactiver ipvs par défaut plus tard |
kubectl run --filename/-f déclaré obsolète | 1.37 | 1.37 | Avertissement seulement, sur un drapeau déjà ignoré. Touche les scripts, pas les clusters |
metrics.k8s.io passe en v1 | 1.37 | 1.37 | Rien ne casse. v1 et v1beta1 sont toutes deux servies pendant la transition |
| Les pods statiques ne peuvent plus référencer de Secrets ni de ConfigMaps | 1.37 | 1.34 | Rien de nouveau — sauf si vous avez sauté des versions, auquel cas cela casse purement et simplement ces pods statiques sur le nœud que vous venez de mettre à jour |
| Le kubelet refuse de démarrer sur cgroup v1 | 1.37 | 1.35 | Rien de nouveau. Si cela vous mord ici, c’est que le nœud porte failCgroupV1: false depuis la v1.35 |
Mode ipvs de kube-proxy déclaré obsolète | « supprimé en 1.37 » | 1.35 (avertissement seulement) | Une ligne de journal au démarrage, comme depuis deux versions. La gate passe à false en 1.40 ; suppression en 1.43 |
ExternalIPs de Service déclarés obsolètes | « supprimé en 1.36 » | 1.36 (avertissement seulement) | Un avertissement de l’API server. Désactivé par défaut pas avant 1.40 ; suppression pas avant 1.43 |
| Repli CRI pour containerd 1.x abandonné | 1.36, parfois 1.37 | 1.38 | Rien pour l’instant. containerd 1.x tourne toujours en 1.37 derrière le repli, et cri_losing_support compte les nœuds qui s’y appuient |
La phrase la plus utile de cet article : si vos nœuds ne font pas tourner SELinux en mode enforcing, la plus grosse section qui suit ne vous concerne pas du tout. Le kubelet saute l’intégralité du chemin de code SELinux quand SELinux est indisponible ou désactivé dans le noyau. Vérifiez cela en premier — une seule commande suffit — parce que c’est ce qui détermine si vous avez devant vous une demi-journée d’audit ou une lecture de dix minutes.
Deux de ces lignes méritent une note sur la façon de les vérifier soi-même plutôt que de croire quelqu’un sur parole. La référence des feature gates publie l’état, version par version, de SELinuxMount, SELinuxChangePolicy et KubeProxyIPVS : c’est le moyen le plus rapide de contrôler une affirmation sur la version qui a fait basculer telle valeur par défaut. Et le billet de l’équipe de release de chaque version porte sa propre liste d’obsolescences ; en lire trois prend vingt minutes et vaut mieux que la plupart de ce qui occupe une fenêtre de maintenance.[gates]
Où vous vous situez réellement
Avant que tout cela devienne actionnable, il vous faut quatre données, et elles sont indépendantes les unes des autres : la version du kubelet par nœud, si SELinux est en mode enforcing sur ce nœud, quelle version de cgroup il utilise et dans quel mode tourne kube-proxy. Sur un parc de taille quelconque les réponses ne sont pas uniformes — les pools de nœuds avancent à leur propre rythme, et la politique de version skew autorise explicitement un kubelet à être en retard sur l’API server, si bien qu’un parc hétérogène est une configuration prise en charge et non un signe de laisser-aller.[skew]
# Four numbers decide how much of this article applies to you, and they are
# independent of each other. Ask for all four rather than assuming.
# 1. Control plane and kubelet versions. Node pools drift; this is normal.
kubectl get nodes -o custom-columns=\
'NODE:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,'\
'RUNTIME:.status.nodeInfo.containerRuntimeVersion,'\
'KERNEL:.status.nodeInfo.kernelVersion,OS:.status.nodeInfo.osImage'
# NODE KUBELET RUNTIME KERNEL OS
# node-01 v1.36.4 containerd://2.3.2 6.8.0-51 Ubuntu 24.04.3 LTS
# node-02 v1.35.9 containerd://1.7.28 5.15.0-118 Ubuntu 22.04.5 LTS <- two problems
# 2. Is SELinux actually in play? If the answer is "no" on every node, the
# largest section of this article does not apply to you at all.
for n in $(kubectl get nodes -o name); do
printf '%-22s ' "${n#node/}"
kubectl debug "$n" -q -it --image=busybox --profile=general -- \
chroot /host sh -c 'getenforce 2>/dev/null || echo "not installed"' 2>/dev/null
done
# node-01 Enforcing <- section "SELinux" applies
# node-02 not installed <- it does not
# 3. Which cgroup version. v2 shows cgroup2fs; v1 shows tmpfs.
kubectl debug node/node-01 -q -it --image=busybox --profile=general -- \
chroot /host stat -fc %T /sys/fs/cgroup
# cgroup2fs
# 4. Which kube-proxy mode. This is the one people are most often wrong about,
# because the answer usually predates everyone currently on the team.
kubectl -n kube-system get configmap kube-proxy \
-o jsonpath='{.data.config\.conf}' | grep -E '^\s*mode:'
# mode: "ipvs"
# Clean up the debug pods. `kubectl debug node/...` names them
# node-debugger-<node>-<suffix> and applies no label, so there is nothing to
# select on - match the name, or they accumulate silently.
kubectl get pods -o name | grep '^pod/node-debugger-' | xargs -r kubectl deleteL’autre donnée qui compte est la marge réelle dont dispose la version sur laquelle vous êtes. Kubernetes prend en charge les trois dernières versions mineures, environ quatorze mois chacune, avec les deux derniers mois en mode maintenance où seuls les correctifs de sécurité critiques arrivent. Ce calendrier est la raison pour laquelle les mises à jour ne sont pas facultatives, et il vaut la peine de l’avoir sous les yeux quand quelqu’un propose de repousser celle-ci.[k8srel]
| Version | Sortie | Mode maintenance à partir du | Fin de vie |
|---|---|---|---|
| 1.34 | 27 août 2025 | 27 août 2026 | 27 octobre 2026 — deux mois restants |
| 1.35 | 17 décembre 2025 | 28 décembre 2026 | 28 février 2027 |
| 1.36 | 22 avril 2026 | 28 avril 2027 | 28 juin 2027 |
| 1.37 | 26 août 2026 | ≈ août 2027 | ≈ octobre 2027 |
SELinuxMount passe en GA : le changement qui arrête les pods
C’est la section qui justifie l’article. SELinuxMount passe en GA en v1.37 et est activé par défaut. Le changement est un gain de performance et le mécanisme est élégant : au lieu que le runtime de conteneurs parcoure le volume et ré-étiquette chaque inode — ce qui, sur un système de fichiers volumineux ou distant, est sincèrement lent —, le kubelet monte le volume avec -o context=<label> et le noyau applique l’étiquette à tous les inodes de ce montage en temps constant. Le problème découle directement du mécanisme. Un montage ne peut porter qu’un seul contexte SELinux. Avec le ré-étiquetage récursif, deux pods portant des étiquettes différentes pouvaient partager un volume ; avec un montage à contexte, ils ne le peuvent plus, et l’un des deux restera en ContainerCreating jusqu’à la disparition de l’autre.[selblog][kep1710]
| Condition | Où le vérifier | Si elle n’est pas remplie |
|---|---|---|
| Le système du nœud prend en charge SELinux et il est en mode enforcing | getenforce sur le nœud | Rien ne change. Le kubelet saute tout le chemin SELinux |
SELinuxMountReadWriteOncePod est activé | En GA et inconditionnel depuis la v1.36 | Sans objet sur une version prise en charge |
SELinuxMount et SELinuxChangePolicy sont activés | Feature gates. SELinuxMount est en bêta et désactivé en 1.36, en GA et activé en 1.37 | Les étiquettes sont appliquées récursivement par le runtime, comme avant |
Le pod expose au moins seLinuxOptions.level | Le securityContext du pod ou du conteneur | Le runtime attribue un niveau aléatoire après le montage et ré-étiquette récursivement de toute façon |
Le pilote CSI positionne seLinuxMount: true | kubectl get csidrivers -o custom-columns=… | Inchangé. Non défini ne vaut pas true. In-tree, seuls fc, iscsi et rbd prennent en charge l’option |
spec.securityContext.seLinuxChangePolicy est non défini ou vaut MountOption | La spec du Pod | Recursive est l’échappatoire explicite et conserve l’ancien comportement |
Le rayon d’action est plus étroit que cela n’en a l’air, et il vaut la peine de le délimiter précisément plutôt que de supposer le pire. Cinq conditions doivent toutes être réunies pour qu’un seul volume change de comportement, et celle qu’on oublie est le pilote CSI : le kubelet n’utilise un montage à contexte que si le pilote a déclaré pouvoir en accepter un, en positionnant seLinuxMount: true sur son objet CSIDriver. Un pilote qui laisse le champ non défini — et non défini ne vaut pas true — conserve l’ancien comportement récursif et n’est absolument pas concerné par la bascule. Les types de volumes in-tree qui prennent en charge l’option de montage sont fc, iscsi et rbd ; tout le reste de l’arbre ré-étiquette récursivement, quoi qu’il arrive.[csidriver]
# The blast radius of the SELinuxMount change is not "clusters with SELinux".
# It is the intersection of three things, and all three have to be true before
# a single pod is at risk.
# (a) SELinux enforcing on the node. Checked above. If not, stop here.
# (b) A CSI driver that has opted in. The kubelet only uses the mount option
# when the driver declares it can take one. Drivers that do not set this
# keep the old recursive relabel and are unaffected by the flip.
kubectl get csidrivers \
-o custom-columns='DRIVER:.metadata.name,SELINUXMOUNT:.spec.seLinuxMount'
# DRIVER SELINUXMOUNT
# ebs.csi.aws.com true <- volumes on this driver change behaviour
# efs.csi.aws.com false <- unchanged
# csi.trident.netapp.io <none> <- unset is not true; unchanged
# The in-tree volume types that support the mount option are fc, iscsi and rbd.
# Everything else in tree relabels recursively regardless.
# (c) Two pods with different SELinux labels sharing one volume. This is the
# part you cannot infer from a manifest, because the label is often
# assigned by the runtime rather than written down. The cheapest proxy is
# to find the volumes that more than one workload mounts at all:
kubectl get pods -A \
-o jsonpath='{range .items[*]}{range .spec.volumes[?(@.persistentVolumeClaim)]}'\
'{.persistentVolumeClaim.claimName}{"\t"}{end}{.metadata.namespace}{"\n"}{end}' \
| awk -F'\t' 'NF>1' | sort | uniq -c | sort -rn | awk '$1>1'
# 3 shared-media default
# 2 build-cache ci
# That list is a starting point, not an answer. The answer comes from the
# controller below, which knows the labels.
# One more thing worth knowing before you panic: a pod that mounts a volume
# through different subPaths used to be able to share it across labels too.
# That case also stops working, and it is rare enough that upstream says it has
# never been seen in practice.Deux schémas de partage cassent, et un seul se rencontre dans la nature. Le premier, ce sont deux pods qui partagent un volume via des subPath différents avec des étiquettes différentes, que l’amont décrit comme très marginal et dit n’avoir jamais observé en pratique. Le second, c’est un pod privilégié et un pod non privilégié qui partagent un volume — rare également, mais observé dans de vraies applications, et c’est celui qu’il faut aller chercher. Le récit de mise à jour du KEP lui-même mérite d’être lu si c’est vous qui devez valider l’opération, parce que c’est ce qui existe de plus proche d’un runbook officiel.[kepstory3]
L’audit qui doit avoir lieu avant la mise à jour
Kubernetes v1.36 a livré un contrôleur fait exactement pour ce problème, et presque personne ne l’a activé, parce qu’il est optionnel et parce que ce contre quoi il prévient n’était pas encore arrivé. selinux-warning-controller tourne à l’intérieur de kube-controller-manager derrière --controllers=*,selinux-warning-controller, surveille tous les pods du cluster et signale chaque paire de pods qui partagent un volume d’une façon que SELinuxMount n’autorisera pas. Il les signale même lorsque les pods sont sur des nœuds différents, au motif tout à fait juste que l’ordonnanceur peut les réunir demain.[k8s136]
# Kubernetes v1.36 shipped a controller whose entire job is to find these
# conflicts before the upgrade turns them into stuck pods. It is off by
# default and it is the single most useful thing in this article.
#
# It runs inside kube-controller-manager. `*` keeps every default controller
# and adds this one; listing it alone would disable all the others.
# kubeadm clusters: edit the static pod manifest on each control plane node.
sudo vi /etc/kubernetes/manifests/kube-controller-manager.yaml
# spec:
# containers:
# - command:
# - kube-controller-manager
# - --controllers=*,bootstrapsigner,tokencleaner,selinux-warning-controller
# ^^^^^^^^^^^^^^^^^^^^^^^^^
# The kubelet restarts the pod when the file changes. Give it a minute.
# Note the second requirement, which is easy to miss: you must NOT have
# explicitly disabled the SELinuxChangePolicy feature gate. It is GA and on by
# default, so this only bites clusters carrying an old --feature-gates line.
kubectl -n kube-system get pod -l component=kube-controller-manager \
-o jsonpath='{.items[*].spec.containers[*].command}' | tr ',' '\n' \
| grep -E 'feature-gates|controllers='
# Confirm it is running before you trust its silence:
kubectl -n kube-system logs -l component=kube-controller-manager --tail=200 \
| grep -i 'selinux'
# ... "Starting controller" controller="selinux-warning-controller"
# Enabling it has one privacy consequence worth stating: the metric it emits
# carries namespace names as labels, so it can leak namespace names to anyone
# who can read kube-controller-manager metrics. Upstream's assumption is that
# only cluster administrators can.Lisez ensuite les deux métriques, car elles répondent à des questions différentes et aucune ne suffit seule. Celle du contrôleur, selinux_warning_controller_selinux_volume_conflict, porte en étiquettes les noms et les namespaces des pods en conflit : c’est votre liste de travail. Celle du kubelet, volume_manager_selinux_volume_context_mismatch_warnings_total, n’a aucune étiquette de nom de pod, mais elle donne le décompte honnête des pods qui échoueraient réellement, et elle est émise pendant que SELinuxMount est encore désactivé. C’est tout l’intérêt de faire cela en v1.36 : le compteur d’avertissements est non nul alors que tout fonctionne encore. Après la mise à jour, la même mesure apparaît sous ..._errors_total, et à ce moment-là les pods ne sont plus en risque, ils sont bloqués.[selblog]
# There are two metrics and they answer two different questions. You need both:
# one tells you WHICH pods, the other tells you HOW MANY will actually fail.
# --- (1) From kube-controller-manager: which pods conflict, by name ---------
# Reported even when the pods are on different nodes, because the scheduler
# may put them together tomorrow.
kubectl get --raw /metrics 2>/dev/null | true # via your metrics stack, or:
kubectl -n kube-system exec -it \
"$(kubectl -n kube-system get pod -l component=kube-controller-manager \
-o name | head -1)" -- \
curl -sk https://127.0.0.1:10257/metrics \
| grep '^selinux_warning_controller_selinux_volume_conflict'
# selinux_warning_controller_selinux_volume_conflict{
# pod1_name="my-other-pod",pod1_namespace="default",
# pod1_value="system_u:object_r:container_file_t:s0:c0,c1",
# pod2_name="my-pod",pod2_namespace="default",
# pod2_value="system_u:object_r:container_file_t:s0:c0,c2",
# property="SELinuxLabel"} 1
# --- (2) From the kubelet: how many pods would actually fail ---------------
# Emitted while SELinuxMount is still DISABLED, which is exactly the window
# you are in before the upgrade. It has no pod-name label - that is what the
# controller above is for - but it is the honest count.
kubectl get --raw "/api/v1/nodes/node-01/proxy/metrics" \
| grep '^volume_manager_selinux_volume_context_mismatch_warnings_total'
# volume_manager_selinux_volume_context_mismatch_warnings_total{...} 2
# After the upgrade the same measurement lives under a different name, and by
# then the pods are already stuck rather than merely at risk:
# volume_manager_selinux_volume_context_mismatch_errors_total
# The whole point of doing this on v1.36 is that the warnings metric is
# non-zero while everything still works. Read it while it is still cheap.Pour chaque charge que les métriques nomment, vous avez deux choix : corriger le partage, ou exempter ce pod. L’exemption est un champ de Pod, spec.securityContext.seLinuxChangePolicy, et c’est une API stable depuis la v1.36 — ce qui veut dire que vous pouvez l’appliquer dès maintenant, sur votre version actuelle, sans aucun changement de comportement, et qu’elle fera ce qu’il faut à la seconde où la mise à jour arrivera. C’est l’assurance la moins chère de toute cette version. La régler sur Recursive conserve l’ancien comportement pour ce pod et vous coûte le gain de performance ; pour une charge qui partageait déjà un volume entre étiquettes différentes, c’est un compromis que vous faisiez de toute façon.[seccontext][mutadm]
# For every workload the metrics named, you have two options: fix the sharing,
# or opt that pod out of the mount-option path. The opt-out is a Pod field and
# it is stable API as of Kubernetes v1.36.
apiVersion: apps/v1
kind: Deployment
metadata:
name: legacy-shared-cache
spec:
template:
spec:
securityContext:
# Three values:
# unset - follow the cluster default. On v1.37 with SELinuxMount
# enabled, that means mount options.
# MountOption - use mount options explicitly. Only valid while the
# SELinuxMount feature gate is on.
# Recursive - the pre-1.37 behaviour: the runtime relabels every
# file. Slower on large volumes, and it is what lets two
# differently labelled pods share one volume.
seLinuxChangePolicy: Recursive
containers:
- name: app
image: registry.internal.example.com/app:1.4.2
---
# Applying this to every affected workload by hand does not scale, and both
# the SELinux blog and the KEP say so. Prefer a policy. In-tree:
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingAdmissionPolicy
metadata:
name: selinux-recursive-optout
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
# Scope this to the namespaces the metrics actually named. A cluster-wide
# opt-out works, but it also throws away the performance win for every
# workload that was never at risk.
matchConditions:
- name: only-flagged-namespaces
expression: "request.namespace in ['default', 'ci']"
failurePolicy: Fail
reinvocationPolicy: IfNeeded
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >
Object{ spec: Object.spec{
securityContext: Object.spec.securityContext{
seLinuxChangePolicy: "Recursive" } } }seLinuxChangePolicy | Comportement en 1.36 | Comportement en 1.37 | Quand s’en servir |
|---|---|---|---|
| non défini (défaut) | Ré-étiquetage récursif — SELinuxMount est désactivé par défaut | Option de montage, si toutes les autres conditions sont réunies | Le défaut pour tout ce qui ne partage pas de volumes entre étiquettes différentes |
MountOption | Valide uniquement avec la feature gate activée | Option de montage, explicitement | Rarement nécessaire. Le défaut fait déjà cela en 1.37 |
Recursive | Ré-étiquetage récursif | Ré-étiquetage récursif — l’échappatoire | Le correctif. À appliquer à chaque charge nommée par les métriques de conflit, avant la mise à jour |
Appliquer ce champ à la main sur chaque Deployment et chaque StatefulSet concerné ne tient pas au-delà d’un petit cluster, et le billet SELinux le dit sans détour, en citant MutatingAdmissionPolicy, les webhooks mutants, Kyverno et Gatekeeper comme les moyens de le faire en masse. Un conseil sur la portée : résistez à la tentation d’appliquer Recursive à tout le cluster par précaution générale. Cela marche, et cela jette le gain de performance pour toutes les charges qui n’ont jamais été en risque — c’est-à-dire, dans la plupart des clusters, la quasi-totalité. Limitez la portée aux namespaces que les métriques ont réellement nommés.[kyverno]
Les pods statiques et leurs références aux Secrets : celui-là s’est passé en 1.34
C’est la panne la plus susceptible d’être imputée à la 1.37 par quelqu’un qui vient de la 1.33. Les pods statiques sont gérés par le kubelet depuis un répertoire sur disque plutôt que créés via l’API server : ils n’étaient donc jamais censés pouvoir lire d’objets de l’API. Un défaut leur permettait de référencer des Secrets et des ConfigMaps par des champs comme configMapRef et secretRef. La restriction qui referme ce défaut, c’est la feature gate PreventStaticPodAPIReferences, et elle est active par défaut depuis la v1.34 — donc sur tout cluster qui est réellement passé par la 1.34, la 1.35 et la 1.36, cela s’est déjà produit et les pods concernés ont déjà cassé. L’avant-goût de la v1.37 annonçait la suppression de la gate elle-même, et donc de l’échappatoire ; la référence des feature gates publiée avec la v1.37 liste pourtant toujours PreventStaticPodAPIReferences comme une gate bêta valant true par défaut, si bien que la porte de sortie est peut-être techniquement encore là. Planifiez comme si elle n’y était pas. C’était un défaut et non une fonctionnalité, il ne reviendra pas, et passer au grep les répertoires de pods statiques coûte moins cher que de découvrir cela nœud par nœud.[staticpod][iss140226]
# Static pods are managed by the kubelet from a directory on disk, not by the
# API server. They were never supposed to be able to read API objects; a bug
# let them, through envFrom.configMapRef, envFrom.secretRef, valueFrom and
# volume references. The gate that closes it - PreventStaticPodAPIReferences -
# has defaulted to true since v1.34, so this is only "new in 1.37" for a
# cluster that skipped releases. The v1.37 sneak peek says the gate was
# removed in this release; the shipped v1.37 feature-gates reference still
# lists it as Beta/true. Plan as if the opt-out is gone: it closed a defect,
# and it is not coming back. Find the references before the upgrade.
# Where the manifests live. Do not assume /etc/kubernetes/manifests: read it
# from the kubelet's own configuration.
sudo grep -E '^staticPodPath:' /var/lib/kubelet/config.yaml
# staticPodPath: /etc/kubernetes/manifests
# The audit, per node. Any hit is a pod that will fail to start on v1.37.
sudo grep -rnE 'configMapRef|secretRef|configMapKeyRef|secretKeyRef|(configMap|secret):' \
/etc/kubernetes/manifests/
# /etc/kubernetes/manifests/node-exporter.yaml:24: secretRef:
# /etc/kubernetes/manifests/node-exporter.yaml:25: name: scrape-creds
# Fleet-wide, without logging into every box. Note that the control plane's
# own static pods (kube-apiserver, etcd, kube-scheduler,
# kube-controller-manager) are generated by kubeadm and do not use these
# references, so a clean result there is expected rather than reassuring.
for n in $(kubectl get nodes -o name); do
printf '%-22s ' "${n#node/}"
kubectl debug "$n" -q -it --image=busybox --profile=general -- \
chroot /host sh -c \
'grep -rlE "configMapRef|secretRef|configMapKeyRef|secretKeyRef" \
/etc/kubernetes/manifests/ 2>/dev/null | tr "\n" " " || true' 2>/dev/null
echo
done
# The fix is to stop being a static pod, or stop needing the reference:
# * A DaemonSet is the right answer for almost everything that is a static
# pod today for historical reasons. It can read Secrets normally.
# * If it has to stay static, put the value in the manifest, or bind-mount a
# file from the host and read it from there. Both are worse than a
# DaemonSet, and both work.Le correctif consiste presque toujours à cesser d’être un pod statique. Beaucoup de pods statiques existent pour des raisons historiques — c’était la façon de faire tourner un agent au niveau du nœud avant que les DaemonSets soient aussi complets qu’aujourd’hui — et un DaemonSet lit les Secrets normalement, bénéficie des mises à jour progressives et apparaît là où les gens regardent. Là où quelque chose doit vraiment rester statique, les options sont d’écrire la valeur en dur dans le manifeste, ou de monter un fichier de l’hôte en bind-mount et de le lire depuis là. Les deux sont moins bien qu’un DaemonSet et les deux fonctionnent. Notez à part que kubectl run --filename/-f est également déclaré obsolète dans cette version, au motif que le pod produit était de toute façon construit uniquement à partir des arguments de la ligne de commande — celui-là touche les scripts, pas les clusters.[iss138671]
kube-proxy ipvs : obsolète depuis la 1.35, pas supprimé
Venons-en au changement dont on parle le plus à tort. Le mode ipvs de kube-proxy porte un avis d’obsolescence depuis la v1.35, et non depuis la v1.37. Ce que la v1.37 ajoute, c’est la feature gate KubeProxyIPVS, elle-même marquée obsolète, à côté de l’avertissement que kube-proxy consigne au démarrage. Rien ne cesse de fonctionner, aucune feature gate ne bascule, et aucun trafic n’est affecté. Le raisonnement derrière l’obsolescence vaut la peine d’être compris parce qu’il explique pourquoi il n’y a jamais eu de correctif : l’API ipvs du noyau ne peut pas exprimer tout ce dont un Service Kubernetes a besoin, si bien que le mode ipvs s’est toujours rabattu sur iptables en dessous pour une partie du travail. KEP-3866 le dit crûment dans un intertitre — le mode ipvs de kube-proxy ne nous sauvera pas.[kep5495][kep3866]
| Version | Ce qui arrive au mode ipvs | Ce que vous devez faire |
|---|---|---|
| 1.35 | Déclaré obsolète. kube-proxy consigne un avertissement au démarrage | Rien — mais c’est là que le compte à rebours a démarré |
| 1.37 | La feature gate KubeProxyIPVS est ajoutée, elle-même marquée obsolète | Rien. Planifiez la migration ; ne la précipitez pas |
| 1.38 – 1.39 | Fonctionne toujours, avertit toujours | Migrez vers le mode nftables dans une fenêtre de votre choix |
| 1.40 | La gate KubeProxyIPVS devrait passer à false par défaut | Réactivez-la via la gate, ou soyez déjà arrivé au bout |
| 1.43 | Support entièrement supprimé | Plus rien à faire — c’est l’échéance |
La destination est le mode nftables, en GA depuis la v1.33 et recommandé pour les nœuds Linux. Les clusters ne migrent pas tout seuls : il faut définir mode: "nftables" explicitement. L’exigence de version du noyau est réelle mais pas contraignante — tous les noyaux trop anciens pour prendre en charge le mode nftables quittent le support long terme d’ici fin 2026 — et les correctifs nftables ont été rétroportés sur les branches 1.33 et 1.34 précisément pour que les utilisateurs d’ipvs sur des versions plus anciennes puissent migrer sans mettre Kubernetes à jour d’abord.[nftblog]
# What actually happens on v1.37 if you run ipvs mode: kube-proxy logs the
# same deprecation warning it has logged since v1.35. That is all. Nothing
# stops working, no feature gate flips, and no traffic is affected. What v1.37
# adds is the KubeProxyIPVS gate, itself marked deprecated - the switch that
# will later be used to turn the mode off by default.
kubectl -n kube-system logs -l k8s-app=kube-proxy --tail=50 | grep -i deprecat
# W0826 ... "ipvs mode of kube-proxy is deprecated and will be removed in a
# future release; see KEP-5495"
# The reason is worth knowing, because it explains why there is no fixing it:
# the kernel's ipvs API cannot express everything a Kubernetes Service needs,
# so ipvs mode has always fallen back to iptables underneath for parts of the
# job. It was never the clean escape from iptables it was sold as.
# The destination is nftables mode, GA since v1.33. Clusters never migrate on
# their own - you have to set it.
# --- migrating, on a kubeadm cluster ---------------------------------------
# 1. Check the kernel. nftables mode wants a reasonably modern kernel; every
# kernel too old for it leaves LTS by the end of 2026.
kubectl get nodes -o jsonpath='{range .items[*]}{.status.nodeInfo.kernelVersion}{"\n"}{end}' \
| sort -u
# 2. Change the mode in the ConfigMap.
kubectl -n kube-system get configmap kube-proxy -o yaml > /tmp/kube-proxy.bak.yaml
kubectl -n kube-system patch configmap kube-proxy --type merge -p \
"$(printf '{"data":{"config.conf":%s}}' \
"$(kubectl -n kube-system get cm kube-proxy -o jsonpath='{.data.config\.conf}' \
| sed 's/^\(\s*mode:\).*/\1 "nftables"/' | jq -Rs .)")"
# 3. Roll the DaemonSet one node at a time and watch, rather than all at once.
kubectl -n kube-system rollout restart daemonset/kube-proxy
kubectl -n kube-system rollout status daemonset/kube-proxy --timeout=10m
# 4. Verify from the data plane, not the control plane. A Service that
# resolves but does not connect is the failure mode here.
kubectl run nft-check --rm -it --restart=Never --image=busybox -- \
sh -c 'wget -qO- --timeout=5 http://kubernetes.default.svc/healthz || echo FAILED'
# Rolling back is the same edit in reverse; keep /tmp/kube-proxy.bak.yaml.
# Do this as its own change, on its own day. Bundling a proxy-mode migration
# into a version upgrade means that when connectivity breaks you will not know
# which one did it.Un conseil de séquencement, donné avec conviction : faites la migration du mode de proxy dans sa propre fenêtre de maintenance, un autre jour, et non groupée avec la montée de version. Les deux changements touchent au plan de données, et quand la connectivité casse dans une fenêtre qui contenait les deux, vous passerez la panne à chercher lequel des deux l’a causée au lieu de la réparer. Aucune urgence ne justifie de les combiner : la politique d’obsolescence garantit une longue marge à une fonctionnalité en bêta ou mieux, et les critères de graduation du KEP lui-même placent le basculement de la feature gate à false en v1.40 et la suppression en v1.43.[kubeproxycfg][deprecpolicy]
cgroup v1 : celui-là s’est passé en 1.35
L’histoire de cgroup v1 est celle que l’on attribue le plus souvent à la mauvaise version, et la rétablir change ce qu’il faut en faire. Le réglage failCgroupV1 du kubelet vaut true par défaut depuis Kubernetes v1.35. Un nœud encore en cgroup v1 refuse donc de démarrer son kubelet depuis cette date, à moins que quelqu’un n’ait ajouté failCgroupV1: false — ce que beaucoup ont fait, dans l’urgence, pendant la mise à jour vers la v1.35, et n’ont jamais revisité ensuite. La bonne question sur le chemin de la v1.37 n’est donc pas « est-ce que ça va casser » mais « qui porte encore le contournement, et pour combien de temps encore ».[kep5573][kubeletcfg]
# This is the change most often misattributed to v1.37. The kubelet setting
# `failCgroupV1` has defaulted to TRUE since Kubernetes v1.35. A node still on
# cgroup v1 has therefore been refusing to start its kubelet since v1.35,
# unless somebody added the override - which many people did, in a hurry, and
# then forgot.
# So the useful question on the way to v1.37 is not "will this break" but
# "who is still carrying the override?"
for n in $(kubectl get nodes -o name); do
printf '%-22s ' "${n#node/}"
kubectl debug "$n" -q -it --image=busybox --profile=general -- \
chroot /host sh -c \
'printf "cgroup=%s override=%s\n" \
"$(stat -fc %T /sys/fs/cgroup)" \
"$(grep -c failCgroupV1 /var/lib/kubelet/config.yaml 2>/dev/null)"' 2>/dev/null
done
# node-01 cgroup=cgroup2fs override=0 <- fine
# node-02 cgroup=tmpfs override=1 <- v1, running on borrowed time
# The override itself, for reference. It is a stopgap and upstream says so:
# apiVersion: kubelet.config.k8s.io/v1beta1
# kind: KubeletConfiguration
# failCgroupV1: false
# v1.37 still honours it. KEP-5573 removes cgroup v1 support outright in a
# later release, and no date has been committed to, so the honest planning
# assumption is "the next one that suits SIG Node" rather than a fixed month.
# What you lose in the meantime is not theoretical. In-place pod resizing and
# tiered memory protection - both features people are actively asking for -
# depend on cgroup v2 and simply do not work on a v1 node.
# Switching the node is a kernel command line change and a reboot:
# systemd.unified_cgroup_hierarchy=1
# ...and then the container runtime's cgroup driver has to agree with the
# kubelet's. That is a longer job than it looks, which is why it has its own
# article.La v1.37 honore toujours le contournement, et KEP-5573 supprime purement et simplement la prise en charge de cgroup v1 dans une version ultérieure sans date engagée — l’hypothèse de planification honnête est donc « la version qui conviendra à SIG Node » plutôt qu’un mois inscriptible dans un plan. Ce que vous perdez entre-temps n’a rien de théorique : le redimensionnement de pods à chaud et la protection mémoire hiérarchisée dépendent tous deux de cgroup v2 et ne fonctionnent tout simplement pas sur un nœud en v1, et ce sont des fonctionnalités que les équipes réclament activement. La bascule elle-même est un changement de ligne de commande du noyau et un redémarrage, plus la mise en accord du pilote de cgroups du runtime avec celui du kubelet — ce qui représente plus de travail que cela n’en a l’air et a son propre article.[cgroups]
containerd 1.x : la falaise est en 1.38, pas maintenant
Celui-là est mal rapporté dans l’autre sens, et se tromper vous coûte une fenêtre de maintenance dont vous n’aviez pas besoin. La prise en charge de containerd 1.x n’a pas été supprimée en v1.36, et elle ne l’est pas non plus en v1.37. Un kubelet v1.37 tourne toujours face à elle : avec KubeletCgroupDriverFromCRI activé, le kubelet demande au runtime son pilote de cgroups via le RPC CRI RuntimeConfig, containerd 1.y n’implémente pas ce RPC, et le kubelet retombe discrètement sur sa propre valeur de --cgroup-driver tout en incrémentant la métrique cri_losing_support. Ce repli devait initialement disparaître en v1.37 et a été reporté d’une version, explicitement pour s’aligner sur la fenêtre de support de containerd v1.7. La documentation Kubernetes sur les runtimes le dit désormais clairement : en v1.38, le repli est abandonné, et les anciennes versions de containerd échouent face aux kubelets plus récents.[k8s134][ctrrel]
# The change most often stated backwards. containerd 1.x was NOT removed in
# v1.36, and it still runs against a v1.37 kubelet. What it runs on is a
# fallback: the kubelet asks the runtime for its cgroup driver over the
# RuntimeConfig CRI RPC, containerd 1.y does not implement that RPC, and the
# kubelet falls back to its own --cgroup-driver value. That fallback was
# scheduled to go in v1.37 and was deferred one release to align with
# containerd v1.7's support window, so it disappears in v1.38.
# The audit is a metric, not a spreadsheet. Every node relying on the fallback
# increments this, so scrape it rather than walking nodes by hand.
kubectl get --raw /metrics | grep '^cri_losing_support'
# cri_losing_support{version="1.38.0"} 1
# Then confirm which nodes, and with what:
kubectl get nodes -o custom-columns=\
'NODE:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,'\
'RUNTIME:.status.nodeInfo.containerRuntimeVersion' | sort -k3
# NODE KUBELET RUNTIME
# node-02 v1.37.0 containerd://1.7.28 <- works today, fails on 1.38
# node-01 v1.37.0 containerd://2.3.2
crictl version
# RuntimeName: containerd
# RuntimeVersion: v2.3.2
# RuntimeApiVersion: v1
# Two things make this less comfortable than "one release of runway" sounds:
# containerd 1.7's own extended support ends in September 2026, and
# containerd's published Kubernetes support matrix has no row for 1.37 yet -
# the last row is 1.36 (2.3.0+, 2.2.0+). Book the migration before 1.38, in
# its own window. Landing two runtime-level changes together means that when a
# node comes back wrong you will be bisecting instead of fixing.L’échéance est donc réelle et se situe à exactement une version d’ici — une position meilleure que ce que la panique laisse croire, et pire que ce que l’inaction suggère. Deux éléments l’aiguisent. Le compte à rebours du runtime s’épuise le premier : containerd 1.7 est la branche LTS dont tout dépend ici, et son support étendu s’achève en septembre 2026, c’est-à-dire maintenant. Et la matrice de support Kubernetes de containerd n’a actuellement aucune ligne pour Kubernetes 1.37 — la dernière ligne publiée est la 1.36, listant 2.3.0+ et 2.2.0+ — donc quiconque vous cite une version de containerd bénie pour la 1.37 extrapole au lieu de citer. Concrètement : collectez cri_losing_support, déjà émise par chaque nœud qui a besoin du repli et bien meilleur audit qu’un parcours de nœuds à la main, puis confirmez avec containerRuntimeVersion. Faites la migration du runtime avant la 1.38 et dans sa propre fenêtre. Poser deux changements au niveau du runtime ensemble signifie que, si un nœud revient de travers, vous ferez de la bissection au lieu de réparer.[runtimes]
metrics.k8s.io atteint enfin la v1
La bonne nouvelle de cette version, et elle est sincèrement bonne. metrics.k8s.io passe en v1 après près de neuf ans de bêta. C’est l’API derrière kubectl top et derrière les métriques CPU et mémoire du HorizontalPodAutoscaler, ce qui en fait l’une des interfaces les plus utilisées de Kubernetes et une curieuse chose à avoir laissée en bêta aussi longtemps. La graduation acte une stabilité plutôt qu’elle n’introduit un changement : aucune différence fonctionnelle n’est attendue, et v1 comme v1beta1 restent servies pendant la transition.[kep5207][metricspipe]
# The good news in this release. metrics.k8s.io graduates to v1 after nearly
# nine years in beta. Both versions stay served during the transition, so
# there is nothing to do on upgrade day - this is a thing you can adopt on
# your own schedule rather than a thing that happens to you.
kubectl get --raw /apis/metrics.k8s.io | jq -r '.versions[].groupVersion'
# metrics.k8s.io/v1
# metrics.k8s.io/v1beta1
# What consumes it: `kubectl top`, and the HorizontalPodAutoscaler's cpu and
# memory metrics. Both keep working without changes.
kubectl top nodes
kubectl top pods -A --sort-by=memory | head
# Where it matters is code you own. Anything that talks to the API directly -
# a custom autoscaler, a capacity report, a dashboard backend - should move
# off v1beta1 while both are available rather than after one is not.
kubectl get --raw /apis/metrics.k8s.io/v1/nodes | jq '.items[0]'
# Find the clients still on the beta path, from the API server's own counters:
kubectl get --raw /metrics \
| grep 'apiserver_requested_deprecated_apis\|metrics.k8s.io.*v1beta1' | head
# There is no removal date for v1beta1 yet. Kubernetes' deprecation policy
# guarantees a beta API at least nine months or three releases after
# deprecation, so this is a housekeeping item, not a deadline.Il n’y a donc rien à faire le jour de la mise à jour, et c’est bien l’idée — c’est une chose que vous adoptez à votre rythme, pas une chose qui vous arrive. Là où cela compte, c’est dans le code qui vous appartient : un autoscaler maison, un rapport de capacité, un back-end de tableau de bord, tout ce qui parle directement à l’API devrait quitter le chemin bêta pendant que les deux sont disponibles, plutôt qu’après. Il n’y a pas encore de date de suppression pour v1beta1, et la politique d’obsolescence garantit à une API bêta au moins neuf mois ou trois versions : traitez cela comme de l’entretien courant et non comme une échéance.[hpa]
Le reste de la version, brièvement
Trois autres points de cette version méritent d’être connus, même si aucun n’affectera une mise à jour. Tous les trois sont des graduations et non des suppressions, et le premier est celui à surveiller.[kep4960]
- Le kubelet dans un espace de noms utilisateur — mode rootless — passe en bêta. Les composants de nœud tournent traditionnellement en root sur l’hôte. Ceci leur permet de tourner en utilisateur non privilégié sur l’hôte tout en apparaissant comme root à l’intérieur d’un espace de noms utilisateur Linux, ce qui limite le rayon d’action d’une vulnérabilité dans un composant de nœud. Bêta signifie ici que la gate est activée par défaut, mais l’activer ne suffit pas à faire tourner le kubelet dans un espace de noms utilisateur — il y a de la configuration hôte derrière — et le changement n’a pas figuré dans l’annonce de version. Voyez-y quelque chose à essayer sur un nœud de test plutôt que quelque chose qui vous est arrivé. Ce n’est pas non plus la même fonctionnalité que les espaces de noms utilisateur pour les pods, passés en bêta en v1.35 et en GA en v1.36.
- Le ré-étiquetage SELinux des volumes ReadWriteOncePod était déjà en GA en v1.36. Il s’agit de
SELinuxMountReadWriteOncePod, une gate plus étroite que leSELinuxMountdont traite cet article, et c’est pourquoi certains clusters montent avec une option de contexte depuis un moment sans que rien ne casse — les volumes RWOP ne peuvent pas être partagés par définition, donc le conflit décrit ici ne peut pas s’y produire. - La surveillance de santé des volumes repart en alpha. Une première implémentation était arrivée en v1.21 et n’a jamais gradué ; KEP-1432 remet le compteur à zéro derrière la gate
CSIVolumeHealthet introduit quatre RPC CSI —ControllerListVolumeHealth,ControllerGetVolumeHealth,NodeGetVolumeHealthetNodeGetStorageHealth— dont les résultats remontent dansPersistentVolumeClaim.status.healthStatus,Pod.status.volumeHealthetCSINode.status.storageHealth. Le vocabulaire est volontairement réduit et lisible par une machine :Inaccessible,DataLossetDegraded, avecreasonetmessagesur la condition pour le détail propre au pilote.
Alpha veut dire désactivé par défaut et pas pour la production, mais le travail sur la surveillance de santé mérite d’être suivi si vous avez déjà eu à recouper un montage figé avec le tableau de bord d’un fournisseur de stockage pour comprendre ce qui n’allait pas. C’est la première réponse lisible par une machine que Kubernetes propose à cette question.[kep1432][userns]
La mise à jour, dans l’ordre
L’ordre compte davantage que les commandes. Tout ce qui est réellement difficile dans cette mise à jour l’est avant elle, et la seule étape qui devient nettement plus coûteuse ensuite est l’audit SELinux — elle passe donc en premier, avec des semaines d’avance plutôt que des minutes.[kubeadmup]
# The order matters more than the commands, and the SELinux audit has to come
# first because it is the only step that is materially harder after the
# upgrade than before it.
# --- WEEKS BEFORE, on v1.36 ------------------------------------------------
# 1. Turn on selinux-warning-controller, read both metrics, apply the opt-outs.
# 2. Audit static pods for Secret and ConfigMap references. Move them to
# DaemonSets where you can.
# 3. Get every node onto containerd 2.x and cgroup v2, if any are not.
# Both of these are already overdue rather than upcoming.
# 4. Decide about ipvs - and then do it in a DIFFERENT window.
# --- THE DAY ---------------------------------------------------------------
# Control plane first, one node at a time. Nothing here is 1.37-specific;
# it is the standard kubeadm sequence and it is standard because it works.
sudo apt-mark unhold kubeadm && sudo apt-get update \
&& sudo apt-get install -y kubeadm='1.37.0-*' && sudo apt-mark hold kubeadm
sudo kubeadm upgrade plan
sudo kubeadm upgrade apply v1.37.0 # first control plane node
# sudo kubeadm upgrade node # every other control plane node
# Then the kubelet and kubectl on that same node:
sudo apt-mark unhold kubelet kubectl && sudo apt-get update \
&& sudo apt-get install -y kubelet='1.37.0-*' kubectl='1.37.0-*' \
&& sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload && sudo systemctl restart kubelet
# --- WORKER NODES, ONE AT A TIME -------------------------------------------
NODE=node-02
kubectl drain "$NODE" --ignore-daemonsets --delete-emptydir-data --timeout=15m
# ... upgrade kubeadm, run `kubeadm upgrade node`, upgrade kubelet, restart ...
kubectl uncordon "$NODE"
# Then STOP and look, before the next node. The failure this release can
# produce is a pod stuck in ContainerCreating, and it is per-node:
kubectl get pods -A --field-selector spec.nodeName="$NODE" \
-o wide | grep -v Running | grep -v Completed
# The version skew policy is what makes the staged rollout legal: a kubelet
# may be up to three minor versions behind the API server, so a fleet halfway
# through this is a supported configuration rather than a risk in itself.Une remarque rassurante sur le fait d’y aller progressivement : la politique de version skew autorise un kubelet à être jusqu’à trois versions mineures en retard sur l’API server, si bien qu’un parc à mi-parcours de ce déploiement est une configuration prise en charge et non un risque en soi. Prenez le temps. Mettez à jour un nœud, regardez-le correctement, et seulement ensuite continuez — parce que la panne que cette version peut produire est propre à un nœud et se manifeste par un pod qui ne démarre jamais plutôt que par une erreur qui réveille quelqu’un.[skew]
Revenir en arrière, et ce qui ne revient pas
Le retour arrière mérite une réponse franche plutôt que rassurante, et sur cette mise à jour la réponse a trois parties. Ce qui est réversible l’est vraiment : le mode de kube-proxy est un ConfigMap et un redémarrage de DaemonSet, et seLinuxChangePolicy est un champ de Pod qui se comporte de façon identique en v1.36 et en v1.37 — c’est précisément pour cela que l’appliquer avant la mise à jour ne coûte rien et vous achète tout le scénario de retour arrière.[kubeadmup]
# Rollback deserves a straight answer. Most of this release rolls back; one
# part of it does not roll back in the way people assume.
# --- What rolls back cleanly ----------------------------------------------
# The kube-proxy mode change: it is a ConfigMap and a DaemonSet restart.
kubectl -n kube-system apply -f /tmp/kube-proxy.bak.yaml
kubectl -n kube-system rollout restart daemonset/kube-proxy
# The seLinuxChangePolicy opt-out: it is a Pod field. Setting it to Recursive
# is safe on v1.36 and v1.37 alike, which is why applying it BEFORE the
# upgrade costs nothing and buys the whole rollback story.
# --- What does not ---------------------------------------------------------
# The control plane. `kubeadm upgrade` has no downgrade path: going back means
# restoring the etcd snapshot you took before you started, which means losing
# everything written to the cluster since. If you did not take one, you do not
# have a rollback - you have a forward fix.
sudo ETCDCTL_API=3 etcdctl \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
snapshot save "/var/backups/etcd-pre-1.37-$(date +%F).db"
# The static pod references. If a static pod was relying on a secretRef, the
# reference is gone on 1.37 and comes back on a downgrade - but the downgrade
# is the control plane operation above, so in practice the fix is forward:
# move it to a DaemonSet.
# --- The one that surprises people ----------------------------------------
# Downgrading the kubelet does NOT un-stick a pod that failed to mount because
# of an SELinux label conflict, because the pod that WON the volume is still
# holding it with its own context. Terminate one of the two, or set
# seLinuxChangePolicy: Recursive on both and let them share again. Version
# numbers are not the lever here; the Pod field is.Le plan de contrôle est la partie qui ne revient pas. kubeadm upgrade n’a pas de chemin de rétrogradation ; revenir en arrière signifie restaurer l’instantané etcd pris avant de commencer, et perdre tout ce qui a été écrit dans le cluster depuis. Si vous n’en avez pas pris, vous n’avez pas de retour arrière — vous avez une correction vers l’avant, et c’est une tout autre conversation à tenir à deux heures du matin. Et il y a une panne qui surprend : rétrograder le kubelet ne débloque pas un pod qui n’a pas pu monter son volume à cause d’un conflit d’étiquettes SELinux, parce que le pod qui a gagné le volume le tient toujours avec son propre contexte. Le levier ici est le champ du Pod, pas le numéro de version.[selblog]
Vérifier plutôt qu’espérer
La vérification de cette mise à jour a une forme particulière, parce que la panne caractéristique laisse tout au vert. Le nœud est Ready. Le kubelet est en bonne santé. Le plan de contrôle va bien. Il y a simplement un pod qui ne finit jamais de se créer, sur un nœud, appartenant à une équipe. Vérifier que le cluster est debout ne prouve donc absolument rien : il faut vérifier ce qui peut être faux en silence.[chlog]
#!/usr/bin/env bash
# Post-upgrade verification. Exits non-zero when something is wrong, so it can
# run between nodes in a pipeline rather than being read by a person at 3am.
set -uo pipefail
rc=0
fail() { printf ' FAIL %s\n' "$*"; rc=1; }
pass() { printf ' ok %s\n' "$*"; }
echo '== versions =='
kubectl version -o json | jq -r '.serverVersion.gitVersion'
echo '== every node Ready and on the expected version =='
bad=$(kubectl get nodes --no-headers | awk '$2!="Ready"{print $1}')
[ -z "$bad" ] && pass 'all nodes Ready' || fail "not Ready: $bad"
echo '== no pod stuck creating (the SELinux failure mode) =='
stuck=$(kubectl get pods -A --no-headers \
| awk '$4=="ContainerCreating"{print $1"/"$2}')
[ -z "$stuck" ] && pass 'nothing in ContainerCreating' || fail "stuck: $stuck"
echo '== SELinux context mismatches that became real failures =='
for n in $(kubectl get nodes -o name); do
v=$(kubectl get --raw "/api/v1/nodes/${n#node/}/proxy/metrics" 2>/dev/null \
| awk '/^volume_manager_selinux_volume_context_mismatch_errors_total/{s+=$2} END{print s+0}')
[ "$v" = "0" ] && pass "${n#node/}: 0 mismatch errors" \
|| fail "${n#node/}: $v SELinux mismatch errors"
done
echo '== static pods all running =='
sp=$(kubectl get pods -A -o json \
| jq -r '.items[] | select(.metadata.annotations["kubernetes.io/config.source"]=="file")
| select(.status.phase!="Running") | .metadata.namespace+"/"+.metadata.name')
[ -z "$sp" ] && pass 'static pods Running' || fail "static pods not Running: $sp"
echo '== the resource metrics API answers on both versions =='
kubectl get --raw /apis/metrics.k8s.io | jq -e \
'[.versions[].groupVersion] | index("metrics.k8s.io/v1")' >/dev/null \
&& pass 'metrics.k8s.io/v1 served' || fail 'metrics.k8s.io/v1 missing'
kubectl top nodes >/dev/null 2>&1 && pass 'kubectl top works' || fail 'kubectl top broken'
echo '== service traffic actually flows =='
kubectl run verify-net --rm -i --restart=Never --image=busybox --timeout=60s -- \
sh -c 'wget -qO- --timeout=5 http://kubernetes.default.svc/healthz' >/dev/null 2>&1 \
&& pass 'in-cluster Service reachable' || fail 'in-cluster Service unreachable'
exit $rcLancez-le entre les nœuds plutôt qu’à la fin. Le script sort avec un code non nul, ce qui lui permet d’occuper une étape de pipeline au lieu d’être lu par un humain à trois heures du matin, et les deux contrôles à conserver même une fois la mise à jour derrière vous sont le balayage des ContainerCreating et le compteur de non-concordance SELinux. Les deux sont peu coûteux, et les deux attrapent une classe de problèmes qui autrement attend que quelqu’un remarque que sa charge n’est jamais revenue.[k8spatch]
L’ordre dans lequel faire tout ça
Résumée, c’est une mise à jour plus petite que ce que l’article laisse croire — à condition d’avoir fait le travail qui appartenait aux deux versions précédentes. Le tableau de décision ci-dessous demande en réalité laquelle des deux vous avez sautée.[sneak]
| Si votre situation est… | alors 1.37 est… | et le travail est… |
|---|---|---|
| En 1.36, aucun SELinux nulle part, containerd 2.x, cgroup v2 | Une mise à jour de routine | La séquence kubeadm standard. Passez au grep les manifestes de pods statiques et allez-y |
En 1.36, SELinux en enforcing, pilotes CSI avec seLinuxMount: true | Celle qui demande un audit | Activez le contrôleur d’avertissement maintenant, lisez les deux métriques, appliquez Recursive là où elles pointent, puis mettez à jour |
| En 1.34 ou 1.35, avec l’idée de sauter directement en 1.37 | Deux ou trois versions de changements d’un coup | Lisez les notes de chaque version que vous enjambez. La restriction sur les pods statiques, le défaut cgroup et l’avertissement ExternalIPs y sont tous les trois |
| Un nœud encore en cgroup v1 | Pas votre problème le plus urgent | Ce nœud porte un contournement depuis la 1.35. Convertissez-le, séparément, avant de mettre à jour |
| Un nœud encore en containerd 1.x | Sans problème aujourd’hui, cassé en 1.38 | Ce n’est pas du travail pour le jour de la mise à jour. Collectez cri_losing_support, puis réservez la migration du runtime dans sa propre fenêtre avant de prendre la 1.38 |
kube-proxy en mode ipvs | Une ligne de journal, rien de plus | Migrez vers nftables un autre jour. Vous avez jusqu’à la 1.43, et grouper les deux masque la cause de toute panne |
| Un service managé (GKE, EKS, AKS) | Ce que le fournisseur planifie | L’audit SELinux reste le vôtre — les charges et les pilotes CSI sont à vous même quand les nœuds ne le sont pas |
- Répondez d’abord à la question SELinux, en une commande. Si aucun nœud ne fait tourner SELinux en mode enforcing, sautez entièrement la plus grosse section de cet article et traitez la 1.37 comme une mise à jour de routine. Si un seul nœud le fait, tout ce qui suit s’applique.
- Activez
selinux-warning-controlleren v1.36 et lisez les deux métriques. Le contrôleur nomme les pods en conflit ; le kubelet compte ceux qui échoueront réellement. Faites-le des semaines à l’avance, parce que c’est nettement plus difficile après la mise à jour et que les pods sont alors bloqués et non plus seulement en risque. - Appliquez
seLinuxChangePolicy: Recursiveaux charges que les métriques ont nommées, par politique plutôt qu’à la main. C’est une API stable en v1.36, cela ne change rien aujourd’hui, et cela fait ce qu’il faut à la seconde où la mise à jour arrive. Limitez la portée aux namespaces concernés plutôt qu’au cluster entier. - Passez au grep le répertoire de pods statiques de chaque nœud à la recherche de
secretRefetconfigMapRef. C’est le défaut depuis la v1.34 : sur un cluster qui a suivi les versions une à une, cela s’est déjà produit ; sur un cluster qui a sauté des versions, cela vous attend. Déplacez ce que vous trouvez vers un DaemonSet, ce qu’il aurait presque toujours dû être. - Soldez la dette cgroup v1 de la 1.35, et inscrivez la migration containerd à l’agenda avant la 1.38. Un nœud en cgroup v1 porte un contournement depuis deux versions. Un nœud en containerd 1.x fonctionne encore aujourd’hui et cessera de fonctionner dans une version. Chacun mérite sa propre fenêtre — et laissez la migration d’ipvs vers nftables pour un tout autre jour.
Deux des dettes ci-dessus ont leur propre runbook, car aucune n’est un travail de cinq minutes : la migration de containerd 1.7 vers 2.x, qui est la suppression de containerd 1.x vue du côté du runtime, et la migration de cgroup v1 vers cgroup v2, qui est l’audit et la conversion derrière le contournement failCgroupV1. Si la même fenêtre de maintenance reconstruit aussi votre ingress, le passage d’ingress-nginx à la Gateway API couvre cette migration. Et si c’est le moment où quelqu’un dans la pièce demande si tout cela en vaut la peine, quand ne pas utiliser Kubernetes est l’autre versant de cet argument, exposé honnêtement.
Questions fréquentes
Quand sort Kubernetes 1.37, et qu’est-ce qui casse réellement ?
La v1.37 est sortie le 26 août 2026. Un seul changement qu’elle contient peut arrêter une charge de travail : SELinuxMount passe en GA et est activé par défaut, ce qui peut laisser des pods en ContainerCreating là où deux pods d’étiquettes différentes partagent un volume, sur un nœud avec SELinux en enforcing et un pilote CSI qui a opté pour le montage à contexte. Le reste de ce qui est réellement nouveau est discret — la feature gate KubeProxyIPVS est ajoutée et aussitôt marquée obsolète, metrics.k8s.io passe en v1 avec les deux versions toujours servies, et kubectl run --filename/-f est déclaré obsolète, ce qui touche les scripts plutôt que les clusters. Tout le reste de ce qu’on attribue à cette version a atterri plus tôt ou n’a pas encore atterri : l’échec du kubelet sur cgroup v1 est de la v1.35, la restriction sur les Secrets des pods statiques est de la v1.34, le mode ipvs est obsolète depuis la v1.35 et sera supprimé en v1.43, et containerd 1.x fonctionne toujours — son repli CRI n’est abandonné qu’en v1.38.
Kubernetes 1.37 supprime-t-il le mode ipvs de kube-proxy ?
Non, et il ne l’a pas non plus déclaré obsolète — cela s’est passé en v1.35, et kube-proxy consigne un avertissement à son démarrage depuis. Ce que la v1.37 ajoute, c’est la feature gate KubeProxyIPVS, elle-même marquée obsolète, qui est l’interrupteur qui désactivera le mode plus tard. Rien ne cesse de fonctionner et aucun trafic n’est affecté. KEP-5495 pose le calendrier : la gate devrait passer à false par défaut d’ici la v1.40, avec un support entièrement supprimé d’ici la v1.43. La raison de l’obsolescence est que l’API ipvs du noyau ne peut pas exprimer tout ce dont un Service Kubernetes a besoin, si bien que le mode ipvs s’est toujours rabattu sur iptables en dessous pour une partie du travail. La cible de migration est le mode nftables, en GA depuis la v1.33 — et les clusters ne basculent jamais tout seuls, il faut définir mode: "nftables" soi-même. Faites-le dans une fenêtre de maintenance distincte de la montée de version.
Pourquoi mes pods sont-ils bloqués en ContainerCreating après le passage en 1.37 ?
Si le nœud fait tourner SELinux en mode enforcing, la cause probable est un conflit de partage de volume introduit par le passage en GA de SELinuxMount. Les volumes sont désormais montés avec -o context=<label> au lieu d’être ré-étiquetés récursivement, et un montage ne peut porter qu’un seul contexte SELinux — deux pods d’étiquettes différentes qui partagent un volume sur un même nœud ne peuvent donc plus coexister. L’un reste en ContainerCreating jusqu’à la fin de l’autre. Le cas classique est un pod privilégié et un pod non privilégié qui partagent un volume. Confirmez-le en lisant la métrique du kubelet volume_manager_selinux_volume_context_mismatch_errors_total sur ce nœud. Le correctif est de poser spec.securityContext.seLinuxChangePolicy: Recursive sur les pods concernés — et notez que rétrograder le kubelet ne libérera pas le volume, parce que le pod qui l’a gagné en tient toujours le contexte.
Comment trouver les conflits de volumes SELinux avant de mettre à jour ?
Activez le selinux-warning-controller livré en v1.36 en passant --controllers=*,selinux-warning-controller à kube-controller-manager, et assurez-vous de ne pas avoir explicitement désactivé la feature gate SELinuxChangePolicy. Lisez ensuite deux métriques. selinux_warning_controller_selinux_volume_conflict porte en étiquettes les noms et les namespaces des pods en conflit — c’est votre liste de travail, et elle signale les conflits même quand les pods sont sur des nœuds différents, parce que l’ordonnanceur peut les regrouper plus tard. volume_manager_selinux_volume_context_mismatch_warnings_total, émise par le kubelet pendant que SELinuxMount est encore désactivé, n’a pas d’étiquette de nom de pod mais donne le décompte honnête des pods qui échoueraient réellement. Il vous faut les deux. Faites-le en v1.36, pendant que le compteur d’avertissements est non nul et que tout fonctionne encore.
Que fait réellement seLinuxChangePolicy: Recursive, et est-il sûr de le poser dès maintenant ?
Il exempte ce pod du chemin par option de montage et conserve le comportement d’avant la 1.37 : le runtime de conteneurs parcourt le volume et ré-étiquette chaque fichier. Le coût est le gain de performance — sur un volume volumineux ou distant, le ré-étiquetage récursif est sincèrement lent — et le bénéfice est que deux pods d’étiquettes différentes peuvent à nouveau partager le volume. C’est une API de Pod stable depuis la v1.36, donc le poser aujourd’hui ne change rien au comportement actuel de votre cluster et fait ce qu’il faut à la seconde où la mise à jour arrive. C’est l’assurance la moins chère de cette version. Appliquez-le via une MutatingAdmissionPolicy ou un moteur de politiques plutôt qu’en éditant chaque Deployment, et limitez la portée aux namespaces que les métriques de conflit ont réellement nommés plutôt qu’au cluster entier — un Recursive général jette l’amélioration pour toutes les charges qui n’ont jamais été en risque.
Mon cluster n’utilise pas SELinux. Est-ce que quoi que ce soit me concerne ?
Presque rien. Quand SELinux est indisponible ou désactivé dans le noyau, le kubelet saute l’intégralité du chemin de code SELinux : le changement SELinuxMount est donc sans effet pour vous et la plus grosse section de cet article peut être ignorée. Ce qui s’applique encore, c’est la restriction sur les pods statiques — passez au grep le répertoire de pods statiques de chaque nœud à la recherche de secretRef et configMapRef, qui est le défaut depuis la v1.34 et ne mord donc que les clusters ayant sauté des versions —, plus la dette cgroup v2 héritée de la v1.35 si vous ne l’avez pas soldée, et la migration vers containerd 2.x, qui n’est pas urgente en 1.37 mais doit être faite avant la 1.38. Vérifiez tout de même getenforce sur chaque nœud plutôt que de supposer : un parc hétérogène où un pool de nœuds embarque une image avec SELinux activé est plus fréquent qu’on ne le croit.
La 1.37 empêche-t-elle le kubelet de démarrer sur cgroup v1 ?
Ce changement ne vient pas de la 1.37. Le réglage failCgroupV1 du kubelet vaut true par défaut depuis la v1.35, donc un nœud en cgroup v1 échoue à démarrer son kubelet depuis cette date, à moins que quelqu’un n’ait ajouté failCgroupV1: false. La v1.37 honore toujours ce contournement. KEP-5573 supprime purement et simplement la prise en charge de cgroup v1 dans une version ultérieure, mais aucune date n’a été engagée : l’hypothèse de planification doit donc être « la version qui conviendra à SIG Node » plutôt qu’un mois. La raison de ne pas rester sur le contournement, c’est que le redimensionnement de pods à chaud et la protection mémoire hiérarchisée exigent tous deux cgroup v2 et ne fonctionnent tout simplement pas sans lui. Basculer un nœud demande un changement de ligne de commande du noyau (systemd.unified_cgroup_hierarchy=1), un redémarrage, et la mise en accord du pilote de cgroups du runtime avec celui du kubelet.
Ai-je besoin de containerd 2.0 pour Kubernetes 1.37 ?
Non — et c’est l’affirmation le plus souvent énoncée à l’envers. containerd 1.x tourne toujours face à un kubelet v1.37. Il le fait grâce à un repli : le kubelet demande au runtime son pilote de cgroups via le RPC CRI RuntimeConfig, containerd 1.y n’implémente pas ce RPC, et le kubelet retombe sur sa propre valeur de --cgroup-driver tout en incrémentant la métrique cri_losing_support. Ce repli devait être supprimé en v1.37 et a été reporté d’une version pour s’aligner sur la fenêtre de support de containerd v1.7 : il disparaît donc en v1.38, moment à partir duquel les anciennes versions de containerd échouent bel et bien face aux kubelets plus récents. Deux réserves rendent la situation moins confortable qu’il n’y paraît : le support étendu de containerd 1.7 s’achève en septembre 2026, et la matrice de support Kubernetes publiée par containerd n’a pas encore de ligne pour la 1.37, si bien que personne ne peut aujourd’hui citer un appariement officiellement béni. Auditez avec cri_losing_support et containerRuntimeVersion, et réservez la migration avant de prendre la 1.38 — dans sa propre fenêtre, pas groupée avec une montée de version.
Par quoi remplacer un pod statique qui lit un Secret ?
Par un DaemonSet, dans presque tous les cas. Les pods statiques sont gérés par le kubelet depuis un répertoire sur disque plutôt que créés via l’API server, donc lire des objets de l’API n’était jamais censé fonctionner — un défaut le laissait passer par des champs comme configMapRef et secretRef. La gate PreventStaticPodAPIReferences qui referme ce défaut est active par défaut depuis la v1.34 : ce n’est donc pas une nouveauté de la 1.37 — cela ne paraît nouveau qu’aux clusters qui ont sauté plusieurs versions. L’avant-goût de la v1.37 annonçait la suppression pure et simple de la gate ; la référence des feature gates publiée avec la v1.37 la liste pourtant toujours comme une gate bêta valant true par défaut. Dans les deux cas, ne bâtissez pas votre plan sur l’échappatoire. La plupart des pods statiques existent pour des raisons historiques, d’avant que les DaemonSets soient aussi complets qu’aujourd’hui ; un DaemonSet lit les Secrets normalement, bénéficie des mises à jour progressives et apparaît là où les gens le cherchent. Si quelque chose doit vraiment rester statique, écrivez la valeur en dur dans le manifeste ou montez un fichier de l’hôte en bind-mount et lisez-le depuis là. Les deux sont moins bien qu’un DaemonSet et les deux fonctionnent. Trouvez-les avant la mise à jour plutôt qu’un nœud à la fois.
Faut-il passer directement de 1.34 ou 1.35 à 1.37 ?
Vous le pouvez — la politique de version skew autorise un kubelet jusqu’à trois versions mineures en retard sur l’API server, et kubeadm traite le plan de contrôle une version à la fois — mais le risque n’est pas la mécanique, c’est la lecture. Sauter des versions signifie que toutes les obsolescences des versions sautées arrivent d’un coup, et qu’elles sont documentées version par version et non cumulativement. En venant de la 1.35, vous héritez aussi des changements de la v1.36 : l’avertissement d’obsolescence des ExternalIPs de Service, le passage en GA de SELinuxMountReadWriteOncePod et de SELinuxChangePolicy, et les espaces de noms utilisateur pour les pods devenus stables. En venant de la 1.34, vous héritez en plus du basculement du défaut de failCgroupV1 en v1.35 — qui arrêtera purement et simplement le kubelet sur un nœud en cgroup v1. Lisez le billet de l’équipe de release pour chaque version que vous enjambez. Trois d’entre eux prennent une vingtaine de minutes et valent mieux que la plupart de ce qui occupe une fenêtre de maintenance.
Peut-on revenir en arrière après une mise à jour vers 1.37 ?
En partie, et les parties comptent. Le changement de mode de kube-proxy revient proprement — c’est un ConfigMap et un redémarrage de DaemonSet. seLinuxChangePolicy est un champ de Pod qui se comporte de la même façon en v1.36 et en v1.37, et c’est pourquoi l’appliquer en amont ne coûte rien. Le plan de contrôle, lui, ne revient pas : kubeadm upgrade n’a pas de chemin de rétrogradation, donc revenir en arrière signifie restaurer l’instantané etcd pris avant de commencer et perdre tout ce qui a été écrit depuis. Prenez cet instantané. Et une panne surprend : rétrograder le kubelet ne débloque pas un pod qui n’a pas pu monter son volume à cause d’un conflit SELinux, parce que le pod qui a gagné le volume en tient toujours le contexte de montage. Le levier est seLinuxChangePolicy sur les deux pods, pas le numéro de version.
La v1beta1 de metrics.k8s.io va-t-elle disparaître ?
Pas encore, et pas sans préavis. metrics.k8s.io passe en v1 en v1.37 après près de neuf ans de bêta, et v1 comme v1beta1 restent servies pendant la transition, précisément pour que l’adoption se fasse à votre rythme. Aucun changement fonctionnel n’est attendu — la graduation acte une stabilité, elle ne l’introduit pas. kubectl top et le HorizontalPodAutoscaler continuent de fonctionner sans aucune action. Ce qui vaut la peine d’être fait, c’est de sortir du chemin bêta le code qui vous appartient — un autoscaler maison, un rapport de capacité, un back-end de tableau de bord — pendant que les deux sont disponibles. La politique d’obsolescence de Kubernetes garantit à une API bêta au moins neuf mois ou trois versions après son obsolescence, et aucune date de suppression n’a été donnée pour v1beta1 : c’est donc de l’entretien courant et non une échéance.
Sources
Sources primaires d’abord. L’avant-goût de l’équipe de release et le changelog de la v1.37 sont les seules déclarations faisant autorité sur ce que contient cette version ; les KEP sont le seul endroit où les calendriers étalés sur plusieurs versions sont écrits, ce qui en fait l’antidote à un résumé qui écrit « supprimé en 1.37 » à propos de quelque chose qui est supprimé en 1.43. Là où cet article corrige quelque chose — que la panne cgroup est un changement de la v1.35, que la restriction sur les pods statiques est un changement de la v1.34, qu’ipvs est déclaré obsolète depuis la v1.35 plutôt que supprimé en v1.37, et que la falaise containerd est devant vous en v1.38 plutôt que derrière vous en v1.36 —, le désaccord porte sur la couverture secondaire, pas sur le projet. Un désaccord est interne au projet et mérite d’être nommé : l’avant-goût affirme que la gate PreventStaticPodAPIReferences a été supprimée dans cette version, et la référence des feature gates publiée avec la v1.37 la liste toujours. C’est la page de référence qu’il faut croire sur ce qui a réellement été livré.
- Kubernetes v1.37 Sneak Peek - the release team's own list of what is deprecated, removed and breaking in this release, published 31 July 2026. This is the document that separates "new in 1.37" from "still in progress", and it carries its own caveat that the information reflects the state of the release before the release date
- Kubernetes CHANGELOG-1.37.md - the authoritative record once the release is cut on 26 August 2026. Where this article and the changelog disagree after that date, the changelog is right and this page is a snapshot of the plan
- SELinux Volume Label Changes goes GA (and likely implications in v1.37) - the pre-announcement by the feature's own authors. It contains the five conditions for a mount-option relabel, the two conflict scenarios, the seLinuxChangePolicy opt-out, the selinux-warning-controller and the recommended upgrade path. This is the single most important source for this article
- KEP-1710: Speed up SELinux volume relabeling using mounts. The enhancement proposal behind SELinuxMount, including why a mount can hold only one context and therefore why volume sharing across differently labelled pods stops working
- KEP-1710, "Story 3: cluster upgrade" - the upgrade scenario written by the authors, which is the closest thing to an official runbook for this change
- Kubernetes - Configure a Security Context for a Pod or Container: seLinuxOptions, the seLinuxChangePolicy field, efficient SELinux volume relabeling and the selinux-warning-controller
- Kubernetes API reference - CSIDriver: the seLinuxMount field a driver has to set to true before the kubelet will mount its volumes with a context option. Drivers that do not set it keep the old recursive behaviour, which is why the blast radius of this change is driver-specific
- Kubernetes - Feature Gates: the per-release state of SELinuxMount, SELinuxChangePolicy, SELinuxMountReadWriteOncePod and KubeProxyIPVS. The table is the fastest way to check a claim about which release flipped which default
- KEP-5495: Deprecate ipvs mode in kube-proxy. The deprecation timeline this article quotes - warning now, feature gate defaulting to false by v1.40, removal by v1.43 - comes from its graduation criteria and nowhere else
- KEP-5495 README on GitHub: the same document at its source, including the graduation criteria table with the release numbers
- KEP-3866: nftables kube-proxy backend, including the section titled "The ipvs mode of kube-proxy will not save us" - the technical argument that ipvs mode never stopped depending on iptables underneath, which is the reason for the deprecation
- NFTables mode for kube-proxy - the introduction to the mode that replaces both iptables and ipvs, and the migration notes for moving to it
- Kubernetes - kube-proxy configuration (v1alpha1) reference: the `mode` field this article tells you to read and change, and the rest of the KubeProxyConfiguration surface
- KEP-5573: Remove CGroup v1 support. The staged removal plan behind the kubelet's failCgroupV1 setting, and the statement that the override is temporary
- Kubernetes - About cgroup v2: how to check which version a node is on, the requirements for cgroup v2, and the features that depend on it
- Kubernetes - kubelet configuration (v1beta1) reference: failCgroupV1 and the rest of the KubeletConfiguration fields this article edits
- Kubernetes - Static Pods: what they are, why the kubelet manages them directly rather than through the API server, and therefore why referencing API objects from one was never supposed to work
- kubernetes/kubernetes issue 140226 - the discussion behind prohibiting Secret and ConfigMap references from static pods, and the fate of the PreventStaticPodAPIReferences feature gate that allowed an opt-out. The gate has defaulted to on since v1.34; the v1.37 sneak peek says it was removed in this release while the shipped v1.37 feature-gates reference still lists it, so the reference page is the one to trust
- kubernetes/kubernetes issue 138671 - the deprecation of `kubectl run --filename/-f`, on the grounds that the pod it produces is always built purely from the command-line arguments
- KEP-5207: metrics.k8s.io API definition. The enhancement that graduates the resource metrics API to stable after nearly nine years in beta, and the statement that v1 and v1beta1 both remain available during the transition
- Kubernetes - Resource metrics pipeline: what metrics.k8s.io actually serves, who serves it, and how `kubectl top` and the HorizontalPodAutoscaler consume it
- Kubernetes - Horizontal Pod Autoscaling: the largest consumer of the resource metrics API, and the reason its graduation matters beyond `kubectl top`
- KEP-2033 / KEP-4960: Kubelet in UserNS, also known as rootless mode. Graduating to beta in v1.37, which lets node components run as an unprivileged user on the host while still appearing as root inside the namespace
- Kubernetes - User namespaces for pods: the workload-level feature that reached GA in v1.36, distinct from the rootless kubelet but built on the same kernel mechanism
- KEP-1432: Volume health monitoring. Reset to alpha in v1.37 with four new CSI RPCs and three new status fields, after an initial implementation in v1.21 that never graduated
- Kubernetes v1.34: Of Wind & Will - the release that opened the containerd 1.x end-of-support discussion and the CRI cgroup-driver work behind it. Read alongside the container runtimes page, which records where that timeline actually ended up: the fallback is dropped in v1.38, not v1.36
- Kubernetes v1.36 release announcement - the release that made user namespaces GA, graduated SELinuxMountReadWriteOncePod, and shipped the selinux-warning-controller that this article tells you to switch on
- Kubernetes v1.36: Deprecation and removal of Service ExternalIPs - note that despite the title, v1.36 only deprecates the field and starts emitting warnings; kube-proxy support goes off by default no earlier than v1.40 and removal is no earlier than v1.43. A good illustration both of why reading one release's notes is not enough, and of how a headline turns into a rumour
- Kubernetes - Releases: the supported branches and their end-of-life dates. The source for the support window this article uses to argue about how much runway a cluster actually has
- Kubernetes - Patch releases: the cadence, the support period and the maintenance-mode window at the end of each minor release's life
- Kubernetes - Version skew policy: how far the kubelet may lag the API server, which is what makes a staged node upgrade legal in the first place
- Kubernetes - Upgrading kubeadm clusters: the control plane first, then one node at a time, with drain and uncordon around each. The sequence the runbook in this article slots into
- Kubernetes - Deprecation policy: the rules that govern how long a deprecated feature must survive before removal, which is why an ipvs deprecation in v1.37 cannot become a removal before v1.43
- Kubernetes - Container runtimes: installing and configuring containerd or CRI-O, including the cgroup driver requirement that ties this article to the cgroup v2 migration. It is also the page that settles the containerd question, stating that older containerd versions still work today through the kubelet's cgroup-driver fallback and that in Kubernetes 1.38 that fallback is dropped and they will fail with newer kubelets
- containerd - Versioning and release: the release-status table and the Kubernetes/containerd support matrix. As of September 2026 the matrix stops at Kubernetes 1.36 (2.3.0+, 2.2.0+) and has no row for 1.37, and containerd 1.7's extended LTS support ends in September 2026
- Kubernetes - Releases: the release and support dates quoted in this article, including v1.35 on 17 December 2025, v1.36 on 22 April 2026 and v1.37 on 26 August 2026
- Kubernetes - MutatingAdmissionPolicy: the in-tree way to apply the seLinuxChangePolicy opt-out across a namespace or a cluster without editing every workload by hand
- Kyverno: a policy engine the SELinux blog names explicitly as a way to apply the opt-out fleet-wide. Listed because the alternative - patching every Deployment and StatefulSet individually - does not scale past a small cluster
- Gateway API v1.6: TCPRoute and UDPRoute graduate to Standard. Out of scope for this article but on the same upgrade window for most clusters, and the reason the ingress migration keeps appearing in the same maintenance plan
Cet article vous a été utile ?