Ghid de Lansare cu Zero Downtime: Rolling Updates, Liveness, Readiness & Startup Probes și Graceful Shutdown în Kubernetes (kubectl)

Deployment-uri și scalare Kubernetes 1.25+ Producție

Ghid cuprinzător pentru lansări fără nicio secundă de indisponibilitate în Kubernetes: strategii de RollingUpdate (maxSurge, maxUnavailable), coordonarea sondelor de sănătate, gestionarea semnalului SIGTERM și hook-ul preStop.

Sintaxă comandă
spec.strategy.rollingUpdate: maxSurge: 25%, maxUnavailable: 0 | lifecycle.preStop: sleep 10

Deși Kubernetes promovează conceptul de lansări fără întrerupere (Zero-Downtime Rolling Updates), o configurare incompletă sau superficială a manifestelor de Deployment este una dintre cele mai frecvente cauze ale erorilor 502 Bad Gateway și 504 Gateway Timeout resimțite de utilizatori în timpul lansărilor de versiuni noi. Obținerea unei tranziții 100% invizibile necesită sincronizarea perfectă a trei mecanisme: parametrii de rolling update, sondele de sănătate și ciclul de viață Graceful Shutdown.

1. Parametrii Esențiali ai Strategiei RollingUpdate

În mod implicit, Kubernetes poate opri până la 25% din Pod-urile vechi înainte ca noile Pod-uri să fie gata de lucru. Pentru o disponibilitate absolută în producție, nu permiteți niciunui Pod sănătos să fie oprit prematur:

Configurația de aur pentru RollingUpdate
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      # maxSurge: Permite crearea temporară a Pod-urilor noi peste numărul dorit:
      maxSurge: 25%
      # maxUnavailable: Garantează că ZERO Pod-uri sănătoase sunt oprite până când cele noi devin Ready:
      maxUnavailable: 0

2. De ce apar erori 502/504 la oprirea unui Pod? Cursa Asincronă

Când o versiune nouă este lansată, Kubernetes trimite semnalul de oprire către Pod-ul vechi. În același timp, Control Plane-ul trebuie să actualizeze resursele EndpointSlice și regulile iptables/ipvs pe toate nodurile din cluster pentru a opri trimiterea traficului. Această propagare de rețea durează între 2 și 5 secunde. Dacă aplicația voastră primește semnalul SIGTERM și își închide imediat socket-ul de ascultare HTTP, cererile utilizatorilor care sunt încă pe fir vor lovi un port închis, generând erori 502!

3. Soluția de Aur: Hook-ul lifecycle.preStop cu Sleep

Adăugarea unei scurte pauze (delay) înainte de trimiterea semnalului SIGTERM garantează că toate routerele Ingress și proxy-urile interne au eliminat Pod-ul vechi din rute înainte ca aplicația să înceapă oprirea conexiunilor:

Manifest complet de producție cu preStop și Probes
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-productie
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: api-productie
    spec:
      # Oferim aplicației până la 60 de secunde pentru a procesa cererile în curs:
      terminationGracePeriodSeconds: 60
      containers:
        - name: api
          image: my-repo/api:v2.4.1
          ports:
            - containerPort: 8080
          lifecycle:
            preStop:
              exec:
                # 1. Așteaptă 10 secunde ca EndpointSlice-ul să fie actualizat în tot clusterul:
                command: ["/bin/sh", "-c", "sleep 10"]
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 2
            periodSeconds: 5
            failureThreshold: 2
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            periodSeconds: 10
            failureThreshold: 3

4. Tratarea Semnalului SIGTERM în Codul Aplicației (Node.js / Go / Python)

După expirarea perioadei preStop, nucleul trimite semnalul SIGTERM. Codul aplicației trebuie să intercepteze acest semnal, să refuze conexiuni noi, să finalizeze cererile HTTP active și să închidă conexiunile cu baza de date:

// Exemplu Node.js:
process.on('SIGTERM', () => {
  console.log('Semnal SIGTERM primit! Închidere conexiuni active...');
  server.close(() => {
    console.log('Server HTTP oprit. Închidere conexiune PostgreSQL...');
    dbPool.end(() => {
      console.log('Oprire curată finalizată. Ieșire.');
      process.exit(0);
    });
  });
});