Cloud

Kubernetes Produzione: Il Primo Deploy, Errori e Soluzioni (2026)

Kubernetes Produzione: Il Primo Deploy, Errori e Soluzioni (2026)

Venerdì pomeriggio. Kubernetes in produzione da 6 ore. 40 pod in CrashLoopBackOff. Il CEO che aspettava la demo. Il team che non capiva cosa stesse succedendo. Avevamo dimenticato di configurare le liveness probe.

Il primo deploy di un cluster Kubernetes in produzione è un rito di passaggio per ogni team DevOps o SysAdmin. È un momento di grande aspettativa, ma anche di potenziale disastro. Nel mio percorso professionale, ho guidato molte migrazioni complesse, ma pochi eventi sono rimasti impressi come il nostro primo vero approccio a Kubernetes in un ambiente enterprise. Quella giornata ci ha insegnato lezioni preziose, forgiando un approccio più robusto e consapevole all’orchestration di container. Se stai per affrontare il tuo primo deploy, o se hai già vissuto momenti simili, questa esperienza ti offrirà spunti pratici per evitare gli errori più comuni e stabilizzare rapidamente il tuo ambiente.

Prerequisiti / Ambiente di test

Il nostro scenario prevedeva la migrazione di una suite di applicazioni monolitiche, precedentemente gestite con Docker Compose, su un nuovo cluster Kubernetes. L’obiettivo era migliorare scalabilità, resilienza e velocizzare il processo di deploy continuo. L’ambiente era composto da 5 nodi worker e 3 nodi master, con storage Ceph e un load balancer esterno. Le applicazioni erano principalmente Java Spring Boot, Node.js e alcuni microservizi Python. Il piano di migrazione era ambizioso: tre settimane per portare tutto online, inclusi test di integrazione e performance.

Il contesto: migrazione da Docker Compose a Kubernetes in 3 settimane

La decisione di passare a Kubernetes era dettata dalla necessità di gestire un carico di lavoro crescente, con picchi imprevedibili, e di standardizzare l’infrastruttura per futuri sviluppi. Docker Compose aveva raggiunto i suoi limiti in termini di orchestrazione e auto-healing. La promessa di Kubernetes era la soluzione a molti dei nostri problemi operativi. Abbiamo dedicato settimane alla formazione del team, alla containerizzazione delle applicazioni e alla definizione dei manifest YAML. Il progetto era sulla buona strada, con un ambiente di staging che sembrava stabile e performante. La pressione era alta, data la timeline aggressiva e l’importanza strategica della migrazione per l’azienda. Un sondaggio di Cloud Native Computing Foundation (CNCF) del 2023 ha rivelato che il 96% delle organizzazioni utilizza o prevede di utilizzare Kubernetes, sottolineando la sua centralità, ma anche la complessità del primo approccio.

Ore 0-2: deploy andato bene, tutto sembra funzionare

Il giorno del deploy in produzione è iniziato con euforia. Dopo aver applicato i manifest, kubectl get pods mostrava tutti i pod in stato Running. I servizi rispondevano correttamente, il traffico veniva instradato senza problemi e le prime verifiche funzionali davano esito positivo. L’intero team tirò un sospiro di sollievo. Il CEO era stato informato del successo iniziale e la demo era stata fissata per il pomeriggio, un momento cruciale per mostrare il valore della nuova infrastruttura. Le metriche di base del cluster, come utilizzo CPU e memoria, sembravano nella norma. In quel momento, pensavamo di aver superato la fase più critica.

Ore 2-6: CrashLoopBackOff su metà dei pod, causa ignota

L’euforia durò poco. Circa due ore dopo il deploy, il monitoraggio iniziò a segnalare anomalie. Un numero crescente di pod entrava nello stato CrashLoopBackOff. Inizialmente uno o due, poi dieci, venti, fino a quasi la metà dei 80 pod totali. I log degli applicativi erano scarni o inesistenti. kubectl logs non aiutava molto, mostrando solo il riavvio continuo dei container. La situazione era critica: la demo del CEO era in procinto di iniziare e il servizio era parzialmente degradato, con un impatto diretto sugli utenti. La pressione si fece insostenibile.

