Health probes, readiness gates en wachtlussen
Implementeer wachtlussen voor afhankelijkheden en liveness probes zodat gecontaineriseerde services betrouwbaar starten.
Health probes, readiness gates en wachtlussen is een gratis DevOps-bootcamp-les op CoddyKit. Dit is les 4 van 4. Je kunt 3 lessen uit dit leerpad gratis volledig lezen — daarna ontgrendelt CoddyKit PRO alle lessen, plus praktische oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject DevOps-bootcamp. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus DevOps-bootcamp bevat in totaal 4 lessen.
Waarom diensten niet opstarten
In gecontaineriseerde omgevingen starten diensten zelden geïsoleerd op. Een webapplicatie heeft een gereedstaande database nodig, een microservice heeft een beschikbare berichtenbroker nodig en een werker heeft een gevulde cache nodig voordat die taken kan verwerken.
Zonder coördinatie starten containers parallel en komen de eerste verzoeken binnen voordat afhankelijkheden gezond zijn. Het resultaat: fouten wegens geweigerde verbindingen, nullpointeruitzonderingen en beschadigde opstarttoestand waarvoor handmatige herstarts nodig zijn.
- Levenscontrole — draait het proces nog en is het niet vastgelopen?
- Gereedheidscontrole — is de dienst gereed om verkeer te accepteren?
- Wachtlus — een opstartscript dat blokkeert totdat afhankelijkheden bereikbaar zijn
Kubernetes biedt ingebouwde mechanismen voor deze controles, maar de shellscripts achter initContainers, entrypoint.sh-omhulsels en zelfstandige gezondheidscontroles zijn geschreven in Bash. Ze beheersen is een essentiële DevOps-vaardigheid.
Het wait-for-patroon
De eenvoudigste wachtlus voor afhankelijkheden controleert een doel met een vast interval totdat het bereikbaar wordt. De standaardvorm gebruikt een while-lus met nc (netcat) of curl om een TCP-poort of HTTP-eindpunt te controleren.
Belangrijke ontwerpkeuzes:
- Time-out — breek na N seconden af, zodat een defecte afhankelijkheid de pod niet voor altijd laat hangen
- Interval tussen pogingen — wacht tussen controles om te voorkomen dat je een dienst die herstelt blijft belasten
- Afsluitcode — gebruik exit 1 bij een time-out, zodat de container opnieuw wordt gestart of de init-container duidelijk faalt
Het onderstaande fragment wacht maximaal 60 seconden totdat een TCP-poort verbindingen accepteert voordat het hoofdproces wordt gestart.
#!/usr/bin/env bash
set -euo pipefail
HOST="${DB_HOST:-postgres}"
PORT="${DB_PORT:-5432}"
TIMEOUT=60
INTERVAL=2
ELAPSED=0
echo "[wait-for] Waiting for ${HOST}:${PORT}..."
until nc -z "$HOST" "$PORT" 2>/dev/null; do
if (( ELAPSED >= TIMEOUT )); then
echo "[wait-for] Timed out after ${TIMEOUT}s waiting for ${HOST}:${PORT}" >&2
exit 1
fi
echo "[wait-for] ${HOST}:${PORT} not ready — retrying in ${INTERVAL}s (${ELAPSED}s elapsed)"
sleep "$INTERVAL"
(( ELAPSED += INTERVAL ))
done
echo "[wait-for] ${HOST}:${PORT} is up — continuing"
exec "$@"HTTP-gereedheidscontroles met curl
Een TCP-verbinding vertelt je alleen dat de poort open is — niet dat de applicatie erachter gereed is om verzoeken te verwerken. Veel diensten bieden een speciaal /health- of /readyz-eindpunt dat alleen HTTP 200 teruggeeft wanneer alle interne subsystemen zijn geïnitialiseerd.
Gebruik curl --fail --silent --output /dev/null om een HTTP-gereedheidseindpunt te controleren. De vlag --fail zorgt ervoor dat curl bij 4xx/5xx-antwoorden afsluit met code 22, waardoor de logica voor nieuwe pogingen netjes wordt aangestuurd.
Belangrijke vlaggen:
--fail— behandel HTTP-fouten als curl-fouten (niet-nulafsluiting)--silent— onderdruk de voortgangsuitvoer--max-time N— time-out per verzoek in seconden--retry N --retry-delay S— curl's eigen laag voor nieuwe pogingen (handig voor eenvoudige gevallen)
#!/usr/bin/env bash
set -euo pipefail
HEALTH_URL="${HEALTH_URL:-http://localhost:8080/healthz}"
TIMEOUT=90
INTERVAL=3
ELAPSED=0
echo "[probe] Polling readiness at ${HEALTH_URL}"
until curl --fail --silent --output /dev/null \
--max-time 2 "${HEALTH_URL}"; do
if (( ELAPSED >= TIMEOUT )); then
echo "[probe] Service not ready after ${TIMEOUT}s" >&2
exit 1
fi
printf '[probe] Not ready yet (%ds elapsed)\n' "$ELAPSED"
sleep "$INTERVAL"
(( ELAPSED += INTERVAL ))
done
echo "[probe] Service is ready"
exec "$@"Exponentiële vertraging in wachtlussen
Een lus met vaste intervallen voor nieuwe pogingen belast een dienst die herstelt voortdurend met dezelfde snelheid. Exponentiële vertraging verdubbelt de wachttijd bij elke poging, waardoor de belasting tijdens het herstel afneemt en je toch snel succes bereikt wanneer de dienst snel beschikbaar komt.
De standaardformule is: sleep_time = min(base * 2^attempt, max_sleep). Een willekeurige verschuiving voorkomt het kudde-effect wanneer veel containers tegelijk opnieuw starten.
Dit patroon wordt gebruikt door productietools zoals wait-for-it, nieuwe pogingen van de AWS SDK en reconciliatielussen voor Kubernetes-aansturing.
#!/usr/bin/env bash
set -euo pipefail
HOST="${HOST:-redis}"
PORT="${PORT:-6379}"
MAX_ATTEMPTS=8
BASE_SLEEP=1
MAX_SLEEP=30
for attempt in $(seq 1 "$MAX_ATTEMPTS"); do
if nc -z "$HOST" "$PORT" 2>/dev/null; then
echo "[backoff] Connected to ${HOST}:${PORT} on attempt ${attempt}"
exec "$@"
fi
# Exponential backoff with jitter
raw=$(( BASE_SLEEP * (2 ** (attempt - 1)) ))
capped=$(( raw < MAX_SLEEP ? raw : MAX_SLEEP ))
jitter=$(( RANDOM % 3 ))
sleep_time=$(( capped + jitter ))
echo "[backoff] Attempt ${attempt}/${MAX_ATTEMPTS} failed — sleeping ${sleep_time}s"
sleep "$sleep_time"
done
echo "[backoff] ${HOST}:${PORT} unreachable after ${MAX_ATTEMPTS} attempts" >&2
exit 1Meerdere afhankelijkheden controleren
Echte applicaties hebben meerdere afhankelijkheden: een database, een cache, een berichtenbroker en mogelijk een externe API. Ze na elkaar controleren verspilt opstarttijd. Een betere aanpak controleert alle afhankelijkheden parallel en wacht totdat ze allemaal slagen.
De Bash-operator & zet elke controle op de achtergrond en wait verzamelt hun afsluitcodes. Als een controle mislukt, wordt het entrypoint afgesloten met een niet-nulstatus, waardoor de container opnieuw wordt gestart.
Belangrijke techniek: leg achtergrond-PID's vast met $! en geef ze expliciet door aan wait, zodat je afzonderlijke afsluitcodes kunt controleren.
#!/usr/bin/env bash
set -euo pipefail
wait_tcp() {
local host="$1" port="$2" timeout="${3:-30}"
local elapsed=0
until nc -z "$host" "$port" 2>/dev/null; do
(( elapsed >= timeout )) && { echo "TIMEOUT ${host}:${port}" >&2; return 1; }
sleep 2; (( elapsed += 2 ))
done
echo "[ok] ${host}:${port}"
}
# Launch all probes in parallel
wait_tcp postgres 5432 60 & PID_PG=$!
wait_tcp redis 6379 30 & PID_RD=$!
wait_tcp rabbitmq 5672 45 & PID_RQ=$!
# Collect results — fail fast if any probe failed
FAILED=0
for pid in $PID_PG $PID_RD $PID_RQ; do
wait "$pid" || (( FAILED++ ))
done
if (( FAILED > 0 )); then
echo "[entrypoint] ${FAILED} dependency probe(s) failed — aborting" >&2
exit 1
fi
echo "[entrypoint] All dependencies ready"
exec "$@"Levenscontrole versus gereedheidscontrole: verschillende scripts voor verschillende controles
Kubernetes maakt onderscheid tussen levenscontroles en gereedheidscontroles, en ze horen verschillende dingen te doen:
- Levenscontrole — beantwoordt de vraag: leeft het proces nog en is het niet vastgelopen? Deze controle moet snel zijn en alleen de interne toestand controleren, bijvoorbeeld of het PID-bestand van het proces bestaat of een lokaal gezondheidseindpunt 200 teruggeeft. Een mislukte levenscontrole beëindigt de container en start die opnieuw.
- Gereedheidscontrole — beantwoordt de vraag: moet deze pod verkeer ontvangen? Deze controle mag afhankelijkheden verderop in de keten controleren. Een mislukte gereedheidscontrole verwijdert de pod uit de load balancer van de Service, maar start die niet opnieuw.
Plaats nooit trage afhankelijkheidscontroles in levenscontroles. Een korte database-uitval zou dan ten onrechte al je applicatiepods opnieuw starten, waardoor het probleem groter wordt.
#!/usr/bin/env bash
# liveness.sh — fast local-only check
# Used in: livenessProbe.exec.command
set -euo pipefail
PID_FILE="/var/run/app/app.pid"
HEALTH_URL="http://127.0.0.1:8080/internal/live"
# Check 1: process is running
[[ -f "$PID_FILE" ]] || { echo "PID file missing" >&2; exit 1; }
kill -0 "$(cat "$PID_FILE")" 2>/dev/null || { echo "Process dead" >&2; exit 1; }
# Check 2: local endpoint responds (2s timeout — never block)
curl --fail --silent --max-time 2 --output /dev/null "$HEALTH_URL" || {
echo "Liveness endpoint unresponsive" >&2
exit 1
}
echo "live"
exit 0Opstartcontroles en initContainers
Kubernetes biedt een derde type controle: startupProbe. Deze wordt uitgevoerd in plaats van levens- en gereedheidscontroles totdat die eenmaal slaagt. Zo krijgen traag opstartende applicaties, bijvoorbeeld tijdens het opwarmen van de JVM of het uitvoeren van databasemigraties, tijd om te initialiseren zonder onterechte fouten van de levenscontrole te veroorzaken.
Voor het wachten op afhankelijkheden zijn initContainers vaak overzichtelijker dan entrypointscripts. Ze worden uitgevoerd voordat applicatiecontainers starten en Kubernetes handelt de logica voor opnieuw proberen en opnieuw starten automatisch af. De image van de init-container heeft alleen sh, nc of curl nodig — je kunt een minimale busybox- of alpine-image gebruiken.
Voorbeeld van een specificatie van een initContainer in een Pod-manifest:
# kubernetes/pod-with-init.yaml (illustrative — not runnable as bash)
# initContainers run sequentially before app containers
initContainers:
- name: wait-for-postgres
image: busybox:1.36
command:
- sh
- -c
- |
set -e
echo 'Waiting for postgres...'
until nc -z postgres 5432; do
echo 'postgres not ready — sleeping 2s'
sleep 2
done
echo 'postgres is up'
- name: run-migrations
image: myapp:latest
command: ['python', 'manage.py', 'migrate', '--noinput']
envFrom:
- secretRef:
name: app-secretsGezondheidscontrolescript met JSON-uitvoer
Productiesystemen voegen de gezondheidsstatus van meerdere subsystemen vaak samen en stellen die beschikbaar als een gestructureerde JSON-lading. Load balancers, orkestrators en bewakingsdashboards gebruiken deze informatie.
Een Bash-gezondheidscontrolescript kan JSON-uitvoer rechtstreeks opbouwen met printf of jq. De afsluitcode stuurt de automatisering nog steeds aan; de JSON-inhoud is bedoeld voor beheerders en bewakingssystemen.
Gebruikelijk is: geef HTTP 200 terug met {"status":"ok"} wanneer alles gezond is, en HTTP 503 met {"status":"degraded", "checks":{...}} wanneer iets niet goed werkt. Het onderstaande script is bedoeld om te worden aangeboden door een lichtgewicht HTTP-omhulsel zoals socat, of rechtstreeks te worden aangeroepen door Kubernetes-exec-controles.
#!/usr/bin/env bash
# health_check.sh — composite health with JSON output
set -uo pipefail
check_postgres() {
pg_isready -h "${DB_HOST:-postgres}" -p "${DB_PORT:-5432}" \
-U "${DB_USER:-app}" -t 2 &>/dev/null
}
check_redis() {
redis-cli -h "${REDIS_HOST:-redis}" -p "${REDIS_PORT:-6379}" \
PING 2>/dev/null | grep -q PONG
}
check_disk() {
local usage
usage=$(df / | awk 'NR==2{gsub(/%/,"",$5); print $5}')
(( usage < 90 ))
}
PG_OK=0; RD_OK=0; DSK_OK=0
check_postgres && PG_OK=1
check_redis && RD_OK=1
check_disk && DSK_OK=1
OVERALL=$(( PG_OK && RD_OK && DSK_OK ))
STATUS=$( (( OVERALL )) && echo 'ok' || echo 'degraded' )
printf '{"status":"%s","checks":{"postgres":%s,"redis":%s,"disk":%s}}\n' \
"$STATUS" "$PG_OK" "$RD_OK" "$DSK_OK"
(( OVERALL )) && exit 0 || exit 1Time-outprogramma met een patroon voor een uiterste tijd
Het GNU-commando timeout wikkelt elk commando in en beëindigt het als het niet binnen de opgegeven duur klaar is. Het is de duidelijkste manier om een harde uiterste tijd af te dwingen voor een wachtlus of gezondheidscontrole, zonder handmatig achtergrondtaken te beheren.
timeout DURATION COMMAND [ARGS...]
Afsluitcodes van timeout:
- 0 — het commando is binnen de uiterste tijd geslaagd
- Afsluitcode van het commando — het commando is uitgevoerd maar gaf een niet-nulwaarde terug
- 124 — het commando overschreed de tijdslimiet (SIGTERM verzonden)
- 137 — het commando is beëindigd met SIGKILL (na
--kill-after)
Als je afsluitcode 124 herkent, kun je een duidelijke time-outmelding afdrukken in plaats van een algemene fout.
#!/usr/bin/env bash
set -euo pipefail
HOST="${DB_HOST:-postgres}"
PORT="${DB_PORT:-5432}"
DEADLINE=60 # seconds
# Inline poll loop, wrapped by timeout
timeout "$DEADLINE" bash -c "
until nc -z '$HOST' '$PORT' 2>/dev/null; do
echo '[wait] ${HOST}:${PORT} not ready...'
sleep 2
done
" && echo "[wait] ${HOST}:${PORT} is up" || {
code=$?
if (( code == 124 )); then
echo "[wait] Timed out after ${DEADLINE}s waiting for ${HOST}:${PORT}" >&2
else
echo "[wait] Probe failed with exit code ${code}" >&2
fi
exit "$code"
}
exec "$@"Zelfstandig wait-for-it in puur Bash
Veel minimale containerimages bevatten geen nc (netcat). Met puur Bash kun je TCP-verbindingen openen via procesvervanging naar /dev/tcp, een niet-standaard maar breed ondersteunde ingebouwde Bash-functie waarvoor geen externe tools nodig zijn.
Syntax: /dev/tcp/HOST/PORT — Bash opent een TCP-verbinding wanneer je naar of vanaf dit pad omleidt. Er wordt een fout gegenereerd (niet-nulafsluiting) als de verbinding wordt geweigerd of de time-out verstrijkt.
Dit is de techniek achter het populaire script wait-for-it.sh, dat bij veel Docker Compose-omgevingen wordt meegeleverd. Het onderstaande script is een volledige, zelfstandige implementatie die je in elke Dockerfile kunt COPY'en.
#!/usr/bin/env bash
# wait-for-it.sh (pure bash, no nc/curl required)
set -uo pipefail
usage() { echo "Usage: $0 HOST:PORT [-t TIMEOUT] [-- COMMAND]"; exit 1; }
parse_hostport() {
HOST="${1%%:*}"
PORT="${1##*:}"
[[ -n "$HOST" && "$PORT" =~ ^[0-9]+$ ]] || usage
}
[[ $# -ge 1 ]] || usage
parse_hostport "$1"; shift
TIMEOUT=30
[[ "${1:-}" == "-t" ]] && { TIMEOUT="$2"; shift 2; }
[[ "${1:-}" == "--" ]] && shift
wait_for() {
local elapsed=0
while (( elapsed < TIMEOUT )); do
# Pure bash TCP probe — no nc, no curl
if (exec 3<>"/dev/tcp/${HOST}/${PORT}") 2>/dev/null; then
exec 3>&-
return 0
fi
sleep 1
(( elapsed++ ))
done
return 1
}
echo "Waiting for ${HOST}:${PORT} (timeout ${TIMEOUT}s)..."
if wait_for; then
echo "${HOST}:${PORT} is available"
[[ $# -gt 0 ]] && exec "$@"
else
echo "Timed out waiting for ${HOST}:${PORT}" >&2
exit 1
fiControles integreren in entrypoint.sh
Het entrypointpatroon is de standaardmanier om wachtlussen, controle van de omgeving en het starten van een proces te combineren in één onderhoudbaar script. Docker's ENTRYPOINT roept dit script aan en het script eindigt met exec "$@" om de besturing met dezelfde PID over te dragen aan CMD (waardoor signalen netjes kunnen worden doorgestuurd).
Een entrypoint.sh van productiekwaliteit doet doorgaans het volgende:
- Vereiste omgevingsvariabelen vroeg controleren (snel falen)
- Wachtlussen voor afhankelijkheden uitvoeren
- Databasemigraties uitvoeren (indien van toepassing)
- Een laatste zelfcontrole uitvoeren
- De besturing overdragen met
exec "$@"
exec gebruiken is essentieel — het vervangt het shellproces, zodat de app PID 1 wordt en SIGTERM van Docker/Kubernetes rechtstreeks ontvangt voor een nette afsluiting.
#!/usr/bin/env bash
# docker/entrypoint.sh
set -euo pipefail
# ── 1. Validate required env vars ────────────────────────────────────
for var in DATABASE_URL REDIS_URL SECRET_KEY; do
[[ -n "${!var:-}" ]] || { echo "FATAL: ${var} is not set" >&2; exit 1; }
done
# ── 2. Parse DB host/port from DATABASE_URL ──────────────────────────
DB_HOST=$(echo "$DATABASE_URL" | sed -E 's|.*@([^:/]+).*|\1|')
DB_PORT=$(echo "$DATABASE_URL" | sed -E 's|.*:([0-9]+)/.*|\1|')
# ── 3. Wait for dependencies ─────────────────────────────────────────
timeout 60 bash -c "
until nc -z '${DB_HOST}' '${DB_PORT}' 2>/dev/null; do sleep 2; done
" || { echo "Database unreachable" >&2; exit 1; }
# ── 4. Run migrations ────────────────────────────────────────────────
echo "[entrypoint] Running migrations..."
python manage.py migrate --noinput
# ── 5. Hand off to CMD (exec preserves PID 1 for signal handling) ────
echo "[entrypoint] Starting application: $*"
exec "$@"Kennischeck: levens- versus gereedheidscontroles
Test je begrip van de belangrijkste concepten uit deze les.
Samenvatting: gezondheidscontroles, gereedheidspoorten en wachtlussen
In deze les bouwde je de Bash-gereedschapset voor betrouwbare coördinatie van containeropstarts. Dit behandelde je:
- Wait-for-patroon — een
until nc -z HOST PORT-lus met een harde time-out en controle op verstreken tijd voorkomt eindeloos blokkeren bij defecte afhankelijkheden. - HTTP-gereedheidscontroles —
curl --fail --max-timecontroleert of een applicatie echt gereed is, en niet alleen of de poort open is. - Exponentiële vertraging — door het wachtinterval tussen nieuwe pogingen te verdubbelen, neemt de belasting door het kudde-effect af en krijgen diensten die herstellen de kans om stabiel te worden.
- Parallelle controle van afhankelijkheden — door controles met
&op de achtergrond uit te voeren en resultaten metwait $PIDte verzamelen, neemt de opstarttijd af wanneer er meerdere afhankelijkheden zijn. - Levenscontrole versus gereedheidscontrole — levenscontroles moeten snel zijn en alleen de lokale toestand controleren; gereedheidscontroles mogen systemen verderop in de keten controleren. Verwissel ze nooit.
- startupProbe — beschermt traag opstartende apps tegen onterechte fouten van de levenscontrole tijdens de initialisatie.
- /dev/tcp-controle — voor een TCP-controle met puur Bash zijn geen externe tools nodig, wat ideaal is voor minimale containerimages.
- entrypoint.sh — controle van omgevingsvariabelen, wachtlussen, migraties en
exec "$@"vormen het standaardopstartpatroon voor gecontaineriseerde diensten.
Deze patronen vormen de basis voor zelfherstellende containerimplementaties van productiekwaliteit op Docker Compose, Kubernetes en ECS.
Leer DevOps-bootcamp met een AI-tutor — gratis
Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.
- Cursussen
- 142
- Lessen
- 568
Veelgestelde vragen
Is de les “Health probes, readiness gates en wachtlussen” gratis?
Ja — je kunt hier op het web alle 3 lessen van het leerpad DevOps-bootcamp, waaronder “Health probes, readiness gates en wachtlussen”, gratis volledig lezen. Daarna ontgrendelt CoddyKit PRO alle lessen, plus interactieve oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. De cursus DevOps-bootcamp bevat in totaal 4 lessen.
Wat leer ik in “Health probes, readiness gates en wachtlussen”?
Implementeer wachtlussen voor afhankelijkheden en liveness probes zodat gecontaineriseerde services betrouwbaar starten. Je oefent met DevOps-bootcamp door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.
Heb ik ervaring nodig om met DevOps-bootcamp te beginnen?
Ervaring vooraf is niet nodig. DevOps-bootcamp op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 4 van 4.
Hoe lang duurt de les “Health probes, readiness gates en wachtlussen”?
De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.
Kan ik code schrijven en uitvoeren in deze les over DevOps-bootcamp?
Ja. Elke les over DevOps-bootcamp bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.
Alle lessen in deze cursus
- Slanke Dockerfiles en shell-entrypoints schrijven
- Configuraties templaten met envsubst en heredocs
- Cloudresources scripten met CLI en jq
- Health probes, readiness gates en wachtlussen