während des Deployments sicher kontrollieren
Laufende Cron-Jobs und Queue Consumer während eines Deployments sind eine unterschätzte Fehlerquelle. Ein Consumer, der im alten Release-Code läuft während der Symlink auf das neue Release wechselt, kann zu Datenkonsistenz-Problemen führen. Dieser Artikel erklärt, wie Cron und Consumer im GitLab-Deployment-Prozess zuverlässig kontrolliert werden.
Inhaltsverzeichnis
- 1. Das Problem mit Cron und Consumer im Deployment
- 2. Magento Cron: Wie er funktioniert und was ihn stört
- 3. Queue Consumer: Supervisor, systemd und manuelle Kontrolle
- 4. Cron und Consumer vor dem Deployment stoppen
- 5. Auf laufende Consumer warten – graceful shutdown
- 6. Symlink-Wechsel und Magento-Schritte in der richtigen Reihenfolge
- 7. Cron und Consumer nach dem Deployment neu starten
- 8. Vergleich: unkontrolliert vs. kontrolliert
- 9. Typische Fehlerbilder bei Cron und Consumer im Deployment
- 10. Zusammenfassung
- 11. FAQ
1. Das Problem mit Cron und Consumer im Deployment
Magento-Deployments haben eine Phase, in der das System kurz in einem inkonsistenten Zustand ist: Der Symlink zeigt bereits auf das neue Release, aber Prozesse, die im alten Kontext gestartet wurden, laufen noch weiter. Cron-Jobs, die alle Minute ausgeführt werden, können in genau diesem Moment starten und auf Code zugreifen, der aus dem neuen Release stammt, aber mit Konfigurationen oder Datenbankzuständen arbeitet, die noch auf das alte Release abgestimmt sind. Queue Consumer, die dauerhaft laufen, sind noch problematischer: Sie laden Klassen beim Start, benutzen sie aber über Minuten oder Stunden hinweg.
Die gute Nachricht: Das Problem ist lösbar, wenn es als expliziter Schritt im Deployment-Prozess behandelt wird. Der Fehler, den viele Teams machen, ist es, Cron und Consumer als gegeben hinzunehmen und den Symlink-Wechsel durchzuführen, ohne vorher sicherzustellen, dass keine dieser Prozesse gerade aktiv ist. Das Ergebnis sind sporadische Fehler nach Deployments – schwer zu reproduzieren, weil sie vom Timing abhängen. Die Lösung ist ein klares Protokoll: Stoppen vor dem Release, warten bis alle Prozesse beendet sind, deployen, neu starten.
2. Magento Cron: Wie er funktioniert und was ihn stört
Magento nutzt zwei Cron-Prozesse: cron:run (verarbeitet geplante Jobs) und cron:run --group=index (Reindexierungen). Beide werden typischerweise über die System-Crontab aufgerufen. Das Problem beim Deployment: Die Crontab läuft kontinuierlich und startet den nächsten Cron-Run automatisch, egal was der Deployment-Prozess gerade tut. Wenn setup:upgrade läuft und gleichzeitig ein Cron-Job Datenbankzugriffe durchführt, können diese sich beißen – bis hin zu Deadlocks in der Magento-Schedulingverwaltung.
Die einfachste Kontrolle über Magento-Cron während des Deployments: bin/magento cron:remove vor dem Deployment (entfernt den Crontab-Eintrag), dann die eigentlichen Deploy-Schritte, dann bin/magento cron:install danach (trägt den Crontab-Eintrag wieder ein). Dieser Ansatz stoppt den Cron sauber, ohne laufende Jobs abrupt abzubrechen – alle bereits gestarteten Jobs laufen durch, aber keine neuen werden gestartet. Für den Zeitraum des Deployments ist das der akzeptable Trade-off zwischen Datensicherheit und minimalem Wartungsfenster.
3. Queue Consumer: Supervisor, systemd und manuelle Kontrolle
Queue Consumer in Magento werden üblicherweise über einen Prozessmanager dauerhaft am Laufen gehalten. Die zwei verbreitetsten Ansätze sind Supervisor und systemd. Supervisor verwaltet Prozesse über eine Konfigurationsdatei und bietet Befehle wie supervisorctl stop all und supervisorctl start all. systemd verwaltet Services über Unit-Files und bietet systemctl stop magento-consumer@* und systemctl start. In beiden Fällen ist die Kontrolle über den Deployment-Prozess einfacher als bei manuell gestarteten Consumer-Prozessen ohne Prozessmanager.
Wer Consumer ohne Prozessmanager startet – z.B. direkt über die Crontab oder manuelle SSH-Sessions – hat das häufigste Anti-Pattern in Magento-Deployments: Prozesse laufen im Hintergrund, niemand weiß genau wie viele, niemand kann sie zuverlässig stoppen. Der Prozessmanager ist nicht nur für die Stabilitätsüberwachung wichtig, sondern auch für die Deployment-Kontrolle: Ein definierter Stop-Befehl, ein definierter Start-Befehl, ein definierbarer Wartestatus. Ohne Prozessmanager muss der Deploy-Skript die Consumer selbst über pkill oder ähnliches stoppen – fehleranfällig, weil Consumer nicht immer dieselben Prozessnamen haben.
# GitLab CI/CD deploy job with controlled cron and consumer shutdown
deploy:production:
stage: deploy
environment: production
only:
- tags
before_script:
- chmod 600 "$SSH_PRIVATE_KEY"
- eval "$(ssh-agent -s)"
- ssh-add "$SSH_PRIVATE_KEY"
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
script:
# Phase 1: Stop background processes before deployment
- |
ssh "$DEPLOY_USER@$DEPLOY_HOST" bash <<'REMOTE'
set -euo pipefail
# Remove Magento cron entries — running jobs finish, no new ones start
php "$DEPLOY_PATH/current/bin/magento" cron:remove || true
# Stop all supervised consumers gracefully
supervisorctl stop all 2>/dev/null || systemctl stop "magento-consumer@*" 2>/dev/null || true
echo "Background processes stopped"
REMOTE
# Phase 2: Wait for active consumers to finish (max 60 seconds)
- |
ssh "$DEPLOY_USER@$DEPLOY_HOST" bash <<'REMOTE'
set -euo pipefail
TIMEOUT=60
ELAPSED=0
while pgrep -f "queue:consumers:start" > /dev/null 2>&1; do
if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
echo "WARNING: Consumers still running after ${TIMEOUT}s, proceeding anyway"
break
fi
echo "Waiting for consumers to finish (${ELAPSED}s / ${TIMEOUT}s)..."
sleep 5
ELAPSED=$((ELAPSED + 5))
done
echo "All consumers stopped"
REMOTE
4. Cron und Consumer vor dem Deployment stoppen
Das Stoppen von Cron und Consumer ist die Phase, in der die meisten Teams zu wenig Zeit investieren. Die richtige Reihenfolge: Zuerst den Cron-Eintrag entfernen (damit kein neuer Cron-Run startet), dann den Prozessmanager anweisen, Consumer zu stoppen. Der Unterschied zwischen einem sofortigen Stop (supervisorctl stop sendet SIGTERM) und einem graceful shutdown ist bedeutsam: Ein Consumer, der gerade eine Queue-Nachricht verarbeitet, sollte diese Verarbeitung idealerweise abschließen, bevor er sich beendet. Die meisten Queue-Systeme in Magento sind so konzipiert, dass eine Nachricht bei einem unerwarteten Prozessende erneut zugestellt wird – aber ein abruptes SIGKILL kann zu inkonsistenten Datenbankzuständen führen, wenn der Consumer gerade mitten in einem mehrstufigen Schreibvorgang ist.
Supervisor bietet dafür stopwaitsecs in der Konfiguration: Der Prozessmanager wartet nach dem SIGTERM maximal diese Anzahl von Sekunden, bevor er ein SIGKILL sendet. Ein sinnvoller Wert für Magento Consumer ist 30-60 Sekunden. Wer systemd nutzt, konfiguriert TimeoutStopSec=60 im Unit-File. Diese Wartezeit muss mit dem Deployment-Skript koordiniert sein: Der Skript darf erst weitermachen, wenn sichergestellt ist, dass alle Consumer gestoppt sind, nicht nur, dass der Stop-Befehl abgesetzt wurde.
5. Auf laufende Consumer warten – graceful shutdown
Zwischen dem Stop-Befehl und dem tatsächlichen Ende aller Consumer-Prozesse liegt eine Lücke. Ein Wait-Loop im Deployment-Skript schließt diese Lücke: Solange noch Consumer-Prozesse laufen, wartet der Skript, prüft periodisch nach und geht nach einem Timeout mit einer Warnung weiter. Dieser Timeout ist wichtig: Ein Consumer, der in einem Deadlock steckt oder eine externes System mit Timeout wartet, soll das Deployment nicht unbegrenzt blockieren. Nach dem Timeout wird mit einem SIGKILL eskaliert oder das Deployment wird trotz noch laufender Prozesse fortgesetzt – mit explizitem Logging, damit das Operations-Team weiß, was passiert ist.
Das Muster für den Wait-Loop ist einfach: pgrep -f "queue:consumers:start" prüft, ob noch Consumer-Prozesse laufen. Wenn ja, 5 Sekunden warten, wieder prüfen. Nach dem konfigurierten Timeout weiter. Dieser Loop gehört in das SSH-Skript auf dem Deployment-Server, nicht in den GitLab-Job – weil der Job sonst bei Netzwerk-Timeout oder SSH-Verbindungsverlust abbricht. Der Loop auf dem Server selbst ist stabiler und unabhängig von der Verbindung zum GitLab-Runner.
6. Symlink-Wechsel und Magento-Schritte in der richtigen Reihenfolge
Nach dem Stoppen von Cron und Consumer folgt die eigentliche Deployment-Sequenz: Artefakt auf den Server übertragen, Shared-Verzeichnisse verlinken, setup:upgrade ausführen, Static Content deployen, Symlink auf das neue Release wechseln, Cache flushen. Die kritische Frage: Wann genau wird der Symlink gewechselt? Idealerweise so spät wie möglich – erst nachdem alle Magento-Schritte auf dem neuen Release-Verzeichnis abgeschlossen sind. Das bedeutet: setup:upgrade und Static Content Deployment laufen im neuen Release-Verzeichnis, bevor der Symlink von current auf das neue Verzeichnis zeigt.
Dieses Sequenzieren minimiert das Zeitfenster der Inkonsistenz auf den atomaren Symlink-Wechsel selbst – typischerweise Millisekunden. Alle langen Operationen (setup:upgrade, Static Content Deploy) laufen vorher im neuen Verzeichnis, während die aktuelle Version noch über den alten Symlink bedient wird. Dieser Ansatz funktioniert nur, wenn Datenbankmigrationen rückwärtskompatibel sind (das Expand-Contract-Pattern), weil während dieser Phase die alte Code-Version mit der neuen Datenbankstruktur laufen muss. Für einfache Releases ohne Schema-Änderungen ist das kein Problem; für komplexe Migrationen braucht es ein separates Konzept.
7. Cron und Consumer nach dem Deployment neu starten
Nach dem Symlink-Wechsel und dem Cache-Flush müssen Cron und Consumer neu gestartet werden – im neuen Release-Kontext. Das geschieht in umgekehrter Reihenfolge zum Stoppen: Zuerst Consumer neu starten (supervisorctl start all oder systemctl start magento-consumer@*), dann Cron neu eintragen (bin/magento cron:install). Der Neustart der Consumer im Prozessmanager lädt automatisch den neuen Code, weil der aktuelle Symlink jetzt auf das neue Release zeigt.
Ein wichtiges Detail beim Neustart: Wenn Magento's queue:consumers:start-Befehl Consumer-Optionen wie --max-messages oder --batch-size hat, müssen diese in der Prozessmanager-Konfiguration hinterlegt sein, nicht im Deployment-Skript. Das Deployment-Skript ist dafür verantwortlich, den Prozessmanager anzuweisen, neu zu starten – nicht dafür, Consumer-Optionen zu kennen. Diese Trennung macht das Deployment-Skript einfacher und die Consumer-Konfiguration zentral verwaltbar in der Supervisor- oder systemd-Konfiguration auf dem Server.
8. Vergleich: unkontrolliert vs. kontrolliert
Der Unterschied zwischen einem Deployment ohne und mit Cron/Consumer-Kontrolle ist in normalen Releases oft unsichtbar – und wird erst bei einem Edge-Case-Timing sichtbar, dessen Reproduzierbarkeit gering ist.
| Phase | Unkontrolliert | Kontrolliert | Risiko |
|---|---|---|---|
| Vor Deployment | Cron läuft weiter | cron:remove | Kein Cron-Start während setup:upgrade |
| Consumer | Consumer laufen weiter | supervisorctl stop all | Kein Consumer im alten Code nach Symlink-Wechsel |
| Warten | Kein Wait | Wait-Loop + Timeout | Deployment startet erst wenn alle Consumer fertig sind |
| Nach Deployment | Manueller Restart | supervisorctl start + cron:install | Consumer starten garantiert im neuen Release-Kontext |
| Rollback | Consumer im falschen Code | Stop, Rollback, Start | Consumer nach Rollback im alten Release-Code |
Die kontrollierte Variante bedeutet ein kurzes zusätzliches Zeitfenster vor und nach dem Deployment – in der Größenordnung von 30-90 Sekunden je nach Consumer-Laufzeiten. Dieses Fenster ist der Trade-off für die Sicherheit, dass keine Prozesse im inkonsistenten Zustand zwischen altem und neuem Release-Code arbeiten. Für die meisten Magento-Projekte ist das ein akzeptabler Trade-off, der die Kategorie sporadischer, schwer reproduzierbarer Post-Deployment-Fehler systematisch eliminiert.
9. Typische Fehlerbilder bei Cron und Consumer im Deployment
Das häufigste Fehlerbild ist der Consumer-Deadlock nach Deployment: Ein Consumer hat beim alten Code-Stand eine Queue-Nachricht begonnen zu verarbeiten, der Symlink hat auf das neue Release gewechselt, und der Consumer greift jetzt auf Klassen zu, die im alten Vendor-Stand vorhanden waren, im neuen aber an anderen Pfaden oder mit anderem Interface liegen. Das Ergebnis ist ein Fatal Error oder eine unbehandelte Exception – die Nachricht wird zurück in die Queue gestellt, aber der Consumer-Prozess stirbt. Supervisor startet ihn neu, er greift wieder auf denselbe Nachricht zu, und der Cycle beginnt von vorne.
Das zweite Fehlerbild ist der Cron-Konflikt während setup:upgrade: setup:upgrade führt Datenbankmigrationen durch, während gleichzeitig ein Cron-Job Schreibzugriffe auf dieselben Tabellen durchführt. Das kann zu Deadlocks in MySQL oder zu einem abgebrochenen setup:upgrade führen, das die Datenbank in einem Zwischenzustand hinterlässt. Diagnose: Im MySQL Slow Query Log und im Magento-Deployment-Log nach Timing-Überschneidungen suchen. Prävention: cron:remove vor setup:upgrade, nicht erst vor dem Symlink-Wechsel.
# Complete deployment with cron and consumer lifecycle control
deploy:production:
stage: deploy
environment: production
script:
- |
ssh "$DEPLOY_USER@$DEPLOY_HOST" bash <<'REMOTE'
set -euo pipefail
CURRENT="$DEPLOY_PATH/current"
RELEASE="$DEPLOY_PATH/releases/$(date +%Y%m%d-%H%M%S)"
echo "=== Phase 1: Stop background processes ==="
php "$CURRENT/bin/magento" cron:remove || true
supervisorctl stop all 2>/dev/null || true
# Wait up to 60 seconds for consumers to finish
for i in $(seq 1 12); do
pgrep -f "queue:consumers:start" > /dev/null 2>&1 || break
echo "Waiting for consumers... ($((i*5))s)"
sleep 5
done
echo "=== Phase 2: Deploy release ==="
mkdir -p "$RELEASE"
rsync -az --delete /tmp/release/ "$RELEASE/"
ln -sfn "$DEPLOY_PATH/shared/app/etc/env.php" "$RELEASE/app/etc/env.php"
ln -sfn "$DEPLOY_PATH/shared/pub/media" "$RELEASE/pub/media"
php "$RELEASE/bin/magento" setup:upgrade --no-interaction
php "$RELEASE/bin/magento" setup:static-content:deploy de_DE -f
echo "=== Phase 3: Switch symlink ==="
ln -sfn "$RELEASE" "$CURRENT"
php "$CURRENT/bin/magento" cache:flush
echo "=== Phase 4: Restart background processes ==="
supervisorctl start all 2>/dev/null || true
php "$CURRENT/bin/magento" cron:install --force
echo "Deployment complete"
REMOTE
10. Zusammenfassung
Cron und Queue Consumer während des Deployments zu kontrollieren ist kein optionaler Schritt, sondern eine Voraussetzung für stabile Magento-Releases. cron:remove vor dem Deployment verhindert Cron-Starts während setup:upgrade. Prozessmanager-Stop hält Consumer an, bevor der Symlink wechselt. Ein Wait-Loop stellt sicher, dass alle Prozesse tatsächlich beendet sind, bevor weitergemacht wird. Nach dem Deploy: Prozessmanager-Start und cron:install starten Cron und Consumer im neuen Release-Kontext.
Die Investition in dieses Protokoll zahlt sich bei jedem Release aus: keine sporadischen Post-Deployment-Fehler durch Timing-Konflikte, kein Consumer-Deadlock-Cycle, keine Cron-DB-Konflikte während Migrationen. Das kombiniert mit einer sauberen Release-Struktur und einem atomaren Symlink-Wechsel ergibt den Deployment-Prozess, der dem Anspruch an Zero-Downtime gerecht wird – zumindest für die Schicht, die Cron und Consumer betrifft.
Cron und Consumer im Deployment — Das Wichtigste auf einen Blick
Vor dem Deployment
cron:remove + supervisorctl stop all. Wait-Loop bis alle Consumer-Prozesse beendet sind (max. 60s Timeout).
Deployment-Sequenz
Artefakt, Shared-Links, setup:upgrade, Static Content – alles vor dem Symlink-Wechsel. Symlink so spät wie möglich wechseln.
Nach dem Deployment
supervisorctl start all + cron:install. Consumer starten im neuen Release-Kontext, nicht im alten.
Prozessmanager
Supervisor oder systemd als Pflicht – für kontrollierten Stop/Start und graceful shutdown mit konfigurierbarem Timeout.