Per indagare, il primo passo fu utilizzare kubectl describe per ottenere una panoramica dettagliata dello stato dei pod problematici:

kubectl describe pod <nome-del-pod>
grep -A20 Events

Questa output ci mostrò una serie di eventi Liveness probe failed e Restarting container. Non era un errore applicativo diretto, ma un problema di gestione del ciclo di vita del container da parte di Kubernetes.

L’errore: liveness probe con timeout troppo basso su applicazione Java

La root cause principale fu identificata nelle livenessProbe configurate in modo troppo aggressivo per le nostre applicazioni Java Spring Boot. Le applicazioni Java, specialmente all’avvio, richiedono un certo tempo per inizializzare il contesto Spring, caricare le dipendenze e aprire le connessioni al database. La nostra livenessProbe era configurata con un initialDelaySeconds troppo basso (es. 5 secondi) e un timeoutSeconds altrettanto restrittivo (es. 1 secondo). Questo faceva sì che Kubernetes considerasse il container non responsivo e lo riavviasse, ancora e ancora, prima che l’applicazione avesse la possibilità di avviarsi completamente. Si creava un circolo vizioso di riavvii continui che impediva ai pod di raggiungere uno stato Running stabile.

La soluzione immediata fu quella di aumentare significativamente initialDelaySeconds e timeoutSeconds per consentire alle applicazioni Java di completare il loro startup. Una configurazione più realistica per un’applicazione Java Spring Boot potrebbe essere:

livenessProbe:
  httpGet:
    path: /actuator/health
    port: 8080
  initialDelaySeconds: 60
  periodSeconds: 10
  failureThreshold: 3

Questo concede all’applicazione 60 secondi per avviarsi prima del primo controllo e permette fino a 3 fallimenti consecutivi prima di dichiarare il pod non sano, con un intervallo di 10 secondi tra un controllo e l’altro. Questo è un errore classico, ma facile da commettere sotto pressione. Il 30% degli incidenti in produzione su Kubernetes, secondo un report del 2024 di Datadog, è legato a configurazioni errate di probe.

Il secondo problema: nessun resource limit configurato

Mentre risolvevamo il problema delle liveness probe, emerse un’altra criticità. Alcuni nodi worker mostravano un utilizzo di memoria e CPU eccessivo, con improvvisi cali di performance. Ispezionando i pod su quei nodi, notammo che non avevamo configurato resources.limits nei manifest. Le nostre applicazioni, sebbene fossero state ottimizzate, non avevano alcun tetto massimo al consumo di risorse. In un ambiente di produzione, questo è un errore grave che può portare a instabilità e a effetti a cascata.

Senza resource limits, un’applicazione mal comportante (ad esempio, con un memory leak) può consumare tutte le risorse disponibili sul nodo, affamando gli altri pod e portando il nodo stesso a uno stato instabile. Kubernetes, in questi casi, interviene terminando i processi che superano le risorse disponibili, spesso con un OOMKilled (Out Of Memory Killed).

Ore 6-24: OOMKilled a cascata su tutto il nodo

La mancanza di resource limits e il problema iniziale delle liveness probe crearono una tempesta perfetta. Dopo aver corretto le probe, le applicazioni iniziarono ad avviarsi, ma senza limiti, alcune iniziarono a consumare più memoria del previsto, scatenando OOMKilled a cascata. Un nodo worker con 32GB di RAM si trovò con diversi pod Java che, senza limiti, tentarono di allocare più memoria del dovuto. Il kernel Linux intervenne, terminando i processi per proteggere il sistema operativo. Questo portò a ulteriori riavvii dei pod e, in alcuni casi, al blocco dell’intero nodo. Per verificare lo stato di OOMKilled di un pod, si può usare:

kubectl get pod <nome-del-pod> -o json | jq '.status.containerStatuses[].lastState'

Questo comando mostra l’ultimo stato del container, e se si trova la voce reason: OOMKilled, si ha la conferma del problema. Questo scenario è particolarmente insidioso perché può essere difficile da diagnosticare senza monitoraggio adeguato e senza una comprensione profonda di come Kubernetes e il kernel Linux gestiscono le risorse.

Come abbiamo recuperato e stabilizzato il cluster

Il recupero richiese un approccio multifase e coordinato:

  1. Correzione delle Liveness/Readiness Probe: Abbiamo applicato un patch a tutti i deployment per aggiornare le configurazioni delle probe, aumentando initialDelaySeconds e timeoutSeconds, specialmente per le applicazioni Java. Abbiamo anche introdotto readinessProbe separate per indicare quando l’applicazione era pronta a ricevere traffico, distinguendola dalla semplice ‘vita’ del pod. Questo ha ridotto drasticamente i CrashLoopBackOff.
  1. Impostazione dei Resource Limits: Abbiamo definito resources.requests e resources.limits per ogni container. Inizialmente, abbiamo optato per limiti conservativi basati sulle osservazioni in staging, con l’intenzione di affinarli in seguito con test di carico più precisi. Questo ha evitato che un singolo pod potesse affamare l’intero nodo.
  1. Scalabilità Orizzontale e Verticale: Abbiamo temporaneamente scalato il numero di nodi worker per distribuire meglio il carico e fornire un buffer di risorse. Allo stesso tempo, abbiamo aumentato le risorse (RAM/CPU) sui nodi esistenti per gestire meglio i picchi. Questo ci ha dato il tempo di analizzare e ottimizzare.
  1. Monitoraggio Avanzato: Abbiamo configurato dashboard di monitoraggio più granulari con Prometheus e Grafana, concentrandoci su metriche chiave come l’utilizzo di CPU, memoria, I/O disco per ogni pod e nodo, e gli stati dei pod. Questo ha permesso di identificare rapidamente le nuove anomalie.
  1. Comunicazione e Rollback Plan: Abbiamo comunicato costantemente con il CEO e stakeholder, spiegando la situazione e i passi per la risoluzione. Fortunatamente, avevamo un piano di rollback (seppur non testato a fondo) che ci avrebbe permesso di tornare a Docker Compose, ma siamo riusciti a stabilizzare il cluster prima di doverlo invocare.

Errori comuni e troubleshooting

Questo incidente ha evidenziato diversi errori comuni nel deploy di Kubernetes:

  • Liveness/Readiness Probe mal configurate: Troppo aggressive, non tengono conto dei tempi di avvio delle applicazioni (specialmente Java). La documentazione ufficiale di Kubernetes sulle probes è un ottimo punto di partenza per configurarle correttamente. Link esterno: Kubernetes Documentation on Probes
  • Mancanza di Resource Limits: Lasciare i container senza limiti di CPU e memoria è una ricetta per il disastro in produzione, portando a OOMKilled e instabilità del nodo.
  • Test di carico insufficienti: L’ambiente di staging non sempre replica fedelmente il carico e i pattern di traffico della produzione. È essenziale eseguire test di carico realistici.
  • Monitoraggio debole: Senza un monitoraggio robusto e alert tempestivi, è difficile diagnosticare e reagire rapidamente agli incidenti.
  • Piani di rollback non testati: Avere un piano è buono, testarlo è fondamentale. Non si può dare per scontato che funzioni sotto stress.

FAQ — Domande Frequenti

Qual è la differenza tra livenessProbe e readinessProbe?

La livenessProbe indica a Kubernetes se il container è “vivo” e funzionante. Se fallisce, Kubernetes riavvia il container. La readinessProbe, invece, indica se il container è pronto a servire traffico. Se fallisce, Kubernetes smette di inviare traffico a quel pod, ma non lo riavvia. Sono complementari per garantire sia la stabilità che la disponibilità del servizio.

Come posso impostare correttamente i resource limits per le mie applicazioni?

