Kubernetes v1.37 erscheint am 26. August 2026, und damit geht SELinuxMount GA und ist standardmäßig aktiv. Konkret heißt das: Wenn deine Nodes SELinux im Enforcing-Modus fahren und zwei Pods mit unterschiedlichen SELinux-Leveln sich auf einem Node einen PersistentVolumeClaim teilen, startet einer von beiden nicht mehr. Kein Manifest hat sich geändert. Kein Image hat sich geändert. Das kubelet meldet schlicht widersprüchliche SELinux-Labels auf dem Volume, und der Pod hängt in ContainerCreating, bis sein Nachbar verschwindet. Meine Position, noch vor allem anderen: Das ist die richtige Änderung, und mir wäre es deutlich lieber, du kassierst den Bruch diese Woche auf einem Staging-Cluster, als dass du die betroffenen Workloads auf seLinuxChangePolicy: Recursive umstellst und das Thema für erledigt erklärst.

terminal
kubectl get csidrivers -o custom-columns='NAME:.metadata.name,SELINUX_MOUNT:.spec.seLinuxMount'

Die Mechanik ist zehn Sekunden wert, weil sie erklärt, warum es hier keinen Mittelweg gibt. Auf dem alten Pfad lief die Container-Runtime beim Pod-Start durch das gesamte Volume und relabelte jede einzelne Inode, mit Kosten proportional zur Dateianzahl, und zwar bei jedem Neustart erneut. Wer schon einmal zugesehen hat, wie ein Pod mit einem fetten PVC auf einem entfernten Dateisystem in ContainerCreating festhängt, hat diese Rechnung vermutlich bezahlt, ohne ihren Namen zu kennen. Der neue Pfad mountet das Volume mit einer context-Option und lässt den Kernel ein einziges Label auf den gesamten Superblock legen. Konstante Zeit. Ein Superblock trägt aber exakt einen SELinux-Kontext, also verweigert der Kernel es, dasselbe Dateisystem zweimal mit zwei verschiedenen Kontexten zu mounten. Red Hat dokumentiert dieses Verhalten seit weit über einem Jahrzehnt. Kubernetes entscheidet sich hier nicht dafür, dich kaputtzumachen, es hört nur auf, den Kernel um etwas zu bitten, was der Kernel nie hergeben wollte.

Interessant finde ich etwas anderes, und es ist nicht die Storage-Verrohrung. Das Muster, das jetzt bricht, funktionierte ausschließlich deshalb, weil das Relabeling pro Datei lief. Pod A relabelte das Volume auf sein Level, Pod B kam vorbei und überschrieb die Labels mit seinen eigenen, und beide lasen munter weiter. Niemand hat das so entworfen. Es steht in keinem KEP, es steht in niemandes Architecture Decision Record, es ist bloß nie umgefallen. Wenn du PHP auf Kubernetes fährst, mit einem php-fpm-Deployment und einem Backup- oder Log-Shipping-Sidecar, der denselben Claim unter einem anderen Security Context mountet, dann verlässt du dich auf einen Nebeneffekt, der die Implementierung überlebt hat, die ihn erzeugte. Diese Form kennen wir aus unserem eigenen Ökosystem: Jede Codebase hat eine Stelle, an der etwas funktioniert und die ehrliche Antwort auf das Warum lautet, dass es schon immer so war.

Das stärkste Gegenargument zu meiner Position ist nicht Nostalgie, es sind zwei sehr konkrete Dinge. Erstens: Context-Mounts verweigern Label-Änderungen pro Datei. Setz chcon in so einem Volume ab und du bekommst Operation not supported, weil das Label nie auf die Platte geschrieben wurde. Wenn ein Teil deiner Workload seine Dateilabels selbst verwaltet, ist Recursive für dich kein Hinhaltemanöver, sondern die korrekte Konfiguration, also setz es und mach dir keine weiteren Sorgen. Zweitens, und das ist der Punkt, der mich stört: Die Erkennung ist auf eine Weise blind, die bisher niemand gelöst hat. Kubernetes v1.36 liefert einen selinux-warning-controller, den du mit --controllers=*,selinux-warning-controller einschalten kannst, und der wirft selinux_warning_controller_selinux_volume_conflict mit beiden beteiligten Pods als Labels aus. Kombiniert mit dem kubelet-Counter volume_manager_selinux_volume_context_mismatch_warnings_total erfährst du, welche Pods gerade jetzt kollidieren. Gerade jetzt ist das Problem. Weder der Controller noch irgendein spontaner API-Scrape sieht das Paar, das erst kollidiert, wenn der Scheduler die beiden nächsten Dienstag auf denselben Node legt.

Ich lande trotzdem beim Upgrade, und zwar aus einem Grund, der mit Startlatenz nichts zu tun hat. Rekursives Relabeling war das, was CVE-2021-25741 in seiner damaligen Form ausnutzbar machte: Bring das kubelet dazu, einen Teil des Host-Dateisystems freizulegen, und die Runtime relabelt diese Host-Dateien hilfsbereit so, dass dein Pod sie lesen kann. Unter einem Context-Mount wird der Pod schlicht per Policy abgewiesen. Das ist eine Sicherheitseigenschaft, die du jedes Mal wieder abgibst, wenn du das Opt-out über einen Workload streust, der es gar nicht gebraucht hätte. Und es gibt einen fiesen Folgeeffekt: seLinuxChangePolicy: Recursive zu setzen sorgt per Design auch dafür, dass der Warning-Controller diesen Pod nicht mehr meldet. Die Metrik also, um die herum du deinen Rollout gebaut hast, wird still, während die aufgeschobene Arbeit exakt dort liegen bleibt, wo sie war. Wenn du etwas herausnimmst, häng eine Annotation und eine Ticketnummer dran, denn die Observability erinnert sich nicht für dich.

Die meisten von euch werden das alles lesen und völlig zu Recht nichts tun, und das gehört laut ausgesprochen. Wenn /sys/fs/selinux/enforce auf deinen Nodes nicht existiert, überspringt das kubelet den ganzen Codepfad. Selbst mit SELinux im Enforcing-Modus sind nur die Volumes betroffen, deren CSIDriver-Objekt .spec.seLinuxMount auf true setzt, und in-tree besteht diese Liste genau aus fc und iscsi. emptyDir, configMap, secret und projected Volumes werden weiterhin rekursiv relabelt, deine Config und deine Credentials bleiben also unangetastet. Der AWS EBS CSI Driver ist ein gutes Beispiel dafür, warum Raten in beide Richtungen schlecht ist: Sein Helm-Chart rendert seLinuxMount: true nur dann, wenn node.selinux gesetzt ist, eine Standardinstallation ändert für EBS-Volumes also gar nichts. Schau in deine eigene values-Datei, statt einer der beiden Hälften dieses Satzes zu vertrauen.

Woran ich weiter kaue: Ein Audit zu einem Zeitpunkt kann einen Cluster nicht als sicher beweisen, weil das Versagen eine Funktion des Schedulings ist und nicht deines YAML. Du kannst die Konflikte von heute mit einem einzigen kubectl-Aufruf aufzählen und trotzdem im November geweckt werden, wenn ein Node-Drain zwei Pods zum ersten Mal zusammenbringt. Admission Policy ist die ehrliche Antwort, ob als MutatingAdmissionPolicy, als Webhook oder als Kyverno, begrenzt auf die Namespaces, die sich tatsächlich Claims teilen, aber das ist eine Policy, die du schreiben musst, bevor du weißt, welche Workloads sie brauchen. Also, Kolleginnen und Kollegen: Wie würdest du tatsächlich beweisen, dass ein scheduling-abhängiger Konflikt in deinem Cluster nicht auftreten kann, außer das Feature Gate auf Staging laufen zu lassen und zu warten? Ich habe keine Antwort gefunden, der ich traue, und ich würde deine gerne klauen.

Ein praktischer Einstieg, falls du einen willst: Lass dir deine Driver auflisten und sieh nach, welche sich eingeklinkt haben. Alles Weitere folgt aus dieser Antwort.