
🌐 Auch auf: English · Français · Español
Deep Dive aus der Reihe rund um die Ansible-Tour. Niveau: fortgeschrittene Linux-Administration. Die Tour erklärt die Rollen; die Deep Dives erklären die Technik darunter – wie sie funktioniert, warum sie hier ist, was genau dieses Setup damit macht, wie man sie im Alltag betreibt und was es sonst noch gegeben hätte.
Dieser Blog ist WordPress, betrieben in einem K3s-Cluster mit einem Knoten (Teil 3 der Tour zeigt die Rollen). WordPress geht von einem klassischen Webserver aus: Es schreibt in sein eigenes Verzeichnis, installiert und aktualisiert Plugins über seine Admin-Seite und plant Aufgaben, indem es sich an Besucheraufrufe hängt. Kubernetes geht vom Gegenteil aus – austauschbare Container, Konfiguration von außen, kein Zustand im Image. Dieser Beitrag erklärt, wie die beiden hier zusammenkommen.
🧭 Wie WordPress funktioniert
Der Weg eines Aufrufs
Ein Aufruf erreicht den Ingress-Controller (TLS, Ratenbegrenzung, Sicherheits-Header), dann den nginx-Sidecar im WordPress-Pod. nginx sucht zuerst nach einer fertig gerenderten HTML-Datei des Seiten-Caches; gibt es eine, ist PHP gar nicht beteiligt. Sonst reicht es den Aufruf an PHP-FPM im selben Pod weiter, wo WordPress seinen Kern, die Plugins und das Theme lädt, Redis nach gecachten Objekten und MariaDB nach dem Rest fragt und die Seite rendert.
Wo WordPress Zustand hält
- Dateien:
wp-content/– Plugins, Themes, Uploads, der Seiten-Cache, Sprachdateien. WordPress schreibt hier selbst, etwa wenn es ein Plugin aktualisiert. - Datenbank: Beiträge, Seiten, Benutzer und die Tabelle
wp_options, in der WordPress und jedes Plugin Einstellungen ablegen – und Transients, kurzlebige gecachte Werte. wp-config.php: Datenbankverbindung und Schlüssel. Im offiziellen Image erzeugt der Entrypoint sie aus Umgebungsvariablen.
Erweiterungsmodell
Alles in WordPress ist ein Hook: Actions („jetzt passiert X“) und Filter („dieser Wert läuft hier durch, ändere ihn, wenn du willst“). Plugins und Themes hängen Funktionen an Hooks. Must-use-Plugins in wp-content/mu-plugins/ laden automatisch, vor den normalen Plugins, und lassen sich über die Admin-Seite nicht abschalten – der richtige Ort für Regeln, die immer gelten müssen.
WP-Cron
WordPress hat keinen Daemon. Standardmäßig prüft es bei Seitenaufrufen, ob geplante Aufgaben fällig sind, und stößt dann einen Aufruf von wp-cron.php an. Auf einer ruhigen Seite laufen Aufgaben zu spät; auf einer belebten kostet die Prüfung bei vielen Aufrufen Zeit.
🛡️ Warum es hier ist
- Ein echter Editor und ein Plugin-Ökosystem. SEO, Bildkompression, Zwei-Faktor-Login, ein Inhaltsverzeichnis – alles ist ein Plugin entfernt.
- Vier Sprachen. Polylang verwaltet englische, deutsche, französische und spanische Fassungen jedes Beitrags mit verknüpften Übersetzungen.
- Open Source und portabel. Datenbank-Dump plus
wp-contentist die ganze Seite; sie kann auf jeden Host umziehen.
🔧 Was dieses Repository macht
Pods und Volumes
| Pod | Container | Zustand |
|---|---|---|
| wordpress | nginx, PHP-FPM, php-fpm_exporter | /var/www/html aus einem hostPath (/srv/wordpress-www) |
| mariadb | MariaDB, mysqld_exporter | hostPath /srv/wordpress-db |
| redis | Redis | keiner (nur Speicher) |
| CronJobs | wordpress-cron alle 5 Min., wordpress-autoupdate um 03:30 | dasselbe Volume, kurzzeitig |
Die Images sind zustandslos; der Zustand liegt in zwei Host-Verzeichnissen mit eigenen SELinux-Labels, sodass der Container dort schreiben darf und nirgends sonst. Der PHP-FPM-Container hat ein schreibgeschütztes Root-Dateisystem und nur fünf Capabilities – die, die der Entrypoint des Images wirklich braucht, um WordPress beim ersten Start ins Volume zu entpacken und von root zu www-data zu wechseln; jede wurde getestet, indem man sie entfernte. WP-CLI lädt ein Init-Container in ein Volume außerhalb des Web-Roots: nginx verweigert nur die Auslieferung von .php-Dateien, und ein .phar im Document Root wäre für jeden ein 7-MB-Download gewesen.
WP-CLI als Steuerzentrale
Ansible klickt sich nicht durch die Admin-Seite. Jede Änderung läuft über WP-CLI im laufenden Container:
k3s kubectl exec deployment/wordpress -n wordpress -c wordpress-fpm -- \
su -s /bin/sh www-data -c \
"php /usr/local/lib/wp-cli/wp-cli.phar --path=/var/www/html plugin list --format=json"Die Rolle blog_wordpress nutzt es für die Installation, die Plugin-Liste, Einstellungen, das Menü und die fünf Must-use-Plugins. Jeder Task liest zuerst und ändert nur, was abweicht – der Plugin-Task etwa installiert nur fehlende Plugins und aktiviert nur inaktive; eine frühere Version installierte bei jedem Lauf jedes Plugin neu und meldete alle als „changed“. Eine Lehre aus dem Betrieb: WP-CLI gehört www-data. Ein als root mit --allow-root gestarteter Cache-Flush hinterließ einmal ein root-eigenes Cache-Verzeichnis, in das WordPress nicht mehr schreiben konnte. Die Plugin-Installation läuft aus historischen Gründen noch als root, gefolgt von einem chown -R in einem always:-Block, damit die Besitzrechte auch dann wiederhergestellt werden, wenn eine Installation scheitert.
Fünf Must-use-Plugins
| mu-Plugin | Tut |
|---|---|
| security-hardening | Versteckt Versionsangaben, schaltet den Datei-Editor im Admin ab (eine Admin-Sitzung wäre sonst Codeausführung), schließt die Benutzer-Aufzählung über die REST-API. |
| upload-images | Wandelt jedes hochgeladene Bild in WebP um, höchstens 1.536 px und unter 280 KB – die Grenze, oberhalb derer Messenger das Vorschaubild weglassen. |
| performance-hints | Lädt die Schrift vor, lädt das größte Bild einer Seite sofort. |
| language-links | „Auch verfügbar in …“ unter jedem Beitrag, aus Polylangs Übersetzungsverknüpfungen. |
| ai-disclosure | Ein dezenter Hinweis im Footer und die Seite zur KI-Nutzung. |
Caching in drei Schichten
- Seiten-Cache (WP Super Cache): fertige HTML-Dateien auf der Platte; nginx liefert sie direkt aus, mit einer Cookie-Prüfung, damit angemeldete Benutzer und Kommentierende immer frische Seiten bekommen.
- Objekt-Cache (Redis): Ergebnisse von Datenbankabfragen innerhalb von PHP.
- OPcache: kompilierter PHP-Code, mit Zeitstempelprüfung alle 60 s – weil Plugins sich nachts an Ort und Stelle aktualisieren.
Sicherheit am Rand
xmlrpc.php,install.php, Backup- und Editor-Reste (.bak,.sql,~,.phar) und die Aufzählung über?author=antworten mit 403.- Ratenbegrenzung im Ingress, pro Client-IP: Login 5 Anfragen pro Sekunde mit einem Burst von 5, Suche 2 pro Sekunde mit einem Burst von 10; darüber 429.
- Login und
/wp-admin/nur aus deutschen Netzen, mit derselben IP-Liste, die SSH schützt (der Geofilter);admin-ajax.phpbleibt offen, weil das Frontend es braucht. Dazu Limit Login Attempts und Zwei-Faktor-Login für Admins. - Eine Content Security Policy im Report-only-Modus – Teil 5 erklärt, warum noch nicht erzwingend.
Cron und Updates ohne Besucher
DISABLE_WP_CRON schaltet den besuchergesteuerten Cron ab; stattdessen führt ein Kubernetes-CronJob alle fünf Minuten php wp-cron.php aus, in einem eigenen Container mit demselben Volume (concurrencyPolicy: Forbid, damit ein langsamer Lauf nie vom nächsten überholt wird). Ein zweiter CronJob um 03:30 aktualisiert den Kern (nur Minor-Versionen), Plugins, Themes und Sprachpakete, führt Datenbank-Migrationen aus, leert den Cache und prüft, ob die Startseite noch antwortet. Major-Updates und Image-Wechsel laufen bewusst über blog-update.yml.
📊 Gemessen
| Wert | |
|---|---|
| WordPress | 7.1, PHP 8.4, Theme MoreNews |
| Veröffentlichte Beiträge (alle vier Sprachen) | 488 |
| Seiten | 16 |
| Datenbank | 49 MB |
| Uploads | 83 MB in 1.253 Dateien (nach der WebP-Umstellung) |
| Plugins | 10 reguläre, 5 Must-use, 2 Drop-ins (Seiten-Cache, Objekt-Cache) |
| Geplante Cron-Ereignisse | 33 |
| Server-Zeit bis zum ersten Byte, gecachte Seite | ~46 ms |
| Server-Zeit bis zum ersten Byte, von PHP gerendert | ~140 ms |
| PHP-Aufrufe pro Tag | ~21.700 |
Die Zeiten sind von außen gemessen, abzüglich des TLS-Handshakes, enthalten also noch eine Netzwerk-Rundreise. Der Seiten-Cache spart etwa 90 ms pro Aufruf – und, wichtiger, PHP-Worker: Eine gecachte Seite kostet nginx einen Dateizugriff, sonst nichts.
🔍 Was ein Neuaufbau allein aus Ansible verpassen würde
Der Sinn von Ansible ist, dass sich der Server aus dem Repository neu aufbauen lässt. Für WordPress stimmt das für die Plattform und nur teilweise für die Seite:
- Inhalte und Einstellungen – Beiträge, Polylangs Spracheinrichtung, Plugin-Einstellungen, die Theme-Konfiguration – liegen in der Datenbank. Sie kommen aus dem nächtlichen Backup, nicht aus dem Repository. Das ist so gewollt.
- Ein Plugin ist nicht in Ansible. Easy Table of Contents wurde über die Admin-Seite installiert und ist als solches dokumentiert; eine frische Installation aus dem Playbook hätte es nicht, eine Wiederherstellung aus dem Backup schon. So oder so muss es eine bewusste Entscheidung sein, deshalb führt die Betriebsdoku es auf.
- All-in-One WP Migration wurde entfernt, im Oktober 2026: Seine
.wpress-Archive im Web-Root waren nur durch eine.htaccess-Datei geschützt, die nginx nicht liest. nginx blockierte sie trotzdem; das Plugin ging, weil das nächtliche Backup undblog.ymlseine Aufgabe schon erledigen.
🧰 Betrieb im Alltag
# a WP-CLI shell function on the host
wp() { k3s kubectl -n wordpress exec deploy/wordpress -c wordpress-fpm -- \
su -s /bin/sh www-data -c "php /usr/local/lib/wp-cli/wp-cli.phar --path=/var/www/html $*"; }
wp core version; wp plugin list
wp cron event list --fields=hook,next_run_relative
wp eval 'wp_cache_clear_cache();' # WP Super Cache (wp cache flush = object cache only)
wp db export - | gzip > before-change.sql.gz
# nightly update log
k3s kubectl -n wordpress logs job/$(k3s kubectl -n wordpress get jobs \
-o name | grep autoupdate | tail -1 | cut -d/ -f2)- Zwei Caches, zwei Befehle.
wp cache flushleert Redis; der Seiten-Cache brauchtwp_cache_clear_cache(). Nach dem Bearbeiten eines Beitrags außerhalb des Editors: beides. - Backups. Ein Backup-Rechner holt jede Nacht einen Datenbank-Dump und das Verzeichnis
wp-content; Wiederherstellen heißt Dump in die Datenbank und Dateien nach/srv/wordpress-www. - Ein Update zurücknehmen. Images sind in
versions.ymlfestgelegt: Pin zurücksetzen und Playbook laufen lassen. Ein Plugin:wp plugin install <slug> --version=<alt> --force. - Inhalte per Skript bearbeiten. Beitragsinhalte nur über WordPress-Funktionen schreiben, die korrekt maskieren – ein direktes Update entfernte einmal die Backslashes aus den Codebeispielen in einem Dutzend Beiträgen.
🆘 Spickzettel: Symptom → Befehl
Die Kurzfassung dieses Artikels für den Moment, in dem etwas nicht stimmt. Auf dem Host als root ausführen; <…> sind Platzhalter. occ und wp sind die Shell-Funktionen aus den Deep Dives zu Nextcloud und WordPress.
| Symptom | Befehl |
|---|---|
| Die Seite zeigt alten Inhalt | wp eval 'wp_cache_clear_cache();' # page cache |
| Weiße Seite oder Fehler 500 | k3s kubectl -n wordpress logs deploy/wordpress -c wordpress-fpm --tail=50 |
| Ein Plugin hat die Seite kaputt gemacht | wp plugin deactivate <slug> |
| Welche Versionen laufen, was steht an? | wp core version |
| Geplante Aufgaben laufen nicht | wp cron event list --fields=hook,next_run_relative |
| Hat das nächtliche Update geklappt? | k3s kubectl -n wordpress get jobs | grep autoupdate |
| Vor jeder Änderung an Inhalt oder Plugins | wp db export - | gzip > before-change.sql.gz |
| Dateien gehören plötzlich root | # wp-cli only as www-data (the function above), never --allow-root |
🔭 Wie es weitergeht
- Die CSP erzwingen, sobald die Report-only-Phase keine Überraschungen mehr bringt.
- Ein Ganzseiten-Cache am Ingress würde die Rundreise in den Pod sparen; bei ~46 ms ist das nicht dringend.
- Uploads im Objektspeicher würden den Pod vom Host-Verzeichnis unabhängig machen – erst mit mehr als einem Knoten relevant.
⚖️ Alternativen – und warum WordPress
| Option | Stärke | Warum nicht hier |
|---|---|---|
| Hugo / statische Seite | Am schnellsten und sichersten: kein PHP, keine Datenbank | Kein Web-Editor, keine Kommentare, Übersetzungs-Workflow von Hand. |
| Ghost | Modern, schnell, guter Editor | Node.js, weit weniger Plugins, Mehrsprachigkeit nur mit Umwegen. |
| Headless WordPress | WordPress als Editor, ein statisches oder JS-Frontend | Zwei Systeme statt einem für einen Blog mit einem Autor. |
| WordPress | Editor, Plugins, Polylang, und der Autor kennt es | Gewählt. |
WordPress auf Kubernetes zu betreiben hat weniger mit Kubernetes zu tun als damit, ehrlich zu sein, wo der Zustand liegt. Hier liegt er in zwei Verzeichnissen und einer Datenbank, alle drei gesichert, alles andere austauschbar – und die Steuerzentrale ist nicht die Admin-Seite, sondern WP-CLI, aufgerufen von Ansible, als der richtige Benutzer.


