Journal-IndexDockup / Feldnotiz
Note / deploy-succeeded-but-site-is-down

Deployment erfolgreich, aber die Website ist nicht erreichbar

Ihr Dashboard meldet den Status „running“, während Ihre Nutzer einen Fehler sehen. Erfahren Sie, warum ein erfolgreiches Deployment und die Anwendungsgesundheit unterschiedliche Signale sind und wie ein grünes Deployment tatsächlich bedeuten kann, dass die App antwortet.

Es gibt eine ganz bestimmte Art von schlechtem Morgen, die mit einem grünen Häkchen beginnt. Das Deployment ist erfolgreich, aber die Website ist nicht erreichbar, das Dashboard meldet running, und jemand schickt Ihnen einen Screenshot eines 502-Fehlers.

Das ist kein seltener Sonderfall. Es ist das vorhersehbare Ergebnis davon, dass eine Plattform eine Sache meldet, aber eine andere misst. Es lohnt sich, das genau zu verstehen, denn die Lösung lautet nicht „genauer prüfen“ – sondern festzulegen, was das Wort running überhaupt bedeuten darf.

Drei unterschiedliche Fragen, eine Statusanzeige

Wenn eine Plattform meldet, dass ein Service läuft, kann sie damit eine der folgenden Fragen beantworten:

  1. Wurde der Container gestartet? Der Prozess existiert und wurde nicht beendet.
  2. Ist der Port geöffnet? An der erwarteten Stelle lauscht etwas.
  3. Antwortet die Anwendung korrekt? Eine Anfrage erhält eine Antwort, die bedeutet, dass die App bereit ist, zu arbeiten.

Diese Zusagen unterscheiden sich grundlegend. Die meisten Vorfälle dieser Art entstehen, weil ein Dashboard Frage 1 beantwortet, während Sie von Frage 3 ausgegangen sind.

Ein Node-Prozess, der startet, keine Verbindung zu seiner Datenbank herstellen kann und in einer Retry-Schleife hängen bleibt, erfüllt Frage 1 dauerhaft. Er ist nicht abgestürzt. Er wird niemals eine Anfrage bedienen. Für den Orchestrator ist der Container in jeder relevanten Hinsicht „running“.

Die Lücke, in der der Ausfall entsteht

Das gefährliche Zeitfenster liegt zwischen „die neue Version wurde gestartet“ und „die neue Version kann arbeiten“. In diesem Zeitraum hat eine naive Plattform den Traffic möglicherweise bereits umgeleitet, weil sie nur den Start gemessen hat.

Was diesen Fall schlimmer macht als einen einfachen Absturz, ist das Rollback-Verhalten. Eine Crash-Schleife ist laut: Der Container wird beendet, neu gestartet, wieder beendet, und die Plattform bemerkt das schließlich. Ein gestarteter, aber hängender Prozess bleibt dagegen unbemerkt. Nichts wird neu gestartet, kein Alarm wird ausgelöst, und die zuvor funktionierende Version wurde normalerweise bereits entfernt.

Genau darin liegt der eigentliche Schaden. Die alte Version war in Ordnung. Sie wurde entfernt, weil eine neue Version gestartet wurde – und „gestartet“ wurde mit „funktioniert“ verwechselt.

Was ein echter Health Gate leistet

Die Lösung ist strukturell, nicht prozedural. Der Traffic sollte erst umgeleitet werden, wenn die neue Version eine Anfrage beantwortet hat.

Bei Dockup läuft ein Release folgendermaßen ab: Die neue Version wird isoliert gebaut, parallel zur aktuell bereitgestellten Version gestartet und anschließend mit einer Frage konfrontiert. Erst wenn sie antwortet, zeigt die Domain auf sie. Wenn sie nie antwortet, wird der Release-Vorgang an dieser Stelle beendet und die vorherige Version bedient weiterhin Anfragen – niemand außerhalb Ihres Dashboards erfährt, dass ein Deployment versucht wurde.

Deshalb bedeutet ein fehlgeschlagenes Deployment bei Dockup keinen Ausfall. Der alte Container wurde nie unter der Annahme entfernt, dass der neue schon funktionieren würde.

# The health gate is per-service configuration, not a platform default you inherit
dockup info my-project/my-api --json

Der healthCheck-Block in dieser Ausgabe ist der vollständige Vertrag: welcher Pfad angefragt wird, wie lange auf eine Antwort gewartet wird, wie oft der Versuch wiederholt wird und wie viel Zeit zwischen den Versuchen liegt.

Konfigurieren Sie die Prüfung so, dass sie Frage 3 beantwortet

Ein Health-Endpunkt, der bedingungslos 200 zurückgibt, ist schlimmer als gar keiner, weil er eine echte Schranke in einen bloßen Stempel verwandelt. Die Prüfung soll fehlschlagen, wenn die Anwendung ihre Aufgabe nicht erfüllen kann.

Ein sinnvoller Readiness-Endpunkt prüft die Dinge, ohne die die App nicht funktionieren kann:

// Not this — it proves only that the process is alive
app.get('/healthz', (req, res) => res.send('ok'))

