Nicht alles davon ist neu in 1.37.
Kubernetes v1.37 ist am 26. August 2026 erschienen. Die meisten der Änderungen, die diesem Release zugeschrieben werden, kamen in v1.34 und v1.35, eine steht erst in v1.38 an – und die eine, die Pods tatsächlich in ContainerCreating hängen lassen kann, wird kaum erwähnt. Hier stehen die ehrliche Aufstellung, die Prüfung vor dem Upgrade und das Runbook.
- Kubernetes
- SELinux
- Upgrades
- Plattform
Kubernetes v1.37 ist am 26. August 2026 erschienen, und die Berichterstattung dazu hat eine Form, die auffallen sollte. Mehrere Änderungen werden diesem Release zugeschrieben, die gar nicht dazugehören: das kubelet, das auf cgroup v1 den Start verweigert (dieser Standardwert kippte in v1.35), Static Pods, die ihre Secret- und ConfigMap-Referenzen verlieren (standardmäßig so seit v1.34), und die Entfernung des ipvs-Modus von kube-proxy (er trägt seit v1.35 einen Veraltungshinweis, die Entfernung ist für v1.43 vorgesehen). Eine Änderung wird in die andere Richtung falsch dargestellt: containerd 1.x läuft weiterhin gegen ein v1.37-kubelet, und die Klippe ist v1.38. Die eine Änderung in diesem Release, die am Upgrade-Tag tatsächlich Pods in ContainerCreating hängen lassen kann – SELinuxMount erreicht GA und schaltet sich standardmäßig ein –, bekommt derweil einen Absatz, weil sie für jeden unsichtbar ist, dessen Cluster kein SELinux fahren.

