
🌐 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.
Am 16. Oktober 2026 bekamen die drei Server eine Optimierungsrunde: der Blog und zwei Nextcloud-Server. Keiner davon war langsam. Die Frage war eine andere: Auf allen dreien war Speicher frei – wo würde er wirklich helfen, und wo würde er nur beschäftigt aussehen? Dieser Beitrag geht die Methode durch, jede Änderung mit der Zahl dahinter, die Dinge, die bewusst unverändert blieben, und was die Messungen vier Tage später sagen.
🧭 Die Methode
- Spitzen über sieben Tage, keine Momentaufnahmen. Ein Blick in
topzeigt einen Moment. Prometheus hält 30 Tage Messwerte im 15-Sekunden-Takt;max_over_time(…[7d])liefert die schlimmste Minute der Woche, und die müssen Grenzen überstehen. - Zählen statt Annehmen. Wie viele PHP-Dateien es gibt, wie viele temporäre Tabellen pro Tag auf die Platte gehen, wie viele inotify-Instanzen belegt sind – jeweils ein einziger Befehl.
- Ein Grund pro Änderung. Neben jeder Einstellung unten steht eine Messung. Einstellungen ohne Messung wurden nicht angefasst.
- Vorher und nachher. Dieselben Abfragen nach dem Rollout noch einmal, dazu Container-Metriken, die zeigen, ob ein Limit jetzt zu eng ist.
Die Ressourcen und was man aus ihnen liest
| Ressource | Metrik | Falle |
|---|---|---|
| CPU | Verbrauch pro Container gegen sein Limit; CFS-Drosselung | Ein Container kann gedrosselt werden, während der Host untätig ist – der Engpass ist das Limit, nicht die Maschine. |
| Speicher | Working Set gegen das Limit | „Belegt“ enthält den Page Cache; freier RAM ist nicht verschenkt, Linux nutzt ihn für Dateien. |
| Worker-Speicher | PSS statt RSS | RSS zählt gemeinsamen Speicher (OPcache) in jedem Prozess mit und übertreibt hier um den Faktor fünf. |
| Warteschlangen | FPM-Listen-Queue, max_children reached | Null in der Queue heißt genug Worker – mehr kosten nur Speicher. |
| Kernel-Grenzen | inotify-Instanzen und -Watches, Dateideskriptoren | Sie scheitern mit seltsamen Fehlern („too many open files“), lange bevor irgendetwas voll aussieht. |
🛡️ Warum sich das lohnt, wenn nichts langsam ist
- Grenzen lassen sich billig anheben, bevor sie zuschlagen, und teuer diagnostizieren danach, wenn das Symptom ein Sync-Client ist, der einmal pro Woche hängt.
- Standardwerte sind die Vermutung von jemand anderem. Zwei davon lagen für Nextcloud um den Faktor vier daneben.
- Zu wissen, was man nicht ändert, ist das halbe Ergebnis: Größere Puffer an der falschen Stelle verschieben den Engpass nur oder verstecken ihn.
🔧 Was geändert wurde
| Bereich | Gemessen | Geändert auf | Wo |
|---|---|---|---|
| PHP-FPM-Worker (Nextcloud) | Spitze 17 von 20 und 10 von 12 gleichzeitig belegt | 30 und 20 Worker; Container-Limits 6 und 5 GiB | Host-Variablen |
| OPcache (Nextcloud) | 15.500 und 17.800 PHP-Dateien; Limit 10.000 = 16.229 Plätze | 256 MB, 20.000 Dateien (32.531 Plätze) | PHP-ConfigMap |
| APCu (Nextcloud) | PHP-Standard 32 MB, ein Segment für alle Worker | 128 MB | PHP-ConfigMap |
| MariaDB-Temp-Dateien | 2.000–3.500 temporäre Tabellen pro Tag auf der Platte | /tmp als tmpfs (Host: 4 GB, Blog-Pod: 512 MiB) | MariaDB-Unit / Pod |
| Collabora | „Office konnte nicht geladen werden“ beim Kaltstart | 10 und 6 vorgestartete Dokumentprozesse | Host-Variablen |
| Hängende PHP-Aufrufe | überhaupt keine Grenze für die echte Laufzeit | request_terminate_timeout 300 s Blog / 3.600 s Nextcloud | FPM-Pool |
| Kernel: inotify | Standard 128 Instanzen pro Benutzer; root nutzte schon 63 | 8.192 Instanzen, 524.288 Watches | common_sysctl |
| Kernel: Write-back | Dirty Pages bei 10 / 30 % des RAM geschrieben | 5 / 10 % | common_sysctl |
Warum diese und keine anderen
- Worker: 17 von 20 sind 85 % in der Spitze – der nächste volle Nachmittag hätte in die Warteschlange geführt. Die Container-Limits folgten den Pools, nicht umgekehrt: PSS pro Worker mal
max_children, plus OPcache und APCu, mit Luft. - OPcache: Auf dem kleineren Nextcloud-Server passten 1.500 Dateien nie hinein und wurden bei jedem Aufruf kompiliert. Das entscheidet die Zahl der Dateien, nicht ein Gefühl.
- Temporäre Tabellen mit TEXT-Spalten können nie die Memory-Engine nutzen, ein höheres
tmp_table_sizehätte also nichts geändert. Das Verzeichnis, in das sie geschrieben werden, in den RAM zu legen, schon. Eine Falle auf dem Weg: Zusammen mitPrivateTmp=yesgewinnt das private Verzeichnis von systemd auf der Platte, und das tmpfs wird stillschweigend ignoriert – gefunden durch Testen mitsystemd-run, nicht durch Lesen. Und im Blog-Pod zählt ein speicherbasiertes Volume gegen das Speicherlimit des Containers, das deshalb von 768 MiB auf 1.280 MiB stieg. - inotify: K3s, containerd und jeder Pod mit einem Datei-Watcher belegen Instanzen als root. 63 von 128 sind heute in Ordnung und nach den nächsten paar Pods ein rätselhafter Fehler.
- Write-back bei 10 % statt 30 % von 23 GB heißt höchstens 2,3 GB ungeschriebene Daten statt 7 GB – kürzere Hänger, wenn der Kernel endlich schreiben muss, und weniger Datenverlust bei einem harten Absturz.
tuned und sysctl: wer gewinnt
AlmaLinux betreibt tuned mit dem Profil virtual-guest, das selbst vm.dirty_ratio = 30 setzt. Unser 10 ist das, was der Kernel meldet, weil tuned nach seinem eigenen Profil /etc/sysctl.d/ erneut anwendet (reapply_sysctl = 1, der Standard). Ohne das würde das Profil die Einstellung bei jedem Start still rückgängig machen. Der einzige verlässliche Beweis ist der Live-Wert mit sysctl -n nach einem Neustart.
🧊 Was bewusst unverändert blieb
| Bereich | Gemessen | Warum nicht |
|---|---|---|
| InnoDB-Buffer-Pool | Trefferquote > 99,8 %, Pools größer als die Daten (2 GB für 344 MB) | Mehr Speicher kann einen Cache nicht verbessern, der schon alles hält. |
| Redis | 1–1,5 MB belegt | Nichts zu gewinnen. |
| Blog-PHP-FPM | Spitze 6 von 12 Workern, Queue höchstens 1 | Der Pool war groß genug. |
| Blog-OPcache | ~4.600 PHP-Dateien, Limit 10.000 | Passt. |
| MariaDB-Verbindungen | Spitze 15 von 50 | Reichlich Luft. |
Freier Speicher, den nichts nutzt, ist auch nicht verloren: Linux füllt ihn mit Datei-Cache, und genau das will ein Dateiserver.
📊 Vier Tage später
| Blog | Nextcloud A | Nextcloud B | |
|---|---|---|---|
| Spitze aktive PHP-Worker (7 Tage) | 8 von 12 | 13 von 30 | 9 von 20 |
max_children erreicht | 0 | 0 | 0 |
| Container-Speicher Spitze / Limit | 538 MiB / 1 GiB | 763 MiB / 6 GiB | 277 MiB / 5 GiB |
| Gedrosselte CPU-Perioden | 2,1 % | 2,2 % | 0,2 % |
| Langsame PHP-Aufrufe (> 5 s) | 0 | 0 | 0 |
| Langsame Datenbankabfragen (> 1 s) | 0 | 0 | 0 |
| Verfügbarer Speicher auf dem Host | 5,0 von 7,5 GB | 19 von 23 GB | 8,5 von 11,4 GB |
Kein Pool kam in die Nähe seiner Grenze, kein Container in die Nähe seines Speicherlimits, die Drosselung bleibt im niedrigen einstelligen Bereich, und die Hosts haben noch den Großteil ihres Speichers frei. Die Änderungen kosteten wenig und entfernten drei echte Decken – Worker, Bytecode-Plätze, eine Laufzeitgrenze –, bevor jemand dagegenstieß.
Der Messfehler
Beim Sammeln der Zahlen oben lieferte die erste Abfrage für den PHP-Container des Blogs eine Spitze von 940 MiB bei 1 GiB Limit – das hätte sofort nach einem höheren Limit verlangt. Die Abfrage summierte alle Serien des Containers: Während eines Rollouts existieren der alte und der neue Pod gleichzeitig, und direkt nach einem Container-Neustart meldet cAdvisor kurz die alte und die neue cgroup. Die Spitze eines einzelnen Pods lag bei 538 MiB. Nach Pod summieren oder das Maximum pro Pod nehmen ist die richtige Lesart; ein einfaches sum über den Containernamen addiert Dinge, die nie im selben Moment in einem Prozess existierten.
🧰 Die Übung wiederholen
# worst minute of the week, per pod (not summed over pods!)
max_over_time(max by (pod) (container_memory_working_set_bytes{container="nextcloud-fpm"})[7d:1m])
# share of CPU periods throttled
sum(increase(container_cpu_cfs_throttled_periods_total{container="nextcloud-fpm"}[7d]))
/ sum(increase(container_cpu_cfs_periods_total{container="nextcloud-fpm"}[7d]))
# PHP-FPM peak and queue
max_over_time(sum(phpfpm_active_processes)[7d:1m])
max_over_time(max(phpfpm_listen_queue)[7d:1m])- Wann: nach einer Laständerung (neue Benutzer, eine neue App), nach einem Major-Upgrade und sonst alle paar Monate.
- Wo zuerst schauen: das Dashboard „Container-Ressourcen“ (Limits, Drosselung, OOM-Kills) und die Alarme zu Container-Speicher und Drosselung, die eine Mail schicken, wenn ein Limit eng wird.
- Was aufschreiben: die Messung neben der Einstellung. Die Betriebsdoku hat genau dafür eine Tabelle, damit die nächste Runde bei Zahlen beginnt und nicht bei Erinnerungen.
🆘 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.
| Symptom | Befehl |
|---|---|
| Fehlt einem Pod Speicher? | max_over_time(max by (pod) (container_memory_working_set_bytes{container="<c>"})[7d:1m]) |
| Wird ein Pod gebremst (Throttling)? | sum(increase(container_cpu_cfs_throttled_periods_total{container="<c>"}[7d])) |
| Reichen die PHP-Worker? | max_over_time(sum(phpfpm_active_processes)[7d:1m]) |
| Wurde etwas wegen Speichermangel beendet? | journalctl -k | grep -i "out of memory" |
| Was hat der Host noch übrig? | free -h; uptime; df -h / |
| Wo zuerst schauen | # Grafana: dashboard "Container-Ressourcen" (limits, throttling, OOM kills) |
🔭 Wie es weitergeht
- Lasttests mit k6 gegen eine Testinstanz, um zu sehen, wo die Decken liegen, statt auf sie zu warten.
- Alarmschwellen aus gemessenen Spitzen statt runder Zahlen – die erste, für die PHP-FPM-Queue, kam im Oktober 2026 aus 14 Tagen Daten.
- Kapazitätsplanung: Datenwachstum des größeren Nextcloud-Servers pro Monat gegen seine Platte.
⚖️ Alternativen – und warum zuerst messen
| Ansatz | Stärke | Schwäche |
|---|---|---|
| Optimieren nach Checkliste | Schnell, kein Werkzeug nötig | Ändert, was der Autor der Liste brauchte, nicht was dieser Server braucht. |
| Größere Maschine | Hilft immer ein bisschen | Versteckt die Decke, statt sie zu verschieben; kostet jeden Monat. |
| Kommerzielle APM-Werkzeuge | Traces bis zur einzelnen Funktion | Kosten, ein Agent in jedem Container, und überdimensioniert für diese Lasten. |
| Messen, dann eine Sache ändern | Jede Änderung hat einen Grund und eine Kontrolle | Braucht Metriken, die schon da sind – deshalb kamen sie zuerst. |
Das wertvollste Ergebnis des Tages war keine Einstellung, sondern eine Liste: acht Änderungen, jede mit einer Zahl, und fünf Dinge, die unverändert blieben, ebenfalls jede mit einer Zahl. Die nächste Optimierungsrunde beginnt dort, und falls sich ein Wert als falsch herausstellt, ist klar, worauf er beruhte.