// This — it proves the app can actually serve a request
app.get('/healthz', async (req, res) => {
  try {
    await db.query('select 1')       // the dependency that is usually the problem
    if (!cacheReady) throw new Error('cache warming')
    res.status(200).json({ ok: true })
  } catch (err) {
    res.status(503).json({ ok: false, reason: err.message })
  }
})

Zwei Regeln sorgen dafür, dass dies in der Praxis funktioniert:

Prüfen Sie Abhängigkeiten, ohne die Sie keine Anfragen bedienen können – und nichts darüber hinaus. Wenn Ihre App bei einem Ausfall des Suchindex kontrolliert mit eingeschränkter Funktionalität weiterarbeiten kann, darf die Readiness nicht am Suchindex scheitern. Andernfalls blockieren Sie Deployments wegen etwas, das keinen Ausfall darstellt.

Halten Sie die Prüfung kostengünstig. Der Endpunkt wird während jedes Releases wiederholt aufgerufen. Eine Readiness-Prüfung, die eine teure Abfrage ausführt, erzeugt ein selbst verursachtes Lastproblem.

Geben Sie ausreichend Zeit, aber keine unbegrenzte

Zwei Einstellungen entscheiden darüber, ob der Gate hilft oder schadet:

  • Das Timeout pro Versuch sollte länger sein als Ihr langsamster legitimer Cold Start. Eine App, die eine Datenbankverbindung herstellt und einen Cache innerhalb von acht Sekunden aufwärmt, wird eine Prüfung mit drei Sekunden Timeout jedes Mal nicht bestehen. Sie werden den Gate dann vermutlich deaktivieren – und landen wieder am Ausgangspunkt.
  • Die Anzahl der Retries sollte die gesamte Startzeit abdecken, nicht nur einen einzelnen Versuch. Intervall × Retries ist das tatsächliche Zeitbudget.

Bei Dockup heißen diese Einstellungen healthCheckInterval, healthCheckTimeout und healthCheckRetries. Sie gelten pro Service, weil ein Rails-Monolith und ein Go-Sidecar nicht nach demselben Zeitplan starten.

Wenn die Website bereits nicht erreichbar ist

Wenn Sie diesen Text gerade während eines Vorfalls lesen, führt die folgende Reihenfolge am schnellsten zur Lösung:

  1. Prüfen Sie, ob die App direkt antwortet, also unter Umgehung der Domain. Wenn sie an ihrem Port antwortet, aber nicht über die Domain, handelt es sich um ein Routing-Problem und nicht um ein Anwendungsproblem. Hören Sie dann auf, Ihren Code zu untersuchen.
  2. Lesen Sie die Runtime-Logs, nicht die Build-Logs. Der Build war erfolgreich – das ist die Ausgangslage. Sie müssen herausfinden, was der Prozess nach dem Start getan hat.
  3. Führen Sie ein Rollback durch, bevor Sie diagnostizieren. Die Diagnose ist günstiger, wenn niemand zusieht.
dockup logs my-project/my-api --follow            # what the running process is saying
dockup deployments my-project/my-api              # what was live before this
dockup rollback <deployment-id> my-project/my-api # put that back

Bei Dockup ist ein Rollback ein Umschalten statt eines erneuten Builds, weil die vorherige Version weiterhin auf dem Datenträger liegt. Das ist um 3 Uhr morgens entscheidend: Die schnellste Wiederherstellung ist diejenige, bei der nichts kompiliert werden muss.

Die Frage, die Sie einer Plattform stellen sollten

Wenn Sie entscheiden, wo Sie Ihre Produktion betreiben, sollten Sie diesen Fall gezielt testen: Deployen Sie eine Anwendung, die erfolgreich startet und anschließend keine Verbindung zu ihrer Datenbank herstellen kann. Beobachten Sie, was das Dashboard meldet.

Wenn dort running steht, wissen Sie jetzt genau, was dieses Wort bei Ihrem nächsten Vorfall wert sein wird.

Häufig gestellte Fragen

Warum meldet mein Dashboard „running“, obwohl die Website nicht erreichbar ist? Weil „running“ normalerweise bedeutet, dass der Container-Prozess existiert – nicht, dass die Anwendung eine Anfrage bedienen kann. Ein Prozess, der bei jedem Versuch erneut eine Datenbankverbindung herzustellen versucht, erfüllt diese Definition unbegrenzt.

Sollte ein Health Check die Datenbank abfragen? Ja, wenn Ihre Anwendung ohne sie keine Anfragen bedienen kann. Prüfen Sie die Abhängigkeiten, die Sie tatsächlich benötigen, und lassen Sie diejenigen aus, deren Ausfall Sie kompensieren können.

Was ist der Unterschied zwischen Liveness und Readiness? Liveness fragt, ob der Prozess neu gestartet werden sollte. Readiness fragt, ob er Traffic erhalten sollte. Der Gate, der diesen Fehler verhindert, ist Readiness – und er muss ausgeführt werden, bevor der Traffic umgeleitet wird.

Wie verhindere ich, dass ein fehlerhaftes Deployment die Website überhaupt offline nimmt? Leiten Sie den Traffic erst um, wenn die neue Version eine echte Anfrage beantwortet, und behalten Sie die vorherige Version, bis der Wechsel bestätigt ist. Dann ist ein fehlgeschlagenes Release ein Release, das nie stattgefunden hat, und kein Ausfall.