Kubernetes: liveness, readiness e startup probe, e cosa succede se le confondi

In un manifest Kubernetes le tre probe si scrivono quasi allo stesso modo: livenessProbe,
readinessProbe, startupProbe, ognuna con un httpGet o un exec e due numeri sotto. Proprio per
questo si confondono. Ma quando falliscono, il kubelet fa tre cose completamente diverse, e scegliere
quella sbagliata trasforma un rallentamento in un’interruzione di servizio.
Il problema: stessa sintassi, conseguenze opposte
La documentazione di Kubernetes lo dice in una riga, nella sezione sui risultati delle probe: in caso di
Failure, “for liveness and startup probes, the kubelet kills the container, and the container is
subjected to its restart policy. For readiness probes, the kubelet marks the container as not ready,
and the Pod stops receiving traffic from matching Services.”
Quindi:
- liveness fallita → il container viene ucciso e riavviato;
- readiness fallita → il container resta acceso, ma il Pod esce dai Service e non riceve più
traffico; - startup fallita → come la liveness: il container viene ucciso.
Una riavvia, l’altra mette da parte. Sono due risposte a due domande diverse.
A cosa serve ognuna
Liveness: “il processo è bloccato senza speranza?” Serve per i casi in cui l’applicazione è in
esecuzione ma non va avanti, l’esempio della documentazione è un deadlock. Il riavvio è la cura.
C’è anche il caso opposto, che la documentazione segnala: se il processo, quando qualcosa va storto,
termina da solo, la liveness probe non serve necessariamente, perché ci pensa già la restartPolicy
del Pod.
Readiness: “adesso posso ricevere richieste?” Serve quando l’applicazione è viva ma
temporaneamente non in grado di rispondere: sta caricando dati o configurazione, scalda una cache,
è sovraccarica. Non si vuole ucciderla, si vuole solo non mandarle traffico. La readiness gira per
tutta la vita del container, non solo all’avvio: un Pod può uscire dal Service e rientrarci più
volte.
Startup: “ha finito di avviarsi?” Serve alle applicazioni lente a partire. Finché la startup
probe non ha successo, Kubernetes non esegue né la liveness né la readiness. Appena ha successo
una volta, smette di girare e passano in campo le altre due.
La soluzione in un manifest
Un esempio per un’applicazione web che espone due endpoint distinti: /healthz, che risponde “il
processo è vivo”, e /ready, che risponde “posso servire richieste”.
containers:
- name: app
image: registry.example.com/app:1.4.2
ports:
- name: http
containerPort: 8080
startupProbe:
httpGet:
path: /healthz
port: http
failureThreshold: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: http
periodSeconds: 5
failureThreshold: 3
Cosa succede, numeri alla mano:
- La startup usa lo stesso controllo della liveness, con
failureThreshold × periodSecondsabbastanza
largo da coprire l’avvio peggiore: qui 30 × 10 = 300 secondi. È lo schema suggerito dalla
documentazione. Se in cinque minuti l’applicazione non risponde, il container viene ucciso e si
applica larestartPolicy. - Dopo l’avvio, la liveness riavvia il container dopo 3 fallimenti consecutivi a 10 secondi di
distanza, cioè circa trenta secondi di blocco. - La readiness toglie il Pod dal Service dopo 3 fallimenti a 5 secondi di distanza, e lo rimette
quando torna a rispondere.
Per un controllo HTTP, qualsiasi codice da 200 a 399 vale come successo, tutto il resto come
fallimento. Il valore di default di timeoutSeconds è 1 secondo: se l’endpoint ci mette di più, la
probe fallisce anche se la risposta sarebbe stata giusta.
L’errore classico: la liveness che controlla il database
Lo schema che si vede più spesso è un unico /health che verifica tutto, database compreso, usato
sia per la readiness sia per la liveness. Sembra prudente. Seguiamolo quando il database rallenta.
- Il database risponde lento. L’endpoint di health di tutti i Pod va in timeout nello stesso
momento, perché dipendono tutti dallo stesso database. - Dopo
failureThresholdfallimenti, il kubelet riavvia tutti i container. - Il riavvio non cura il database. In compenso ogni container che riparte riapre le connessioni,
proprio mentre il database è già in difficoltà. - Nel frattempo le richieste degli utenti cadono, perché i container si stanno riavviando.
La documentazione lo mette tra gli avvertimenti: le liveness probe vanno configurate in modo che
indichino davvero un guasto non recuperabile dell’applicazione, e un’implementazione sbagliata “can
lead to cascading failures”: riavvii sotto carico, richieste fallite, più lavoro sui Pod rimasti.
La regola pratica che ne viene fuori:
- la liveness guarda solo il processo stesso: risponde, il thread principale non è bloccato.
Niente database, niente servizi esterni. Se il problema è fuori dal container, riavviare il
container non serve; - le dipendenze, se proprio si vogliono controllare, stanno nella readiness, che toglie traffico
senza uccidere niente. Con un limite da tenere presente: se il database cade, tutti i Pod diventano
not ready insieme e il Service resta senza endpoint. A volte è quello che si vuole, a volte è meglio
che l’applicazione resti raggiungibile e risponda lei con un errore chiaro. È una scelta da fare
consapevolmente, non un default.
Cosa evitare
- Usare la liveness per gestire un avvio lento alzando
initialDelaySecondsa valori enormi.
Funziona, ma se l’applicazione si blocca subito dopo l’avvio ve ne accorgete tardi. La startup probe
esiste esattamente per questo. - Pensare che liveness e readiness si aspettino a vicenda. La documentazione è esplicita: non
dipendono l’una dall’altra. La liveness non aspetta che la readiness abbia successo; per ritardarla
servonoinitialDelaySecondso una startup probe. - Mettere
successThresholddiverso da 1 su liveness o startup: deve valere 1, ha senso solo sulla
readiness. - Probe
execpesanti e frequenti. Ogni esecuzione crea nuovi processi nel container; la
documentazione avverte che su molti Pod questo pesa sulla CPU del nodo. Per un’applicazione web, un
httpGetsu un endpoint leggero è quasi sempre la scelta giusta. - Copiare la stessa probe nei tre campi senza pensarci. Startup e liveness possono legittimamente
condividere il controllo; la readiness risponde a una domanda diversa e spesso merita un endpoint
suo.
Le tre probe non sono tre livelli di severità dello stesso controllo. Sono tre strumenti: uno per
riavviare, uno per mettere da parte, uno per aspettare. Scegliete in base a cosa volete che succeda
quando falliscono, non in base a cosa volete controllare.
Fonti: kubernetes.io, Liveness, Readiness and Startup Probes,
Configure Liveness, Readiness and Startup Probes
e Pod Lifecycle.
Se avete un’applicazione da portare su Kubernetes o un cluster che si riavvia senza un motivo
chiaro, scriveteci.
