Tancàvem el mòdul 1 amb botiga-web funcionant a rutas-norte-dev i amb una lliçó incòmoda apresa: en esborrar aquell pod solt, no va tornar mai. Abans d'arreglar-ho amb controladors, cal entendre bé la peça sobre la qual es construeix tota la resta. El Pod és la unitat mínima de desplegament de Kubernetes: no desplegues contenidors, desplegues pods que contenen contenidors. Tota la maquinària del curs —ReplicaSets, Deployments, Serveis, sondes, autoescalat, polítiques de xarxa— existeix per crear, mantenir, assolir, vigilar o protegir pods. En aquesta lliçó obrim el pod en canal: per què és la unitat mínima i no el contenidor, què comparteixen exactament els contenidors que hi viuen dins, com es llegeix un manifest camp per camp, què significa cada fase i cada estat que veuràs a kubectl get pods, com neix i com mor un pod, i com fer servir pods efímers com a eina de diagnòstic. En acabar, sabràs interpretar un ImagePullBackOff o un CrashLoopBackOff sense buscar per internet.
Contingut
- Per què la unitat mínima és el pod i no el contenidor
- Què comparteixen els contenidors d'un mateix pod (i què no)
- El contenidor de pausa: el que sosté el pod
- Anatomia d'un manifest de pod:
api-reservescamp per camp - Fases del pod i estats dels contenidors
- Motius típics:
ContainerCreating,ImagePullBackOff,CrashLoopBackOff restartPolicy: qui reinicia què- Cicle de vida complet: de la creació al
SIGTERM - Pods efímers per depurar
- Per què en producció mai no es gestionen pods solts
- Per què la unitat mínima és el pod i no el contenidor
És la primera pregunta que es fa tothom qui ve de Docker. Si Kubernetes orquestra contenidors, per què introdueix un embolcall intermedi en lloc de planificar contenidors directament?
La resposta és que hi ha processos que necessiten anar enganxats. No "a prop" ni "a la mateixa màquina", sinó literalment compartint xarxa i disc, arrencant i morint alhora, planificats com una sola unitat indivisible. Exemples que veuràs en qualsevol plataforma seriosa:
- Un servidor d'aplicació i un recol·lector de logs que llegeix els fitxers que aquell escriu.
- Un servei i un proxy que li acaba el TLS i li afegeix mètriques (el patró service mesh).
- Un contenidor principal i un altre que descarrega configuració o certificats abans que arrenqui.
Si la unitat mínima fos el contenidor, el planificador podria col·locar el recol·lector de logs en un node i l'aplicació en un altre, i el disseny es trencaria. Kubernetes ho resol amb un contracte clar:
El pod és un grup d'un o més contenidors que es planifiquen junts al mateix node, comparteixen xarxa i emmagatzematge, i viuen i moren com una unitat.
Dues conseqüències pràctiques que convé gravar des d'ara:
- El pod és la unitat de planificació. El scheduler assigna pods a nodes, no contenidors. Els recursos que se sumen per decidir si "hi cap" en un node són els de tots els seus contenidors.
- El pod és la unitat d'escalat. Quan escales
api-reservesa 6 rèpliques, crees 6 pods sencers. No pots escalar un contenidor dins d'un pod.
Aquesta segona conseqüència és la regla de disseny més important: si dos processos escalen a ritmes diferents, no van al mateix pod. A Rutas Norte, api-reserves i redis-cache podrien semblar bons companys de pod (l'API consulta la memòria cau constantment), però seria un error greu: cada rèplica de l'API tindria la seva pròpia cau aïllada, i escalar l'API multiplicaria les caus. Van en pods separats, i es parlen a través d'un Servei.
El cas aclaparadorament majoritari, i el que farem servir gairebé sempre al curs, és un contenidor per pod. Els patrons multicontenidor (init containers i sidecars) tenen la seva pròpia lliçó: Init Containers, Sidecars i Patrons Multicontenidor.
- Què comparteixen els contenidors d'un mateix pod (i què no)
Un pod no és una metàfora: és una construcció molt concreta de namespaces de Linux compartits entre processos. Aquesta taula és la referència exacta.
| Recurs | Es comparteix dins del pod? | Què implica a la pràctica |
|---|---|---|
| Namespace de xarxa | Sí | Tots els contenidors tenen la mateixa IP i el mateix espai de ports; es veuen entre ells per localhost |
| Namespace IPC | Sí | Poden fer servir memòria compartida i semàfors de System V entre ells |
Volums (spec.volumes) |
Sí, els que cadascun munti | Un contenidor escriu un fitxer i un altre el llegeix, si tots dos munten el mateix volum |
| Cicle de vida i node | Sí | Es planifiquen al mateix node; el pod s'esborra sencer, mai a mitges |
| Namespace d'UTS (hostname) | Sí | Mateix hostname, que per defecte és el nom del pod |
| Sistema de fitxers arrel | No | Cada contenidor té el seu, el de la seva imatge. /app d'un no és /app de l'altre |
| Namespace de PID | No per defecte | Cada contenidor veu només els seus processos, tret que activis shareProcessNamespace: true |
| Recursos (CPU, memòria) | No | requests i limits es declaren per contenidor, no per pod |
| Estat de reinici | No | El kubelet reinicia contenidors individualment; no reinicia el pod sencer |
flowchart TB
subgraph POD["Pod api-reserves · IP 10.244.0.34"]
direction TB
NET["Namespace de xarxa compartit<br/>una sola IP, un sol espai de ports"]
subgraph C1["Contenidor: api"]
FS1["Sistema de fitxers propi<br/>imatge node:20-alpine"]
end
subgraph C2["Contenidor: recollector-logs"]
FS2["Sistema de fitxers propi<br/>imatge fluent-bit"]
end
VOL[("Volum compartit<br/>/var/log/app")]
end
C1 -.->|escriu| VOL
C2 -.->|llegeix| VOL
C1 --- NET
C2 --- NET
La conseqüència més útil i la més perillosa del namespace de xarxa compartit:
- Útil: dins d'un pod, dos contenidors es parlen per
http://localhost:8080. Sense DNS, sense Service, sense latència de xarxa. - Perillosa: no hi pot haver dos contenidors escoltant al mateix port dins d'un pod. Si poses dos
nginxal pod, el segon fallarà amb "address already in use".
- El contenidor de pausa: el que sosté el pod
Com aconsegueix Kubernetes que diversos contenidors comparteixin la xarxa si cadascun arrenca i mor pel seu compte? Amb un truc elegant: per cada pod, el kubelet arrenca un contenidor invisible anomenat contenidor de pausa (pause, també anomenat contenidor d'infraestructura o sandbox).
El seu comportament:
- Es crea el primer, abans que cap contenidor teu.
- Crea i posseeix els namespaces de xarxa, IPC i UTS del pod. És qui rep la IP.
- La resta de contenidors del pod s'arrenquen unint-se als namespaces del contenidor de pausa.
- La seva única feina és dormir. El seu codi font ocupa unes poques desenes de línies: es queda bloquejat esperant senyals i fa
reapdels processos orfes.
El seu valor rau en el fet que sobreviu als reinicis dels teus contenidors. Si el procés d'api-reserves cau i el kubelet el reinicia, el contenidor de pausa segueix viu, així que la IP del pod no canvia. Només quan es destrueix el pod sencer desapareix el sandbox i s'allibera la IP. Això explica exactament el que vas observar a l'exercici final del mòdul 1: en matar el procés, RESTARTS pujava a 1 però la IP continuava sent la mateixa.
El pots veure al node, tot i que no apareix mai a kubectl get pods:
No l'has de gestionar mai. Però conèixer-lo evita confusions quan llegeixis documentació o depuris a baix nivell.
- Anatomia d'un manifest de pod:
api-reserves camp per camp
api-reserves camp per campEscriurem el pod d'api-reserves, el segon component de Rutas Norte. Com que registry.rutasnorte.example és fictici, simulem l'API amb una imatge pública de Node.js que aixeca un servidor mínim. Desa'l al repositori del projecte.
# k8s/base/api-reserves-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: api-reserves
namespace: rutas-norte-dev
labels:
app: api-reserves
app.kubernetes.io/part-of: rutas-norte
entorn: dev
annotations:
rutasnorte.example/responsable: [email protected]
spec:
restartPolicy: Always
terminationGracePeriodSeconds: 30
containers:
- name: api
image: node:20-alpine
command: ["node", "-e"]
args:
- |
const http = require('http');
http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({servei: 'api-reserves', ruta: req.url}));
}).listen(3000, () => console.log('api-reserves escoltant al 3000'));
ports:
- name: http
containerPort: 3000
protocol: TCP
env:
- name: ENTORN
value: "dev"
- name: NODE
valueFrom:
fieldRef:
fieldPath: spec.nodeName
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
volumeMounts:
- name: temporal
mountPath: /tmp/reserves
volumes:
- name: temporal
emptyDir: {}Repàs camp per camp:
apiVersion: v1/kind: Pod: el Pod pertany al grup core de l'API, per això no porta prefix de grup. Ho vam veure a Objectes, Manifests YAML i el Model Declaratiu.metadata.name: únic dins del namespace. Serà també el hostname del pod.metadata.labels: les tres etiquetes obligatòries del projecte. Encara no les fa servir ningú, però a la lliçó següent seran la clau amb què un ReplicaSet reconeix els "seus" pods. La seva sintaxi completa és a Etiquetes, Selectors i Anotacions.spec.restartPolicy: Always: és el valor per defecte; l'escrivim per fer-lo explícit. Apartat 7.spec.terminationGracePeriodSeconds: 30: segons que es concedeixen al contenidor per acabar ordenadament abans de matar-lo per la força. També és el valor per defecte. Apartat 8.containers[].name: identificador del contenidor dins del pod. És el que passes akubectl logs -cokubectl exec -cquan n'hi ha diversos.image: etiqueta explícita, mailatest, segons la convenció del projecte.command/args: sobreescriuen l'ENTRYPOINTi elCMDde la imatge respectivament. Aquí els fem servir perquè una imatge genèrica de Node es comporti com la nostra API. Compte amb l'equivalència, que és una font clàssica d'errors:
| Docker | Kubernetes |
|---|---|
ENTRYPOINT |
command |
CMD |
args |
ports: informatiu, no obre res. Serveix per documentar i, sobretot, per donar un nom al port (http), que després un Service podrà referenciar pel nom en lloc de pel número.env: variables d'entorn. La primera és un valor literal; la segona fa servirfieldRefper injectar una dada del propi pod (la downward API). Les fonts serioses de configuració —ConfigMaps i Secrets— arriben a Variables d'Entorn.resources:requestsés el que el scheduler reserva;limitsés el sostre que imposa el kubelet. Detall complet a Quotes i Límits de Recursos.volumeMounts/volumes: el volumemptyDirés un directori buit que viu i mor amb el pod. Útil per a fitxers temporals i per compartir dades entre contenidors del mateix pod. L'emmagatzematge persistent és el mòdul 5.
Aplica'l i comprova'l:
pod/api-reserves created
NAME READY STATUS RESTARTS AGE
api-reserves 1/1 Running 0 6s
botiga-web 1/1 Running 0 22mFixa't que hem cridat localhost:3000 des de dins del propi pod: això és el namespace de xarxa compartit en acció.
- Fases del pod i estats dels contenidors
Aquí hi ha dos conceptes que es confonen constantment i que convé separar amb nitidesa.
5.1. La fase del pod (status.phase)
És un resum d'alt nivell del pod sencer. Només té cinc valors possibles:
| Fase | Significat | Situació típica |
|---|---|---|
Pending |
Acceptat per l'API, però algun contenidor encara no està en marxa | Esperant el scheduler, o descarregant imatges |
Running |
Assignat a un node, tots els contenidors creats i almenys un en execució | Estat normal d'una càrrega de servei |
Succeeded |
Tots els contenidors han acabat amb èxit i no es reiniciaran | Un Job o un pod amb restartPolicy: Never que ha acabat bé |
Failed |
Tots han acabat i almenys un ha fallat (codi de sortida diferent de 0) | Un procés que ha avortat amb restartPolicy: Never |
Unknown |
No es pot determinar l'estat, normalment perquè no hi ha contacte amb el node | Node caigut o partició de xarxa |
Un detall important: Running no vol dir "funcionant bé". Vol dir "hi ha processos vius". Un pod pot estar Running i retornar errors 500 a tothom. Distingir "viu" de "sa" és exactament la feina de les sondes, a Verificacions de Salut i Sondes.
5.2. L'estat de cada contenidor (status.containerStatuses[].state)
Cada contenidor té el seu propi estat, amb només tres valors, cadascun amb un motiu (reason) que és el que de debò t'explica què passa:
| Estat | Significat | Motius habituals |
|---|---|---|
Waiting |
Encara no s'executa; està fent alguna cosa prèvia o esperant | ContainerCreating, ImagePullBackOff, ErrImagePull, CrashLoopBackOff, CreateContainerConfigError |
Running |
S'està executant; inclou startedAt |
— |
Terminated |
Ha acabat; inclou exitCode, reason, startedAt i finishedAt |
Completed (codi 0), Error, OOMKilled |
La columna STATUS de kubectl get pods és un invent de la CLI que barreja les dues coses per ser útil: mostra la fase, tret que hi hagi un motiu de contenidor més informatiu, cas en què mostra aquest. Per això veus ImagePullBackOff en aquella columna encara que no sigui una fase.
Per veure les dades reals, sense la barreja:
kubectl get pod api-reserves -o jsonpath='{.status.phase}{"\n"}'
kubectl get pod api-reserves -o jsonpath='{range .status.containerStatuses[*]}{.name}{" -> "}{.state}{"\n"}{end}'
- Motius típics:
ContainerCreating, ImagePullBackOff, CrashLoopBackOff
ContainerCreating, ImagePullBackOff, CrashLoopBackOffAquests tres motius cobreixen la gran majoria dels problemes que veuràs. Mereixen tractament individual perquè el diagnòstic de cadascun és diferent.
6.1. ContainerCreating
Estat transitori i normal: el kubelet està descarregant la imatge, muntant volums i configurant la xarxa del pod. Només és un problema si no en passa d'aquí. Si s'hi queda minuts, kubectl describe et dirà per què: gairebé sempre un volum que no es pot muntar o un Secret/ConfigMap que no existeix.
6.2. ImagePullBackOff i ErrImagePull
El kubelet no pot descarregar la imatge. Primer veuràs ErrImagePull (fallada immediata) i després ImagePullBackOff (reintents amb espera creixent). Provoquem-ho a propòsit, fent servir el registre fictici del projecte:
kubectl run prova-registre --image=registry.rutasnorte.example/api-reserves:2.4.0
kubectl get pod prova-registreEl diagnòstic és sempre als esdeveniments:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 40s default-scheduler Successfully assigned rutas-norte-dev/prova-registre to rutas-norte
Normal Pulling 39s kubelet Pulling image "registry.rutasnorte.example/api-reserves:2.4.0"
Warning Failed 24s kubelet Failed to pull image: dial tcp: lookup registry.rutasnorte.example: no such host
Warning Failed 24s kubelet Error: ErrImagePull
Normal BackOff 10s (x2 over 23s) kubelet Back-off pulling imageLes quatre causes possibles, en ordre de freqüència:
- Nom o etiqueta mal escrits (la més comuna amb diferència).
- Registre privat sense credencials: falta un
imagePullSecrets. Es veu al mòdul 8. - Registre inabastable des del node, com en aquest exemple.
- L'etiqueta no existeix al registre: típic en desplegar una versió que encara no s'ha publicat.
Neteja la prova:
6.3. CrashLoopBackOff
El més malinterpretat. No és un error en si mateix: vol dir que el contenidor arrenca, acaba, el kubelet el reinicia, i torna a acabar. Per no consumir el node, el kubelet espera cada cop més entre reintents: 10s, 20s, 40s, 80s… fins a un màxim de 5 minuts. CrashLoopBackOff és el nom d'aquella espera.
kubectl run api-trencada --image=node:20-alpine -- node -e "console.error('falta DATABASE_URL'); process.exit(1)"
kubectl get pod api-trencada -wNAME READY STATUS RESTARTS AGE
api-trencada 0/1 ContainerCreating 0 0s
api-trencada 0/1 Error 0 3s
api-trencada 0/1 CrashLoopBackOff 1 (5s ago) 8s
api-trencada 0/1 Error 2 (18s ago) 21s
api-trencada 0/1 CrashLoopBackOff 2 (14s ago) 35sLa clau del diagnòstic: el motiu no és mai a describe, és als logs de l'intent anterior.
Aquí hi ha la causa real. Recorda el flag --previous (o -p): sense ell, kubectl logs intenta llegir el contenidor actual, que pot estar encara en l'espera i no tenir res a explicar.
Les causes més freqüents de CrashLoopBackOff:
| Causa | Com es detecta |
|---|---|
| Error de configuració (falta una variable, credencials invàlides) | kubectl logs --previous mostra el missatge |
| El procés acaba tan bon punt arrenca perquè no és un servidor | exitCode: 0 i tot i així reinicia: restartPolicy: Always mal triada |
| Manca de memòria | reason: OOMKilled a describe; puja el limits.memory |
Un command mal escrit |
exitCode: 127 (ordre no trobada) o 126 (no executable) |
| Sonda de vitalitat mal configurada | Reinicis cíclics amb l'aplicació aparentment sana (07-01) |
restartPolicy: qui reinicia què
restartPolicy: qui reinicia quèspec.restartPolicy s'aplica a tots els contenidors del pod i només admet tres valors. És un camp immutable: no es pot canviar en un pod ja creat.
| Valor | Comportament | Quan es fa servir |
|---|---|---|
Always (per defecte) |
Reinicia el contenidor sempre que acabi, amb èxit o amb error | Serveis de llarga durada: botiga-web, api-reserves, redis-cache |
OnFailure |
Reinicia només si acaba amb codi diferent de 0 | Tasques que han de completar-se: informes-ocupacio |
Never |
No reinicia mai | Tasques d'un sol intent, depuració |
Tres precisions que eviten confusions molt comunes:
- Qui aplica la política és el kubelet del node, no un controlador del pla de control. Per això funciona fins i tot en pods solts, sense cap Deployment al darrere.
- Reiniciar un contenidor no és recrear el pod. El pod continua sent el mateix objecte, amb el mateix nom, el mateix node i la mateixa IP. Només puja el comptador
RESTARTS. Aquesta és la diferència exacta que vas comprovar a l'exercici 3 del mòdul 1. - Els Deployments exigeixen
Always. Com veurem a Deployments, la seva plantilla de pod no admet cap altre valor.OnFailureiNeversón terreny de Treballs i CronJobs.
- Cicle de vida complet: de la creació al
SIGTERM
SIGTERM8.1. Naixement
Aquest és el recorregut que ja vas veure a Arquitectura de Kubernetes, ara centrat en el pod:
flowchart TD
A["kubectl apply<br/>manifest del pod"] --> B["kube-apiserver valida,<br/>aplica valors per defecte<br/>i persisteix a etcd"]
B --> C["Fase: Pending<br/>spec.nodeName és buit"]
C --> D["kube-scheduler tria node<br/>i escriu spec.nodeName"]
D --> E["El kubelet del node ho detecta<br/>i crea el contenidor de pausa"]
E --> F["Estat del contenidor: Waiting<br/>motiu ContainerCreating"]
F --> G["Descàrrega d'imatge<br/>i muntatge de volums"]
G --> H["Arrencada dels contenidors"]
H --> I["Fase: Running<br/>estat del contenidor: Running"]
8.2. Mort: l'aturada ordenada
Aquesta part és la que gairebé ningú estudia i la que causa els errors 502 durant els desplegaments. Quan esborres un pod, això és el que passa exactament:
- L'API marca el pod amb
deletionTimestampi li assigna un termini de gràcia (terminationGracePeriodSeconds, 30 s per defecte). La fase passa aTerminating. - En paral·lel, el pod s'elimina dels Endpoints de tots els Serveis que el seleccionaven, per deixar de rebre trànsit nou (02-05).
- El kubelet envia
SIGTERMal procés PID 1 de cada contenidor. Aquí és on la teva aplicació ha de deixar d'acceptar connexions noves, acabar les que tingui en curs i tancar netament. - S'espera fins a exhaurir el termini de gràcia.
- Si en acabar el termini el procés continua viu, el kubelet envia
SIGKILL. Sense negociació possible. - Es destrueix el sandbox i l'objecte desapareix de l'API.
Per veure-ho a càmera lenta, esborra amb un termini llarg des d'una altra terminal i observa:
Conseqüències pràctiques per a Rutas Norte:
worker-notificacionsha de capturarSIGTERM. Si l'ignora, cada desplegament el mata als 30 segons ambSIGKILLa mig enviament de correu, i un client es queda sense la seva confirmació.--grace-period=0 --forceés perillós. Diu a l'API que doni el pod per mort sense esperar confirmació del kubelet. En una base de dades compostgres-reservespot provocar corrupció o dues instàncies escrivint alhora. Fes-lo servir només amb pods encallats en un node perdut.- Un termini de gràcia massa curt talla peticions en curs. Si
api-reservesté peticions que triguen 20 segons, un termini de 10 les tallarà en sec.
- Pods efímers per depurar
Un pod no és només una càrrega de treball: també és la millor navalla suïssa de diagnòstic dins del clúster. kubectl run amb --rm -it crea un pod, et dona una terminal a dins i l'esborra en sortir.
A dins tens curl, dig, nslookup, ping, tcpdump, netstat... tot el que la imatge de la teva aplicació no porta. Desglossament dels flags:
| Flag | Què fa |
|---|---|
--rm |
Esborra el pod en acabar la sessió |
-it |
Terminal interactiva connectada (stdin + tty) |
--restart=Never |
Crea un Pod solt en lloc d'un Deployment |
-- /bin/bash |
Ordre a executar dins del contenidor |
Un exemple real que farem servir molt a partir de la lliçó de Serveis: comprovar si api-reserves respon des de dins del clúster.
kubectl run test-api --rm -it --image=curlimages/curl:8.8.0 --restart=Never -- \
curl -s http://10.244.0.34:3000/disponibilitatSi el que vols és depurar un pod que ja existeix i la imatge del qual no té eines (nginx:alpine no porta ni curl), la solució moderna són els contenidors efímers, que s'injecten en un pod viu sense reiniciar-lo:
Comparteix el namespace de xarxa del contenidor nginx, així que pots fer curl http://localhost:80 com si fossis ell. És l'eina que aprofundirem a Depuració d'Aplicacions i Esdeveniments del Clúster.
- Per què en producció mai no es gestionen pods solts
Ja ho vas experimentar al final del mòdul 1, però ara que coneixes el cicle de vida pots entendre'n la raó profunda: el pod no té mecanisme de recuperació propi. El kubelet reinicia els seus contenidors, sí, però el pod com a objecte només existeix mentre algú el declari.
I hi ha situacions, molt freqüents, en què el pod desapareix sense que ningú l'esborri:
| Situació | Què li passa al pod solt |
|---|---|
| El node s'apaga o es reinicia | Desapareix per sempre; ningú el recrea en un altre node |
| El node es queda sense memòria | El kubelet el desallotja (Evicted) i no torna |
| Es drena el node per manteniment | S'elimina i no es reprograma |
| Algú l'esborra per error | No torna |
Per a Rutas Norte, qualsevol d'aquestes quatre files equival a la caiguda nocturna de quatre hores i mitja que va patir l'empresa al març. Per això la regla del projecte és taxativa:
A Rutas Norte, l'únic pod solt admès és un pod efímer de depuració amb
--rm. Tota la resta va governada per un controlador.
El que necessitem és alguna cosa que vigili permanentment i digui "vull 3 pods com aquest, sempre". Això és un ReplicaSet, i és la lliçó següent.
Abans de continuar, deixa el clúster amb els dos pods en marxa:
Errors Comuns i Consells
- Posar diversos processos sense relació al mateix pod. La pregunta de control és: "escalarien al mateix ritme i moririen alhora?". Si la resposta és no, són pods separats. Un
api-reservesamb el seuredis-cachea dins seria un error de disseny greu. - Confondre "reiniciar el contenidor" amb "recrear el pod". El primer el fa el kubelet, conserva nom i IP i puja
RESTARTS. El segon el fa un controlador i genera un objecte nou amb IP nova. - Buscar la causa d'un
CrashLoopBackOffakubectl describe. És akubectl logs --previous.describeet dirà que reinicia, no per què. - Interpretar
Runningcom a "funciona". Només vol dir que hi ha processos vius. La salut real la determinen les sondes. - Oblidar
--previousamb contenidors que reinicien. Sense ell perds justament el log que t'interessa. - Ignorar
SIGTERMa l'aplicació. És la causa número u de peticions tallades durant els desplegaments. Captura el senyal, deixa d'acceptar connexions noves, drena les obertes i surt amb codi 0. - Abusar de
--force --grace-period=0. Se salta l'aturada ordenada. En càrregues amb estat pot corrompre dades. - Posar dos contenidors escoltant al mateix port dins d'un pod. Comparteixen l'espai de ports: el segon fallarà en arrencar.
- Consell: genera l'esquelet de qualsevol manifest amb
kubectl run api-reserves --image=node:20-alpine --dry-run=client -o yaml > pod.yamli edita'l. És molt més ràpid i segur que escriure YAML des de zero. - Consell:
kubectl explain pod.spec.containers.lifecycle --recursiveés la teva documentació offline i sempre coincideix amb la versió del teu clúster.
Exercicis
Exercici 1: Diagnosticar tres pods trencats
Crea deliberadament aquests tres pods a rutas-norte-dev i, per a cadascun, identifica la fase del pod, l'estat i el motiu del contenidor i l'ordre exacta que revela la causa arrel:
pod-a: imatgenginx:9.9.9-inventada.pod-b: imatgeredis:7.2-alpineambcommand: ["redis-server", "--fitxer-inexistent"].pod-c: imatgebusybox:1.36ambcommand: ["sh", "-c", "echo informe generat"]irestartPolicy: Never.
Explica per què el tercer no és un error, encara que el seu STATUS no sigui Running.
Exercici 2: Compartició dins d'un pod
Escriu k8s/base/pod-compartit.yaml amb un pod anomenat demo-compartit a rutas-norte-dev que tingui dos contenidors i un volum emptyDir anomenat intercanvi:
escriptor: imatgebusybox:1.36, escriu cada 5 segons una línia amb la data a/dades/ocupacio.log.lector: imatgebusybox:1.36, fatail -fd'aquest mateix fitxer muntat a/entrada/ocupacio.log.
Després demostra amb ordres: (a) que el lector veu el que escriu l'escriptor, (b) que tots dos contenidors comparteixen la mateixa IP i (c) que no comparteixen el sistema de fitxers arrel.
Exercici 3: Aturada ordenada de worker-notificacions
Crea un pod worker-notificacions amb busybox:1.36 que capturi SIGTERM, escrigui al log tancant: acabant enviaments pendents, esperi 5 segons i surti. Fixa terminationGracePeriodSeconds: 30.
- Esborra'l i comprova als logs que el missatge es va escriure abans de morir.
- Repeteix l'experiment amb
terminationGracePeriodSeconds: 2i explica què canvia i per què és perillós per a Rutas Norte.
Solucions
Solució 1
kubectl run pod-a --image=nginx:9.9.9-inventada
kubectl run pod-b --image=redis:7.2-alpine -- redis-server --fitxer-inexistent
kubectl run pod-c --image=busybox:1.36 --restart=Never -- sh -c "echo informe generat"
kubectl get podsNAME READY STATUS RESTARTS AGE
pod-a 0/1 ImagePullBackOff 0 45s
pod-b 0/1 CrashLoopBackOff 3 (28s ago) 75s
pod-c 0/1 Completed 0 45s| Pod | Fase | Estat i motiu del contenidor | Ordre de diagnòstic |
|---|---|---|---|
pod-a |
Pending |
Waiting / ImagePullBackOff |
kubectl describe pod pod-a (secció Events) |
pod-b |
Running |
Waiting / CrashLoopBackOff |
kubectl logs pod-b --previous |
pod-c |
Succeeded |
Terminated / Completed, exitCode: 0 |
kubectl logs pod-c |
Warning Failed 30s kubelet Failed to pull image "nginx:9.9.9-inventada": not found
Bad directive: --fitxer-inexistent
informe generatpod-c no és un error: l'etiqueta 9.9.9-inventada de pod-a no existeix al registre i el redis-server de pod-b rebutja el paràmetre. En canvi pod-c va fer exactament el que se li va demanar, va acabar amb codi 0 i, gràcies a restartPolicy: Never, no es va reiniciar: fase Succeeded. És el comportament normal d'una tasca finita com informes-ocupacio. Si li haguéssim deixat restartPolicy: Always, entraria en CrashLoopBackOff tot i no fallar mai, perquè Always reinicia també les sortides amb èxit.
Solució 2
# k8s/base/pod-compartit.yaml
apiVersion: v1
kind: Pod
metadata:
name: demo-compartit
namespace: rutas-norte-dev
labels:
app: demo-compartit
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
containers:
- name: escriptor
image: busybox:1.36
command: ["sh", "-c"]
args: ["while true; do echo \"$(date) ocupacio calculada\" >> /dades/ocupacio.log; sleep 5; done"]
volumeMounts:
- name: intercanvi
mountPath: /dades
- name: lector
image: busybox:1.36
command: ["sh", "-c"]
args: ["sleep 3; tail -f /entrada/ocupacio.log"]
volumeMounts:
- name: intercanvi
mountPath: /entrada
volumes:
- name: intercanvi
emptyDir: {}kubectl apply -f k8s/base/pod-compartit.yaml
# (a) el lector veu el que escriu l'escriptor
kubectl logs demo-compartit -c lector --tail=3Wed Aug 5 11:02:14 UTC 2026 ocupacio calculada
Wed Aug 5 11:02:19 UTC 2026 ocupacio calculada
Wed Aug 5 11:02:24 UTC 2026 ocupacio calculada# (b) mateixa IP: la del pod
kubectl exec demo-compartit -c escriptor -- hostname -i
kubectl exec demo-compartit -c lector -- hostname -i# (c) sistemes de fitxers arrel diferents
kubectl exec demo-compartit -c escriptor -- ls /dades
kubectl exec demo-compartit -c lector -- ls /dadesEl fitxer només existeix sota /dades per a l'escriptor i sota /entrada per al lector: el volum es comparteix, el sistema de fitxers arrel no. Cada contenidor veu el volum a la ruta on l'ha muntat, i res més de l'altre.
Solució 3
# k8s/base/worker-notificacions-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: worker-notificacions
namespace: rutas-norte-dev
labels:
app: worker-notificacions
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
terminationGracePeriodSeconds: 30
containers:
- name: worker
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
trap 'echo "tancant: acabant enviaments pendents"; sleep 5; exit 0' TERM
echo "worker-notificacions a punt"
while true; do sleep 1; donekubectl apply -f k8s/base/worker-notificacions-pod.yaml
kubectl delete pod worker-notificacions --wait=false
sleep 3 && kubectl logs worker-notificacionsEl procés va rebre SIGTERM, va executar la seva rutina de tancament i va sortir pel seu propi peu dins del termini de 30 segons.
Amb terminationGracePeriodSeconds: 2, el kubelet envia SIGTERM, espera només 2 segons i etziba SIGKILL quan el worker encara és al seu sleep 5. El procés mor a mig tancament. Per a Rutas Norte això significa un correu de confirmació de reserva que es queda a mig enviar a cada desplegament: el client ha pagat el bitllet i no rep justificant. La regla és dimensionar el termini de gràcia per sobre del temps real que triga el component a drenar la feina pendent.
Conclusió
El pod ha deixat de ser una caixa negra. Saps per què Kubernetes va triar el pod i no el contenidor com a unitat mínima: hi ha processos que necessiten compartir xarxa, volums i cicle de vida, i el planificador els ha de tractar com una sola cosa indivisible. Coneixes exactament què comparteixen els seus contenidors —namespace de xarxa amb una única IP, localhost, IPC, volums, node i destí— i què no —sistema de fitxers arrel, PIDs i recursos—, i saps que qui sosté aquests namespaces és el discret contenidor de pausa, la raó que la IP sobrevisqui als reinicis.
Has escrit el manifest d'api-reserves camp per camp, distingint command/args d'ENTRYPOINT/CMD, i has après a llegir l'estat real d'un pod separant les cinc fases (Pending, Running, Succeeded, Failed, Unknown) dels tres estats de contenidor (Waiting, Running, Terminated) amb els seus motius: ContainerCreating és normal, ImagePullBackOff es diagnostica als esdeveniments i CrashLoopBackOff a kubectl logs --previous. Saps què fa restartPolicy i qui l'aplica, i has seguit el cicle de vida complet fins al final incòmode: SIGTERM, termini de gràcia i SIGKILL, la diferència entre un desplegament net i un client sense la seva confirmació de reserva. I ja tens al cinturó els pods efímers --rm -it i kubectl debug com a eines de diagnòstic.
Queda pendent el que ens ha portat fins aquí: el pod, tot sol, no es recupera. Si el node cau, si el desallotgen per manca de memòria o si algú l'esborra, api-reserves desapareix del clúster i no torna. A la lliçó següent, ReplicaSets, coneixerem el primer controlador del curs: el que executa sense descans el bucle de reconciliació que vam estudiar a l'arquitectura, compta quants pods hi ha davant de quants n'hi hauria d'haver i crea els que falten. Esborrarem un pod de botiga-web a propòsit i el veurem renéixer en segons.
Curs de Kubernetes
Mòdul 1: Introducció a Kubernetes
- Què és Kubernetes?
- Arquitectura de Kubernetes
- Conceptes i Terminologia Clau
- Configuració d'un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objectes, Manifests YAML i el Model Declaratiu
- El Projecte del Curs: la Plataforma Rutas Norte
Mòdul 2: Components Principals de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualitzacions, Rollbacks i Estratègies de Desplegament
- Serveis
- Namespaces
- Etiquetes, Selectors i Anotacions
Mòdul 3: Gestió de Configuració i Secrets
- ConfigMaps
- Secrets
- Variables d'Entorn
- Quotes i Límits de Recursos
- LimitRanges i Classes de Qualitat de Servei (QoS)
- ServiceAccounts i Accés a l'API des dels Pods
Mòdul 4: Xarxes a Kubernetes
- Xarxes de Clúster
- Tipus de Serveis
- DNS Intern i Descobriment de Serveis
- Controladors d'Ingress
- TLS i Gestió de Certificats amb cert-manager
- Polítiques de Xarxa
Mòdul 5: Emmagatzematge a Kubernetes
- Volums
- Volums Persistents
- Reclamacions de Volums Persistents
- Classes d'Emmagatzematge
- Aprovisionament Dinàmic, Expansió i Snapshots
- Còpies de Seguretat i Restauració de Dades
Mòdul 6: Conceptes Avançats de Kubernetes
- StatefulSets
- DaemonSets
- Treballs i CronJobs
- Init Containers, Sidecars i Patrons Multicontenidor
- Planificació: Afinitat, Taints i Toleracions
- Definicions de Recursos Personalitzats (CRDs)
- Operadors i el Patró Controlador
Mòdul 7: Monitoratge i Registre
- Verificacions de Salut i Sondes
- Servidor de Mètriques i kubectl top
- Monitoratge amb Prometheus
- Visualització i Alertes amb Grafana i Alertmanager
- Registre Centralitzat amb Elasticsearch, Fluentd i Kibana (EFK)
- Depuració d'Aplicacions i Esdeveniments del Clúster
Mòdul 8: Seguretat a Kubernetes
- Control d'Accés Basat en Rols (RBAC)
- Contextos de Seguretat i Enduriment del Contenidor
- Polítiques de Seguretat de Pods i Pod Security Standards
- Seguretat de Xarxa
- Seguretat d'Imatges
- Auditoria, Escaneig i Gestió de Vulnerabilitats
Mòdul 9: Escalat i Rendiment
- Autoescalat Horitzontal de Pods
- Autoescalat Vertical de Pods
- Autoescalat de Clúster
- Escalat per Esdeveniments i Mètriques Personalitzades amb KEDA
- Alta Disponibilitat: PodDisruptionBudgets i Topologia
- Ajust de Rendiment
Mòdul 10: Ecosistema i Eines de Kubernetes
- Minikube i Entorns Locals amb kind
- Kubeadm
- Helm
- Kustomize
- GitOps amb Argo CD i Flux
- Kubernetes Gestionat: EKS, AKS i GKE
Mòdul 11: Estudis de Cas i Aplicacions del Món Real
- Desplegament d'una Aplicació Web
- Execució d'Aplicacions amb Estat
- CI/CD amb Kubernetes
- Estratègies de Desplegament: Blue-Green i Canary
- Gestió Multi-Clúster
- Operació en Producció: Incidències, Runbooks i Costos
