1. Warum bleibt mein Server bei jedem Deploy stehen?



Bei privaten Projekten stößt man häufig auf leistungsschwache Umgebungen – etwa die kleinsten GCP‑Instanzen oder einen Raspberry Pi. Ich betreibe mehrere Maschinen: von Hochleistungs‑GPU‑Servern für KI‑Inference bis hin zu datenbanklosen VMs, die nur als Nameserver dienen. Besonders gern arbeite ich mit dem Raspberry Pi 5, weil er rund um die Uhr läuft, kaum Strom kostet und solide Performance liefert.

Doch sobald ich diesem „Haustier“ zu viel Arbeit aufbürde, schnellt die CPU bei Deployments auf 100 %. Der Übeltäter: Celery‑Worker. Beim klassischen Blue‑Green‑Deploy werden beide Umgebungen gleichzeitig gestartet, wodurch sich die Anzahl der Worker verdoppelt und das System überlastet wird. Reduzieren wir die Worker, leidet die Verarbeitungsgeschwindigkeit; lassen wir sie laufen, droht ein Crash – ein klassisches Dilemma.

Symbolisches Bild für automatisierte Skripte auf Low‑End‑Hardware

Um dieses Problem zu lösen, habe ich ein maßgeschneidertes Blue‑Green‑Deploy‑Skript entwickelt, das Ressourcen spart und gleichzeitig Stabilität garantiert.


2. Die Strategie: Ressourcen schonen, Stabilität erhöhen

Ein einfaches Blue‑Green‑Deploy betreibt beide Umgebungen parallel, aber ich habe folgenden Ansatz gewählt, um die CPU‑Last zu reduzieren:

  1. Vorzeitiges Stoppen von Hintergrunddiensten (Celery): Bevor die neue Web‑Instanz startet, werden die ressourcenintensiven Celery‑Worker und -Beat gestoppt, um CPU‑Kapazität freizugeben.
  2. Stufenweises Hochfahren: Zuerst werden nur Web + Redis gestartet und ein Health‑Check durchgeführt, bevor weitere Services folgen.
  3. Human‑in‑the‑Loop: Nach Abschluss der Automatisierung prüft ein Administrator manuell die neue Version und gibt erst dann das endgültige „Delete‑Old‑Version“-Signal.

Der Kern des Problems liegt in der sofortigen Initialisierung der Celery‑Worker, die nach dem Neustart sofort viele Aufgaben erhalten und damit die CPU auslasten. Das Senken der Concurrency‑Einstellung wäre eine mögliche Lösung, würde aber die Asynchronverarbeitung verlangsamen – nicht wünschenswert während eines Deployments.

Meine Idee war, während eines Zero‑Downtime‑Deploys die alten Celery‑Instanzen kurzzeitig zu pausieren, CPU‑Ressourcen freizugeben und anschließend den neuen Code zu starten.


3. Kerncode im Überblick



Den vollständigen Code finden Sie in meinem GitHub‑Repository. Hier sind ein paar zentrale Code‑Ausschnitte:

① Projektisolation mit Docker Compose

dc() {
  # Projektname (-p) dynamisch setzen, um Isolation zu erreichen
  docker compose -f "$COMPOSE_FILE" "$@"
}

② Strikter Health‑Check

health_check() {
  # Prüft bis zu 10‑mal, ob der Ziel‑Port 200 OK zurückgibt
  if curl -fsSIL --max-time "$HEALTH_TIMEOUT" "$url"; then
    ok "Health check passed"
    return 0
  fi
}

③ Wiederherstellung im Fehlerfall

Falls die neue Version Probleme bereitet, werden die gestoppten alten Services sofort wieder hochgefahren, sodass kein Ausfall für die Nutzer entsteht. Statt die alten Celery‑Worker und Beat mit rm -f zu entfernen, setzen wir auf stop, um schnell wieder Ressourcen freizugeben und die alte Umgebung im Notfall zu reaktivieren.


4. Praxis‑Tipp: Die Kunst des „Nach Bestätigung löschen“

Am Ende des Skripts wird die alte Version nicht automatisch entfernt, sondern der Administrator erhält eine Meldung:

"Deployment erfolgreich. Bitte prüfen Sie die neue Version manuell. Wenn alles in Ordnung ist, führen Sie den untenstehenden Befehl aus, um die alte Umgebung zu bereinigen."

Diese kleine Sicherheitsabfrage verhindert seltene Fehlerfälle, die automatisierte Systeme übersehen könnten.


5. Fazit

Dieses Skript fasst meine Erfahrungen zusammen, wie man mit begrenzten Ressourcen das Maximum an Effizienz herausholt. Obwohl es ein simples Beispiel ist, hat es mir persönlich sehr geholfen und dürfte auch anderen Entwicklern mit ähnlichen Herausforderungen in kleineren Projekten nützlich sein.

Weiterführende Links: