Tanquem el mòdul 6 amb un diagnòstic incòmode: la plataforma Rutas Norte ja és potent —té estat, és extensible i està automatitzada— però és completament opaca. No sabem si api-reserves està realment sana, ni quanta memòria consumeix de debò postgres-reserves, ni què va passar ahir a la nit a les tres de la matinada. El mòdul 7 es dedica a obrir aquesta caixa negra, i el primer pas no és mesurar: és ensenyar a Kubernetes a distingir un contenidor viu d'una aplicació que funciona.

Aquesta lliçó tracta de les sondes (probes): els tres mecanismes amb què Kubernetes pregunta periòdicament a cada contenidor si està viu, si està preparat per rebre trànsit i si ha acabat d'arrencar. Són la peça que faltava des del mòdul 2: allà vam deixar dit explícitament que un desplegament sense tall de servei no és possible sense sondes, i aquí saldem aquest deute. També són, de llarg, la funcionalitat de Kubernetes que més incidents de producció causa quan es configura malament: una livenessProbe massa agressiva pot tombar una plataforma sencera en pocs minuts. Anem a entendre-les a fons.

Contingut

  1. El problema: "el procés és viu" no vol dir "l'aplicació funciona"
  2. Les tres sondes i què fa Kubernetes amb cada resultat
  3. Els quatre manejadors: httpGet, tcpSocket, exec i grpc
  4. Els paràmetres temporals i el càlcul exacte del temps de detecció
  5. L'error més car del mòdul: la liveness en cascada
  6. startupProbe: la solució als arrencades lentes de postgres-reserves
  7. Sondes i desplegaments sense tall: tancant el deute de 02-04
  8. El tancament net: terminationGracePeriodSeconds, preStop i els 502
  9. Disseny de les sondes de cada component de Rutas Norte
  10. Errors comuns i consells
  11. Exercicis

  1. El problema: "el procés és viu" no vol dir "l'aplicació funciona"

Fins ara, l'única senyal de salut que Kubernetes tenia dels nostres contenidors era brutalment simple: continua executant-se el procés principal (PID 1)? Si el procés acaba, el kubelet aplica la restartPolicy que vam estudiar a 02-01 i reinicia el contenidor. Si el procés no acaba, Kubernetes assumeix que tot va bé.

Aquesta suposició és falsa la major part del temps. Vegem el cas real d'api-reserves.

api-reserves és una API Node.js que manté un pool de connexions contra postgres-reserves, amb un màxim de 20 connexions. Un dimarts a la tarda, una consulta mal indexada de la pantalla d'"històric de viatges" comença a trigar 40 segons. Els usuaris recarreguen. En dos minuts, les 20 connexions del pool estan ocupades per consultes lentes. A partir d'aquí:

  • El procés Node.js continua viu: el PID 1 existeix, no ha llançat cap excepció no capturada.
  • El servidor HTTP continua acceptant connexions: el sòcol està obert.
  • Però tota petició que necessiti la base de dades es queda esperant un lloc lliure al pool i acaba fent timeout.

Des del punt de vista de Kubernetes, el pod està perfecte. Des del punt de vista del client que vol comprar un bitllet Bilbao–Santander, la plataforma està caiguda. I el pitjor: el Service api-reserves continua enviant trànsit a aquest pod, i als altres quatre que estan exactament igual.

flowchart LR
    C[Client] --> S[Service api-reserves]
    S --> P1[Pod 1<br/>procés viu<br/>pool exhaurit]
    S --> P2[Pod 2<br/>procés viu<br/>pool exhaurit]
    S --> P3[Pod 3<br/>procés viu<br/>pool exhaurit]
    P1 -.timeout.-> DB[(postgres-reserves)]
    P2 -.timeout.-> DB
    P3 -.timeout.-> DB

Les sondes existeixen precisament per tancar aquesta escletxa. Donen a l'aplicació l'oportunitat de respondre per si mateixa a dues preguntes diferents:

  • Estic tan trencada que el millor és que em reiniciïs?livenessProbe.
  • Puc atendre peticions ara mateix?readinessProbe.

Són preguntes diferents, amb conseqüències diferents, i confondre-les és l'origen de gairebé tots els desastres d'aquesta lliçó.

  1. Les tres sondes i què fa Kubernetes amb cada resultat

Kubernetes defineix tres sondes per contenidor. Totes són opcionals i totes es declaren dins de l'especificació del contenidor, no del pod.

livenessProbe — continues viu?

Comprova si el contenidor està en un estat del qual ja no es pot recuperar tot sol. Si falla el nombre de vegades configurat, el kubelet mata el contenidor i aplica la restartPolicy (normalment Always, així que el reinicia). El pod no es recrea: és el mateix pod, amb el mateix nom i la mateixa IP, amb el seu comptador RESTARTS incrementat.

Casos legítims: un interbloqueig (deadlock) del qual el procés no surt, un bucle infinit que no respon, una fuita de memòria coneguda sense arranjament a curt termini. Si la teva aplicació no té cap estat del qual només se surti reiniciant, és perfectament vàlid no posar livenessProbe.

readinessProbe — pots atendre trànsit?

Comprova si el contenidor pot servir peticions ara mateix. Si falla, Kubernetes no reinicia res: simplement marca el pod com a no llest (Ready=False) i el controlador d'endpoints retira la seva adreça IP de l'EndpointSlice del Service. Deixa d'arribar-li trànsit, però continua viu. Quan la sonda torna a passar, la IP s'hi reincorpora automàticament.

Aquest és el mecanisme correcte per al cas del pool exhaurit: el pod s'aparta un moment, deixa de rebre peticions que no pot atendre, drena les seves consultes lentes i torna.

startupProbe — has acabat d'arrencar?

És una sonda d'arrencada. Mentre s'està executant i no ha passat, les altres dues sondes queden deshabilitades. Quan passa per primera vegada, deixa d'executar-se per sempre i liveness i readiness entren en joc. Si no passa mai dins del seu pressupost de temps, el contenidor es mata i es reinicia.

Serveix per a aplicacions amb arrencades lentes o de durada molt variable, on posar un initialDelaySeconds enorme a la liveness faria que la detecció d'errors reals fos lentíssima la resta de la vida del contenidor.

Taula comparativa

