Magento Cron und Message Queue Consumer in Containern betreiben
AI generated
FROM
RUN
Docker · Magento · Cron · Message Queue
Magento Cron und Message Queue Consumer in Containern betreiben
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.

18 Min. Lesezeit Cron · Consumer · Locking · Graceful Shutdown Magento 2.4.8 · Docker · RabbitMQ

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.

11. FAQ: Magento Cron und Message Queue Consumer in Containern

1Cron auf mehreren Containern gleichzeitig?
Nicht ohne Locking, sonst starten mehrere Container dieselben Jobs doppelt.
2Reicht cron_schedule-Locking allein?
Meist ja, robuster mit zusätzlichem Redis-Distributed-Lock gegen Rennbedingungen.
3Wie starten Consumer in Containern?
Über queue:consumers:start als dauerhaften Prozess, eigener Container pro Consumer-Typ.
4Warum --max-messages nötig?
Verhindert unbegrenztes Laufen und Speicherlecks, sauberer Neustart mit frischem Speicher.
5Wie viele Consumer-Instanzen?
Abhängig von Nachrichtenrate, niedriger Prefetch plus mehrere Replikas verteilen besser.
6Wie Cron-Ausfall erkennen?
Health Check auf Alter des letzten erfolgreichen cron_schedule-Eintrags.
7Container-Stop bei laufendem Consumer?
SIGTERM abfangen, Nachricht fertig verarbeiten, großzügige stop_grace_period setzen.
8Alle Cron-Gruppen im selben Container?
Nicht zwingend, Reindex-Gruppe kann eigenen Container mit mehr Ressourcen bekommen.
9Rückstau in Queue erkennen?
RabbitMQ Management API liefert Queue-Länge, Consumer-Zahl und Verarbeitungsrate.
10Cron-Container horizontal skalieren?
Nein, genau eine Instanz ist Pflicht, anders als bei Web-Containern.