+39 3662317539/+34 620899163
info@simonecosci.com

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

Sviluppo Siti Web a Tenerife

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 × periodSeconds abbastanza
    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 la restartPolicy.
  • 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.

  1. Il database risponde lento. L’endpoint di health di tutti i Pod va in timeout nello stesso
    momento, perché dipendono tutti dallo stesso database.
  2. Dopo failureThreshold fallimenti, il kubelet riavvia tutti i container.
  3. Il riavvio non cura il database. In compenso ogni container che riparte riapre le connessioni,
    proprio mentre il database è già in difficoltà.
  4. 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 initialDelaySeconds a 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
    servono initialDelaySeconds o una startup probe.
  • Mettere successThreshold diverso da 1 su liveness o startup: deve valere 1, ha senso solo sulla
    readiness.
  • Probe exec pesanti 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
    httpGet su 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.