Aspecte livenessProbe readinessProbe startupProbe
Pregunta que respon T'he de reiniciar? Et mano trànsit? Has acabat d'arrencar?
Acció en fallar Mata i reinicia el contenidor Treu la IP dels Endpoints Mata i reinicia el contenidor
Acció en passar Res (continua vigilant) Torna la IP als Endpoints Es desactiva per sempre
Quan s'executa? Tota la vida del contenidor Tota la vida del contenidor Només fins al primer èxit
Pot comprovar dependències externes? Mai Sí, i ho ha de fer Només el mínim per arrencar
Afecta el Service? No directament Sí, directament Indirectament (bloqueja readiness)
És obligatòria? No Pràcticament sí, si hi ha Service Només si l'arrencada és lenta
Risc de mal ús Molt alt (reinicis en cascada) Baix Baix

Una regla que convé memoritzar: la liveness protegeix el contenidor d'ell mateix; la readiness protegeix els clients del contenidor.

  1. Els quatre manejadors: httpGet, tcpSocket, exec i grpc

Cada sonda utilitza exactament un d'aquests quatre mecanismes per fer la comprovació.

httpGet

El més habitual i el més recomanable per a serveis HTTP. El kubelet fa una petició GET a la IP del pod, al port i la ruta indicats.

livenessProbe:
  httpGet:
    path: /salut                 # ruta que atendrà api-reserves
    port: 8080                   # número o nom de port declarat a ports
    scheme: HTTP                 # HTTP (per defecte) o HTTPS
    httpHeaders:
      - name: X-Origen-Sonda
        value: kubelet

Punts importants per a principiants:

  • Es considera èxit qualsevol codi de resposta entre 200 i 399, tots dos inclosos. Un 204 és èxit. Un 301 també, i això sorprèn: si la teva aplicació redirigeix /salut a /login, la sonda passarà encara que l'aplicació estigui trencada. Un 400, 404, 500 o 503 és error.
  • La petició la fa el kubelet del node, no un altre pod. Per això les NetworkPolicies de rutas-norte-pro no la bloquegen: no passa per la xarxa de pods normal.
  • El camp port admet el nom d'un port declarat a ports, cosa que és molt més llegible i resistent a canvis.
  • Les capçaleres httpHeaders serveixen per a dues coses molt pràctiques: identificar als logs de l'aplicació quines peticions vénen del kubelet (i així poder excloure-les de les mètriques de trànsit) i passar capçaleres que l'aplicació exigeixi, com un Host concret.
  • El kubelet no envia galetes ni segueix cap autenticació: l'endpoint de salut ha de ser accessible sense credencials des de dins del pod.

tcpSocket

El kubelet intenta obrir una connexió TCP al port indicat. Si l'handshake es completa, és èxit; si la connexió es rebutja o expira, és error.

readinessProbe:
  tcpSocket:
    port: 5432

És l'opció per a serveis que no parlen HTTP, com redis-cache o el mateix PostgreSQL a baix nivell. La seva gran limitació: només comprova que hi ha alguna cosa escoltant. PostgreSQL pot tenir el port obert i estar rebutjant connexions per haver arribat a max_connections, o estar en mode recuperació. La sonda TCP passaria igualment.

exec

El kubelet executa una ordre dins del contenidor. Èxit si el codi de sortida és 0, error en qualsevol altre cas.

readinessProbe:
  exec:
    command:
      - /bin/sh
      - -c
      - pg_isready -U rutasnorte -d reserves -h 127.0.0.1

És el més flexible i el més car. Cada execució implica crear un procés nou dins del contenidor: consumeix CPU i memòria del pod (comptabilitzats contra els seus limits, cosa que pot provocar un OOMKilled si vas just) i afegeix càrrega al runtime del node. Amb periodSeconds: 5 en 60 pods estàs llançant 12 processos per segon al clúster només per preguntar com estan.

Consells:

  • Fes servir exec només quan no hi hagi alternativa HTTP o TCP.
  • Puja el periodSeconds (10-30 s) respecte al que faries servir amb httpGet.
  • El binari ha d'existir a la imatge. És un error habitual escriure una sonda amb curl en una imatge distroless que no en té: la sonda falla sempre i el contenidor entra en CrashLoopBackOff.

grpc

Des de Kubernetes 1.24 és una funcionalitat estable. El kubelet actua com a client del protocol estàndard de health checking de gRPC.

livenessProbe:
  grpc:
    port: 9000
    service: reserves.Disponibilitat   # opcional: nom del servei a consultar

L'aplicació ha d'implementar el servei grpc.health.v1.Health. És èxit si respon SERVING. Evita haver d'instal·lar grpc_health_probe com a binari a la imatge, que era la solució anterior amb exec.

Comparativa de manejadors

Manejador Quan fer-lo servir Cost Risc principal
httpGet Serveis HTTP/REST (api-reserves, botiga-web) Molt baix Els 3xx compten com a èxit
tcpSocket Serveis TCP sense HTTP (redis-cache) Molt baix Superficial: només mira el sòcol
exec Comprovacions que exigeixen lògica local (pg_isready) Alt Consum de recursos i binaris absents
grpc Serveis gRPC Baix Requereix implementar el servei de salut

  1. Els paràmetres temporals i el càlcul exacte del temps de detecció

Les cinc variables temporals són idèntiques per a les tres sondes. Entendre-les de debò significa poder respondre a la pregunta "quant triga Kubernetes a adonar-se que aquest contenidor és mort?" amb un número, no amb una intuïció.

Paràmetre Per defecte Mínim Significat
initialDelaySeconds 0 0 Segons d'espera des que arrenca el contenidor fins a la primera comprovació
periodSeconds 10 1 Cada quants segons es repeteix la comprovació
timeoutSeconds 1 1 Segons que s'espera la resposta abans de comptar-la com a error
successThreshold 1 1 Èxits consecutius per passar d'error a èxit
failureThreshold 3 1 Errors consecutius per donar la sonda per fallida

Dos matisos que s'obliden sempre:

  • successThreshold ha de valer 1 obligatòriament a livenessProbe i startupProbe. Només la readinessProbe admet valors més grans. Té sentit: no pots "reiniciar a mitges".
  • timeoutSeconds: 1 (el valor per defecte) és perillosament baix per a una aplicació real sota càrrega. Un endpoint de salut que normalment triga 20 ms pot trigar 1,5 s quan el node està saturat, i llavors la sonda falla per timeout encara que l'aplicació estigui bé.

