Zero-Downtime-Bereitstellungen mit Nginx und Systemd
Eine Schritt-für-Schritt-Anleitung zur Erreichung echter Zero-Downtime-Releases mit Nginx-Upstream-Switching und systemd-Socket-Aktivierung — kein Kubernetes erforderlich.
Zero-Downtime-Deployments werden oft als Kubernetes-only-Fähigkeit präsentiert. Sie sind es nicht. Mit Nginx als Reverse-Proxy und systemd Socket-Activation können Sie echte Zero-Downtime-Anwendungs-Updates auf einem einzelnen Server erreichen, ohne Container-Orchestrator erforderlich — und genau verstehen, was bei jedem Schritt passiert.
Die Grundidee
Die Strategie hat zwei Teile: (1) führen Sie zwei Versionen Ihrer Anwendung gleichzeitig für eine kurze Periode während der Bereitstellung aus, und (2) verwenden Sie Nginx, um Traffic atomisch von der alten zur neuen Version zu verschieben. Systemd Socket-Activation stellt sicher, dass neue Verbindungen gehalten — nicht abgebrochen — während des Switchovers werden.
Schritt 1: systemd Socket-Aktivierung
Socket-Activation lässt systemd den Listening-Socket besitzen. Wenn Ihre Anwendung neu startet, bleibt der Socket offen — eingehende Verbindungen warten in der Kernel-Queue — und werden dem neuen Prozess übergeben, sobald er bereit ist. Keine Verbindungen werden abgelehnt.
# /etc/systemd/system/myapp.socket
[Unit]
Description=MyApp socket
[Socket]
ListenStream=127.0.0.1:3000
Accept=no
[Install]
WantedBy=sockets.target# /etc/systemd/system/myapp.service
[Unit]
Description=MyApp application server
Requires=myapp.socket
After=myapp.socket
[Service]
ExecStart=/usr/bin/node /srv/myapp/current/server.js
WorkingDirectory=/srv/myapp/current
User=myapp
Group=myapp
Restart=on-failure
# Dem Service mitteilen, dass er den von systemd übergebenen Socket verwendet
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.targetSchritt 2: Nginx-Upstream-Konfiguration
# /etc/nginx/conf.d/myapp.conf
upstream myapp {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://myapp;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Nginx darf Verbindungen während des Neustarts kurz halten
proxy_next_upstream error timeout;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
}
}Schritt 3: Das Deployment-Skript
#!/bin/bash
# deploy.sh — Zero-Downtime-Deployment
set -euo pipefail
APP_DIR=/srv/myapp
RELEASE=$(date +%Y%m%d%H%M%S)
RELEASE_DIR="$APP_DIR/releases/$RELEASE"
echo "==> Release-Verzeichnis erstellen: $RELEASE_DIR"
mkdir -p "$RELEASE_DIR"
echo "==> Aktuellen Code ziehen"
git clone --depth 1 git@github.com:yourorg/myapp.git "$RELEASE_DIR"
echo "==> Abhängigkeiten installieren"
cd "$RELEASE_DIR"
npm ci --production
echo "==> Datenbank-Migrationen ausführen"
npm run migrate
echo "==> Symlink atomar umschalten"
ln -sfn "$RELEASE_DIR" "$APP_DIR/current"
echo "==> Anwendung neu laden (Socket bleibt offen)"
# systemd startet den Service neu und hält den Socket offen
systemctl reload-or-restart myapp.service
echo "==> Health-Check verifizieren"
sleep 2
curl -sf http://127.0.0.1:3000/health || { echo "Health-Check fehlgeschlagen!"; exit 1; }
echo "==> Alte Releases aufräumen (letzte 5 behalten)"
ls -dt "$APP_DIR/releases"/* | tail -n +6 | xargs rm -rf
echo "==> Deployment abgeschlossen: $RELEASE"Der Schlüssel ist ln -sfn, das den Symlink atomisch ersetzt. Ab dem Moment, in dem der Symlink ändert, werden alle neuen Worker-Prozesse, die von systemd gestartet werden, den neuen Code servieren. Bestehende In-Flight-Requests fahren auf den alten Workern fort, bis sie fertig sind.
Schritt 4: Zero Downtime prüfen
# In einem zweiten Terminal während des Deployments ausführen
# Meldet sofort, wenn eine Anfrage fehlschlägt
while true; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://example.com/health)
echo "$(date +%H:%M:%S) — HTTP $STATUS"
sleep 0.2
doneSie sollten einen kontinuierlichen Strom von "HTTP 200"-Zeilen sehen — einschließlich durch die Bereitstellung. Wenn Sie 502 oder 503 sehen, überprüfen Sie Ihre proxy_next_upstream-Einstellungen und stellen Sie sicher, dass Ihre Anwendung schnell Verbindungen akzeptiert (innerhalb des proxy_connect_timeout-Fensters).
Wann sich Kubernetes lohnt
Dieser Ansatz funktioniert hervorragend für Teams, die einen bis eine Handvoll Server mit einer einzelnen Anwendung betreiben. Wenn Sie Multi-Node-Deployments, automatische horizontale Skalierung oder komplexe Service-Meshes benötigen, verdient Kubernetes seinen Komplexitätskost. Bis dahin ist Nginx + systemd einfacher, schneller zu debuggen und erfordert keinen Cluster zu warten.