Il modo migliore è monitorare il consumo di risorse delle tue applicazioni in un ambiente di staging sotto carico tipico. Inizia con requests leggermente inferiori al consumo medio e limits leggermente superiori al consumo di picco rilevato. Affina i valori nel tempo, basandoti su metriche e test di stress. Un buon punto di partenza è impostare requests.cpu e requests.memory per garantire uno scheduling equo, e limits.memory per prevenire OOMKilled.

Cosa fare quando un pod è in CrashLoopBackOff?

La prima cosa è usare kubectl describe pod per vedere gli eventi recenti e kubectl logs per i log del container. Spesso la causa è un’applicazione che non si avvia correttamente, un crash interno, o una livenessProbe mal configurata. Controlla anche i log del controller manager o dell’kubelet per errori più a livello di sistema.

Come prevenire OOMKilled nei container?

Principalmente, definendo resources.limits.memory per ogni container. Assicurati che il limite sia sufficiente per l’applicazione, ma non così alto da permettere un consumo eccessivo. Monitora attentamente l’utilizzo della memoria e ottimizza le tue applicazioni per ridurre il loro footprint. Considera anche l’uso di requests.memory per garantire che il pod venga schedulato solo su nodi con memoria sufficiente.

Conclusioni con takeaway operativi

Il nostro primo deploy Kubernetes in produzione è stato un’esperienza ricca di apprendimenti, costellata di errori comuni ma risolvibili. La lezione più importante è che la teoria e la pratica possono divergere significativamente, specialmente in ambienti complessi. La definizione accurata di livenessProbe e readinessProbe, unita alla configurazione meticolosa dei resource limits, sono pilastri fondamentali per la stabilità di qualsiasi cluster Kubernetes. Non sottovalutare mai l’importanza di test di carico realistici e di un monitoraggio proattivo. Questo approccio reattivo ci ha permesso di stabilizzare un servizio critico in poche ore, imparando a gestire la pressione e a identificare rapidamente i colpevoli. Con una preparazione adeguata e una metodologia di troubleshooting robusta, è possibile trasformare un potenziale disastro in un’opportunità di crescita e miglioramento continuo.

5 cose da fare PRIMA del primo deploy in produzione

  1. Testare le Probe in Staging: Simula l’avvio delle tue applicazioni con le configurazioni di livenessProbe e readinessProbe che userai in produzione. Assicurati che i tempi di avvio siano gestiti correttamente.
  2. Definire Resource Limits Realistici: Non andare in produzione senza requests e limits per CPU e memoria. Monitora le tue applicazioni e imposta valori basati su dati reali.
  3. Eseguire Test di Carico e Stress: Non fidarti solo dei test funzionali. Simula il carico di produzione (e oltre) per vedere come il cluster reagisce e dove sono i colli di bottiglia.
  4. Configurare Monitoraggio e Alerting: Implementa un sistema di monitoraggio completo (es. Prometheus/Grafana) con alert configurati per stati critici dei pod, utilizzo risorse elevate e errori di sistema. Un buon monitoraggio riduce il MTTR (Mean Time To Resolution).
  5. Preparare un Piano di Rollback: Anche se speri di non usarlo, avere un piano dettagliato per tornare alla configurazione precedente (es. Docker Compose, o una versione precedente dei manifest) è cruciale. E, soprattutto, testalo!

Aggiornato: Luglio 2026

Leggi anche: Kubernetes Networking: DNS, Services, Ingress — Guida Pratica Sysadmin (2026)

Leggi anche: Proxmox Cluster HA: Checklist Completa Installazione e Verifica (2026)

Leggi anche: CI/CD Pipeline Sicura: Checklist 12 Punti per GitLab e GitHub Actions (2026)

Condividi questo articolo:

Scritto da

Rosario Giordano

Rosario Giordano è System Administrator e consulente IT specializzato in cybersecurity e cloud, con oltre 20 anni di esperienza nella gestione di infrastrutture Linux enterprise. Le sue aree di competenza includono hardening di SSH, piattaforme Kubernetes, database PostgreSQL, ambient i virtualizzati VMware e Proxmox, nonché la conformità ai framework di sicurezza NIS2 e ISO 27001.