fix: Health-Endpunkte trennen Liveness von Readiness
Readiness- und Liveness-Probe zeigten beide auf "/" — eine
force-dynamic-Seite samt Datenbankabfragen und vollem React-Rendering, alle
10 bzw. 30 Sekunden.
Der teure Teil ist dabei nicht das eigentliche Problem. Entscheidend ist,
dass damit auch die LIVENESS-Probe an der Datenbank haengt: faellt die DB
voruebergehend aus, wertet Kubernetes den Container als tot und startet ihn
neu — woraufhin der neue Container laut Dockerfile-CMD zuerst
"prisma migrate deploy" und "prisma db seed" gegen genau die gestoerte
Datenbank faehrt. Aus einer voruebergehenden Stoerung wird so eine
Neustart-Schleife.
Jetzt zwei Endpunkte mit klar getrennter Aufgabe:
- /api/health — nur "Prozess antwortet", ohne Datenbankzugriff.
Liveness-Probe.
- /api/health/ready — zusaetzlich "SELECT 1" gegen die Datenbank, sonst 503.
Readiness-Probe und Docker-Healthcheck.
Faellt die Readiness-Probe aus, nimmt Kubernetes den Pod nur aus dem
Load-Balancing und startet ihn nicht neu — genau das richtige Verhalten.
"SELECT 1" laeuft unveraendert auf PostgreSQL und MySQL/MariaDB und
beruehrt keine Tabelle, ist also unabhaengig vom Migrationsstand. Die
Fehlerdetails gehen nur ins Server-Log, nicht in die oeffentliche Antwort.
docker-compose.yml bekommt denselben Readiness-Endpunkt als Healthcheck,
mit start_period fuer den Migrations-/Seed-Lauf vor dem Serverstart.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
1 parent
d3020cd4da
commit
4b76247648
4 files changed
+86
-2
No files matched your search
@@ -79,6 +79,16 @@ services:
|
|||||||
required: false
|
required: false
|
||||||
expose:
|
expose:
|
||||||
- "3000"
|
- "3000"
|
||||||
|
# Prueft denselben Endpunkt wie die Kubernetes-Readiness-Probe (siehe
|
||||||
|
# k8s/05-app-deployment.yaml): nicht nur "Prozess laeuft", sondern "App
|
||||||
|
# erreicht die Datenbank". start_period deckt den Migrations-/Seed-Lauf
|
||||||
|
# ab, den der Container laut Dockerfile-CMD vor dem Serverstart ausfuehrt.
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:3000/api/health/ready').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 90s
|
||||||
volumes:
|
volumes:
|
||||||
# Persistiert hochgeladene Admin-Bilder/-Videos (Dockerfile legt
|
# Persistiert hochgeladene Admin-Bilder/-Videos (Dockerfile legt
|
||||||
# /app/uploads bereits mit passendem Besitzer fuer den non-root
|
# /app/uploads bereits mit passendem Besitzer fuer den non-root
|
||||||
|
|||||||
@@ -60,15 +60,26 @@ spec:
|
|||||||
# (fehlende Volume-Referenz).
|
# (fehlende Volume-Referenz).
|
||||||
- name: uploads
|
- name: uploads
|
||||||
mountPath: /app/uploads
|
mountPath: /app/uploads
|
||||||
|
# Prueft die Datenbank mit (siehe app/api/health/ready/route.ts).
|
||||||
|
# Schlaegt sie fehl, nimmt Kubernetes den Pod nur aus dem
|
||||||
|
# Load-Balancing, startet ihn aber nicht neu.
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /api/health/ready
|
||||||
port: 3000
|
port: 3000
|
||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 10
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
|
# Bewusst der DB-FREIE Endpunkt (siehe app/api/health/route.ts):
|
||||||
|
# eine Liveness-Probe an der Datenbank wuerde aus einer
|
||||||
|
# voruebergehenden DB-Stoerung eine Neustart-Schleife machen, weil
|
||||||
|
# der neu startende Container laut Dockerfile-CMD zuerst
|
||||||
|
# "prisma migrate deploy" gegen dieselbe gestoerte Datenbank faehrt.
|
||||||
|
# Beide Probes zeigten vorher auf "/" — also auf eine
|
||||||
|
# force-dynamic-Seite mit DB-Abfragen und vollem React-Rendering
|
||||||
|
# alle 10 bzw. 30 Sekunden.
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /api/health
|
||||||
port: 3000
|
port: 3000
|
||||||
# initialDelaySeconds/failureThreshold grosszuegiger als sonst
|
# initialDelaySeconds/failureThreshold grosszuegiger als sonst
|
||||||
# ueblich: der Container migriert/seedet die DB selbst vor dem
|
# ueblich: der Container migriert/seedet die DB selbst vor dem
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Readiness-Probe: darf dieser Pod Kundenverkehr bekommen? Anders als die
|
||||||
|
* Liveness-Probe (siehe ../route.ts) prueft sie die Datenbank, denn ohne DB
|
||||||
|
* kann die Seite weder Produkte anzeigen noch eine Bestellung annehmen.
|
||||||
|
*
|
||||||
|
* Faellt sie aus, nimmt Kubernetes den Pod nur aus dem Service-Load-Balancing
|
||||||
|
* heraus und startet ihn NICHT neu — genau das richtige Verhalten bei einer
|
||||||
|
* voruebergehenden DB-Stoerung.
|
||||||
|
*
|
||||||
|
* "SELECT 1" funktioniert unveraendert auf PostgreSQL und MySQL/MariaDB
|
||||||
|
* (beide Provider sind per DATABASE_PROVIDER moeglich, siehe lib/prisma.ts)
|
||||||
|
* und beruehrt keine Tabelle, ist also unabhaengig vom Migrationsstand.
|
||||||
|
*/
|
||||||
|
export async function GET(): Promise<NextResponse> {
|
||||||
|
try {
|
||||||
|
await prisma.$queryRaw`SELECT 1`;
|
||||||
|
return NextResponse.json(
|
||||||
|
{ status: "ready", database: "up" },
|
||||||
|
{ headers: { "Cache-Control": "no-store" } }
|
||||||
|
);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
// Details nur ins Server-Log, nicht in die Antwort: der Endpunkt ist
|
||||||
|
// oeffentlich erreichbar und soll keine Verbindungszeichenfolge oder
|
||||||
|
// internen Hostnamen preisgeben.
|
||||||
|
console.error("Readiness-Probe: Datenbank nicht erreichbar", error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ status: "not-ready", database: "down" },
|
||||||
|
{ status: 503, headers: { "Cache-Control": "no-store" } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
// Nie zwischengespeichert: eine aus dem Cache beantwortete Probe wuerde einen
|
||||||
|
// haengenden Prozess als gesund melden.
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liveness-Probe: beantwortet ausschliesslich die Frage "laeuft der
|
||||||
|
* Node-Prozess noch und nimmt er Verbindungen an?" — bewusst OHNE
|
||||||
|
* Datenbankzugriff.
|
||||||
|
*
|
||||||
|
* Grund fuer die Trennung von /api/health/ready: eine Liveness-Probe, die an
|
||||||
|
* der Datenbank haengt, macht aus einer voruebergehenden DB-Stoerung eine
|
||||||
|
* Neustart-Schleife. Kubernetes toetet den Container, der Container faehrt
|
||||||
|
* hoch und laesst dabei laut Dockerfile-CMD erst "prisma migrate deploy" und
|
||||||
|
* "prisma db seed" gegen genau die Datenbank laufen, die gerade nicht
|
||||||
|
* erreichbar ist — der Neustart verschlimmert die Lage, statt sie zu
|
||||||
|
* beheben. Vorher zeigten beide Probes auf "/", also auf eine
|
||||||
|
* force-dynamic-Seite samt DB-Abfragen und vollstaendigem React-Rendering
|
||||||
|
* alle 10 bzw. 30 Sekunden.
|
||||||
|
*/
|
||||||
|
export function GET(): NextResponse {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ status: "ok", uptimeSeconds: Math.round(process.uptime()) },
|
||||||
|
{ headers: { "Cache-Control": "no-store" } }
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user