Hälsokontroller, readiness-grindar och vänteslingor
Implementera vänteslingor för beroenden och livskraftskontroller som gör att containeriserade tjänster startar tillförlitligt.
Hälsokontroller, readiness-grindar och vänteslingor är en gratis lektion i DevOps-bootcamp på CoddyKit. Detta är lektion 4 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för DevOps-bootcamp, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i DevOps-bootcamp innehåller totalt 4 lektioner.
Därför misslyckas tjänster vid uppstart
I containeriserade miljöer startar tjänster sällan isolerat. En webbapplikation behöver att databasen är redo, en mikrotjänst behöver att sin message broker är tillgänglig och en worker behöver att cachen är ifylld innan den kan bearbeta jobb.
Utan samordning startar containrar parallellt, och de första förfrågningarna anländer innan beroendena är friska. Resultatet blir connection refused-fel, null pointer-undantag och korrupt starttillstånd som kräver manuella omstarter.
- Liveness-prob — körs processen fortfarande och har den inte fastnat?
- Readiness-prob — är tjänsten redo att ta emot trafik?
- Wait-loop — ett startskript som blockerar tills beroendena kan nås
Kubernetes tillhandahåller inbyggda probmekanismer, men skalskripten som driver initContainers, entrypoint.sh-omslag och fristående hälsokontroller skrivs i Bash. Att behärska dem är en central DevOps-färdighet.
Wait-for-mönstret
Den enklaste wait-loopen kontrollerar ett mål med ett fast intervall tills det blir nåbart. Den klassiska formen använder en while-loop med nc (netcat) eller curl för att kontrollera en TCP-port eller HTTP-endpoint.
Viktiga designbeslut:
- Tidsgräns — avbryt efter N sekunder så att ett trasigt beroende inte blockerar poden för alltid
- Backoff-intervall — vänta mellan kontrollerna för att undvika att överbelasta en tjänst som håller på att återhämta sig
- Exitstatus — avsluta med 1 vid timeout så att containern startas om, eller så att init-containern misslyckas tydligt
Kodavsnittet nedan väntar i högst 60 sekunder på att en TCP-port ska acceptera anslutningar innan huvudprocessen startas.
#!/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-readiness-prober med curl
En TCP-anslutning visar bara att porten är öppen — inte att applikationen bakom den är redo att hantera förfrågningar. Många tjänster exponerar en särskild /health- eller /readyz-endpoint som returnerar HTTP 200 först när alla interna delsystem har initierats.
Använd curl --fail --silent --output /dev/null för att kontrollera en HTTP-readiness-endpoint. Flaggan --fail gör att curl avslutas med kod 22 vid svar från 4xx/5xx, vilket ger en ren retry-logik.
Viktiga flaggor att känna till:
--fail— behandla HTTP-fel som curl-fel, alltså som en exitstatus skild från noll--silent— undertryck förloppsutdata--max-time N— tidsgräns i sekunder för varje förfrågan--retry N --retry-delay S— curls eget retry-lager, användbart i enkla fall
#!/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 "$@"Exponentiell backoff i wait-loopar
En retry-loop med fast intervall belastar en tjänst som håller på att återhämta sig i konstant takt. Exponentiell backoff fördubblar väntetiden vid varje försök, vilket minskar belastningen under återhämtningen samtidigt som loopen ändå konvergerar snabbt när tjänsten startar snabbt.
Standardformeln är: sleep_time = min(base * 2^attempt, max_sleep). Ett jitter-tillägg, alltså en slumpmässig bråkdel, förhindrar thundering herd-problem när många containrar startas om samtidigt.
Det här mönstret används av produktionsverktyg som wait-for-it, AWS SDK:s retries och Kubernetes-styrenheters reconciliation-loopar.
#!/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 1Kontroll av flera beroenden
Verkliga applikationer har flera beroenden: en databas, en cache, en message broker och kanske ett externt API. Om ni kontrollerar dem sekventiellt går starttiden till spillo. Ett bättre sätt är att kontrollera alla beroenden parallellt och vänta på att alla ska lyckas.
Bash-operatorn & kör varje kontroll i bakgrunden, och wait samlar in deras exitstatusar. Om någon kontroll misslyckas avslutas entrypoint-skriptet med en exitstatus skild från noll, vilket utlöser en omstart av containern.
En viktig teknik är att fånga PID-värden för bakgrundsprocesserna med $! och skicka dem explicit till wait så att ni kan kontrollera individuella exitstatusar.
#!/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 "$@"Liveness kontra readiness: olika skript för olika prober
Kubernetes skiljer mellan liveness- och readiness-prober, och de bör göra olika saker:
- Liveness-prob — svarar på frågan: lever processen fortfarande och har den inte fastnat? Den bör vara snabb och endast kontrollera internt tillstånd, till exempel att processens PID-fil finns eller att en lokal health-endpoint returnerar 200. En misslyckad liveness-prob avslutar och startar om containern.
- Readiness-prob — svarar på frågan: bör den här poden ta emot trafik? Den kan kontrollera nedströmsberoenden. En misslyckad readiness-prob tar bort poden från Services lastbalanserare, men startar inte om den.
Lägg aldrig långsamma beroendekontroller i liveness-prober. Ett kort databasavbrott skulle då felaktigt starta om alla era applikationspoddar och förvärra problemet.
#!/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 0Startup-prober och initContainers
Kubernetes erbjuder en tredje probtyp: startupProbe. Den körs i stället för liveness- och readiness-prober tills den lyckas en gång, vilket ger långsamt startande applikationer, till exempel JVM-uppvärmning och databas-migreringar, tid att initieras utan att falska liveness-fel utlöses.
För att vänta på beroenden är initContainers ofta renare än entrypoint-skript. De körs innan appcontainrarna startar, och Kubernetes hanterar retry- och omstartslogiken automatiskt. Init-containerns avbild behöver endast sh, nc eller curl — ni kan använda en minimal busybox- eller alpine-avbild.
Exempel på en initContainer-specifikation i ett 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-secretsHälsokontrollskript med JSON-utdata
Produktionssystem sammanställer ofta hälsostatus från flera delsystem och exponerar den som en strukturerad JSON-nyttolast. Den används av lastbalanserare, orkestrerare och övervakningspaneler.
Ett Bash-hälsokontrollskript kan bygga JSON-utdata direkt med hjälp av printf eller jq. Exitstatusen styr fortfarande automatiseringen, medan JSON-innehållet är avsett för operatörer och övervakningssystem.
Konventionen är att returnera HTTP 200 med {"status":"ok"} när systemet är friskt och HTTP 503 med {"status":"degraded", "checks":{...}} när det inte är det. Skriptet nedan är avsett att tillhandahållas av ett lättviktigt HTTP-omslag som socat eller att anropas direkt av Kubernetes exec-prober.
#!/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 1Timeout-verktyget med deadline-mönstret
GNU-kommandot timeout omsluter valfritt kommando och avslutar det om det inte blir klart inom den angivna tidsperioden. Det är det renaste sättet att införa en hård tidsgräns för en wait-loop eller hälsokontroll utan att manuellt hantera bakgrundsjobb.
timeout DURATION COMMAND [ARGS...]
Exitstatusar från timeout:
- 0 — kommandot lyckades inom tidsgränsen
- Kommandots exitstatus — kommandot kördes men returnerade en exitstatus skild från noll
- 124 — kommandot överskred tidsgränsen, SIGTERM skickades
- 137 — kommandot avslutades med SIGKILL efter
--kill-after
Om ni identifierar exitstatus 124 kan ni skriva ut ett tydligt timeout-meddelande i stället för ett generiskt fel.
#!/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 "$@"Självständigt wait-for-it i ren Bash
Många minimala containeravbilder saknar nc (netcat). Ren Bash kan öppna TCP-anslutningar genom processubstitution till /dev/tcp, en icke-standardiserad men brett stödd inbyggd Bash-funktion som inte kräver några externa verktyg.
Syntax: /dev/tcp/HOST/PORT — Bash öppnar en TCP-anslutning när ni omdirigerar till eller från den här sökvägen. Ett fel uppstår, med en exitstatus skild från noll, om anslutningen nekas eller överskrider tidsgränsen.
Det här är tekniken som används av det populära skriptet wait-for-it.sh, som följer med många Docker Compose-konfigurationer. Skriptet nedan är en komplett, fristående implementation som ni kan COPY in i valfri Dockerfile.
#!/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
fiIntegrera prober i entrypoint.sh
Entrypoint-mönstret är standardlösningen för att kombinera wait-loopar, validering av miljövariabler och processstart i ett enda lättunderhållet skript. Dockers ENTRYPOINT anropar skriptet, och skriptet avslutas med exec "$@" för att lämna över till CMD med samma PID, vilket möjliggör korrekt vidarebefordran av signaler.
Ett produktionsklart entrypoint.sh brukar:
- tidigt validera obligatoriska miljövariabler, så att fel upptäcks direkt
- köra wait-loopar för beroenden
- köra databas-migreringar, om det är tillämpligt
- köra en slutlig självkontroll
- lämna över kontrollen med
exec "$@"
Det är viktigt att använda exec — det ersätter skalprocessen så att appen blir PID 1 och tar emot SIGTERM direkt från Docker/Kubernetes vid en ordnad avstängning.
#!/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 "$@"Kunskapskontroll: liveness- och readiness-prober
Testa er förståelse av de viktigaste begreppen i den här lektionen.
Sammanfattning: hälsoprober, readiness-grindar och wait-loopar
I den här lektionen byggde ni Bash-verktygslådan för tillförlitlig samordning vid containerstart. Ni gick igenom följande:
- Wait-for-mönstret — en
until nc -z HOST PORT-loop med en hård tidsgräns och kontroll av förfluten tid förhindrar oändlig blockering på trasiga beroenden. - HTTP-readiness-prober —
curl --fail --max-timevaliderar att en applikation verkligen är redo, inte bara att porten är öppen. - Exponentiell backoff — genom att fördubbla väntintervallet mellan återförsök minskar belastningen från thundering herd-problem och tjänster som återhämtar sig får möjlighet att stabiliseras.
- Parallell kontroll av beroenden — genom att köra prober i bakgrunden med
&och samla resultaten medwait $PIDminskar startfördröjningen när det finns flera beroenden. - Liveness kontra readiness — liveness-prober måste vara snabba och endast kontrollera lokalt tillstånd, medan readiness-prober kan kontrollera nedströmsystem. Blanda aldrig ihop dem.
- startupProbe — skyddar långsamt startande appar mot falska liveness-fel under initieringen.
- /dev/tcp-prob — en ren Bash-baserad TCP-kontroll kräver inga externa verktyg och passar därför bra för minimala containeravbilder.
- entrypoint.sh — validering av miljövariabler, wait-loopar, migreringar och
exec "$@"utgör standardmönstret för uppstart av containeriserade tjänster.
Dessa mönster utgör grunden för självläkande, produktionsklara containerdistributioner i Docker Compose, Kubernetes och ECS.
Lär dig DevOps-bootcamp med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 142
- Lektioner
- 568
Vanliga frågor
Är lektionen ”Hälsokontroller, readiness-grindar och vänteslingor” gratis?
Ja – du kan läsa vilka 3 lektioner som helst i lärvägen DevOps-bootcamp, inklusive ”Hälsokontroller, readiness-grindar och vänteslingor”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i DevOps-bootcamp innehåller totalt 4 lektioner.
Vad lär jag mig i ”Hälsokontroller, readiness-grindar och vänteslingor”?
Implementera vänteslingor för beroenden och livskraftskontroller som gör att containeriserade tjänster startar tillförlitligt. Ni övar på DevOps-bootcamp med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig DevOps-bootcamp?
Du behöver inga förkunskaper. Utbildningen i DevOps-bootcamp på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 4 av 4.
Hur lång tid tar lektionen ”Hälsokontroller, readiness-grindar och vänteslingor”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här DevOps-bootcamp-lektionen?
Ja. Varje DevOps-bootcamp-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Skriv slimmade Dockerfiler och shell-entrypoints
- Skapa konfigurationsmallar med envsubst och heredocs
- Skripta molnresurser med CLI och jq
- Hälsokontroller, readiness-grindar och vänteslingor