Go + systemd Zero-Downtime Deploy Kit

Einen Go-Service auf einem VPS ausliefern, ohne Anfragen zu verlieren, nur mit systemd, Caddy und bash. Kein pm2, keine Container.

Bei Polar kaufen: $29 Einmalkauf · v0.1.0 · Sofort-Download über Polar

Cover von Go + systemd Zero-Downtime Deploy Kit
Ausgabe eines echten Testlaufs, kein Mock-up.

Für wen es ist

Sie liefern ein Go-Binary auf einen Server aus und betreiben es unter systemd. Ihr Deployment besteht heute aus scp, systemctl restart und einem kurzen Blick ins Log. Das geht gut, bis ein Neustart in eine geschäftige Minute fällt und Caddy ein paar Sekunden lang 502 liefert. Oder bis Sie einen Build ausrollen, der sauber startet und dann jede Anfrage scheitern lässt, und nichts verhindert, dass er den Traffic bekommt.

Dieses Kit ersetzt das durch ein Deploy-Skript, das das neue Release neben dem alten startet, es prüft, bevor es Traffic erhält, umschaltet und von selbst zurückgeht, wenn das neue Release schlecht wird. Es ist das Gegenstück in Go und systemd zu unserem Kit für Node und pm2, für Leute, die kein pm2 betreiben und auch keins wollen.

Das bekommen Sie

  • deploy.sh: Release-Ordner mit einem atomaren current-Symlink, Deployment eines in der CI gebauten Binarys oder eines Builds auf dem Server, Health Checks (N Antworten in Folge), Umschalten, Drain, automatisches Rollback, deploy.sh rollback und ein Lock, damit sich zwei Deployments nicht überschneiden.
  • Gehärtete systemd-Units: Type=notify, Graceful Stop, Restart-Policy, ein unprivilegierter Benutzer, Secrets in einer env-Datei mit Modus 600, die der Service-Benutzer nicht lesen kann.
  • Ein Installer, der Benutzer, Verzeichnisse, Units und die env-Datei einrichtet.
  • Eine kleine Beispiel-App in Go (nur Standardbibliothek) mit Graceful Shutdown und einem getesteten Helfer für Socket Activation und sd_notify.
  • Alarme per Telegram oder Webhook, wenn ein Deployment fehlschlägt. Erfolgreiche Deployments bleiben still.
  • Ein Test-Harness und die Ausgabe seines letzten Laufs.

Zwei Wege zum Umschalten

Blue/Green (der Standard). Zwei Instanzen, app@a und app@b, stehen hinter Caddy. Das neue Release startet auf dem freien Port und muss den Health Check mehrmals in Folge beantworten, bevor Caddy davon erfährt. Dann lädt Caddy graceful neu, die alte Instanz läuft per Drain aus und stoppt. Wird das neue Release nach dem Umschalten krank, stellt das Skript alles zurück. Das Rollback ist dieselbe Bewegung rückwärts, und mit KEEP_PREVIOUS=1 bleibt die alte Instanz warm, sodass ein Rollback nur ein Caddy-Umschalten ist.

Socket Activation. systemd hält den lauschenden Socket offen, während der Service neu startet, Verbindungen werden also eingereiht statt abgelehnt. Das neue Release läuft zuerst als Canary auf einem anderen Port, ein kaputter Build erreicht den Restart also gar nicht erst.

Blue/Green ist der Standard, weil dabei die langsamste Anfrage nahe an der Dauer der Anfrage selbst bleibt. Der Socket-Modus ist für Fälle gedacht, in denen Sie nicht zwei Instanzen betreiben können: ein fester Port, den Sie nicht verschieben können, oder eine Ressource, die nur ein Prozess halten darf. Die Messungen unten decken beide ab.

Gemessen

Aufbau: ein Debian-12-Container mit systemd 252 als PID 1, Caddy 2.6.2, eine Beispiel-App, die 1.5 s zum Starten braucht, konstant 20 Anfragen pro Sekunde durch Caddy, drei Läufe pro Szenario. Eine Anfrage zählt als fehlgeschlagen bei jedem Verbindungsfehler, Timeout oder Status außer 200.

SzenarioGesendetFehlgeschlagen
Einfaches systemctl restart, eine Instanz152 bis 16233 bis 42 (alle HTTP 502)
Dasselbe, mit lb_try_duration 5s in Caddy1620, aber die langsamste Anfrage dauerte 2.3 s
Blue/Green, gutes Deployment293 bis 2950
Blue/Green, Release, dessen /health 500 liefert458 bis 4600
Blue/Green, Release stürzt beim Start ab1320
Blue/Green, Release wird nach dem Umschalten krank381 bis 3830 (das schlechte Release beantwortete 182 oder 183 Anfragen vor dem Rollback)
Blue/Green, Rollback293 bis 2950
Blue/Green, Rollback mit KEEP_PREVIOUS=1222 bis 2230, der Traffic wechselte nach 0.1 s
Blue/Green, gutes Deployment mit 2 s langen Anfragen in Arbeit2340, die langsamen Anfragen liefen durch, wurden nicht abgebrochen
Socket-Modus, gutes Deployment268 bis 2690
Socket-Modus, gutes Deployment mit 2 s langen Anfragen in Arbeit307 bis 3100, langsamste Anfrage 5.65 s

Ein Deployment dauert im Blue/Green-Modus 8 bis 9 Sekunden, und nach etwa 3.6 Sekunden liegt der Traffic auf dem neuen Release. Das sind die Werte für diese App und diese Maschine.

So läuft ein Deployment ab

Das README geht sieben Schritte durch. Kurz gefasst: Lock nehmen, Release-Ordner anlegen, die freie Instanz starten und auf ihre Bereitschaftsmeldung warten, sie direkt per Health Check prüfen, den neuen Upstream schreiben und Caddy neu laden, den Symlink umlegen, warten, erneut prüfen und erst dann die alte Instanz stoppen. Jeder Schritt hat einen definierten Fehlerpfad, und die wichtigen sind getestet: ein Release, das nicht gesund ist, ein Absturz beim Start und ein Release, das nach dem Umschalten schlecht wird.

Voraussetzungen

Linux mit systemd, Caddy, curl und flock. Go und git nur, wenn Sie auf dem Server bauen. Ein Service auf einem Host.

Häufige Fragen

Ich nutze Node und pm2. Ist das etwas für mich?

Nein, schauen Sie sich das VPS Deploy & Backup Kit an. Es erledigt dieselbe Aufgabe für diesen Stack.

Brauche ich Caddy?

Blue/Green braucht es für das Umschalten. Wir haben beide Modi mit Caddy davor gemessen.

Läuft es auch unter Ubuntu?

Ubuntu nutzt ebenfalls systemd, und das Kit braucht nur systemd und Caddy, es passt also. Getestet wurde es unter Debian 12.

Was macht ein zweites Deployment, solange eines läuft?

Es beendet sich sofort mit Exit-Code 75 und sendet keinen Alarm. Das ist der Lock.

Lizenz

Sie dürfen es auf Ihren eigenen Servern einsetzen. Bitte verkaufen Sie die Dateien nicht weiter.

Änderungsprotokoll

  • 0.1.0: erstes Release.

Weiterlesen

Herunterladen

Bei Polar kaufen: $29

Zahlung, Steuern und Rechnung übernimmt Polar als Merchant of Record. Den Download-Link erhalten Sie nach dem Checkout.