TYPO3 auf Kubernetes unter IT-Grundschutz, Teil 8: TYPO3 zustandslos machen
Kubernetes geht davon aus, dass jeder Pod jederzeit durch einen anderen ersetzt werden kann. TYPO3 ist so nicht gebaut. Es legt eine erstaunliche Menge an Zustand im eigenen Dateisystem ab, und das merkt man erst, wenn der zweite Pod dazukommt. Dieser Teil geht den Zustand Stück für Stück durch und zeigt, wohin er im Cluster gehört.
Teil 8 von 14 der Serie „TYPO3 auf Kubernetes unter IT-Grundschutz“. Die Übersicht aller Teile steht in Teil 1.
01 — Welchen Zustand TYPO3 mitbringt
Eine klassische TYPO3-Installation hält an mehreren Stellen Zustand, den ein zweiter Pod nicht sieht:
- Sessions für Frontend und Backend.
- fileadmin mit den Uploads der Redaktion.
- typo3temp, darin die
_processed_-Bilder und generierte Assets. - Caches im Dateisystem und in der Datenbank.
- Locks, die TYPO3 etwa beim Seiten-Rendering setzt.
- Scheduler-Aufgaben, die genau einmal laufen sollen.
- Setup und Migrationen, zum Beispiel
extension:setupund Datenbank-Updates.
Solange nur ein Pod läuft, fällt das kaum auf. Mit dem zweiten Pod beginnen die seltsamen Fehler. Nutzer werden abgemeldet, Bilder fehlen, Caches widersprechen sich. Die Regel für den Cluster lautet deshalb: Der Pod enthält nur Code und Konfiguration. Alles andere lebt außerhalb.
02 — Sessions, Locks und Caches
Sessions und Locks gehören in einen gemeinsamen Key-Value-Store wie Valkey oder Redis. Für Sessions bringt der TYPO3-Core ein Redis-Backend mit. Für Locks braucht es eine Strategie, die über Pods hinweg wirkt. Die dateibasierten Strategien des Core tun das nur mit einem gemeinsamen Dateisystem. Eine Strategie für Valkey bringt der Core nicht mit, er lässt sich aber erweitern. Eine eigene Klasse implementiert das LockingStrategyInterface und wird in ext_localconf.php über LockFactory::addLockingStrategy() angemeldet. Liefert sie eine höhere Priorität als die Dateistrategie mit 75, nutzt der Core sie auch für das Seiten-Rendering. Technisch reicht ein SET … NX PX mit Ablaufzeit. So hinterlässt ein abgestürzter Pod keinen Lock, der alle anderen blockiert. Wichtig ist, dass der Store selbst abgesichert ist: TLS, ein eigener Nutzer pro Mandant, keine Verbindung von außen. Wie das mit mTLS auf jeder Strecke aussieht, beschreibt Teil 5.
Bei den Caches gibt es zwei Wege. Häufig genutzte Caches wie pages und hash können in Valkey liegen. Für Caches, die auf dem Dateisystem liegen müssen, braucht es ein Backend, das mit mehreren Pods umgehen kann. Wie das ohne ReadWriteMany-Volume geht, zeigt der Beitrag TYPO3 auf Kubernetes ohne RWX-Volume. Entscheidend ist in beiden Fällen: Ein Cache-Flush muss alle Pods erreichen, nicht nur den, auf dem er ausgelöst wurde.
03 — fileadmin und verarbeitete Bilder auf Objektspeicher
Uploads und _processed_-Bilder müssen alle Pods sehen. Ein einzelnes Volume mit ReadWriteOnce bindet die Anwendung an einen Knoten. Ein Netzwerk-Dateisystem mit ReadWriteMany ist oft langsam und schwer zu betreiben. Ein Objektspeicher wie S3 ist die robustere Basis.
Zwei Wege führen dorthin. TYPO3 kann über einen FAL-Treiber direkt mit S3 sprechen. Oder ein CSI-Treiber bindet einen Bucket als Dateisystem in den Pod ein, und TYPO3 merkt nichts davon. Der zweite Weg ist transparent, bringt aber Latenz: Jeder Dateizugriff wird zu einer Anfrage an den Speicher. Ein Cache davor ist deshalb Pflicht, für Metadaten wie für ausgelieferte Dateien. Wie das in der Praxis aussieht, beschreibt fileadmin auf S3 mit Caching.
Der Bucket gehört pro Mandant getrennt, mit Versionierung und eigener Zugriffsrolle. Uploads sollten zusätzlich auf Schadsoftware geprüft werden, bevor sie ausgeliefert werden.
04 — Nur-Lese-Code, Setup einmal je Release, ein Scheduler
Wenn aller Zustand draußen liegt, kann der Code schreibgeschützt sein. Das Root-Dateisystem des Containers ist read-only. Beschreibbar sind nur ausgewählte Verzeichnisse, etwa ein tmpfs für temporäre Dateien. Ein Angreifer, der Code ausführen kann, kann ihn so nicht dauerhaft verändern.
Setup und Migrationen laufen nicht im Hauptcontainer und auch nicht in jedem Pod, sondern einmal je Release in einem Hook vor dem Rollout. Eine Sperre in der Datenbank sorgt dafür, dass nie zwei Migrationen gegeneinander laufen, und scheitert der Hook, bleibt das Deployment, wie es ist. Der Pod selbst kopiert nur noch die im Build vorgewärmten Code-Caches und ist damit nach wenigen Sekunden bereit. Der Scheduler wird ein eigener CronJob mit concurrencyPolicy: Forbid, damit keine Aufgabe doppelt läuft. Und der Cache-Flush nach einem Deployment wird ein Hook, der nach dem Rollout genau einmal auslöst. Wie der Start eines Pods so von 54 auf zwölf Sekunden fiel, steht in der Feldnotiz zum Pod-Start.
apiVersion: batch/v1
kind: CronJob
metadata:
name: typo3-scheduler
spec:
schedule: "*/5 * * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
automountServiceAccountToken: false
initContainers:
# same verification chain as the web pod (part 3), shortened, key volume omitted
- name: verify-signature
image: registry.example.org/tools/cosign@sha256:...
args: ["verify", "--key", "/keys/release.pub",
"registry.example.org/typo3/site@sha256:..."]
containers:
- name: scheduler
image: registry.example.org/typo3-runtime@sha256:...
workingDir: /app
args: ["php", "vendor/bin/typo3", "scheduler:run"]
volumeMounts:
- {name: app, mountPath: /app, readOnly: true}
volumes:
- name: app
image:
reference: registry.example.org/typo3/site@sha256:...
pullPolicy: IfNotPresent
Der CronJob braucht dieselbe Kette wie der Web-Pod aus Teil 3. Das Laufzeit-Image enthält keinen TYPO3-Code. Also prüft auch hier ein initContainer das signierte Artefakt, bevor der kubelet es als schreibgeschütztes Image-Volume einhängt. Fehlt dieser Schritt, startet der Scheduler entweder gar nicht oder mit Code, den niemand geprüft hat.
Logs gehen auf stdout und stderr, nicht in Dateien im Container. So sammelt sie die Plattform ein, und nichts geht verloren, wenn ein Pod verschwindet. Das Install-Tool bleibt im Cluster abgeschaltet.
Bezug zum IT-Grundschutz
- APP.4.4.A4 Separierung von Pods: kein gemeinsamer Zustand über geteilte Volumes, jeder Pod isoliert.
- APP.4.4.A6 Initialisierung von Pods: Prüfung in initContainern, Setup und Migrationen einmal je Release vor dem Rollout.
- APP.4.4.A21 Regelmäßiger Restart von Pods: Nur ein zustandsloser Pod lässt sich jederzeit ersetzen.
- SYS.1.6.A19 Einbinden von Datenspeichern in Container: gezielt, pro Mandant, mit eigener Rolle.
- SYS.1.6.A23 Unveränderlichkeit der Container: schreibgeschütztes Root-Dateisystem.
- APP.3.1 Webanwendungen und Webservices: Uploads kontrolliert speichern und prüfen.
Häufige Fragen
Reicht nicht ein ReadWriteMany-Volume für das ganze TYPO3?+
Technisch ja, im Betrieb selten gut. Netzwerk-Dateisysteme sind bei vielen kleinen Dateien langsam. Caches, Sessions und Locks auf einem gemeinsamen Dateisystem erzeugen außerdem schwer zu findende Konflikte. Besser ist, jede Zustandsart dorthin zu legen, wo sie hingehört.
Wo sollten TYPO3-Sessions im Cluster liegen?+
In einem gemeinsamen Key-Value-Store wie Valkey oder Redis, abgesichert mit TLS und eigenem Nutzer. Der Core bringt ein Redis-Backend für Sessions mit. Die Datenbank funktioniert auch, belastet sie bei vielen Frontend-Sessions aber unnötig.
Wie verhindere ich doppelte Scheduler-Läufe?+
Den Scheduler aus dem Web-Pod herausnehmen und als eigenen CronJob mit concurrencyPolicy: Forbid betreiben. Dann läuft er genau einmal, egal wie viele Web-Pods gerade aktiv sind.
Kann das Root-Dateisystem wirklich schreibgeschützt sein?+
Ja, wenn alle beschreibbaren Pfade bewusst ausgelagert sind: temporäre Dateien in ein tmpfs, Caches in Valkey oder ein clusterfähiges Backend, Uploads in den Objektspeicher. Was dann noch schreiben will, zeigt der erste Start mit read-only sehr schnell.
Fazit
Zustandslosigkeit ist die Voraussetzung für alles Weitere: Rolling Updates, Autoscaling, Pods auf mehreren Knoten. Sessions und Locks in Valkey, Dateien im Objektspeicher, Caches mit einem Backend, das mehrere Pods kennt, Setup im initContainer. Danach ist jeder TYPO3-Pod austauschbar, vorausgesetzt, Valkey, Objektspeicher und Datenbank sind selbst ausfallsicher. Ein einzelner Valkey ohne Persistenz meldet bei einem Neustart alle Redakteure ab. Welche Irrtümer auf dem Weg dahin typisch sind, fasst der Beitrag Fünf Irrtümer: TYPO3 auf Kubernetes zusammen.
Wie sich diese Austauschbarkeit nutzen lässt, zeigt der nächste Teil: Autoscaling und Probes. Zurück zu Teil 7: Secrets ohne Git.
Ich mache dein TYPO3 fit für mehrere Pods. Sessions, Dateien, Caches und Scheduler dorthin, wo sie im Cluster hingehören.
Bestandsaufnahme des Zustands, Umstellung von Sessions und Locks, fileadmin auf Objektspeicher mit Cache, Setup im initContainer und ein Scheduler, der genau einmal läuft.
Plattform-Betrieb statt Beratung auf Papier: Auf Wunsch betreibe ich den zustandslosen Betrieb dauerhaft mit, inklusive Speicher und Cache.
Über den Autor

Kai Ole Hartwig
Programmiert seit 2002 – autodidaktisch gelernt, 2012 mit KO-Web selbständig gemacht. Über 100 Projekte, Fokus auf Security, Performance, Automatisierung und Qualität. Heute freiberuflich: DevSecOps-Beratung, Schulungen und Softwareentwicklung.
