Locking, Skalierung und Graceful Shutdown richtig lösen
Magento Cron und Message Queue Consumer laufen in Containern anders als auf einem klassischen Server mit Host-Crontab. Ohne dedizierte Container, klares Locking und sauberes Signal Handling entstehen doppelte Jobs, hängende Consumer und verlorene Nachrichten. Dieser Artikel zeigt, wie Magento Cron und Consumer als eigenständige, überwachte Container zuverlässig betrieben werden.
Inhaltsverzeichnis
- 1. Warum Magento Cron in Containern anders behandelt wird
- 2. Cron-Container-Architektur statt Host-Crontab
- 3. cron_schedule und Locking zwischen Cron-Containern
- 4. Cron-Gruppen und Job-Prioritäten containerisiert steuern
- 5. Message Queue Consumer als eigene Container
- 6. Consumer-Skalierung und Instanzenzahl pro Queue
- 7. Monitoring von Cron und Consumer Health
- 8. Graceful Shutdown und Signal Handling bei Consumern
- 9. Cron vs. Consumer Patterns im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Magento Cron in Containern anders behandelt wird
Magento Cron ist auf einem klassischen Server ein einziger Prozess, der über crontab alle 60 Sekunden angestoßen wird und intern die geplanten Jobs abarbeitet. In einer Container-Umgebung mit mehreren PHP-FPM-Replikas stellt sich sofort die Frage, welcher Container den Cron ausführen darf, denn ein Cron-Aufruf pro Replika würde jeden Job mehrfach gleichzeitig starten. Wer Magento Cron in Containern naiv als Teil jedes App-Containers laufen lässt, produziert doppelte Newsletter-Versendungen, doppelte Reindex-Läufe und im schlimmsten Fall doppelte Zahlungsabgleiche.
Die saubere Lösung ist ein dedizierter Cron-Container, der als einzige Instanz im gesamten Stack für Magento Cron zuständig ist, unabhängig davon, wie viele PHP-FPM-Container für den Web-Traffic skaliert werden. Dieser Cron-Container teilt sich Codebase und Datenbank mit den Web-Containern, führt aber ausschließlich bin/magento cron:run in einer Schleife aus und bedient keine HTTP-Requests.
Für Message Queue Consumer gilt eine ähnliche, aber eigenständige Überlegung: Consumer sind lang laufende Prozesse, die kontinuierlich Nachrichten aus RabbitMQ oder der Datenbank-Queue ziehen und verarbeiten. Sie brauchen eigene Container, eigenes Signal Handling für Graceful Shutdown und eine Skalierungsstrategie, die sich von der des Cron-Containers unterscheidet.
2. Cron-Container-Architektur statt Host-Crontab
Ein dedizierter Cron-Container für Magento Cron unterscheidet sich vom Web-Container nur in der Startbefehl-Definition, nicht im zugrundeliegenden Image. Dasselbe PHP-Image mit denselben Extensions und derselben Codebase wird verwendet, aber statt PHP-FPM zu starten, läuft eine Schleife, die cron:run minütlich aufruft. Das stellt sicher, dass Cron-Jobs exakt dieselbe Umgebung wie die Web-Anwendung sehen, inklusive Umgebungsvariablen, Extensions und Konfigurationsdateien.
Wichtig für Magento Cron in Containern: Der Cron-Container darf niemals horizontal skaliert werden wie die Web-Container. Genau eine Instanz reicht aus und ist auch notwendig, denn zwei parallel laufende Cron-Container ohne zusätzliches Locking würden trotz gemeinsamer Datenbank versuchen, dieselben Jobs gleichzeitig zu starten, bevor der Lock-Mechanismus in cron_schedule greifen kann.
# docker-compose.yml — dedicated single-replica cron container
services:
magento-web:
image: registry.mironsoft.de/magento-shop:latest
deploy:
replicas: 3
command: ["php-fpm"]
networks: [backend]
magento-cron:
image: registry.mironsoft.de/magento-shop:latest
deploy:
replicas: 1
entrypoint: ["/usr/local/bin/cron-loop.sh"]
environment:
CRON_INTERVAL_SECONDS: "60"
networks: [backend]
magento-consumer-orders:
image: registry.mironsoft.de/magento-shop:latest
deploy:
replicas: 2
command: ["bin/magento", "queue:consumers:start", "product_action_attribute.update"]
networks: [backend]
networks:
backend:
3. cron_schedule und Locking zwischen Cron-Containern
Magento verwaltet geplante Jobs in der Tabelle cron_schedule, die als natürlicher Locking-Mechanismus für Magento Cron dient. Jeder Job-Eintrag hat einen Status pending, running, success oder error, und Magento setzt den Status atomar über eine Datenbank-Transaktion auf running, bevor der Job tatsächlich ausgeführt wird. Selbst wenn versehentlich zwei Cron-Container gleichzeitig laufen, verhindert dieser Status-Übergang in den meisten Fällen doppelte Ausführung, solange die Datenbank-Transaktionsisolation korrekt konfiguriert ist.
Trotzdem sollte man sich bei Magento Cron in Containern nicht allein auf dieses Locking verlassen, weil es Rennbedingungen bei sehr kurzen Zeitfenstern zwischen Statusabfrage und Statusänderung geben kann. Ein zusätzliches Container-Level-Lock über einen Redis-basierten Distributed Lock, der vor jedem cron:run-Aufruf geprüft wird, ist die robustere Lösung, gerade wenn während eines Blue-Green-Deployments kurzzeitig zwei Cron-Container aktiv sein könnten.
#!/usr/bin/env bash
# cron-loop.sh — single-replica cron runner with Redis-based distributed lock
set -euo pipefail
LOCK_KEY="magento:cron:lock"
LOCK_TTL=90
acquire_lock() {
redis-cli -h redis SET "$LOCK_KEY" "$(hostname)" NX EX "$LOCK_TTL" | grep -q "OK"
}
while true; do
if acquire_lock; then
echo "[INFO] $(date -Iseconds) running cron:run"
php bin/magento cron:run || echo "[WARN] cron:run exited non-zero" >&2
else
echo "[WARN] Could not acquire cron lock, another instance may be running" >&2
fi
sleep "${CRON_INTERVAL_SECONDS:-60}"
done
4. Cron-Gruppen und Job-Prioritäten containerisiert steuern
Magento gruppiert Cron-Jobs in crontab.xml-Gruppen wie default, index und individuelle Modul-Gruppen. In einer Container-Umgebung lässt sich diese Gruppierung nutzen, um ressourcenintensive Gruppen wie den Reindex in einen eigenen Cron-Container mit mehr CPU-Limit auszulagern, während der default-Cron-Container mit leichtgewichtigen Jobs schlank bleibt. Das verhindert, dass ein langlaufender Reindex-Job zeitkritische Jobs wie Newsletter-Queues oder Sitemap-Generierung blockiert.
Für Magento Cron in Containern bedeutet das konkret: Statt eines einzigen Cron-Containers, der alle Gruppen über bin/magento cron:run gemeinsam abarbeitet, startet man mehrere Cron-Container mit cron:run --group=index beziehungsweise cron:run --group=default als separate Prozesse. Jede Gruppe bekommt so ihr eigenes Ressourcenlimit und ihre eigene Lock-Domäne, ohne sich gegenseitig zu blockieren.
5. Message Queue Consumer als eigene Container
Ein Message Queue Consumer in Magento ist ein lang laufender PHP-Prozess, der über bin/magento queue:consumers:start <consumer-name> gestartet wird und kontinuierlich Nachrichten aus einer RabbitMQ-Queue oder der Datenbank-Queue verarbeitet, bis er beendet wird. Anders als Cron, das kurzlebige Jobs im Minutentakt startet, läuft ein Consumer dauerhaft und muss deshalb als eigener, langlebiger Container-Prozess verwaltet werden, nicht als wiederholter Aufruf in einer Schleife.
Jeder Message Queue Consumer-Typ, etwa für Produkt-Attribut-Updates, Bestellversand-Benachrichtigungen oder Preisindex-Aktualisierungen, bekommt idealerweise einen eigenen Container mit eigenem Startbefehl. Das erlaubt, jeden Consumer unabhängig zu skalieren, neu zu starten oder mit individuellen Ressourcenlimits zu versehen, ohne die anderen Consumer zu beeinflussen.
# docker-compose.consumers.yml — one container per consumer type
services:
consumer-product-update:
image: registry.mironsoft.de/magento-shop:latest
command: ["bin/magento", "queue:consumers:start", "product_action_attribute.update", "--max-messages=1000"]
deploy:
replicas: 2
restart_policy:
condition: any
delay: 5s
networks: [backend]
consumer-order-shipment:
image: registry.mironsoft.de/magento-shop:latest
command: ["bin/magento", "queue:consumers:start", "sales_rule_quote_trigger_recollect", "--max-messages=500"]
deploy:
replicas: 1
restart_policy:
condition: any
delay: 5s
networks: [backend]
networks:
backend:
Der --max-messages-Parameter ist beim Betrieb von Message Queue Consumern in Containern entscheidend: Ohne dieses Limit läuft der Consumer-Prozess unbegrenzt weiter und akkumuliert über Tage hinweg Speicherlecks aus PHP-Extensions oder Bibliotheken. Mit einem gesetzten Limit beendet sich der Prozess nach der definierten Anzahl verarbeiteter Nachrichten sauber, und die restart_policy des Containers startet ihn automatisch neu, mit frischem Speicher.
6. Consumer-Skalierung und Instanzenzahl pro Queue
Die richtige Anzahl an Replikas für einen Message Queue Consumer hängt von der Nachrichtenrate der jeweiligen Queue und der Verarbeitungsdauer pro Nachricht ab, nicht von einer pauschalen Regel. Eine Queue für Produkt-Reindex-Trigger mit hoher Nachrichtenfrequenz braucht mehr parallele Consumer-Instanzen als eine Queue für seltene administrative Events. RabbitMQ erlaubt mehrere Consumer pro Queue, die sich die Nachrichten automatisch über Round-Robin-Verteilung teilen, solange prefetch_count korrekt konfiguriert ist.
Ein zu hoher prefetch_count führt dazu, dass ein einzelner überlasteter Consumer viele Nachrichten vorab reserviert, während andere Consumer-Instanzen im selben Moment leerlaufen. Für Magento Message Queue Consumer in Containern empfiehlt sich ein niedriger Prefetch-Wert von 1 bis 5 kombiniert mit horizontaler Skalierung über mehrere Container-Replikas, statt einem einzigen Consumer mit hohem Prefetch-Wert.
7. Monitoring von Cron und Consumer Health
Ein Cron-Container, der abgestürzt ist, meldet sich nicht von selbst. Ohne aktives Monitoring bemerkt niemand, dass Magento Cron seit Stunden nicht mehr läuft, bis ausbleibende Newsletter oder veraltete Preise auffallen. Ein einfacher Healthcheck-Ansatz prüft, ob die letzte Zeile aus cron_schedule mit Status success nicht älter als die doppelte Cron-Intervalldauer ist, und meldet andernfalls einen kritischen Fehler an das Monitoring-System.
Für Message Queue Consumer ist die RabbitMQ Management API die zuverlässigste Quelle für Health-Daten: Sie liefert die aktuelle Queue-Länge, die Anzahl aktiver Consumer und die Nachrichtenverarbeitungsrate pro Sekunde. Ein wachsender Rückstau in der Queue bei gleichzeitig stabiler Consumer-Anzahl deutet auf einen Performance-Engpass in der Nachrichtenverarbeitung selbst hin, nicht auf einen Ausfall des Consumers.
#!/usr/bin/env bash
# monitor-cron-consumer.sh — health check for cron freshness and queue backlog
set -euo pipefail
MAX_CRON_AGE_SECONDS=180
QUEUE_BACKLOG_THRESHOLD=5000
last_success=$(mysql -h mysql -N -e \
"SELECT UNIX_TIMESTAMP(finished_at) FROM cron_schedule WHERE status='success' ORDER BY finished_at DESC LIMIT 1")
now=$(date +%s)
age=$((now - last_success))
if (( age > MAX_CRON_AGE_SECONDS )); then
echo "[CRITICAL] Last successful cron job is ${age}s old" >&2
exit 2
fi
queue_length=$(curl -s -u guest:guest \
"http://rabbitmq:15672/api/queues/%2F/product_action_attribute.update" \
| jq '.messages')
if (( queue_length > QUEUE_BACKLOG_THRESHOLD )); then
echo "[WARNING] Queue backlog at ${queue_length} messages" >&2
exit 1
fi
echo "[OK] Cron fresh (${age}s), queue backlog nominal (${queue_length})"
8. Graceful Shutdown und Signal Handling bei Consumern
Wenn Docker einen Container stoppt, sendet er zunächst SIGTERM und nach einer Karenzzeit von standardmäßig zehn Sekunden ein hartes SIGKILL. Ein Message Queue Consumer, der gerade eine Nachricht verarbeitet, etwa einen Bestellversand mit Zahlungsabgleich, darf nicht mitten im Verarbeitungsschritt abgebrochen werden, weil das zu inkonsistenten Daten führt. Der Consumer-Prozess muss SIGTERM abfangen, die aktuell laufende Nachricht zu Ende verarbeiten und sich erst danach sauber beenden.
Magentos eingebauter Consumer-Mechanismus reagiert grundsätzlich auf SIGTERM, aber die Karenzzeit in der Container-Orchestrierung muss großzügig genug bemessen sein, damit auch die langsamste einzelne Nachrichtenverarbeitung abgeschlossen werden kann. Eine stop_grace_period von 30 bis 60 Sekunden ist für die meisten Magento Message Queue Consumer ein realistischer Wert, abhängig von der komplexesten Nachricht, die die jeweilige Queue verarbeitet.
9. Cron vs. Consumer Patterns im Vergleich
Cron und Message Queue Consumer lösen unterschiedliche Probleme in Magento und brauchen deshalb unterschiedliche Container-Patterns. Die folgende Tabelle stellt die wichtigsten Unterschiede beim containerisierten Betrieb gegenüber.
| Merkmal | Cron-Container | Consumer-Container | Empfehlung |
|---|---|---|---|
| Replika-Anzahl | Immer genau 1 | Beliebig skalierbar | Cron nie horizontal skalieren |
| Prozesslaufzeit | Schleife, minütliche Jobs | Dauerhaft, event-getrieben | Unterschiedliche Neustart-Strategie |
| Locking | cron_schedule + Distributed Lock | Queue-Broker übernimmt Verteilung | Bei Cron zusätzliches Lock nötig |
| Speicherhygiene | Neustart pro Cron-Intervall | --max-messages erzwingt Neustart | Limits gegen Memory-Leaks setzen |
| Graceful Shutdown | Kurze Karenzzeit ausreichend | Lange Karenzzeit erforderlich | stop_grace_period für Consumer erhöhen |
Beide Container-Typen teilen sich dieselbe Codebase und dieselbe Datenbank wie die Web-Container, unterscheiden sich aber fundamental in Skalierungslogik, Locking-Anforderungen und Shutdown-Verhalten. Wer Magento Cron und Message Queue Consumer mit denselben Container-Regeln behandelt, wie sie für Web-Container gelten, produziert entweder doppelte Jobs oder abgebrochene Nachrichtenverarbeitung.
Mironsoft
Cron- und Consumer-Infrastruktur für Magento-Shops
Cron-Jobs und Consumer, die zuverlässig im Hintergrund laufen?
Wir richten dedizierte Cron- und Consumer-Container mit Distributed Locking, Skalierungsstrategie und Graceful Shutdown ein, damit Hintergrundprozesse in eurem Magento-Stack nicht mehr unbemerkt ausfallen.
Cron-Audit
Bestehende Cron-Konfiguration auf Doppelläufe und fehlendes Locking prüfen
Consumer-Setup
Message Queue Consumer containerisieren, skalieren und mit Graceful Shutdown ausstatten
Monitoring
Health Checks für Cron-Freshness und Queue-Backlog in bestehendes Monitoring einbinden
10. Zusammenfassung
Magento Cron in Containern braucht genau einen dedizierten Container mit zusätzlichem Distributed Lock, niemals mehrere horizontal skalierte Instanzen, weil sonst Jobs doppelt ausgeführt werden. Message Queue Consumer dagegen profitieren von horizontaler Skalierung über mehrere Container-Replikas, solange der Prefetch-Wert niedrig gehalten wird und jeder Consumer über --max-messages regelmäßig neu startet, um Speicherlecks zu vermeiden.
Beide Prozessarten brauchen aktives Monitoring, weil ein stiller Ausfall erst durch Symptome wie ausbleibende Newsletter oder wachsende Warteschlangen auffällt, nicht durch einen offensichtlichen Fehler. Graceful Shutdown mit ausreichender Karenzzeit stellt sicher, dass Consumer bei einem Deployment nicht mitten in der Verarbeitung einer Nachricht abgebrochen werden. Mit diesen Patterns laufen Cron und Consumer in Magento-Container-Stacks genauso zuverlässig wie auf einem klassischen dedizierten Server, nur mit besserer Skalierbarkeit.
Magento Cron und Message Queue Consumer — Das Wichtigste auf einen Blick
Cron-Container
Genau eine Instanz, Distributed Lock über Redis zusätzlich zu cron_schedule.
Consumer-Container
Beliebig horizontal skalierbar, niedriger Prefetch-Wert für gleichmäßige Verteilung.
Speicherhygiene
--max-messages erzwingt regelmäßigen Neustart und verhindert Memory-Leaks.
Shutdown
Großzügige stop_grace_period, damit laufende Nachrichten vollständig verarbeitet werden.