CI/CD
.yml
GitLab · Magento · Cron · Queue Consumer · Deployment
Cron und Queue Consumer
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.

12 Min. Lesezeit cron:run · queue:consumers:start · Supervisor · systemd Magento 2.4.x · GitLab 16.x

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.

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.

11. FAQ: Cron und Consumer im Deployment kontrollieren

1Warum Cron vor dem Deployment stoppen?
Verhindert Cron-Starts während setup:upgrade – die Datenbankdeadlocks verursachen können. cron:remove entfernt Crontab-Eintrag; laufende Jobs beenden sich normal.
2cron:remove vs. maintenance:enable?
cron:remove stoppt nur Magento-Cron. maintenance:enable blockt alle Web-Requests. cron:remove ist die minimal-invasive Option ohne Wartungsfenster.
3Consumer sicher stoppen?
supervisorctl stop all (Supervisor) oder systemctl stop magento-consumer@* (systemd). Danach Wait-Loop bis alle Prozesse beendet sind.
4Was ist graceful shutdown?
SIGTERM senden – Consumer beendet die aktuelle Nachricht, dann sich selbst. stopwaitsecs (Supervisor) / TimeoutStopSec (systemd) definiert max. Wartezeit bis SIGKILL.
5Wann Symlink wechseln?
So spät wie möglich – erst nach setup:upgrade und Static Content Deploy. Minimiert das Inkonsistenz-Fenster auf den atomaren Symlink-Wechsel selbst.
6Consumer nach Deployment neu starten?
supervisorctl start all. Consumer starten im neuen Release-Kontext – Symlink zeigt jetzt auf neues Verzeichnis.
7Consumer stoppt nicht – was tun?
Nach Timeout (60s) Deployment mit Warnung fortsetzen. Oder SIGKILL eskalieren. Prozessmanager-Konfiguration auf stopwaitsecs prüfen.
8Prozessmanager Pflicht?
Für produktive Systeme ja. Ohne Prozessmanager kein kontrollierter Stop-Befehl. pkill ist kein zuverlässiger Ersatz.
9Rollback mit Consumer?
Wie normales Deployment: Consumer stoppen, Symlink zurückschalten, Consumer neu starten. Laufen dann im alten Release-Code.
10Wie viel Zeit kostet Consumer-Stop?
Bei 60s Timeout und 30s Consumer-Shutdown ca. 90s zusätzlich. Trade-off für Sicherheit gegen Consumer-Deadlock-Cycles nach dem Deployment.