Probe di salute, controlli di readiness e cicli di attesa
Implementi cicli di attesa per le dipendenze e probe di liveness che consentano l'avvio affidabile dei servizi containerizzati.
Probe di salute, controlli di readiness e cicli di attesa è una lezione Linux Command Line & Bash Scripting Mastery gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Linux Command Line & Bash Scripting Mastery, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Linux Command Line & Bash Scripting Mastery include 4 lezioni in totale.
Perché i servizi falliscono all'avvio
Negli ambienti containerizzati, i servizi raramente si avviano in isolamento. Un'applicazione web ha bisogno che il database sia pronto, un microservizio che il message broker sia disponibile e un worker che la cache sia popolata prima di poter elaborare i job.
Senza coordinamento, i container si avviano in parallelo e le prime richieste arrivano prima che le dipendenze siano operative. Il risultato: errori di connessione rifiutata, eccezioni di puntatore nullo e stato di avvio corrotto che richiedono riavvii manuali.
- Probe di liveness — il processo è ancora in esecuzione e non è in deadlock?
- Probe di readiness — il servizio è pronto ad accettare traffico?
- Ciclo di attesa — uno script di avvio che si blocca finché le dipendenze non sono raggiungibili
Kubernetes offre meccanismi di probe integrati, ma gli script shell alla base di initContainers, dei wrapper entrypoint.sh e dei controlli dello stato autonomi sono scritti in Bash. Padroneggiarli è una competenza fondamentale di DevOps.
Il pattern wait-for
Il più semplice ciclo di attesa per una dipendenza esegue il polling di una destinazione a intervalli fissi finché questa non diventa raggiungibile. La struttura canonica usa un ciclo while con nc (netcat) o curl per controllare una porta TCP o un endpoint HTTP.
Decisioni progettuali chiave:
- Timeout — interrompere dopo N secondi, così una dipendenza guasta non mantiene il pod bloccato per sempre
- Intervallo di backoff — attendere tra una probe e l'altra per evitare di sovraccaricare un servizio in fase di ripristino
- Codice di uscita — uscire con codice 1 al timeout, così il container viene riavviato o l'init container segnala chiaramente il fallimento
Il frammento qui sotto attende fino a 60 secondi che una porta TCP accetti connessioni prima di avviare il processo principale.
#!/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 "$@"Probe di readiness HTTP con curl
Una connessione TCP indica soltanto che la porta è aperta, non che l'applicazione che vi sta dietro sia pronta a gestire richieste. Molti servizi espongono un endpoint dedicato /health o /readyz che restituisce HTTP 200 solo quando tutti i sottosistemi interni sono stati inizializzati.
Usi curl --fail --silent --output /dev/null per controllare un endpoint HTTP di readiness. Il flag --fail fa sì che curl termini con il codice 22 in caso di risposte 4xx/5xx, pilotando in modo pulito la logica di retry.
Flag importanti da conoscere:
--fail— trattare gli errori HTTP come errori di curl, con uscita diversa da zero--silent— sopprimere l'output di avanzamento--max-time N— timeout della singola richiesta in secondi--retry N --retry-delay S— livello di retry interno di curl, utile nei casi semplici
#!/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 "$@"Backoff esponenziale nei cicli di attesa
Un ciclo di retry a intervallo fisso sottopone un servizio in fase di ripristino a richieste a ritmo costante. Il backoff esponenziale raddoppia il tempo di attesa a ogni tentativo, riducendo il carico durante il ripristino e raggiungendo comunque rapidamente l'obiettivo quando il servizio torna disponibile.
La formula standard è: sleep_time = min(base * 2^attempt, max_sleep). Una componente di jitter, cioè uno scostamento frazionale casuale, previene i problemi di thundering herd quando molti container si riavviano contemporaneamente.
Questo pattern viene usato da strumenti di produzione come wait-for-it, dai retry degli AWS SDK e dai cicli di riconciliazione dei controller Kubernetes.
#!/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 1Controllo di più dipendenze
Le applicazioni reali hanno più dipendenze: un database, una cache, un message broker e magari un'API esterna. Controllarle in sequenza fa perdere tempo all'avvio. Un approccio migliore esegue tutte le probe in parallelo e attende che abbiano esito positivo.
L'operatore Bash & manda in background ogni probe e wait raccoglie i relativi codici di uscita. Se una probe fallisce, l'entrypoint termina con un codice diverso da zero, attivando il riavvio del container.
Tecnica fondamentale: catturare i PID dei processi in background con $! e passarli esplicitamente a wait, così da poter controllare i singoli codici di uscita.
#!/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 e readiness: script diversi per probe diverse
Kubernetes distingue tra probe di liveness e di readiness, che devono svolgere funzioni diverse:
- Probe di liveness — risponde alla domanda: il processo è ancora attivo e non è in deadlock? Deve essere veloce e controllare solo lo stato interno, ad esempio verificando che esista il file PID del processo o che un endpoint locale di health restituisca 200. Una probe di liveness fallita termina e riavvia il container.
- Probe di readiness — risponde alla domanda: questo pod deve ricevere traffico? Può controllare le dipendenze a valle. Una probe di readiness fallita rimuove il pod dal load balancer del Service, ma non lo riavvia.
Non inserisca mai controlli lenti delle dipendenze nelle probe di liveness. Una breve indisponibilità del database riavvierebbe erroneamente tutti i pod dell'applicazione, aggravando il problema.
#!/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 0Probe di startup e initContainers
Kubernetes offre un terzo tipo di probe: startupProbe. Viene eseguita al posto delle probe di liveness/readiness finché non ha esito positivo una volta, dando alle applicazioni che si avviano lentamente, ad esempio durante il warm-up della JVM o le migrazioni del database, il tempo di inizializzarsi senza causare falsi fallimenti delle probe di liveness.
Per attendere le dipendenze, gli initContainers sono spesso una soluzione più ordinata degli script di entrypoint. Vengono eseguiti prima dell'avvio dei container dell'applicazione e Kubernetes gestisce automaticamente la logica di retry e riavvio. L'immagine dell'init container necessita soltanto di sh, nc o curl; è possibile usare un'immagine minimale busybox o alpine.
Esempio di specifica initContainer in un manifest Pod:
# 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-secretsScript di health check con output JSON
I sistemi di produzione spesso aggregano lo stato di più sottosistemi e lo espongono come payload JSON strutturato. Questo payload viene utilizzato da load balancer, orchestratori e dashboard di monitoraggio.
Uno script Bash per il controllo dello stato può creare direttamente l'output JSON usando printf o jq. Il codice di uscita continua a pilotare l'automazione; il corpo JSON è destinato agli operatori e ai sistemi di monitoraggio.
Convenzione: restituire HTTP 200 con {"status":"ok"} quando il sistema è integro, e HTTP 503 con {"status":"degraded", "checks":{...}} quando non è disponibile. Lo script qui sotto è pensato per essere esposto tramite un wrapper HTTP leggero come socat o richiamato direttamente dalle probe exec di Kubernetes.
#!/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 1Utility timeout con il pattern della deadline
Il comando GNU timeout incapsula qualsiasi comando e lo termina se non si conclude entro la durata specificata. È il modo più pulito per imporre una deadline rigida a un ciclo di attesa o a una probe di health senza gestire manualmente i processi in background.
timeout DURATION COMMAND [ARGS...]
Codici di uscita restituiti da timeout:
- 0 — il comando è riuscito entro la deadline
- Codice di uscita del comando — il comando è stato eseguito ma ha restituito un valore diverso da zero
- 124 — il comando ha superato il timeout, con invio di SIGTERM
- 137 — il comando è stato terminato con SIGKILL, dopo
--kill-after
Rilevare il codice di uscita 124 consente di stampare un messaggio chiaro di timeout invece di un errore generico.
#!/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 "$@"wait-for autonomo in Bash puro
Molte immagini container minimali non includono nc (netcat). Bash puro può aprire connessioni TCP usando la sostituzione di processo tramite /dev/tcp, una funzionalità integrata di Bash non standard ma ampiamente supportata, che non richiede strumenti esterni.
Sintassi: /dev/tcp/HOST/PORT — Bash apre una connessione TCP quando si reindirizza verso o da questo percorso. Genera un errore, con uscita diversa da zero, se la connessione viene rifiutata o va in timeout.
Questa è la tecnica usata dal popolare script wait-for-it.sh, incluso in molte configurazioni Docker Compose. Lo script qui sotto è un'implementazione completa e autonoma che può essere copiata con COPY in qualsiasi 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
fiIntegrare le probe in entrypoint.sh
Il pattern dell'entrypoint è il modo standard per riunire in un unico script facile da gestire i cicli di attesa, la convalida dell'ambiente e l'avvio del processo. Docker richiama questo script tramite ENTRYPOINT e lo script termina con exec "$@" per passare il controllo a CMD mantenendo lo stesso PID, così da consentire il corretto inoltro dei segnali.
In genere, un entrypoint.sh di livello production-grade:
- convalida tempestivamente le variabili d'ambiente obbligatorie, seguendo il principio del fail fast
- esegue i cicli di attesa delle dipendenze
- esegue le migrazioni del database, se applicabile
- esegue un controllo finale dello stato
- trasferisce il controllo tramite
exec "$@"
Usare exec è fondamentale: sostituisce il processo della shell, così l'applicazione diventa PID 1 e riceve direttamente SIGTERM da Docker/Kubernetes durante lo shutdown ordinato.
#!/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 "$@"Verifica delle conoscenze: probe di liveness e readiness
Verifichi la propria comprensione dei concetti chiave trattati in questa lezione.
Riepilogo: health probe, readiness gate e cicli di attesa
In questa lezione ha costruito il toolkit Bash per coordinare in modo affidabile l'avvio dei container. Ecco gli argomenti trattati:
- Pattern wait-for — un ciclo
until nc -z HOST PORTcon timeout rigido e controllo del tempo trascorso evita il blocco infinito in caso di dipendenze guaste. - Probe HTTP di readiness —
curl --fail --max-timeverifica che un'applicazione sia realmente pronta, non soltanto che la porta sia aperta. - Backoff esponenziale — raddoppiare l'intervallo di attesa tra i retry riduce il carico da thundering herd e consente ai servizi in ripristino di stabilizzarsi.
- Controllo parallelo delle dipendenze — eseguire le probe in background con
&e raccogliere i risultati conwait $PIDriduce la latenza di avvio quando sono presenti più dipendenze. - Liveness e readiness — le probe di liveness devono essere rapide e limitate allo stato locale; le probe di readiness possono controllare i sistemi a valle. Non le confonda mai.
- startupProbe — protegge le applicazioni che si avviano lentamente dai falsi fallimenti delle probe di liveness durante l'inizializzazione.
- /dev/tcp probe — il controllo TCP in Bash puro non richiede strumenti esterni ed è ideale per le immagini container minimali.
- entrypoint.sh — la convalida dell'ambiente, i cicli di attesa, le migrazioni e
exec "$@"costituiscono il pattern standard di avvio dei servizi containerizzati.
Questi pattern costituiscono la base per deployment di container autoriparanti e di livello production-grade su Docker Compose, Kubernetes ed ECS.
Domande Frequenti
La lezione «Probe di salute, controlli di readiness e cicli di attesa» è gratuita?
Sì — il testo completo di «Probe di salute, controlli di readiness e cicli di attesa» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Linux Command Line & Bash Scripting Mastery, passa a CoddyKit PRO. Il corso Linux Command Line & Bash Scripting Mastery include 4 lezioni in totale.
Cosa imparerò in «Probe di salute, controlli di readiness e cicli di attesa»?
Implementi cicli di attesa per le dipendenze e probe di liveness che consentano l'avvio affidabile dei servizi containerizzati. Eserciti Linux Command Line & Bash Scripting Mastery con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare Linux Command Line & Bash Scripting Mastery?
Non è richiesta alcuna esperienza precedente. Linux Command Line & Bash Scripting Mastery su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.
Quanto tempo richiede la lezione «Probe di salute, controlli di readiness e cicli di attesa»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione Linux Command Line & Bash Scripting Mastery?
Sì. Ogni lezione Linux Command Line & Bash Scripting Mastery include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Scrivere Dockerfile essenziali ed entrypoint shell
- Creare template di configurazione con envsubst e heredoc
- Gestire risorse cloud tramite CLI e jq
- Probe di salute, controlli di readiness e cicli di attesa