Diese Unterscheidung ist keine Pedanterie, denn jede Spalte bedeutet andere Arbeit. Wenn Sie die cgroup-Änderung auf 1.37 überrascht, waren Sie schon zwei Releases zu spät dran, und die Behebung ist ein Neustart je Knoten statt eines Rollbacks. Wenn Sie Ihre Container-Runtime in Panik neu bauen, weil Ihnen jemand gesagt hat, containerd 1.x funktioniere nicht mehr, haben Sie ein Wartungsfenster für eine Frist verbraucht, die noch ein Release entfernt ist – real, aber nicht heute. Und wenn Sie die SELinux-Änderung auf 1.37 überrascht, haben Sie Pods, die nicht starten – und die Prüfung, die sie gefunden hätte, ist nach dem Upgrade spürbar mühsamer durchzuführen. Dieser Artikel tut deshalb zweierlei: Er trennt die Aufstellung ehrlich, und dann geht er dort in die Tiefe, wo Arbeit anfällt – die SELinux-Prüfung und der Opt-out vollständig, die tatsächlichen Zeitpläne zu ipvs und containerd und ein Runbook, das die unumkehrbaren Schritte in die richtige Reihenfolge bringt.
Die Fehlerbilder – und zu welchem Release jedes gehört
Beginnen Sie mit dem, was Sie tatsächlich sehen würden, denn keiner dieser Fehler nennt seine eigene Ursache. Ein Pod hängt auf einem Knoten dauerhaft in ContainerCreating, während ein identischer Pod auf einem anderen einwandfrei läuft – das ist ein SELinux-Label-Konflikt, und entschieden hat, welcher Pod zuerst am Volume war. Ein Knoten kommt aus einem Upgrade zurück und sein kubelet startet überhaupt nicht – das ist mit ziemlicher Sicherheit cgroup v1, und das gilt seit v1.35. Ein Monitoring-Agent, der seit 2021 als Static Pod läuft, scheitert auf genau einem Knoten, nämlich dem gerade aktualisierten – das ist die Secret-Referenz, die er nie hätte nutzen können dürfen, und der Standardwert, der sie ihm genommen hat, kam schon in v1.34.[sneak]
| Was Sie sehen | Was es meist bedeutet | Wo es behandelt wird |
|---|---|---|
Ein Pod hängt auf einem Knoten in ContainerCreating, ein identischer Pod läuft anderswo | Ein SELinux-Label-Konflikt auf einem geteilten Volume. Der Pod, der zuerst am Volume war, hält den einzigen Kontext des Mounts | SELinuxMount |
| Das kubelet startet nach einem Upgrade überhaupt nicht | cgroup v1 ohne die Ausnahme failCgroupV1: false. Das gilt seit v1.35 | cgroup v1 |
| Ein Monitoring-Agent, der seit Jahren ein Static Pod ist, scheitert nur auf dem aktualisierten Knoten | Ein secretRef oder configMapRef im Manifest eines Static Pod. Standardmäßig seit v1.34 verboten – es beißt also Cluster, die Releases übersprungen haben | Static Pods |
| kube-proxy protokolliert eine Veraltungswarnung, und alles läuft weiter | Der ipvs-Modus, veraltet seit v1.35. v1.37 fügt das Gate KubeProxyIPVS hinzu; die Entfernung ist für v1.43 vorgesehen | ipvs |
Pods laufen, und die Metrik cri_losing_support ist auf einigen Knoten ungleich null | containerd 1.x hinter dem CRI-Fallback für den cgroup-Treiber. Auf v1.37 weiterhin unterstützt; der Fallback fällt in v1.38 weg | containerd |
Der API-Server protokolliert bei jedem Anwenden eines Service mit externalIPs eine Warnung | Gar nicht v1.37 – Service-ExternalIPs wurde in v1.36 für veraltet erklärt. Entfernt ist noch nichts, und nichts hört auf zu funktionieren | Die Aufstellung |
kubectl top und der HPA laufen nach dem Upgrade weiter | Richtig so. metrics.k8s.io steigt auf v1 auf, und beide Versionen bleiben ausgeliefert | metrics.k8s.io |
Das Muster, das man verinnerlichen sollte: Ein Versions-Upgrade bringt jede Änderung seit dem Release an die Oberfläche, bei dem Sie zuletzt sicher waren – nicht nur die Änderungen des Releases, auf das Sie wechseln. In der Praxis aktualisieren Cluster nicht Minor für Minor; sie sitzen ein Jahr auf 1.34 und ziehen dann um. Service-ExternalIPs ist das aktuelle Beispiel dafür: Es wurde in v1.36 für veraltet erklärt, seither warnt der API-Server bei jeder Verwendung, und wer aus früheren Versionen bei v1.37 ankommt, begegnet dieser Warnung zum ersten Mal. Entfernt ist daran noch nichts – die Unterstützung in kube-proxy soll frühestens ab v1.40 standardmäßig aus sein, die Entfernung frühestens in v1.43 –, und genau so wird aus einer Änderung, die drei Releases entfernt ist, ein Ausfall: Die Warnung kommt in einem Release an, dessen Notes niemand gelesen hat. Genau deshalb ist der zweite Abschnitt dieses Artikels eine Aufstellung und keine Zusammenfassung der Release Notes.[extip]
Die Aufstellung: neu in 1.37 gegenüber längst gelandet
Hier die ehrliche Buchführung. Die linke Spalte enthält, was der Sneak Peek des Release-Teams für v1.37 auflistet; die rechten Spalten, woher die Änderung tatsächlich stammt oder wohin sie noch unterwegs ist. Alles in den Zeilen mit einem früheren Release ist etwas, das Sie längst erledigt haben sollten – und falls nicht, ist das Upgrade-Fenster der Moment, in dem Sie es erfahren; alles in den Zeilen mit einem späteren Release ist ein Termin für den Kalender und keine Arbeit für diese Woche.[sneak]
| Änderung | Üblicherweise zugeschrieben | Passiert tatsächlich in | Was es am Upgrade-Tag auf 1.37 tut |
|---|---|---|---|
SELinuxMount GA, standardmäßig aktiviert | 1.37 | 1.37 | Kann Pods in ContainerCreating hängen lassen, wo sich zwei unterschiedlich gelabelte Pods ein Volume teilen und der CSI-Treiber zugestimmt hat. Das ist das eine, das Arbeit macht |
Feature Gate KubeProxyIPVS ergänzt, als veraltet markiert | 1.37 | 1.37 | Nichts. Das Gate existiert, damit ipvs später standardmäßig abgeschaltet werden kann |
kubectl run --filename/-f veraltet | 1.37 | 1.37 | Nur eine Warnung, auf einem Flag, das ohnehin schon ignoriert wurde. Betrifft Skripte, nicht Cluster |
metrics.k8s.io steigt auf v1 auf | 1.37 | 1.37 | Nichts bricht. v1 und v1beta1 werden während des Übergangs beide ausgeliefert |
| Static Pods dürfen nicht auf Secrets oder ConfigMaps verweisen | 1.37 | 1.34 | Nichts Neues – es sei denn, Sie haben Releases übersprungen, dann bricht es diese Static Pods auf dem gerade aktualisierten Knoten vollständig |
| Das kubelet verweigert den Start auf cgroup v1 | 1.37 | 1.35 | Nichts Neues. Wenn es Sie hier beißt, schleppt der Knoten seit v1.35 failCgroupV1: false mit |
kube-proxy-Modus ipvs veraltet | „entfernt in 1.37“ | 1.35 (nur Warnung) | Eine Logzeile beim Start, wie schon seit zwei Releases. Das Gate steht ab 1.40 standardmäßig auf false; die Entfernung folgt in 1.43 |
Service-ExternalIPs für veraltet erklärt | „entfernt in 1.36“ | 1.36 (nur Warnung) | Eine Warnung des API-Servers. Standardmäßig aus frühestens ab 1.40; Entfernung frühestens in 1.43 |
| CRI-Fallback für containerd 1.x fällt weg | 1.36, manchmal 1.37 | 1.38 | Noch nichts. containerd 1.x läuft auf 1.37 weiterhin hinter dem Fallback, und cri_losing_support zählt die Knoten, die darauf angewiesen sind |
Der nützlichste Satz dieses Artikels: Wenn Ihre Knoten SELinux nicht im Enforcing-Modus betreiben, betrifft Sie der größte Abschnitt hier überhaupt nicht. Das kubelet überspringt den gesamten SELinux-Codepfad, wenn SELinux nicht verfügbar oder im Kernel deaktiviert ist. Prüfen Sie das zuerst – ein einziger Befehl genügt –, denn davon hängt ab, ob dies ein halber Tag Prüfarbeit ist oder eine Lektüre von zehn Minuten.
Bei zwei Punkten lohnt ein Hinweis, wie Sie das selbst nachprüfen, statt es jemandem zu glauben. Die Referenz der Feature Gates veröffentlicht den Stand von SELinuxMount, SELinuxChangePolicy und KubeProxyIPVS je Release; das ist der schnellste Weg, eine Behauptung darüber zu prüfen, welches Release welchen Standardwert gekippt hat. Und der Blogbeitrag des Release-Teams trägt für jede Version die eigene Liste der Veraltungen; drei davon zu lesen dauert zwanzig Minuten und ist eine bessere Verwendung eines Wartungsfensters als das meiste, was sonst hineingeht.[gates]
Wo Sie tatsächlich stehen
Bevor irgendetwas davon handlungsleitend wird, brauchen Sie vier Angaben, und sie sind voneinander unabhängig: die kubelet-Version je Knoten, ob SELinux auf diesem Knoten enforcing ist, welche cgroup-Version er fährt und in welchem Modus kube-proxy läuft. In einer Flotte jeder Größe fallen die Antworten nicht einheitlich aus – Node-Pools rollen nach eigenem Takt vorwärts, und die Version-Skew-Richtlinie erlaubt einem kubelet ausdrücklich, hinter dem API-Server zurückzuliegen. Eine gemischte Flotte ist damit eine unterstützte Konfiguration und kein Zeichen von Vernachlässigung.[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 deleteDie andere Zahl, die zählt, ist der Vorlauf, den die Version, auf der Sie sitzen, tatsächlich noch hat. Kubernetes unterstützt die drei jüngsten Minor-Releases, jeweils rund vierzehn Monate, wobei die letzten beiden Monate im Maintenance-Modus laufen, in dem nur noch kritische Sicherheitsfixes landen. Dieser Zeitplan ist der Grund, warum Upgrades nicht optional sind, und man sollte ihn zur Hand haben, wenn jemand vorschlägt, dieses hier zu verschieben.[k8srel]
| Release | Veröffentlicht | Maintenance-Modus ab | End of Life |
|---|---|---|---|
| 1.34 | 27. August 2025 | 27. August 2026 | 27. Oktober 2026 – noch zwei Monate |
| 1.35 | 17. Dezember 2025 | 28. Dezember 2026 | 28. Februar 2027 |
| 1.36 | 22. April 2026 | 28. April 2027 | 28. Juni 2027 |
| 1.37 | 26. August 2026 | ≈ August 2027 | ≈ Oktober 2027 |
SELinuxMount wird GA: die Änderung, die Pods anhält
Dieser Abschnitt rechtfertigt den Artikel. SELinuxMount erreicht in v1.37 GA und ist standardmäßig aktiviert. Die Änderung bringt Performance, und der Mechanismus ist elegant: Statt dass die Container-Runtime ein Volume durchläuft und jede Inode neu labelt – was auf einem großen oder entfernten Dateisystem wirklich langsam ist –, hängt das kubelet das Volume mit -o context=<label> ein, und der Kernel legt das Label in konstanter Zeit auf jede Inode dieses Mounts. Das Problem folgt unmittelbar aus dem Mechanismus. Ein Mount kann genau einen SELinux-Kontext tragen. Beim rekursiven Relabeln konnten sich zwei Pods mit unterschiedlichen Labels ein Volume teilen; bei einem Kontext-Mount können sie es nicht, und einer von beiden sitzt in ContainerCreating, bis der andere weg ist.[selblog][kep1710]
| Bedingung | Wo Sie es prüfen | Wenn sie nicht zutrifft |
|---|---|---|
| Das Betriebssystem des Knotens unterstützt SELinux, und es ist enforcing | getenforce auf dem Knoten | Nichts ändert sich. Das kubelet überspringt den gesamten SELinux-Pfad |
SELinuxMountReadWriteOncePod ist aktiviert | Ab v1.36 GA und ohne Bedingung | Auf einer unterstützten Version nicht zutreffend |
SELinuxMount und SELinuxChangePolicy sind aktiviert | Feature Gates. SELinuxMount ist in 1.36 Beta und aus, in 1.37 GA und an | Labels werden von der Runtime rekursiv gesetzt, wie bisher |
Der Pod gibt mindestens seLinuxOptions.level an | securityContext von Pod oder Container | Die Runtime vergibt nach dem Mount ein zufälliges Level und labelt ohnehin rekursiv |
Der CSI-Treiber setzt seLinuxMount: true | kubectl get csidrivers -o custom-columns=… | Unverändert. Nicht gesetzt ist nicht true. Im Baum unterstützen nur fc, iscsi und rbd die Option |
spec.securityContext.seLinuxChangePolicy ist nicht gesetzt oder MountOption | Die Pod-Spezifikation | Recursive ist der ausdrückliche Opt-out und behält das alte Verhalten |
Der Radius ist enger, als das klingt, und es lohnt sich, ihn genau zu bestimmen, statt vom Schlimmsten auszugehen. Fünf Bedingungen müssen alle zutreffen, bevor sich ein einzelnes Volume anders verhält, und übersehen wird dabei der CSI-Treiber: Das kubelet nutzt einen Kontext-Mount nur dann, wenn der Treiber erklärt hat, dass er einen entgegennehmen kann – indem er seLinuxMount: true in seinem CSIDriver-Objekt setzt. Ein Treiber, der das Feld nicht setzt – und nicht gesetzt ist nicht true –, behält das alte rekursive Verhalten und ist von der Umstellung überhaupt nicht betroffen. Die In-Tree-Volume-Typen, die die Mount-Option unterstützen, sind fc, iscsi und rbd; alles andere im Baum labelt unverändert rekursiv.[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.Zwei Muster der gemeinsamen Nutzung brechen, und nur eines kommt in freier Wildbahn vor. Das erste sind zwei Pods, die sich ein Volume über verschiedene subPaths mit verschiedenen Labels teilen, was Upstream als sehr speziell beschreibt und nach eigener Aussage in der Praxis nie gesehen hat. Das zweite ist ein privilegierter und ein unprivilegierter Pod, die sich ein Volume teilen – ebenfalls selten, aber in echten Anwendungen beobachtet, und das ist das Muster, nach dem man suchen sollte. Die Upgrade-Betrachtung im KEP selbst ist lesenswert, wenn Sie derjenige sind, der das abzeichnen muss, denn sie ist das Nächste an einem offiziellen Runbook, das es gibt.[kepstory3]
Die Prüfung, die vor dem Upgrade stattfinden muss
Kubernetes v1.36 hat für genau dieses Problem einen Controller ausgeliefert, und fast niemand hat ihn eingeschaltet – weil er Opt-in ist und weil das, wovor er warnt, noch nicht eingetreten war. selinux-warning-controller läuft im kube-controller-manager hinter --controllers=*,selinux-warning-controller, beobachtet jeden Pod im Cluster und meldet jedes Paar von Pods, die sich ein Volume auf eine Weise teilen, die SELinuxMount nicht zulassen wird. Er meldet sie auch dann, wenn die Pods auf verschiedenen Knoten liegen – zu Recht, denn der Scheduler kann sie morgen zusammenlegen.[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.Lesen Sie danach beide Metriken, denn sie beantworten verschiedene Fragen, und keine genügt für sich. Die Metrik des Controllers, selinux_warning_controller_selinux_volume_conflict, trägt die Namen und Namespaces der konfligierenden Pods als Labels – das ist Ihre Arbeitsliste. Die Metrik des kubelet, volume_manager_selinux_volume_context_mismatch_warnings_total, hat überhaupt kein Pod-Label, ist aber die ehrliche Zahl der Pods, die tatsächlich scheitern würden, und sie wird ausgegeben, solange SELinuxMount noch deaktiviert ist. Genau darin liegt der Sinn, das auf v1.36 zu tun: Der Warnungszähler ist ungleich null, während noch alles funktioniert. Nach dem Upgrade erscheint dieselbe Messung als ..._errors_total, und dann hängen die Pods bereits, statt gefährdet zu sein.[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.Für jeden Workload, den die Metriken nennen, haben Sie zwei Möglichkeiten: die gemeinsame Nutzung auflösen oder diesen Pod ausnehmen. Der Opt-out ist ein Pod-Feld, spec.securityContext.seLinuxChangePolicy, und er ist seit v1.36 stabile API – das heißt, Sie können ihn jetzt setzen, auf Ihrer aktuellen Version, ohne jede Verhaltensänderung, und er tut in dem Moment das Richtige, in dem das Upgrade landet. Das ist die billigste Versicherung in diesem ganzen Release. Auf Recursive gesetzt, behält er das alte Verhalten für diesen Pod bei und kostet Sie den Performance-Gewinn – bei einem Workload, der ein Volume über Label-Grenzen hinweg geteilt hat, ein Handel, den Sie ohnehin schon eingegangen sind.[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 | Verhalten auf 1.36 | Verhalten auf 1.37 | Wann man es einsetzt |
|---|---|---|---|
| nicht gesetzt (Standard) | Rekursives Relabeln – SELinuxMount ist standardmäßig aus | Mount-Option, wenn alle übrigen Bedingungen zutreffen | Der Standard für alles, was keine Volumes über Label-Grenzen hinweg teilt |
MountOption | Nur mit aktiviertem Feature Gate gültig | Mount-Option, ausdrücklich | Selten nötig. Der Standard tut das auf 1.37 bereits |
Recursive | Rekursives Relabeln | Rekursives Relabeln – der Opt-out | Die Lösung. Vor dem Upgrade auf jeden Workload anwenden, den die Konfliktmetriken genannt haben |
Dieses Feld von Hand auf jedes betroffene Deployment und StatefulSet zu setzen, skaliert über einen kleinen Cluster hinaus nicht, und der SELinux-Blogbeitrag sagt das auch direkt – er nennt MutatingAdmissionPolicy, Mutating Webhooks, Kyverno und Gatekeeper als die Wege, das in der Breite zu tun. Ein Hinweis zum Geltungsbereich: Widerstehen Sie der Versuchung, Recursive vorsorglich clusterweit auszurollen. Es funktioniert – und es verschenkt die Performance-Verbesserung für jeden Workload, der nie gefährdet war, und das sind in den meisten Clustern nahezu alle. Beschränken Sie es auf die Namespaces, die die Metriken tatsächlich genannt haben.[kyverno]
Static Pods und ihre Secret-Referenzen: das war 1.34
Das ist der Fehler, den am ehesten jemand 1.37 anlasten wird, der von 1.33 aus aktualisiert hat. Static Pods werden vom kubelet aus einem Verzeichnis auf der Platte verwaltet, statt über den API-Server erstellt zu werden, sie sollten also nie API-Objekte lesen können; ein Defekt ließ sie über Felder wie configMapRef und secretRef auf Secrets und ConfigMaps verweisen. Die Einschränkung, die das schließt, ist das Feature Gate PreventStaticPodAPIReferences, und es steht seit v1.34 standardmäßig auf an – auf jedem Cluster, der tatsächlich durch 1.34, 1.35 und 1.36 gegangen ist, ist das also längst passiert, und die betroffenen Pods sind längst gebrochen. Der Sneak Peek zu v1.37 kündigte an, das Gate selbst werde entfernt und damit auch der Opt-out; die mit v1.37 ausgelieferte Referenz der Feature Gates führt PreventStaticPodAPIReferences jedoch weiterhin als Beta-Gate mit Standardwert true, der Notausgang könnte technisch also noch da sein. Planen Sie so, als wäre er es nicht. Es war ein Defekt und keine Funktion, er kommt nicht zurück, und die Static-Pod-Verzeichnisse zu durchsuchen kostet weniger, als das je Knoten zu entdecken.[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.Die Lösung besteht fast immer darin, kein Static Pod mehr zu sein. Sehr viele Static Pods existieren aus historischen Gründen – sie waren der Weg, einen Agenten auf Knotenebene zu betreiben, bevor DaemonSets so leistungsfähig waren wie heute –, und ein DaemonSet liest Secrets ganz normal, bekommt Rolling Updates und taucht dort auf, wo Menschen hinschauen. Wo etwas wirklich statisch bleiben muss, bleibt die Wahl, den Wert im Manifest einzutragen oder eine Datei vom Host per Bind-Mount einzuhängen und von dort zu lesen. Beides ist schlechter als ein DaemonSet, und beides funktioniert. Getrennt davon: kubectl run --filename/-f ist in diesem Release ebenfalls veraltet, mit der Begründung, dass der erzeugte Pod ohnehin immer ausschließlich aus den Kommandozeilenargumenten gebaut wurde – das betrifft Skripte, nicht Cluster.[iss138671]
kube-proxy ipvs: veraltet seit 1.35, nicht entfernt
Nun die Änderung, über die am meisten falsch berichtet wird. Der ipvs-Modus von kube-proxy trägt seit v1.35 einen Veraltungshinweis, nicht erst seit v1.37. Was v1.37 hinzufügt, ist das Feature Gate KubeProxyIPVS, selbst als veraltet markiert, neben der Warnung, die kube-proxy beim Start ins Log schreibt. Nichts hört auf zu funktionieren, kein Feature Gate kippt, und kein Datenverkehr ist betroffen. Die Begründung lohnt das Verständnis, weil sie erklärt, warum es nie eine Behebung gab: Die ipvs-API des Kernels kann nicht alles ausdrücken, was ein Kubernetes-Service braucht, deshalb ist der ipvs-Modus für Teile der Aufgabe immer auf iptables darunter zurückgefallen. KEP-3866 sagt es in einer Abschnittsüberschrift unverblümt – der ipvs-Modus von kube-proxy wird uns nicht retten.[kep5495][kep3866]
| Release | Was mit dem ipvs-Modus passiert | Was Sie tun müssen |
|---|---|---|
| 1.35 | Veraltet. kube-proxy schreibt beim Start eine Warnung ins Log | Nichts – aber hier hat die Uhr zu laufen begonnen |
| 1.37 | Das Feature Gate KubeProxyIPVS kommt hinzu, selbst als veraltet markiert | Nichts. Die Migration planen, nicht überstürzen |
| 1.38 – 1.39 | Läuft weiter, warnt weiter | In einem Fenster Ihrer Wahl auf den nftables-Modus umstellen |
| 1.40 | Das Gate KubeProxyIPVS steht voraussichtlich standardmäßig auf false | Über das Gate wieder einschalten – oder bis dahin fertig sein |
| 1.43 | Support vollständig entfernt | Nichts mehr zu tun – das ist die Frist |
Das Ziel ist der nftables-Modus, der seit v1.33 GA ist und für Linux-Knoten empfohlen wird. Cluster migrieren nicht von selbst; Sie müssen mode: "nftables" ausdrücklich setzen. Die Kernel-Anforderung ist real, aber nicht drückend – jeder Kernel, der zu alt für den nftables-Modus ist, verlässt bis Ende 2026 den LTS-Status –, und die nftables-Bugfixes wurden eigens in die Branches 1.33 und 1.34 zurückportiert, damit ipvs-Nutzer auf älteren Releases migrieren können, ohne zuvor Kubernetes zu aktualisieren.[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.Ein Hinweis zur Reihenfolge, mit Nachdruck vorgetragen: Machen Sie die Umstellung des Proxy-Modus in einem eigenen Wartungsfenster, an einem eigenen Tag, und nicht gebündelt mit dem Versions-Upgrade. Beide Änderungen berühren die Datenebene, und wenn in einem Fenster, das beide enthielt, die Konnektivität bricht, verbringen Sie den Ausfall damit herauszufinden, welche von beiden es war, statt ihn zu beheben. Es gibt hier keinen Zeitdruck, der eine Bündelung rechtfertigen würde – die Deprecation-Richtlinie sichert einer Funktion ab Beta einen langen Vorlauf zu, und die Graduation-Kriterien des KEP setzen das Umkippen des Feature Gate auf false auf v1.40 und die Entfernung auf v1.43.[kubeproxycfg][deprecpolicy]
cgroup v1: das war 1.35
Die cgroup-v1-Geschichte wird am häufigsten falsch zugeordnet, und sie richtigzustellen ändert, was zu tun ist. Die kubelet-Einstellung failCgroupV1 steht seit Kubernetes v1.35 standardmäßig auf true. Ein Knoten, der noch auf cgroup v1 läuft, verweigert seinem kubelet seitdem also den Start – es sei denn, jemand hat failCgroupV1: false ergänzt, was während des v1.35-Upgrades viele im Eiltempo taten und danach nie wieder anfassten. Die nützliche Frage auf dem Weg zu v1.37 lautet deshalb nicht „bricht das etwas“, sondern „wer schleppt die Ausnahme noch mit sich, und wie lange noch“.[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.v1.37 respektiert die Ausnahme weiterhin, und KEP-5573 entfernt die cgroup-v1-Unterstützung in einem späteren Release vollständig, ohne dass ein Datum zugesagt wäre – die ehrliche Planungsannahme lautet also „das Release, das SIG Node passt“, und nicht ein Monat, den Sie in einen Plan schreiben können. Was Sie in der Zwischenzeit aufgeben, ist nicht theoretisch: Das Resizing von Pods im laufenden Betrieb und der gestufte Speicherschutz hängen beide an cgroup v2 und funktionieren auf einem v1-Knoten schlicht nicht – und das sind Funktionen, nach denen Teams aktiv fragen. Die Umstellung selbst ist eine Änderung der Kernel-Kommandozeile und ein Neustart, dazu die Angleichung des cgroup-Treibers der Container-Runtime an den des kubelet – das ist mehr Arbeit, als es klingt, und hat einen eigenen Artikel.[cgroups]
containerd 1.x: die Klippe ist 1.38, nicht jetzt
Diese hier wird in die andere Richtung falsch dargestellt, und sich zu irren kostet Sie ein Wartungsfenster, das Sie nicht hätten aufwenden müssen. Die Unterstützung für containerd 1.x wurde in v1.36 nicht entfernt, und sie wird auch in v1.37 nicht entfernt. Ein v1.37-kubelet läuft weiterhin dagegen: Mit aktiviertem KubeletCgroupDriverFromCRI fragt das kubelet die Runtime über den CRI-RPC RuntimeConfig nach ihrem cgroup-Treiber, containerd 1.y implementiert diesen RPC nicht, und das kubelet fällt still auf seinen eigenen --cgroup-driver-Wert zurück und zählt dabei die Metrik cri_losing_support hoch. Dieser Fallback sollte ursprünglich in v1.37 verschwinden und wurde um ein Release verschoben, ausdrücklich zur Abstimmung mit dem eigenen Support-Zeitraum von containerd v1.7. Die Kubernetes-Dokumentation zu Runtimes sagt es inzwischen klar: In v1.38 fällt der Fallback weg, und ältere containerd-Versionen scheitern gegen neuere kubelets.[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.Die Frist ist also real und genau ein Release entfernt – eine bessere Lage, als die Panik nahelegt, und eine schlechtere, als Nichtstun suggeriert. Zwei Dinge verschärfen sie. Auf der Runtime-Seite läuft die Uhr zuerst ab: containerd 1.7 ist der LTS-Branch, an dem das hängt, und sein erweiterter Support endet im September 2026, also jetzt. Und die eigene Kubernetes-Support-Matrix von containerd hat derzeit überhaupt keine Zeile für Kubernetes 1.37 – die zuletzt veröffentlichte Zeile ist 1.36 mit 2.3.0+ und 2.2.0+ –, wer Ihnen also eine von containerd gesegnete Version für 1.37 nennt, extrapoliert, statt zu zitieren. Praktisch: Sammeln Sie cri_losing_support ein, das jeder Knoten, der den Fallback braucht, ohnehin schon ausgibt und das die bessere Prüfung ist, als die Knoten von Hand durchzugehen, und bestätigen Sie es dann über containerRuntimeVersion. Machen Sie die Runtime-Migration vor 1.38 und in einem eigenen Fenster. Zwei Änderungen auf Runtime-Ebene gemeinsam bedeuten, dass Sie bei einem Knoten, der falsch zurückkommt, bisektieren statt beheben.[runtimes]
metrics.k8s.io erreicht endlich v1
Die gute Nachricht in diesem Release, und sie ist wirklich gut. metrics.k8s.io steigt nach fast neun Jahren in Beta auf v1 auf. Das ist die API hinter kubectl top und hinter den CPU- und Speichermetriken des HorizontalPodAutoscaler, womit sie eine der meistgenutzten Schnittstellen in Kubernetes ist – und ein merkwürdiges Ding, um es so lange in Beta zu belassen. Der Aufstieg erkennt Stabilität an, statt Änderungen einzuführen: Funktionale Unterschiede sind nicht zu erwarten, und während des Übergangs werden sowohl v1 als auch v1beta1 weiter ausgeliefert.[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.Am Upgrade-Tag ist also nichts zu tun, und das ist der Punkt – das hier ist etwas, das Sie nach eigenem Zeitplan übernehmen, statt dass es Ihnen zustößt. Wo es doch zählt, ist Code, der Ihnen gehört: ein eigener Autoscaler, ein Kapazitätsbericht, das Backend eines Dashboards – alles, was direkt mit der API spricht, sollte den Beta-Pfad verlassen, solange beide verfügbar sind, und nicht erst, wenn einer es nicht mehr ist. Für v1beta1 gibt es noch überhaupt kein Entfernungsdatum, und die Deprecation-Richtlinie sichert einer Beta-API mindestens neun Monate oder drei Releases zu – behandeln Sie es also als Aufräumarbeit und nicht als Frist.[hpa]
Der Rest des Releases, kurz
Drei weitere Dinge in diesem Release sind wissenswert, auch wenn keines davon ein Upgrade beeinflusst. Alle drei sind Aufstiege und keine Entfernungen, und das erste ist das, auf das man ein Auge haben sollte.[kep4960]
- Das kubelet in einem User-Namespace – Rootless-Modus – erreicht Beta. Knotenkomponenten laufen traditionell als root auf dem Host. Damit können sie als unprivilegierter Nutzer auf dem Host laufen und dabei innerhalb eines Linux-User-Namespace weiterhin als root erscheinen, was den Radius einer Schwachstelle in einer Knotenkomponente begrenzt. Beta heißt hier, dass das Gate standardmäßig an ist – es einzuschalten lässt das kubelet aber für sich genommen noch nicht in einem User-Namespace laufen, dahinter steht Konfiguration auf dem Host –, und die Änderung hat es nicht in die Release-Ankündigung geschafft. Behandeln Sie es als etwas, das man auf einem Testknoten ausprobiert, und nicht als etwas, das Ihnen zugestoßen ist. Es ist außerdem nicht dieselbe Funktion wie User-Namespaces für Pods, die in v1.35 Beta und in v1.36 GA erreicht haben.
- Das SELinux-Relabeln von Volumes für ReadWriteOncePod-Volumes war bereits in v1.36 GA. Das ist
SELinuxMountReadWriteOncePod, ein engeres Gate als dasSELinuxMount, um das es in diesem Artikel geht, und es ist der Grund, warum manche Cluster schon eine Weile mit einer Kontext-Option einhängen, ohne dass etwas gebrochen wäre – RWOP-Volumes lassen sich per Definition nicht teilen, der hier beschriebene Konflikt kann dort also gar nicht erst entstehen. - Die Überwachung der Volume-Gesundheit beginnt neu bei Alpha. Eine erste Umsetzung kam in v1.21 und ist nie aufgestiegen; KEP-1432 setzt sie hinter dem Gate
CSIVolumeHealthzurück und führt vier CSI-RPCs ein –ControllerListVolumeHealth,ControllerGetVolumeHealth,NodeGetVolumeHealthundNodeGetStorageHealth–, die nachPersistentVolumeClaim.status.healthStatus,Pod.status.volumeHealthundCSINode.status.storageHealthberichten. Das Vokabular ist bewusst klein und maschinenlesbar:Inaccessible,DataLossundDegraded, dazureasonundmessagean der Condition für treiberspezifische Details.
Alpha heißt standardmäßig aus und nicht für die Produktion, aber die Arbeit an der Gesundheitsüberwachung lohnt die Beobachtung, wenn Sie je einen hängenden Mount gegen das Dashboard eines Storage-Herstellers abgleichen mussten, um herauszufinden, was los war. Es ist die erste maschinenlesbare Antwort, die Kubernetes auf diese Frage anbietet.[kep1432][userns]
Das Upgrade, der Reihe nach
Die Reihenfolge zählt mehr als die Befehle. Alles, was an diesem Upgrade wirklich schwer ist, ist davor schwer, und der eine Schritt, der danach deutlich teurer wird, ist die SELinux-Prüfung – sie kommt deshalb zuerst, mit Wochen statt Minuten Vorlauf.[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.Eine Beruhigung zum schrittweisen Vorgehen: Die Version-Skew-Richtlinie erlaubt einem kubelet, bis zu drei Minor-Versionen hinter dem API-Server zu laufen, eine Flotte auf halbem Weg durch diesen Rollout ist also eine unterstützte Konfiguration und kein Risiko für sich. Nehmen Sie sich die Zeit. Aktualisieren Sie einen Knoten, schauen Sie ihn richtig an, und fahren Sie erst dann fort – denn der Fehler, den dieses Release erzeugen kann, tritt je Knoten auf und zeigt sich als Pod, der nie startet, und nicht als Störung, wegen der Sie jemand aus dem Bett holt.[skew]
Zurückrollen – und was sich nicht zurückrollen lässt
Das Rollback verdient eine klare statt einer beruhigenden Antwort, und bei diesem Upgrade hat die Antwort drei Teile. Die umkehrbaren Änderungen sind wirklich umkehrbar: Der kube-proxy-Modus ist eine ConfigMap und ein DaemonSet-Neustart, und seLinuxChangePolicy ist ein Pod-Feld, das sich auf v1.36 und v1.37 identisch verhält – genau deshalb kostet es nichts, es vor dem Upgrade zu setzen, und genau das kauft Ihnen die ganze Rollback-Geschichte.[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.Die Control Plane ist der Teil, der sich nicht zurückrollen lässt. kubeadm upgrade hat keinen Downgrade-Pfad; rückwärts zu gehen heißt, den etcd-Snapshot wiederherzustellen, den Sie vor dem Beginn gezogen haben, und alles zu verlieren, was seitdem in den Cluster geschrieben wurde. Wer keinen gezogen hat, hat kein Rollback – er hat eine Vorwärtsbehebung, und das ist um zwei Uhr nachts ein anderes Gespräch. Und ein Fehler überrascht die Leute: Ein Downgrade des kubelet löst einen Pod nicht wieder, der wegen eines SELinux-Label-Konflikts nicht einhängen konnte, denn der Pod, der das Volume gewonnen hat, hält es weiterhin mit seinem eigenen Kontext. Der Hebel liegt beim Pod-Feld, nicht bei der Versionsnummer.[selblog]
Nachweisen statt hoffen
Die Prüfung hat bei diesem Upgrade eine besondere Gestalt, weil der charakteristische Fehler alles grün lässt. Der Knoten ist Ready. Das kubelet ist gesund. Die Control Plane ist in Ordnung. Es gibt schlicht einen Pod, der nie fertig erstellt wird, auf einem Knoten, gehörend zu einem Team. Zu prüfen, dass der Cluster oben ist, beweist also überhaupt nichts – Sie müssen die Dinge prüfen, die still falsch sein können.[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 $rcFühren Sie es zwischen den Knoten aus statt am Ende. Das Skript liefert einen Rückgabewert ungleich null, kann also in einem Pipeline-Schritt sitzen, statt um drei Uhr nachts von einem Menschen gelesen zu werden – und die beiden Prüfungen, die auch nach dem Upgrade bleiben sollten, sind der Durchlauf über ContainerCreating und der SELinux-Mismatch-Zähler. Beide sind billig, und beide fangen eine Klasse von Problemen ab, die sonst wartet, bis jemand bemerkt, dass sein Workload nie zurückgekommen ist.[k8spatch]
Die Reihenfolge, in der das gemacht wird
Verdichtet ist das ein kleineres Upgrade, als der Artikel nahelegt – vorausgesetzt, Sie haben die Arbeit erledigt, die zu den beiden Releases davor gehörte. Die Entscheidungstabelle unten ist in Wahrheit die Frage, welche davon Sie ausgelassen haben.[sneak]
| Wenn Ihre Lage ist … | dann ist 1.37 … | und die Arbeit ist … |
|---|---|---|
| Auf 1.36, nirgends SELinux, containerd 2.x, cgroup v2 | ein Routine-Upgrade | Die übliche kubeadm-Abfolge. Die Static-Pod-Manifeste durchsuchen und los |
Auf 1.36, SELinux enforcing, CSI-Treiber mit seLinuxMount: true | das eine, das eine Prüfung braucht | Jetzt den Warning-Controller einschalten, beide Metriken lesen, Recursive dort setzen, wohin sie zeigen, dann aktualisieren |
| Auf 1.34 oder 1.35, mit dem Plan, direkt auf 1.37 zu springen | zwei oder drei Releases an Änderungen auf einmal | Lesen Sie die Notes zu jeder Version, die Sie überspringen. Die Einschränkung für Static Pods, der cgroup-Standardwert und die ExternalIPs-Warnung stehen alle darin |
| Ein Knoten noch auf cgroup v1 | nicht Ihr dringendstes Problem | Dieser Knoten schleppt seit 1.35 eine Ausnahme mit. Stellen Sie ihn getrennt um, bevor Sie aktualisieren |
| Ein Knoten noch auf containerd 1.x | heute in Ordnung, in 1.38 kaputt | Keine Arbeit für den Upgrade-Tag. Sammeln Sie cri_losing_support ein und buchen Sie die Runtime-Migration für sich, bevor Sie 1.38 nehmen |
kube-proxy läuft im ipvs-Modus | eine Logzeile, mehr nicht | An einem anderen Tag auf nftables umstellen. Sie haben bis 1.43, und ein Bündeln verdeckt die Ursache jedes Ausfalls |
| Ein Managed Service (GKE, EKS, AKS) | was der Anbieter einplant | Die SELinux-Prüfung bleibt Ihre – die Workloads und die CSI-Treiber gehören Ihnen, auch wenn die Knoten es nicht tun |
- Beantworten Sie die SELinux-Frage zuerst, mit einem einzigen Befehl. Wenn kein Knoten SELinux im Enforcing-Modus betreibt, überspringen Sie den größten Abschnitt hier vollständig und behandeln 1.37 als Routine-Upgrade. Trifft es auf auch nur einen Knoten zu, gilt alles Folgende.
- Schalten Sie
selinux-warning-controllerauf v1.36 ein und lesen Sie beide Metriken. Der Controller nennt die konfligierenden Pods; das kubelet zählt die, die tatsächlich scheitern werden. Machen Sie das Wochen im Voraus, denn nach dem Upgrade ist es spürbar mühsamer, und die Pods hängen dann bereits, statt nur gefährdet zu sein. - Setzen Sie
seLinuxChangePolicy: Recursiveauf die Workloads, die die Metriken genannt haben – per Policy statt von Hand. Es ist auf v1.36 stabile API, ändert heute nichts und tut in dem Moment das Richtige, in dem das Upgrade landet. Beschränken Sie es auf die betroffenen Namespaces statt auf den ganzen Cluster. - Durchsuchen Sie das Static-Pod-Verzeichnis jedes Knotens nach
secretRefundconfigMapRef. Das ist seit v1.34 der Standard – auf einem Cluster, der die Releases der Reihe nach genommen hat, ist es längst passiert; auf einem, der gesprungen ist, wartet es noch. Überführen Sie, was Sie finden, in ein DaemonSet – was es fast immer von Anfang an hätte sein sollen. - Räumen Sie die cgroup-v1-Altlast aus 1.35 ab und tragen Sie die containerd-Migration vor 1.38 in den Kalender. Ein Knoten auf cgroup v1 schleppt seit zwei Releases eine Ausnahme mit. Ein Knoten auf containerd 1.x funktioniert heute noch und hört ein Release später auf zu funktionieren. Beides bekommt ein eigenes Fenster – und lassen Sie die Migration von ipvs auf nftables ganz für einen anderen Tag.
Zwei der oben genannten Altlasten haben ein eigenes Runbook, denn keine davon ist in fünf Minuten erledigt: die Migration von containerd 1.7 auf 2.x, also die Entfernung von containerd 1.x von der Runtime-Seite her, und die Migration von cgroup v1 auf cgroup v2, also die Prüfung und Umstellung hinter der Ausnahme failCgroupV1. Wenn dasselbe Wartungsfenster auch noch Ihren Ingress neu baut, deckt der Wechsel von ingress-nginx zur Gateway API diese Migration ab. Und wenn an dieser Stelle jemand im Raum fragt, ob sich das Ganze überhaupt lohnt, ist wann man Kubernetes nicht einsetzen sollte die andere Seite dieses Arguments, ehrlich vorgetragen.
Häufige Fragen
Wann erscheint Kubernetes 1.37, und was bricht tatsächlich?
v1.37 ist am 26. August 2026 erschienen. Nur eine Änderung darin kann einen Workload anhalten: SELinuxMount erreicht GA und ist standardmäßig aktiviert, was Pods in ContainerCreating hängen lassen kann, wo sich zwei unterschiedlich gelabelte Pods auf einem Knoten mit SELinux im Enforcing-Modus ein Volume teilen und der CSI-Treiber zugestimmt hat. Der Rest des wirklich Neuen ist leise – das Feature Gate KubeProxyIPVS kommt hinzu und ist sofort als veraltet markiert, metrics.k8s.io steigt auf v1 auf, wobei beide Versionen weiter ausgeliefert werden, und kubectl run --filename/-f ist veraltet, was Skripte betrifft und nicht Cluster. Alles andere, was diesem Release zugeschrieben wird, kam früher oder ist noch gar nicht da: Der kubelet-Fehler auf cgroup v1 stammt aus v1.35, die Einschränkung für Secrets in Static Pods aus v1.34, der ipvs-Modus ist seit v1.35 veraltet und wird in v1.43 entfernt, und containerd 1.x funktioniert weiterhin – sein CRI-Fallback fällt in v1.38 weg.
Entfernt Kubernetes 1.37 den ipvs-Modus von kube-proxy?
Nein – und für veraltet erklärt hat es ihn auch nicht: Das geschah in v1.35, und kube-proxy schreibt seitdem beim Start eine Warnung ins Log. Was v1.37 hinzufügt, ist das Feature Gate KubeProxyIPVS, selbst als veraltet markiert – der Schalter, der den Modus später abschalten wird. Nichts hört auf zu funktionieren, und kein Datenverkehr ist betroffen. KEP-5495 legt den Zeitplan fest: Das Gate soll ab v1.40 standardmäßig auf false stehen, der Support wird bis v1.43 vollständig entfernt. Der Grund für die Veraltung ist, dass die ipvs-API des Kernels nicht alles ausdrücken kann, was ein Kubernetes-Service braucht, weshalb der ipvs-Modus für Teile der Aufgabe immer auf iptables darunter zurückfiel. Das Migrationsziel ist der nftables-Modus, GA seit v1.33 – und Cluster stellen nie von selbst um, Sie müssen mode: "nftables" also selbst setzen. Machen Sie das in einem eigenen Wartungsfenster, getrennt vom Versions-Upgrade.
Warum hängen meine Pods nach dem Upgrade auf 1.37 in ContainerCreating?
Wenn der Knoten SELinux im Enforcing-Modus betreibt, ist die wahrscheinliche Ursache ein Konflikt beim gemeinsamen Nutzen eines Volume, den der Aufstieg von SELinuxMount zu GA einführt. Volumes werden jetzt mit -o context=<label> eingehängt, statt rekursiv neu gelabelt zu werden, und ein Mount kann genau einen SELinux-Kontext tragen – zwei Pods mit unterschiedlichen Labels, die sich auf einem Knoten ein Volume teilen, können also nicht mehr nebeneinander bestehen. Einer sitzt in ContainerCreating, bis der andere beendet ist. Der klassische Fall ist ein privilegierter und ein unprivilegierter Pod, die sich ein Volume teilen. Bestätigen lässt es sich über die kubelet-Metrik volume_manager_selinux_volume_context_mismatch_errors_total auf diesem Knoten. Die Lösung ist, auf den betroffenen Pods spec.securityContext.seLinuxChangePolicy: Recursive zu setzen – beachten Sie, dass ein Downgrade des kubelet das Volume nicht freigibt, weil der Pod, der es gewonnen hat, den Kontext weiterhin hält.
Wie finde ich SELinux-Volume-Konflikte vor dem Upgrade?
Schalten Sie den selinux-warning-controller ein, der mit v1.36 kam, indem Sie dem kube-controller-manager --controllers=*,selinux-warning-controller mitgeben, und stellen Sie sicher, dass Sie das Feature Gate SELinuxChangePolicy nicht ausdrücklich deaktiviert haben. Lesen Sie dann zwei Metriken. selinux_warning_controller_selinux_volume_conflict trägt die Namen und Namespaces der konfligierenden Pods als Labels – das ist Ihre Arbeitsliste, und sie meldet Konflikte auch dann, wenn die Pods auf verschiedenen Knoten liegen, weil der Scheduler sie später zusammenlegen kann. volume_manager_selinux_volume_context_mismatch_warnings_total, ausgegeben vom kubelet, solange SELinuxMount noch deaktiviert ist, hat kein Pod-Label, liefert aber die ehrliche Zahl der Pods, die tatsächlich scheitern würden. Sie brauchen beide. Tun Sie das auf v1.36, solange der Warnungszähler ungleich null ist und noch alles funktioniert.
Was bewirkt seLinuxChangePolicy: Recursive genau, und kann ich es jetzt schon setzen?
Es nimmt diesen Pod vom Pfad über die Mount-Option aus und behält das Verhalten vor 1.37 bei: Die Container-Runtime durchläuft das Volume und labelt jede Datei neu. Der Preis ist der Performance-Gewinn – auf einem großen oder entfernten Volume ist rekursives Relabeln wirklich langsam –, und der Nutzen ist, dass sich zwei unterschiedlich gelabelte Pods das Volume wieder teilen können. Seit v1.36 ist es stabile Pod-API, es heute zu setzen ändert also nichts am aktuellen Verhalten Ihres Clusters und tut in dem Moment das Richtige, in dem das Upgrade landet. Das macht es zur billigsten Versicherung in diesem Release. Rollen Sie es über eine MutatingAdmissionPolicy oder eine Policy-Engine aus, statt jedes Deployment einzeln zu bearbeiten, und beschränken Sie es auf die Namespaces, die die Konfliktmetriken tatsächlich genannt haben, statt auf den ganzen Cluster – ein pauschales Recursive verschenkt die Verbesserung für jeden Workload, der nie gefährdet war.
Mein Cluster nutzt kein SELinux. Betrifft mich davon irgendetwas?
Fast nichts. Wenn SELinux nicht verfügbar oder im Kernel deaktiviert ist, überspringt das kubelet den gesamten SELinux-Codepfad, die SELinuxMount-Änderung ist für Sie also wirkungslos, und der größte Abschnitt dieses Artikels lässt sich überspringen. Was weiterhin gilt, ist die Einschränkung für Static Pods – durchsuchen Sie das Static-Pod-Verzeichnis jedes Knotens nach secretRef und configMapRef; das ist seit v1.34 der Standard und beißt deshalb nur Cluster, die Releases übersprungen haben – dazu die cgroup-v2-Altlast aus v1.35, falls Sie sie noch nicht abgeräumt haben, und die Migration auf containerd 2.x, die auf 1.37 nicht dringend ist, aber vor 1.38 erledigt sein muss. Prüfen Sie aber getenforce auf jedem Knoten, statt es anzunehmen: Eine gemischte Flotte, in der ein Node-Pool ein Image mit aktiviertem SELinux mitbringt, ist häufiger, als man erwartet.
Verhindert 1.37, dass das kubelet auf cgroup v1 startet?
Diese Änderung stammt nicht aus 1.37. Die kubelet-Einstellung failCgroupV1 steht seit v1.35 standardmäßig auf true, ein Knoten auf cgroup v1 bekommt sein kubelet seitdem also nicht mehr gestartet, sofern nicht jemand failCgroupV1: false ergänzt hat. v1.37 respektiert diese Ausnahme weiterhin. KEP-5573 entfernt die cgroup-v1-Unterstützung in einem späteren Release vollständig, doch ein Datum ist nicht zugesagt, die Planungsannahme sollte also „das Release, das SIG Node passt“ lauten und kein Monat sein. Der Grund, nicht auf der Ausnahme sitzen zu bleiben, ist, dass das Resizing von Pods im laufenden Betrieb und der gestufte Speicherschutz beide cgroup v2 voraussetzen und ohne es schlicht nicht funktionieren. Einen Knoten umzustellen heißt eine Änderung der Kernel-Kommandozeile (systemd.unified_cgroup_hierarchy=1), einen Neustart und die Angleichung des cgroup-Treibers der Runtime an den des kubelet.
Brauche ich containerd 2.0 für Kubernetes 1.37?
Nein – und das ist die Behauptung, die am häufigsten verkehrt herum aufgestellt wird. containerd 1.x läuft weiterhin gegen ein v1.37-kubelet. Es tut das über einen Fallback: Das kubelet fragt die Runtime über den CRI-RPC RuntimeConfig nach ihrem cgroup-Treiber, containerd 1.y implementiert diesen RPC nicht, und das kubelet fällt auf seinen eigenen --cgroup-driver-Wert zurück und zählt dabei die Metrik cri_losing_support hoch. Dieser Fallback sollte in v1.37 entfernt werden und wurde um ein Release verschoben, zur Abstimmung mit dem Support-Zeitraum von containerd v1.7 – er verschwindet also in v1.38, und dann scheitern ältere containerd-Versionen tatsächlich gegen neuere kubelets. Zwei Einschränkungen machen das weniger bequem, als es klingt: Der erweiterte Support von containerd 1.7 endet im September 2026, und die veröffentlichte Kubernetes-Support-Matrix von containerd hat noch keine Zeile für 1.37, niemand kann derzeit also eine offiziell gesegnete Paarung zitieren. Prüfen Sie über cri_losing_support und containerRuntimeVersion, und buchen Sie die Migration, bevor Sie 1.38 nehmen – in einem eigenen Fenster, nicht gebündelt mit einem Versions-Upgrade.
Was ersetzt einen Static Pod, der ein Secret liest?
Ein DaemonSet, in nahezu jedem Fall. Static Pods werden vom kubelet aus einem Verzeichnis auf der Platte verwaltet, statt über den API-Server erstellt zu werden, das Lesen von API-Objekten sollte also nie funktionieren – ein Defekt ließ es über Felder wie configMapRef und secretRef durch. Das Gate PreventStaticPodAPIReferences, das den Defekt schließt, steht seit v1.34 standardmäßig auf an, das ist in 1.37 also nicht neu – es fühlt sich nur für Cluster neu an, die mehrere Releases übersprungen haben. Der Sneak Peek zu v1.37 sagte, das Gate werde ganz entfernt; die mit v1.37 ausgelieferte Referenz der Feature Gates führt es weiterhin als Beta-Gate mit Standardwert true. So oder so: Planen Sie nicht mit dem Opt-out. Die meisten Static Pods existieren aus historischen Gründen, aus der Zeit, bevor DaemonSets so leistungsfähig waren wie heute; ein DaemonSet liest Secrets ganz normal, bekommt Rolling Updates und taucht dort auf, wo Menschen danach suchen. Muss etwas wirklich statisch bleiben, tragen Sie den Wert ins Manifest ein oder hängen eine Datei vom Host per Bind-Mount ein und lesen von dort. Beides ist schlechter als ein DaemonSet, und beides funktioniert. Finden Sie sie vor dem Upgrade, statt einen Knoten nach dem anderen.
Sollte ich direkt von 1.34 oder 1.35 auf 1.37 aktualisieren?
Sie können – die Version-Skew-Richtlinie erlaubt einem kubelet bis zu drei Minor-Versionen Rückstand auf den API-Server, und kubeadm nimmt die Control Plane eine Version nach der anderen –, aber das Risiko liegt nicht in der Mechanik, sondern in der Lektüre. Releases zu überspringen heißt, dass jede Veraltung aus den übersprungenen Versionen auf einmal ankommt, und dokumentiert ist sie je Release und nicht kumulativ. Von 1.35 kommend erben Sie zusätzlich die Änderungen aus v1.36: die Veraltungswarnung zu Service-ExternalIPs, den Aufstieg von SELinuxMountReadWriteOncePod und SELinuxChangePolicy zu GA und User-Namespaces für Pods, die stabil werden. Von 1.34 kommend erben Sie obendrein das Umkippen des Standardwerts von failCgroupV1 aus v1.35 – was das kubelet auf einem cgroup-v1-Knoten vollständig anhält. Lesen Sie den Blogbeitrag des Release-Teams zu jeder Version, die Sie überspringen. Drei davon dauern etwa zwanzig Minuten und sind eine bessere Verwendung eines Wartungsfensters als das meiste, was sonst hineingeht.
Kann ich ein Upgrade auf 1.37 zurückrollen?
Teilweise, und die Teile zählen. Die Änderung des kube-proxy-Modus rollt sauber zurück – sie ist eine ConfigMap und ein DaemonSet-Neustart. seLinuxChangePolicy ist ein Pod-Feld, das sich auf v1.36 und v1.37 gleich verhält, weshalb es nichts kostet, es vorher zu setzen. Die Control Plane rollt nicht zurück: kubeadm upgrade hat keinen Downgrade-Pfad, rückwärts zu gehen heißt also, den vor dem Beginn gezogenen etcd-Snapshot wiederherzustellen und alles seitdem Geschriebene zu verlieren. Ziehen Sie diesen Snapshot. Und ein Fehler überrascht die Leute: Ein Downgrade des kubelet löst einen Pod nicht wieder, der wegen eines SELinux-Konflikts nicht einhängen konnte, denn der Pod, der das Volume gewonnen hat, hält den Kontext des Mounts weiterhin. Der Hebel ist seLinuxChangePolicy auf beiden Pods, nicht die Versionsnummer.
Verschwindet metrics.k8s.io v1beta1?
Noch nicht, und nicht ohne Vorwarnung. metrics.k8s.io steigt in v1.37 nach fast neun Jahren in Beta auf v1 auf, und sowohl v1 als auch v1beta1 bleiben während des Übergangs ausgeliefert – eigens damit die Umstellung in Ihrem Tempo geschehen kann. Funktionale Änderungen sind nicht zu erwarten; der Aufstieg erkennt Stabilität an, statt sie herzustellen. kubectl top und der HorizontalPodAutoscaler laufen ohne jedes Zutun weiter. Lohnend ist es, eigenen Code – einen eigenen Autoscaler, einen Kapazitätsbericht, das Backend eines Dashboards – vom Beta-Pfad zu holen, solange beide verfügbar sind. Die Deprecation-Richtlinie von Kubernetes sichert einer Beta-API nach der Veraltung mindestens neun Monate oder drei Releases zu, und für v1beta1 gibt es überhaupt kein Entfernungsdatum – das ist also Aufräumarbeit und keine Frist.
Quellen
Primärquellen zuerst. Der Sneak Peek des Release-Teams und das Changelog zu v1.37 sind die einzigen maßgeblichen Aussagen darüber, was in diesem Release steckt; die KEPs sind der einzige Ort, an dem die release-übergreifenden Zeitpläne festgehalten sind, und genau das macht sie zum Gegenmittel gegen eine Zusammenfassung, die „entfernt in 1.37“ über etwas schreibt, das in 1.43 entfernt wird. Wo dieser Artikel etwas korrigiert – dass der cgroup-Fehler eine Änderung aus v1.35 ist, dass die Einschränkung für Static Pods aus v1.34 stammt, dass ipvs seit v1.35 veraltet und nicht in v1.37 entfernt ist und dass die containerd-Klippe mit v1.38 vor Ihnen liegt statt mit v1.36 hinter Ihnen –, richtet sich der Widerspruch gegen die Sekundärberichterstattung, nicht gegen das Projekt. Ein Widerspruch verläuft innerhalb des Projekts und gehört benannt: Der Sneak Peek sagt, das Gate PreventStaticPodAPIReferences sei in diesem Release entfernt worden, und die mit v1.37 veröffentlichte Referenz der Feature Gates führt es weiterhin. Für das, was tatsächlich ausgeliefert wurde, ist die Referenzseite die verlässlichere Quelle.
- 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
War das hilfreich?