El càlcul

El temps màxim des que un contenidor deixa de respondre fins que el kubelet el mata és:

T_deteccio = initialDelaySeconds (només la primera vegada)
           + (failureThreshold - 1) * periodSeconds
           + timeoutSeconds

Més un marge de fins a periodSeconds perquè l'error pot passar just després d'una comprovació correcta. A la pràctica es fa servir la fórmula pessimista:

T_pitjor = failureThreshold * periodSeconds + timeoutSeconds

Exemple amb la configuració que farem servir a api-reserves:

livenessProbe:
  httpGet:
    path: /salut
    port: http
  periodSeconds: 10
  timeoutSeconds: 3
  failureThreshold: 3
T_pitjor = 3 * 10 + 3 = 33 segons

És a dir: si api-reserves es penja, passaran fins a 33 segons fins que el kubelet el reiniciï, i després caldrà sumar el temps d'arrencada del contenidor. Si això és inacceptable per al negoci, baixes periodSeconds a 5 i failureThreshold a 3 → 18 segons. Però com més agressiva la sonda, més gran el risc de falsos positius, i aquest risc és el tema de l'apartat següent.

I per a la readiness, el mateix càlcul determina quant de temps continuarà arribant trànsit a un pod que ja no el pot atendre:

readinessProbe:
  httpGet:
    path: /preparat
    port: http
  periodSeconds: 5
  timeoutSeconds: 2
  failureThreshold: 2
T_pitjor = 2 * 5 + 2 = 12 segons de trànsit enviat a un pod trencat

Regla pràctica: la readiness ha de ser més ràpida i més sensible que la liveness. Apartar un pod és una acció barata i reversible; reiniciar-lo, no.

  1. L'error més car del mòdul: la liveness en cascada

Aquest és l'apartat que cal llegir dues vegades. És l'error que més vegades ha tombat plataformes senceres de producció, i sempre pel mateix motiu.

L'escenari

Un enginyer de Rutas Norte, amb la millor intenció, configura així la liveness d'api-reserves:

# INCORRECTE — no ho copieu
livenessProbe:
  httpGet:
    path: /salut/complet     # comprova API + PostgreSQL + Redis + passarel·la de pagaments
    port: 8080
  periodSeconds: 5
  timeoutSeconds: 1
  failureThreshold: 2

L'endpoint /salut/complet fa un SELECT 1 contra postgres-reserves, un PING a redis-cache i una crida a pagos.proveedorexterno.example. Sembla completíssim. És una bomba.

Arriba un pont de maig. El trànsit es multiplica per sis. postgres-reserves comença a respondre en 1,2 s en lloc de en 5 ms. Aleshores:

  1. La sonda de salut fa timeout (1 s) en els deu pods d'api-reserves alhora, perquè tots depenen de la mateixa base de dades.
  2. Als 10 segons (2 * 5), el kubelet mata els deu contenidors simultàniament.
  3. Els deu arrenquen de nou. En arrencar, cadascun obre el seu pool de 20 connexions contra PostgreSQL: 200 connexions noves de cop contra una base de dades que ja anava ofegada.
  4. PostgreSQL se satura encara més. Les sondes tornen a fallar. Tornem al pas 2.
  5. Kubernetes aplica backoff exponencial als reinicis, així que els pods entren en CrashLoopBackOff. La plataforma passa de lenta a totalment caiguda.
flowchart TD
    A[Pic de trànsit] --> B[postgres-reserves respon lent]
    B --> C[La liveness comprova PostgreSQL i fa timeout]
    C --> D[kubelet mata els 10 pods d'api-reserves]
    D --> E[10 pods arrenquen alhora i obren 200 connexions]
    E --> B
    D --> F[CrashLoopBackOff: caiguda total]

Fixa't en l'essencial: la base de dades estava lenta, no caiguda. Sense sondes, la plataforma hauria anat lenta durant vint minuts i s'hauria recuperat sola. Amb aquella liveness, va caure del tot. La sonda va amplificar l'error en lloc de mitigar-lo.

Les dues regles d'or

Regla 1: la livenessProbe MAI ha de comprovar dependències externes. Ni bases de dades, ni cachés, ni APIs de tercers, ni altres microserveis. Només ha de respondre a "aquest procés, aïllat, continua sent capaç de processar una petició?".

Regla 2: la readinessProbe SÍ ha de comprovar les dependències que necessita per servir. Si api-reserves no pot parlar amb PostgreSQL, no pot servir reserves, i el correcte és que surti dels Endpoints fins que pugui.

I què passa si la readiness falla als deu pods alhora? Que el Service es queda sense Endpoints i l'Ingress retorna 503. És dolent, però és honest i reversible: tan bon punt PostgreSQL es recupera, els deu pods tornen a estar llestos en cinc segons, sense arrencades en fred, sense allau de connexions, sense CrashLoopBackOff. La diferència entre una degradació de servei i un desastre.

Per això api-reserves tindrà dos endpoints diferents:

Endpoint Qui l'utilitza Què comprova Què NO comprova
/salut livenessProbe El bucle d'esdeveniments de Node respon; no hi ha deadlock; hi ha memòria per respondre PostgreSQL, Redis, passarel·la de pagaments
/preparat readinessProbe Tot l'anterior més una connexió lliure al pool de PostgreSQL i PING a redis-cache La passarel·la de pagaments externa (vegeu la nota)

Nota sobre la passarel·la de pagaments: pagos.proveedorexterno.example és un tercer fora del nostre control. Si el fiquem a la readiness, una caiguda del proveïdor deixa tota la plataforma sense Endpoints, inclosa la consulta d'horaris, que no necessita pagaments. La decisió correcta és no incloure'l en cap sonda i gestionar-lo amb un circuit breaker dins de l'aplicació que retorni un error concret només al flux de pagament.

  1. startupProbe: la solució a les arrencades lentes de postgres-reserves

postgres-reserves, després del creixement de l'últim any, triga entre 20 i 180 segons a acceptar connexions quan arrenca: si el tancament anterior va ser brut, ha de reproduir el WAL, i aquest temps depèn del volum pendent.

Sense startupProbe, tenim dues males opcions:

  • Opció A: initialDelaySeconds: 200 a la liveness. Cobreix el pitjor cas, però durant els 200 primers segons de cada reinici ningú vigila el contenidor. I pitjor: no pots baixar el període de detecció després.
  • Opció B: failureThreshold: 40 amb periodSeconds: 5. Cobreix l'arrencada, però també significa que, ja en règim, una penjada trigarà 40 * 5 = 200 segons a detectar-se.

La startupProbe separa els dos pressupostos de temps: un de generós per arrencar, un altre d'estricte per a la vida en règim.

# Fragment del contenidor postgres del StatefulSet postgres-reserves
startupProbe:
  exec:
    command: ["/bin/sh", "-c", "pg_isready -U rutasnorte -h 127.0.0.1"]
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 30        # 30 * 10 = 300 s de pressupost d'arrencada
livenessProbe:
  exec:
    command: ["/bin/sh", "-c", "pg_isready -U rutasnorte -h 127.0.0.1"]
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3         # ja en règim: 35 s per detectar una penjada
readinessProbe:
  exec:
    command: ["/bin/sh", "-c", "pg_isready -U rutasnorte -h 127.0.0.1 && psql -U rutasnorte -d reserves -c 'SELECT 1' -tA"]
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 2

Com es comporta això en el temps:

sequenceDiagram
    participant K as kubelet
    participant C as contenidor postgres
    K->>C: t=10s startupProbe → error (recuperant WAL)
    K->>C: t=20s startupProbe → error
    Note over K: liveness i readiness NO s'executen
    K->>C: t=90s startupProbe → ÈXIT
    Note over K: startupProbe es desactiva per sempre
    K->>C: t=100s livenessProbe → èxit
    K->>C: t=100s readinessProbe → èxit
    Note over K: el pod entra als Endpoints del Service headless

Si als 300 segons la startupProbe continua fallant, el contenidor es mata i es reinicia. És el comportament correcte: alguna cosa va realment malament.

Detall important per a l'exec: pg_isready i psql existeixen a la imatge postgres:16.4, i ens connectem a 127.0.0.1 (dins del mateix pod), no al Service. Comprovar el Service des de la sonda d'un pod seria comprovar una dependència externa, amb tots els problemes de l'apartat 5.

  1. Sondes i desplegaments sense tall: tancant el deute de 02-04

A 02-04 vam veure RollingUpdate amb maxSurge i maxUnavailable, i vam deixar escrit que allò encara no era un desplegament sense tall. Ara s'entén per què.

El controlador de Deployment considera que un pod nou "està bé" quan el pod és Ready. Sense readinessProbe, un pod es considera Ready tan bon punt els seus contenidors arrenquen. Per a api-reserves, això passa uns 400 ms després de llançar node, molt abans que Express escolti al 8080 i que el pool de connexions estigui obert.

Seqüència del desastre silenciós:

  1. kubectl apply amb la nova imatge.
  2. Es crea el pod nou. Als 400 ms és Running i, sense readiness, Ready.
  3. El controlador d'endpoints afegeix la seva IP a l'EndpointSlice: comença a rebre trànsit real.
  4. El Deployment, en veure un pod nou llest, acaba un pod antic.
  5. Durant els següents 3-8 segons, el pod nou retorna ECONNREFUSED a tot el trànsit que li arriba.
  6. Es repeteix pod a pod. El resultat: uns segons d'errors per cada rèplica substituïda, invisibles a kubectl get pods, molt visibles a la taxa d'errors.

Amb readinessProbe, el pas 2 canvia: el pod és Running però Ready=False, no entra als Endpoints i el Deployment no avança fins que passi la sonda. Aquest és, literalment, el mecanisme del desplegament sense tall.

minReadySeconds

Hi ha un cas residual: aplicacions que passen la readiness i cauen tres segons després (per exemple, en rebre la primera petició real que toca un codi nou defectuós). minReadySeconds obliga el Deployment a esperar N segons amb el pod contínuament llest abans de donar-lo per bo i continuar substituint pods.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-reserves
  namespace: rutas-norte-pro
  labels:
    app: api-reserves
    app.kubernetes.io/part-of: rutas-norte
    entorn: pro
spec:
  replicas: 6
  minReadySeconds: 15            # 15 s llest de manera continuada abans d'avançar
  progressDeadlineSeconds: 300   # si en 5 min no progressa, es marca com a fallit
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 0          # mai menys de 6 pods servint
  selector:
    matchLabels:
      app: api-reserves
      entorn: pro
  template:
    metadata:
      labels:
        app: api-reserves
        app.kubernetes.io/part-of: rutas-norte
        entorn: pro
    spec:
      containers:
        - name: api
          image: registry.rutasnorte.example/api-reserves:2.8.1
          ports:
            - name: http
              containerPort: 8080

Amb maxUnavailable: 0 + readinessProbe + minReadySeconds: 15, un desplegament d'api-reserves és genuïnament sense tall. El preu: triga més. Un desplegament de 6 rèpliques passa de 40 segons a uns 3 minuts. És un preu raonable.

Un avís sobre progressDeadlineSeconds: si la readiness no passa mai (per exemple, perquè la nova versió té un error de configuració), el Deployment es queda encallat però els pods vells continuen servint. Als 300 segons el Deployment es marca com a ProgressDeadlineExceeded, cosa que permet detectar-ho automàticament. No fa rollback tot sol: això ho decideixes tu amb kubectl rollout undo.

  1. El tancament net: terminationGracePeriodSeconds, preStop i els 502

Hem resolt l'entrada de pods nous. Queda la sortida dels vells, que és on neixen els 502 que Rutas Norte veu a cada desplegament.

La cursa

Quan Kubernetes decideix acabar un pod, passen dues coses en paral·lel, sense coordinació entre elles:

sequenceDiagram
    participant API as API Server
    participant EPC as Controlador d'Endpoints
    participant KP as kube-proxy / Ingress
    participant KL as kubelet
    participant P as Pod api-reserves
    API->>EPC: pod marcat per esborrar
    API->>KL: pod marcat per esborrar
    par Camí A (xarxa)
        EPC->>EPC: treu la IP de l'EndpointSlice
        EPC->>KP: propaga la regla actualitzada
        KP->>KP: reprograma iptables/IPVS (100-2000 ms)
    and Camí B (procés)
        KL->>P: SIGTERM immediat
    end
    Note over P,KP: el procés mor ABANS que deixin d'enviar-li trànsit → 502

El camí B (matar el procés) és gairebé instantani. El camí A (propagar la retirada de l'endpoint a tots els nodes i als controladors d'Ingress) triga des d'uns centenars de mil·lisegons fins a un parell de segons en un clúster amb càrrega. En aquesta finestra, el balancejador continua enviant peticions a un procés que ja s'està tancant. Cadascuna d'aquestes peticions és un 502 per a un client de Rutas Norte.

La solució: preStop

El hook preStop s'executa abans del SIGTERM i el kubelet espera que acabi abans d'enviar-lo. Inserir-hi una pausa dóna temps al camí A.

lifecycle:
  preStop:
    exec:
      command: ["/bin/sh", "-c", "sleep 10"]

Sembla un truc brut, i en certa manera ho és, però és la solució recomanada per la mateixa documentació de Kubernetes i la que fa servir pràcticament tothom en producció. Durant aquests 10 segons:

  • El contenidor continua atenent peticions amb normalitat (ningú li ha dit que pari).
  • El controlador d'endpoints ja l'ha retirat, així que deixa d'arribar-li trànsit nou.
  • Les peticions en vol acaben tranquil·lament.

Si la teva aplicació té un endpoint que li fa fallar la readiness voluntàriament, una variant més elegant és cridar-lo i després esperar. Però el sleep funciona i no requereix tocar l'aplicació.

El pressupost complet de terminació

spec:
  terminationGracePeriodSeconds: 45
  containers:
    - name: api
      lifecycle:
        preStop:
          exec:
            command: ["/bin/sh", "-c", "sleep 10"]

Cronologia real:

Moment Què passa
t=0 s El pod passa a Terminating. Arrenca el preStop i el rellotge de terminationGracePeriodSeconds
t=0 a 2 s L'endpoint es retira de tots els nodes i de l'Ingress
t=0 a 10 s preStop dormint. El contenidor serveix les peticions en vol
t=10 s Acaba el preStop. El kubelet envia SIGTERM
t=10 a 45 s L'aplicació tanca el servidor HTTP, drena el pool de PostgreSQL, fa commit del que queda pendent i surt
t=45 s Si el procés continua viu, el kubelet envia SIGKILL. Sense pietat

Punt crític que s'oblida sempre: el temps del preStop es descompta del període de gràcia, no s'hi suma. Amb terminationGracePeriodSeconds: 45 i un preStop de 10 s, l'aplicació només té 35 segons per tancar després del SIGTERM. Ajusta el número tenint-ho en compte.

I com vam veure a 02-01, si el teu procés arrenca via shell (CMD npm start), el SIGTERM pot arribar-li al shell i no a Node. Fes servir la forma exec (CMD ["node", "server.js"]) o shareProcessNamespace/tini per assegurar-te que el senyal arriba a qui ha d'arribar.

Per a worker-notificacions, que processa correus de confirmació, el període de gràcia ha de cobrir l'enviament en curs més llarg: fixem terminationGracePeriodSeconds: 90 i l'aplicació deixa d'agafar missatges nous de la cua en rebre el SIGTERM.

  1. Disseny de les sondes de cada component de Rutas Norte

Apliquem tot l'anterior component a component. La taula de disseny primer, els manifests després.

Component Liveness Readiness Startup Notes
botiga-web (nginx) httpGet / port 80 httpGet / port 80 No Estàtic i ràpid; que siguin iguals és acceptable
api-reserves httpGet /salut httpGet /preparat httpGet /salut, 60 s El cas de referència de la lliçó
postgres-reserves exec pg_isready local exec pg_isready + SELECT 1 exec pg_isready, 300 s Arrencada variable pel WAL
redis-cache exec redis-cli PING exec redis-cli PING No Arrencada rapidíssima
worker-notificacions httpGet /salut port 8081 Cap No No té Service: la readiness no aporta res
informes-ocupacio (CronJob) Cap Cap No Job de vida curta: l'èxit és el codi de sortida

Dues decisions que mereixen explicació:

  • worker-notificacions sense readiness: la readiness només té sentit si alguna cosa consulta els Endpoints. Aquest worker no està darrere de cap Service; consumeix d'una cua. Posar-hi readiness no faria res. Sí que té liveness, amb un petit servidor HTTP intern que respon 200 mentre el bucle de consum estigui actiu.
  • CronJob sense sondes: un pod que viu 4 minuts i l'èxit del qual es mesura pel seu codi de sortida no guanya res amb sondes. En el seu lloc, a 07-04 alertarem sobre kube_job_status_failed.

Manifest complet d'api-reserves

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-reserves
  namespace: rutas-norte-pro
  labels:
    app: api-reserves
    app.kubernetes.io/part-of: rutas-norte
    entorn: pro
spec:
  replicas: 6
  minReadySeconds: 15
  progressDeadlineSeconds: 300
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 0
  selector:
    matchLabels:
      app: api-reserves
      entorn: pro
  template:
    metadata:
      labels:
        app: api-reserves
        app.kubernetes.io/part-of: rutas-norte
        entorn: pro
    spec:
      terminationGracePeriodSeconds: 45
      containers:
        - name: api
          image: registry.rutasnorte.example/api-reserves:2.8.1
          ports:
            - name: http
              containerPort: 8080
          resources:
            requests:
              cpu: "250m"
              memory: "256Mi"
            limits:
              cpu: "1"
              memory: "512Mi"

          # ---- ARRENCADA: pressupost generós, sense vigilància encara ----
          startupProbe:
            httpGet:
              path: /salut
              port: http
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 12      # 12 * 5 = 60 s per arrencar

          # ---- VIVACITAT: només el procés, MAI dependències externes ----
          livenessProbe:
            httpGet:
              path: /salut
              port: http
              httpHeaders:
                - name: X-Origen-Sonda
                  value: kubelet-liveness
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3       # pitjor cas: 33 s fins al reinici

          # ---- PREPARACIÓ: sí que comprova PostgreSQL i Redis ----
          readinessProbe:
            httpGet:
              path: /preparat
              port: http
              httpHeaders:
                - name: X-Origen-Sonda
                  value: kubelet-readiness
            periodSeconds: 5
            timeoutSeconds: 2
            failureThreshold: 2       # pitjor cas: 12 s fora d'Endpoints
            successThreshold: 1

          # ---- TANCAMENT NET: evitar els 502 del desplegament ----
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 10"]

Què ha de fer cada endpoint per dins

Pseudocodi del /salut (liveness). Barat, local, sense xarxa sortint:

// GET /salut  -> utilitzat per livenessProbe
// Respon 200 si el procés pot processar peticions. RES de dependències.
app.get('/salut', (req, res) => {
  const retardBucleMs = mesurarRetardDelBucleDEsdeveniments();  // p. ex. amb perf_hooks
  if (retardBucleMs > 5000) {
    // El bucle d'esdeveniments porta 5 s bloquejat: el procés no es recupera sol
    return res.status(503).json({ estat: 'bloquejat', retardBucleMs });
  }
  return res.status(200).json({ estat: 'viu', versio: process.env.APP_VERSION });
});

Pseudocodi del /preparat (readiness). Comprova dependències, amb timeouts curts:

// GET /preparat -> utilitzat per readinessProbe
// Respon 200 només si podem servir una reserva de debò ARA.
app.get('/preparat', async (req, res) => {
  const comprovacions = {};
  try {
    // 1. Queda alguna connexió lliure al pool? (el cas de l'apartat 1)
    comprovacions.poolLliure = poolPg.idleCount > 0 || poolPg.totalCount < poolPg.options.max;
    if (!comprovacions.poolLliure) throw new Error('pool de connexions exhaurit');

    // 2. PostgreSQL respon en menys d'1 s?
    await poolPg.query({ text: 'SELECT 1', timeout: 1000 });
    comprovacions.postgres = 'ok';

    // 3. Redis respon? Degradació acceptable: sense cau servim igual, més lent
    try {
      await redis.ping();
      comprovacions.redis = 'ok';
    } catch (e) {
      comprovacions.redis = 'degradat';   // NO impedeix estar preparat
    }

    // ATENCIÓ: no comprovem pagos.proveedorexterno.example expressament
    return res.status(200).json({ estat: 'preparat', comprovacions });
  } catch (err) {
    return res.status(503).json({ estat: 'no-preparat', motiu: err.message, comprovacions });
  }
});

postgres-reserves i redis-cache

# Fragment del StatefulSet postgres-reserves (contenidor principal)
containers:
  - name: postgres
    image: postgres:16.4
    ports:
      - name: pg
        containerPort: 5432
    startupProbe:
      exec:
        command: ["/bin/sh", "-c", "pg_isready -U rutasnorte -h 127.0.0.1"]
      periodSeconds: 10
      timeoutSeconds: 5
      failureThreshold: 30       # 300 s: cobreix la recuperació del WAL
    livenessProbe:
      exec:
        command: ["/bin/sh", "-c", "pg_isready -U rutasnorte -h 127.0.0.1"]
      periodSeconds: 15
      timeoutSeconds: 5
      failureThreshold: 3
    readinessProbe:
      exec:
        command:
          - /bin/sh
          - -c
          - pg_isready -U rutasnorte -h 127.0.0.1 && psql -U rutasnorte -d reserves -tAc 'SELECT 1'
      periodSeconds: 10
      timeoutSeconds: 5
      failureThreshold: 2
# Fragment del StatefulSet redis-cache
containers:
  - name: redis
    image: redis:7.4-alpine
    ports:
      - name: redis
        containerPort: 6379
    livenessProbe:
      exec:
        command: ["redis-cli", "ping"]
      periodSeconds: 15
      timeoutSeconds: 3
      failureThreshold: 3
    readinessProbe:
      exec:
        command: ["redis-cli", "ping"]
      periodSeconds: 5
      timeoutSeconds: 2
      failureThreshold: 2

Verificació al clúster

# Estat de preparació de tots els pods del namespace
kubectl -n rutas-norte-pro get pods -l app.kubernetes.io/part-of=rutas-norte

# Veure els esdeveniments de sondes fallides d'un pod concret
kubectl -n rutas-norte-pro describe pod api-reserves-7d9f8c4b5-x2klm | grep -A 20 Events

# Veure la condició Ready i el seu motiu exacte
kubectl -n rutas-norte-pro get pod api-reserves-7d9f8c4b5-x2klm \
  -o jsonpath='{.status.conditions[?(@.type=="Ready")]}' | jq

# Comprovar quines IPs són realment als Endpoints (la veritat del Service)
kubectl -n rutas-norte-pro get endpointslices -l kubernetes.io/service-name=api-reserves -o yaml

Sortida típica d'un pod la readiness del qual falla:

NAME                            READY   STATUS    RESTARTS   AGE
api-reserves-7d9f8c4b5-x2klm    0/1     Running   0          4m12s

Para atenció: STATUS és Running i RESTARTS és 0. Tot "sembla" bé. L'única senyal és el 0/1 de la columna READY. I als esdeveniments:

Events:
  Type     Reason     Age                  From     Message
  ----     ------     ----                 ----     -------
  Warning  Unhealthy  2m (x24 over 4m)     kubelet  Readiness probe failed: HTTP probe failed with statuscode: 503

Aquesta és la traça que cal buscar. A 07-06 sistematitzarem aquesta manera de llegir esdeveniments.

Errors Comuns i Consells

1. Posar la mateixa sonda a liveness i readiness comprovant dependències. És l'error de l'apartat 5 disfressat. Si copies el mateix httpGet /salut/complet a totes dues, has creat el bucle de reinicis en cascada. Endpoints diferents, sempre.

2. timeoutSeconds: 1 (el valor per defecte). Sota càrrega, un endpoint sa triga més d'un segon amb facilitat. Puja a 2-3 s a readiness i 3-5 s a liveness. Aquest paràmetre per defecte ha causat més falsos positius que cap altre.

3. Oblidar la startupProbe en aplicacions lentes. El símptoma clàssic: CrashLoopBackOff en un contenidor que "en local arrenca bé". El que passa és que la liveness el mata als 30 segons i mai no acaba d'arrencar. Si veus reinicis cíclics sense logs d'error de l'aplicació, sospita d'això el primer.

4. Sonda exec amb un binari que no existeix. curl, wget o nc no són a les imatges distroless ni a moltes alpine minimalistes. Comprova amb kubectl exec -it <pod> -- which curl abans d'escriure la sonda. L'esdeveniment delator és Liveness probe errored: exec: "curl": executable file not found in $PATH.

5. Endpoint de salut que exigeix autenticació. El kubelet no envia credencials. Un /salut protegit per JWT retorna 401 i la sonda falla sempre. Deixa l'endpoint de salut obert, sense dades sensibles a la resposta, i protegeix-lo per xarxa si cal.

6. Confiar en els codis 3xx. Recorda: 200-399 és èxit. Un /salut que redirigeix a / continuarà "passant" encara que l'aplicació estigui completament trencada. Retorna 200 explícit.

7. readinessProbe sense preStop. Tens desplegaments sense tall a l'entrada però continues generant 502 a la sortida. Els dos mecanismes són complementaris i calen tots dos.

8. Sondes massa cares. Un /preparat que fa SELECT count(*) FROM reserves està llançant una consulta pesada cada 5 segons per cada rèplica. Amb 6 rèpliques són 72 consultes per minut només per preguntar com estem. Les comprovacions han de ser trivials: SELECT 1 i poca cosa més.

9. Fer servir sondes per al que serveix un PodDisruptionBudget. Les sondes no protegeixen d'un drenatge de node ni d'una actualització del clúster. Això és 09-05.

10. Un preStop més llarg que el període de gràcia. Si poses preStop: sleep 60 amb terminationGracePeriodSeconds: 30, als 30 segons arriba el SIGKILL i l'aplicació mai no rep el SIGTERM: no tanca res netament. El preStop sempre ha de ser bastant menor que el període de gràcia.

Exercicis

Exercici 1 — Calcular i ajustar el pressupost de detecció

Un company ha configurat així la liveness de worker-notificacions:

livenessProbe:
  httpGet:
    path: /salut
    port: 8081
  initialDelaySeconds: 20
  periodSeconds: 30
  timeoutSeconds: 1
  failureThreshold: 5

Respon:

  1. Quant triga, en el pitjor cas, a detectar-se que el worker s'ha penjat?
  2. El negoci exigeix que un worker penjat es reiniciï en menys de 60 segons. Proposa una configuració que ho compleixi sense fer-la propensa a falsos positius.
  3. El worker triga 8 segons a arrencar i connectar-se a la cua. És correcte l'initialDelaySeconds: 20? Quina alternativa millor existeix?

Exercici 2 — Detectar i arreglar una liveness perillosa

Aquest és el manifest real de botiga-web a rutas-norte-pre. Conté tres problemes relacionats amb les sondes i el tancament. Identifica'ls i escriu el manifest corregit.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: botiga-web
  namespace: rutas-norte-pre
  labels:
    app: botiga-web
    entorn: pre
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  selector:
    matchLabels:
      app: botiga-web
      entorn: pre
  template:
    metadata:
      labels:
        app: botiga-web
        entorn: pre
    spec:
      containers:
        - name: nginx
          image: registry.rutasnorte.example/botiga-web:5.2.0
          ports:
            - containerPort: 80
          livenessProbe:
            httpGet:
              path: /api/estat-complet     # proxy a api-reserves i a PostgreSQL
              port: 80
            periodSeconds: 5
            timeoutSeconds: 1
            failureThreshold: 2

Exercici 3 — Dissenyar les sondes d'un component nou

Rutas Norte afegeix un component: cercador-rutes, un servei Java (Spring Boot) que manté en memòria un índex de totes les rutes i horaris. Dades:

  • En arrencar carrega l'índex complet des de postgres-reserves: entre 45 i 150 segons segons el volum.
  • Un cop carregat l'índex, no torna a tocar PostgreSQL: ho serveix tot des de memòria.
  • Exposa /actuator/health/liveness i /actuator/health/readiness (Spring Boot Actuator).
  • Està darrere d'un Service cercador-rutes que consulta botiga-web.
  • Ocasionalment pateix pauses llargues del recol·lector d'escombraries (fins a 4 segons).

Escriu el bloc de sondes complet i justifica cada valor.

Solucions

Solució 1

1. Temps de detecció.

T_pitjor = failureThreshold * periodSeconds + timeoutSeconds
         = 5 * 30 + 1
         = 151 segons

Més de dos minuts i mig amb el worker penjat i sense enviar ni un correu de confirmació. Molt per sobre del requisit de 60 segons.

2. Configuració proposada.

livenessProbe:
  httpGet:
    path: /salut
    port: 8081
  periodSeconds: 10
  timeoutSeconds: 3      # 1 s era massa ajustat sota càrrega
  failureThreshold: 4
T_pitjor = 4 * 10 + 3 = 43 segons  ✅ compleix el requisit de < 60 s

S'ha baixat el període de 30 a 10 s (detecció més ràpida) i pujat el timeoutSeconds d'1 a 3 s (menys falsos positius). El failureThreshold: 4 dóna marge perquè un pic puntual no dispari un reinici: calen quatre errors consecutius, és a dir 40 segons de problema sostingut.

3. L'initialDelaySeconds.

És una solució grollera. Amb 20 s fixos: si un dia l'arrencada triga 25 s per lentitud del node, la liveness comença a fallar durant l'arrencada i el pod pot entrar en CrashLoopBackOff. I si arrenca en 8 s, hem perdut 12 s de vigilància.

L'alternativa correcta és una startupProbe que separi els pressupostos:

startupProbe:
  httpGet:
    path: /salut
    port: 8081
  periodSeconds: 3
  timeoutSeconds: 2
  failureThreshold: 15      # 45 s de marge d'arrencada, de sobres per a 8 s
livenessProbe:
  httpGet:
    path: /salut
    port: 8081
  periodSeconds: 10         # sense initialDelaySeconds: ja no cal
  timeoutSeconds: 3
  failureThreshold: 4

Avantatge afegit: el startupProbe amb periodSeconds: 3 detecta l'arrencada acabada tan bon punt passa, així que un pod que arrenca en 8 s està llest en ~9 s, no en 20.

Solució 2

Els tres problemes:

  1. La liveness comprova dependències externes. /api/estat-complet és un proxy cap a api-reserves i d'allà a PostgreSQL. Si l'API o la base de dades van lentes, es reinicien els tres pods de botiga-web alhora, deixant el web públic completament caigut encara que nginx estigués perfectament. És exactament l'escenari de l'apartat 5.
  2. No hi ha readinessProbe. Combinat amb maxUnavailable: 1, cada desplegament envia trànsit a pods d'nginx que encara no han acabat d'arrencar → errors durant cada actualització.
  3. No hi ha preStop ni període de gràcia ajustat. Els pods que es retiren moren abans que es propagui la retirada de l'endpoint → 502 a cada desplegament. Afegit: timeoutSeconds: 1 és massa ajustat.

Manifest corregit:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: botiga-web
  namespace: rutas-norte-pre
  labels:
    app: botiga-web
    app.kubernetes.io/part-of: rutas-norte
    entorn: pre
spec:
  replicas: 3
  minReadySeconds: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0        # mai baixar de 3 pods servint
  selector:
    matchLabels:
      app: botiga-web
      entorn: pre
  template:
    metadata:
      labels:
        app: botiga-web
        app.kubernetes.io/part-of: rutas-norte
        entorn: pre
    spec:
      terminationGracePeriodSeconds: 30
      containers:
        - name: nginx
          image: registry.rutasnorte.example/botiga-web:5.2.0
          ports:
            - name: http
              containerPort: 80
          # Liveness: NOMÉS nginx, sense sortir del pod
          livenessProbe:
            httpGet:
              path: /nginx-salut     # ubicació estàtica que retorna 200 des d'nginx
              port: http
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          # Readiness: el web estàtic se serveix sense dependre de l'API
          readinessProbe:
            httpGet:
              path: /nginx-salut
              port: http
            periodSeconds: 5
            timeoutSeconds: 2
            failureThreshold: 2
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 5"]

Amb la configuració d'nginx corresponent:

location = /nginx-salut {
    access_log off;
    return 200 "ok\n";
    add_header Content-Type text/plain;
}

L'access_log off; evita que les sondes (una cada 5 s per pod, 3 pods → més de 50.000 línies al dia) inundin els logs que centralitzarem a 07-05.

Solució 3

# ---- ARRENCADA: pressupost molt generós, cobreix el pitjor cas de 150 s ----
startupProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 24        # 24 * 10 = 240 s > 150 s del pitjor cas

# ---- VIVACITAT: la JVM respon; RES de PostgreSQL ----
livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  periodSeconds: 15
  timeoutSeconds: 6           # > 4 s de la pausa màxima del GC
  failureThreshold: 3         # pitjor cas: 51 s

# ---- PREPARACIÓ: està l'índex carregat i serveix consultes? ----
readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  periodSeconds: 10
  timeoutSeconds: 6
  failureThreshold: 3
  successThreshold: 1

Justificació de cada decisió:

  • startupProbe amb 240 s de pressupost. L'arrencada va de 45 a 150 s. Donar un 60 % de marge sobre el pitjor cas conegut evita reinicis espuris el dia que PostgreSQL vagi lent durant la càrrega. Sense aquesta sonda, la liveness mataria el pod als ~50 s i entraria en CrashLoopBackOff etern: no arribaria mai a carregar l'índex.
  • timeoutSeconds: 6 a liveness i readiness. Les pauses del recol·lector d'escombraries arriben a 4 segons. Si el timeout fos de 3 s, cada pausa llarga comptaria com a error. Amb 6 s, una pausa de GC no dispara res. Aquest és un ajust específic de la JVM que cal fer conscientment.
  • periodSeconds: 15 a liveness. El component és estable; no necessitem detecció subsegon. Un període alt redueix la pressió de sondes sobre l'aplicació.
  • Liveness contra /actuator/health/liveness i no contra l'endpoint general. Spring Boot separa per defecte els grups liveness (estat de la JVM) i readiness (dependències llestes). Fer servir /actuator/health a seques inclouria els indicadors de PostgreSQL a la liveness: l'error de l'apartat 5.
  • Readiness que sí que comprova l'índex. Encara que cercador-rutes no toqui PostgreSQL en règim, sí que necessita l'índex carregat. Que la readiness falli mentre l'índex no estigui llest és correcte: el pod no ha de rebre cerques que no pot resoldre.
  • successThreshold: 1. Tornar ràpid al servei tan bon punt es recupera.

Detall pràctic: a Spring Boot cal activar els grups de sondes amb management.endpoint.health.probes.enabled=true (s'activa sol si detecta que corre a Kubernetes).

Conclusió

Les sondes són el primer pas cap a una plataforma observable, i també el primer cap a una plataforma fiable. En aquesta lliçó hem vist que:

  • Un procés viu no és una aplicació sana, i el cas del pool de connexions exhaurit d'api-reserves ho demostra sense ambigüitat.
  • livenessProbe reinicia, readinessProbe retira del Service, startupProbe retarda les altres dues. Tres preguntes diferents amb tres conseqüències diferents.
  • Els quatre manejadors (httpGet, tcpSocket, exec, grpc) tenen costos i precisions molt diferents; exec és el més flexible i el més car.
  • El temps de detecció es calcula, no s'intueix: failureThreshold * periodSeconds + timeoutSeconds.
  • La regla que evita la caiguda més cara del mòdul: la liveness mai no comprova dependències externes; la readiness sí que ho ha de fer.
  • Les sondes tanquen el deute de 02-04: juntament amb maxUnavailable: 0, minReadySeconds i un preStop que guanya la cursa contra el SIGTERM, tenim per fi desplegaments genuïnament sense tall i sense els 502 que vèiem a cada actualització.

Rutas Norte ja sap si cada component està sa. El que encara no sap és quant consumeix. A 03-04 vam fixar les requests i els limits de cada component pràcticament a ull, i vam deixar escrit que els calibraríem observant el consum real. Aquest moment ha arribat: a la propera lliçó instal·larem metrics-server, la primera font de dades de consum del clúster, i amb kubectl top compararem el que demanem amb el que de debò gastem.

Curs de Kubernetes

Mòdul 1: Introducció a Kubernetes

Mòdul 2: Components Principals de Kubernetes

Mòdul 3: Gestió de Configuració i Secrets

Mòdul 4: Xarxes a Kubernetes

Mòdul 5: Emmagatzematge a Kubernetes

Mòdul 6: Conceptes Avançats de Kubernetes

Mòdul 7: Monitoratge i Registre

Mòdul 8: Seguretat a Kubernetes

Mòdul 9: Escalat i Rendiment

Mòdul 10: Ecosistema i Eines de Kubernetes

Mòdul 11: Estudis de Cas i Aplicacions del Món Real

Mòdul 12: Preparació per a la Certificació de Kubernetes

© Copyright 2026. Tots els drets reservats