502-Fehler bei Node.js während Rolling Deploys in der Cloud beheben
Node.js-Anwendungen, die in die Cloud migriert werden, erleiden häufig kurze Ausfälle während des Deployments aufgrund unsachgemäßer Signalbehandlung und fehlerhafter Health-Check-Konfiguration.
Automatisch aus dem englischen Original übersetzt.
Teams, die Node.js-Anwendungen in containerisierte Cloud-Umgebungen migrieren, stoßen bei jedem Deployment häufig auf kurze, aber disruptive Wellen von 502-Fehlern. Diese Ausfälle treten auf, weil Standard-Node.js-Prozesse nicht inhärent mit Load Balancern koordiniert werden, wenn sie Shutdown-Signale empfangen, was dazu führt, dass aktive Anfragen abgebrochen werden. Die Implementierung spezifischer Health-Check-Endpunkte und einer Logik für den kontrollierten Herunterfahren (Graceful Shutdown) löst diese zeitlichen Diskrepanzen zwischen dem Orchestrator und der Infrastruktur.
Was passiert ist
Wenn ein Service von einer einzelnen virtuellen Maschine in eine verwaltete Container-Umgebung wie Kubernetes oder ECS wechselt, verschiebt sich die Deployment-Strategie typischerweise zu Rolling Updates. In diesem Modell starten neue Instanzen, während alte gestoppt werden. Das Kernproblem entsteht durch eine Race Condition: Der Orchestrator sendet ein SIGTERM-Signal an den alten Container und weist den Load Balancer gleichzeitig an, ihn aus der Rotation zu entfernen. Allerdings hinkt der Load Balancer oft um mehrere Sekunden bei der Aktualisierung seiner Routing-Tabelle hinterher. Da Node.js standardmäßig sofort beim Empfang von SIGTERM beendet wird, stirbt der Prozess, bevor der Load Balancer aufhört, Traffic an ihn zu senden, was zu fehlgeschlagenen Anfragen führt.
Das Problem wird durch Startverhalten und Verbindungsmanagement noch verschärft. Neue Container können Traffic erhalten, bevor ihre Datenbankverbindungen vollständig aufgebaut sind, wenn kein Readiness-Check vorhanden ist. Zusätzlich führen falsch konfigurierte Keep-Alive-Timeouts zwischen Node.js und Cloud-Load-Balancern zu weiteren Fehlern. Beispielsweise halten AWS Application Load Balancers idle Verbindungen für 60 Sekunden offen, während Node.js standardmäßig nur 5 Sekunden verwendet. Wenn der Balancer versucht, einen Socket wiederzuverwenden, den Node bereits geschlossen hat, erleben Benutzer 502-Fehler auch außerhalb der Deployment-Zeiträume.
Wichtige Details
- Rolling Deploys lösen SIGTERM-Signale aus, die Node.js-Prozesse beenden, bevor Load Balancer aufhören, Traffic an sie zu routen.
- Liveness Probes sollten nur prüfen, ob der Event Loop reaktionsfähig ist, niemals die Datenbankkonnectivität, um kaskadierende Neustarts zu vermeiden.
- Readiness Probes müssen die Verfügbarkeit von Abhängigkeiten überprüfen und während des Herunterfahrens einen 503-Status zurückgeben, um Traffic sicher abzuleiten.
- Der
keepAliveTimeoutin Node.js muss höher eingestellt sein als der Idle Timeout des Load Balancers, um Fehler bei der Socket-Wiederverwendung zu verhindern. - Die Verwendung von
CMD npm startin Docker verhindert die korrekte Weiterleitung von Signalen; Entwickler sollten die Exec-Form oder einen Init-Prozess wie tini verwenden. - Datenbank-Pools und andere Ressourcen dürfen erst geschlossen werden, nachdem der HTTP-Server die Verarbeitung aller laufenden Anfragen abgeschlossen hat.
Hintergrund
Das Verständnis der Unterscheidung zwischen Liveness und Readiness ist entscheidend für stabile Cloud-Betrieb. Liveness Checks beantworten, ob ein Prozess feststeckt und neu gestartet werden muss, während Readiness Checks bestimmen, ob eine Instanz Traffic akzeptieren kann. Die Verwechslung dieser beiden führt zu häufigen Fallstricken, wie dem gleichzeitigen Neustart aller Instanzen, wenn eine Datenbank verlangsamt wird, was einen geringen Latenzanstieg in einen vollständigen Ausfall verwandelt. Graceful Shutdown ist die Praxis, neue Arbeit zu stoppen, bestehende Aufgaben abzuschließen und dann zu beenden, um sicherzustellen, dass während des Übergangs keine Daten verloren gehen oder beschädigt werden.
Die Signalbehandlung in Linux-Containern erfordert auch Aufmerksamkeit für Prozess-IDs. In vielen Docker-Konfigurationen läuft die Shell oder der Paketmanager als PID 1, was Terminationssignale nicht zuverlässig an den untergeordneten Node.js-Prozess weiterleitet. Ohne korrekte Signalweiterleitung ignoriert die Anwendung die Aufforderung zum Herunterfahren, bis der Orchestrator nach Ablauf der Grace Period einen Kill erzwingt, wodurch jeglicher Cleanup-Code vollständig umgangen wird.
Warum es wichtig ist
Das Ignorieren dieser kurzen Deployment-Fehler erzeugt eine versteckte Steuer auf die Engineering-Geschwindigkeit und die Systemzuverlässigkeit. Teams tolerieren oft kleine Störungen, was zu selteneren Deployments und größeren, riskanteren Release-Batches führt. Diese Zögerlichkeit verlangsamt Feedback-Loops und macht Rollbacks schwieriger. Darüber hinaus können clientseitige Wiederholungsversuche, ausgelöst durch zufällige 502-Fehler, nicht-idempotente Aktionen duplizieren, wie das Erstellen mehrerer Bestellungen oder doppeltes Abbuchen von Kunden, was einen technischen Glitch in einen geschäftlichen Vorfall verwandelt.
Für IT-Leiter und DevOps-Ingenieure degradieren diese durch Rauschen verursachten Alerts die Wirksamkeit des Monitorings. Wenn echte Vorfälle unter routinemäßigen Deployment-Fehlern begraben werden, lockern Teams Alarm-Schwellenwerte, was ihre Fähigkeit reduziert, echte Probleme zu erkennen. Zero-Downtime-Deployments stellen sicher, dass Vertrauen in den Release-Prozess wiederhergestellt wird, sodass Teams kleinere Änderungen häufiger ausliefern können, ohne Angst vor Nutzerstörungen oder Datenkorruption zu haben.
Was Sie tun können
- Implementieren Sie separate
/healthz- und/readyz-Endpunkte und stellen Sie sicher, dass Liveness Checks leichtgewichtig bleiben und unabhängig von externen Abhängigkeiten sind. - Konfigurieren Sie Ihren Node.js-Server so, dass er auf SIGTERM lauscht, setzen Sie ein Flag, um neue Anfragen abzulehnen, und warten Sie auf die Deregistrierungsverzögerung des Load Balancers, bevor Sie Verbindungen schließen.
- Setzen Sie
server.keepAliveTimeoutauf einen Wert, der den Idle Timeout Ihres Load Balancers überschreitet, z. B. 65 Sekunden für AWS ALB. - Verwenden Sie die Exec-Form
CMD ["node", "server.js"]in Ihrer Dockerfile oder integrieren Sie einen Init-Prozess, um sicherzustellen, dass SIGTERM Ihre Anwendung erreicht. - Schließen Sie Datenbank-Pools und Consumer für Hintergrund-Jobs erst, nachdem der HTTP-Server bestätigt hat, dass alle laufenden Anfragen abgeschlossen sind.
- Validieren Sie Ihr Setup, indem Sie Lasttests während eines Rollout-Restarts durchführen, mit dem Ziel, während des gesamten Prozesses null Nicht-2xx-Antworten zu erzielen.



