En tancar la lliçó anterior teníem les tres senyals de l'observabilitat: les sondes diuen si un component està sa, les mètriques diuen quant i com funciona, i els logs diuen exactament què va passar. El que encara no tenim és un mètode per fer-les servir juntes.
Perquè quan a les 03:14 sona el telèfon i la botiga web retorna 502, saber fer servir Grafana i Kibana no basta. Sota pressió, sense dormir, amb el director preguntant cada cinc minuts, la diferència entre resoldre en deu minuts o en dues hores no està a conèixer més ordres: està a tenir un procediment que vagi del símptoma a la causa sense donar voltes.
Aquesta lliçó construeix aquesta metodologia. Veurem l'arbre de decisió que ordena la investigació, els esdeveniments de Kubernetes com la font més important i pitjor aprofitada —amb el fet crític que caduquen en una hora—, una taula mestra de símptoma → causa probable → ordre que ho confirma, les eines d'inspecció inclosos els contenidors efímers de kubectl debug, i finalment un incident real de Rutas Norte resolt pas a pas fent servir tot el que hem après al mòdul. És l'última lliçó del mòdul 7.
Contingut
- Una metodologia, no una llista d'ordres
- L'arbre de decisió: del símptoma a la causa
- Els esdeveniments: la font principal i pitjor aprofitada
- La taula mestra: símptoma → causes probables → ordre
- Diagnòstic detallat de cada símptoma
- Les eines d'inspecció
kubectl debug: contenidors efímers, còpies i nodes- Cas real: la botiga retorna 502 durant els desplegaments
- Recollir proves abans de reiniciar
- Errors comuns i consells
- Exercicis
- Una metodologia, no una llista d'ordres
L'error més comú en depurar a Kubernetes no és desconèixer una ordre: és començar a mirar pel lloc equivocat.
Davant d'un "el web no funciona", la reacció instintiva sol ser kubectl logs del primer pod que aparegui. Però si el pod està en Pending, no hi ha logs per llegir, perquè mai no ha arrencat. Si el problema és que el Service no té Endpoints, els logs de l'aplicació estaran perfectes i no diran res. Mitja hora perduda mirant al lloc on no és la resposta.
Una metodologia ordena la feina amb dos principis:
Principi 1: seguir el cicle de vida del pod, en ordre. Un pod passa per fases successives, i un error en una fase fa irrellevant tot el posterior. No té sentit preguntar si un pod rep trànsit si encara no ha estat programat en un node.
Principi 2: confirmar cada hipòtesi amb una ordre concreta. "Crec que és la memòria" no és un diagnòstic. kubectl get pod X -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}' retornant OOMKilled sí que ho és.
Les cinc preguntes en ordre, que són la columna vertebral de tot el que segueix:
| # | Pregunta | Si la resposta és NO, mira... |
|---|---|---|
| 1 | El pod existeix? | El controlador (Deployment, StatefulSet, CronJob) |
| 2 | Està programat en un node? | El planificador: recursos, taints, afinitat, PVC |
| 3 | Ha arrencat el contenidor? | Imatge, volums, ConfigMaps, Secrets |
| 4 | Està llest? | Les sondes de 07-01 i les seves dependències |
| 5 | Rep trànsit? | Service, EndpointSlice, selector, Ingress, NetworkPolicy |
Cada pregunta té una ordre que la respon en un segon. La disciplina consisteix a no saltar-se'n cap.
- L'arbre de decisió: del símptoma a la causa
flowchart TD
S["Símptoma reportat"] --> Q1{"Existeix el pod?<br/>kubectl get pods"}
Q1 -->|No| C1["Revisar el controlador:<br/>kubectl describe deploy/sts<br/>ReplicaSet creat? Quota exhaurida?"]
Q1 -->|Sí| Q2{"Quin és el seu STATUS?"}
Q2 -->|Pending| C2["El planificador no el col·loca:<br/>recursos, taints, afinitat,<br/>PVC sense enllaçar"]
Q2 -->|ContainerCreating| C3["El kubelet no l'arrenca:<br/>volum, Secret o ConfigMap<br/>que no existeix"]
Q2 -->|ImagePullBackOff| C4["No descarrega la imatge:<br/>nom, etiqueta,<br/>credencials del registre"]
Q2 -->|CrashLoopBackOff| C5["Arrenca i mor:<br/>logs --previous,<br/>OOMKilled, liveness"]
Q2 -->|Terminating| C6["No acaba:<br/>finalitzadors,<br/>període de gràcia"]
Q2 -->|Running| Q3{"READY és n/n?"}
Q3 -->|No| C7["Les sondes fallen:<br/>describe → esdeveniments Unhealthy<br/>07-01"]
Q3 -->|Sí| Q4{"El Service té<br/>Endpoints?"}
Q4 -->|No| C8["Selector malament, o cap<br/>pod està Ready"]
Q4 -->|Sí| Q5{"Respon per<br/>port-forward directe?"}
Q5 -->|No| C9["Problema a l'aplicació:<br/>logs, exec, mètriques"]
Q5 -->|Sí| C10["Problema a la capa de xarxa:<br/>Ingress, NetworkPolicy,<br/>DNS, TLS"]
Les ordres que responen a cada node de decisió:
NS=rutas-norte-pro
# Q1 — Existeix el pod?
kubectl -n $NS get pods -l app=api-reserves
# Q2 — Quin és el seu estat? Amb -o wide es veu a més en quin node és
kubectl -n $NS get pods -o wide
# Q3 — Està llest? La columna READY és la que importa
kubectl -n $NS get pods -l app=api-reserves
# Q4 — El Service té Endpoints? LA COMPROVACIÓ MÉS INFRAVALORADA
kubectl -n $NS get endpointslices -l kubernetes.io/service-name=api-reserves
# Q5 — Respon saltant-se el Service i l'Ingress?
kubectl -n $NS port-forward pod/api-reserves-7d9f8c4b5-x2klm 8080:8080
curl -s localhost:8080/salutLa pregunta 4 mereix un comentari especial. Un EndpointSlice buit és la causa del 80 % dels "el Service no funciona", i es comprova en dos segons:
kubectl -n rutas-norte-pro get endpointslices -l kubernetes.io/service-name=api-reserves \
-o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{"\t"}{.conditions.ready}{"\n"}{end}'Allà es veu directament quins pods estan rebent trànsit i quins no.
- Els esdeveniments: la font principal i pitjor aprofitada
Els esdeveniments són objectes de l'API de Kubernetes que registren el que els components del clúster fan i observen: el planificador quan no pot col·locar un pod, el kubelet quan falla en descarregar una imatge, el controlador d'endpoints quan actualitza un Service.
Són, de llarg, la font de diagnòstic més útil i la que menys es consulta.
Com consultar-los
NS=rutas-norte-pro
# L'ORDRE MÉS ÚTIL D'AQUESTA LLIÇÓ: esdeveniments ordenats cronològicament.
# Sense --sort-by surten en ordre arbitrari i són gairebé inútils.
kubectl -n $NS get events --sort-by=.lastTimestamp
# Només els problemes
kubectl -n $NS get events --field-selector type=Warning --sort-by=.lastTimestamp
# De tot el clúster (fonamental quan el problema és d'infraestructura)
kubectl get events -A --sort-by=.lastTimestamp | tail -40
# Els d'un objecte concret
kubectl -n $NS get events --field-selector involvedObject.name=api-reserves-7d9f8c4b5-x2klm
# Combinant selectors de camp
kubectl -n $NS get events \
--field-selector type=Warning,involvedObject.kind=Pod \
--sort-by=.lastTimestamp
# L'ordre moderna (Kubernetes 1.26+), més llegible
kubectl -n $NS events --for pod/api-reserves-7d9f8c4b5-x2klm
kubectl -n $NS events --types=Warning
kubectl -n $NS events --watch # en temps real
# I la via més freqüent a la pràctica: la secció Events de describe
kubectl -n $NS describe pod api-reserves-7d9f8c4b5-x2klm | tail -20Sortida típica:
LAST SEEN TYPE REASON OBJECT MESSAGE
5m12s Normal Scheduled pod/api-reserves-7d9f8c4b5-x2klm Successfully assigned rutas-norte-pro/api-reserves-7d9f8c4b5-x2klm to rutas-norte-worker-2
5m10s Normal Pulled pod/api-reserves-7d9f8c4b5-x2klm Container image "registry.rutasnorte.example/api-reserves:2.8.1" already present on machine
5m10s Normal Created pod/api-reserves-7d9f8c4b5-x2klm Created container api
5m09s Normal Started pod/api-reserves-7d9f8c4b5-x2klm Started container api
4m22s Warning Unhealthy pod/api-reserves-7d9f8c4b5-x2klm Readiness probe failed: HTTP probe failed with statuscode: 503
3m18s Warning BackOff pod/api-reserves-7d9f8c4b5-x2klm Back-off restarting failed container apiAquesta seqüència explica una història completa en sis línies: es va programar bé, la imatge hi era, va arrencar, la readiness va començar a fallar amb 503 i va acabar en backoff. Diagnòstic pràcticament fet.
Normal davant de Warning
| Tipus | Significat | Exemples |
|---|---|---|
Normal |
Operació esperada del cicle de vida | Scheduled, Pulled, Created, Started, Killing |
Warning |
Alguna cosa no ha anat com havia d'anar | FailedScheduling, Failed, BackOff, Unhealthy, FailedMount, Evicted |
Regla pràctica: comença sempre pels Warning. Però no ignoris els Normal: l'absència d'un esdeveniment Scheduled esperat, o un Killing que no esperaves, és informació valuosa. I en el cas de la secció 8, un esdeveniment Normal serà la peça clau del diagnòstic.
Els esdeveniments més freqüents i què signifiquen
REASON |
Emissor | Què significa |
|---|---|---|
FailedScheduling |
planificador | No hi ha cap node on càpiga el pod |
Scheduled |
planificador | Assignat a un node |
Pulling / Pulled |
kubelet | Descarregant / imatge llesta |
Failed (ErrImagePull) |
kubelet | No va poder descarregar la imatge |
Created / Started |
kubelet | Contenidor creat / arrencat |
BackOff |
kubelet | Espera exponencial abans de reintentar |
Unhealthy |
kubelet | Una sonda ha fallat (07-01) |
Killing |
kubelet | Acabant el contenidor i per què |
FailedMount |
kubelet | Volum, Secret o ConfigMap no disponible |
FailedAttachVolume |
controlador de volums | El volum continua associat a un altre node |
Evicted |
kubelet | Desallotjat per pressió de recursos del node |
NodeNotReady |
controlador de nodes | El node va deixar de reportar |
Preempting |
planificador | Desallotjant pods de menor prioritat (06-05) |
⚠️ El fet crític: els esdeveniments caduquen en una hora
Aquest és el punt que cal gravar a foc, perquè canvia per complet la manera de treballar.
Els esdeveniments de Kubernetes es desen a etcd amb un TTL per defecte d'1 hora. Passat aquest temps, l'API Server els esborra automàticament. No és una rotació per espai: és un esborrat per temps.
# Veure el TTL configurat al clúster (paràmetre de l'API Server)
kubectl -n kube-system get pod -l component=kube-apiserver \
-o jsonpath='{.items[0].spec.containers[0].command}' | tr ',' '\n' | grep event-ttlLes conseqüències són molt concretes:
- Si l'incident va passar a les 03:14 i ho investigues a les 09:00, els esdeveniments ja no existeixen.
kubectl describeno mostrarà res útil, encara que el problema continuï present. - Un pod que porta tres dies en
CrashLoopBackOffnomés té esdeveniments de l'última hora, no del moment en què va començar a fallar. - L'evidència més valuosa per reconstruir una cronologia desapareix sola, sense avisar.
Com resoldre-ho: exportar els esdeveniments.
Opció A — Captura manual i immediata. El primer que fas en començar a investigar:
kubectl get events -A --sort-by=.lastTimestamp \
-o json > /tmp/esdeveniments-incident-$(date +%Y%m%d-%H%M).jsonOpció B — kube-state-metrics. Com vam veure a 07-03, exposa mètriques de l'estat dels objectes. No són els esdeveniments en si, però permeten reconstruir molt:
# Reinicis al llarg del temps: sobreviu al TTL dels esdeveniments
increase(kube_pod_container_status_restarts_total{namespace="rutas-norte-pro"}[1h])
# Motiu de l'última terminació del contenidor
kube_pod_container_status_last_terminated_reason{namespace="rutas-norte-pro"}Opció C (la recomanada) — Exportar els esdeveniments al sistema de logs. Un component com kubernetes-event-exporter observa els esdeveniments i els envia a Elasticsearch o Loki, on queden subjectes a la retenció de 30 dies de 07-05, no a la d'una hora.
# k8s/base/registre/event-exporter-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: event-exporter-config
namespace: registre
data:
config.yaml: |
logLevel: info
logFormat: json
route:
routes:
- match:
- receiver: registre-centralitzat
receivers:
- name: registre-centralitzat
# Sortida a stdout: la recull Fluent Bit com qualsevol altre log,
# amb la qual cosa hereta tota la pila i la retenció de 07-05.
stdout:
deDot: true
layout:
timestamp: "{{ .LastTimestamp }}"
nivell: "{{ if eq .Type \"Warning\" }}warn{{ else }}info{{ end }}"
component: "kubernetes-esdeveniments"
missatge: "{{ .Message }}"
motiu: "{{ .Reason }}"
objecte_tipus: "{{ .InvolvedObject.Kind }}"
objecte_nom: "{{ .InvolvedObject.Name }}"
namespace: "{{ .InvolvedObject.Namespace }}"
node: "{{ .Source.Host }}"
recompte: "{{ .Count }}"Amb això, a Kibana es pot buscar:
i veure tots els OOMKilled de les últimes quatre setmanes. És una de les millores de major relació valor/esforç de tota l'observabilitat.
Regla operativa de Rutas Norte: la primera ordre de qualsevol investigació és capturar els esdeveniments a fitxer. La segona, mirar l'hora de l'incident i comprovar si el TTL ja se'ls ha emportat.
- La taula mestra: símptoma → causes probables → ordre
Aquesta taula és el mapa mental que cal interioritzar. Cada fila desenvolupa el seu diagnòstic a la secció següent.
| Símptoma | Causes probables (per ordre de freqüència) | Ordre que ho confirma |
|---|---|---|
Pending |
Recursos insuficients; PVC sense enllaçar; taints sense tolerar; afinitat impossible; quota exhaurida | kubectl describe pod → esdeveniment FailedScheduling |
ImagePullBackOff / ErrImagePull |
Nom o etiqueta malament; credencials del registre; imatge esborrada; xarxa del node | kubectl describe pod → esdeveniment Failed amb el missatge del registre |
CrashLoopBackOff |
Error d'arrencada; configuració absent; OOMKilled; liveness massa agressiva; ordre malament |
kubectl logs --previous |
OOMKilled (sortida 137) |
limits.memory curt; fuita de memòria; pic legítim |
kubectl get pod -o jsonpath='{...lastState.terminated}' |
Error / Completed inesperat |
Codi de sortida diferent de 0; procés que acaba quan no ha de fer-ho | kubectl logs --previous + exitCode |
ContainerCreating encallat |
Secret o ConfigMap inexistent; PVC no enllaçat; volum encallat en un altre node | kubectl describe pod → esdeveniment FailedMount |
Terminating etern |
Finalitzadors pendents; procés que ignora SIGTERM; node caigut |
kubectl get pod -o jsonpath='{.metadata.finalizers}' |
0/3 Ready |
Sonda de readiness fallant; dependència caiguda | kubectl describe pod → esdeveniment Unhealthy |
503 de l'Ingress |
Service sense Endpoints; selector malament; cap pod llest | kubectl get endpointslices |
502 de l'Ingress |
El backend tanca la connexió; cursa del SIGTERM; timeout |
Logs del controlador d'Ingress + preStop (07-01) |
Evicted |
Pressió de memòria o disc al node; QoS BestEffort |
kubectl describe node → condicions de pressió |
- Diagnòstic detallat de cada símptoma
Pending: el planificador no el col·loca
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 2m14s default-scheduler 0/4 nodes are available:
1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: },
2 Insufficient memory,
1 node(s) had volume node affinity conflict.
preemption: 0/4 nodes are available: 4 No preemption victims found.El missatge de FailedScheduling és un diagnòstic complet, node per node. Traducció d'aquesta línia:
| Fragment | Significat | Solució |
|---|---|---|
1 node(s) had untolerated taint |
El pla de control té taint (06-05) | Correcte: no volem càrregues allà |
2 Insufficient memory |
Dos nodes sense memòria lliure per a la requests |
Baixar la requests (07-02) o afegir nodes |
1 node(s) had volume node affinity conflict |
El PV existeix en una altra zona (05-02) | El pod ha d'anar on és el seu volum |
Comprovacions complementàries:
# Quant queda realment reservat a cada node?
kubectl describe node rutas-norte-worker-2 | grep -A 8 "Allocated resources"
# S'ha exhaurit la ResourceQuota del namespace? (03-04)
kubectl -n rutas-norte-pro describe resourcequota
# El PVC està enllaçat?
kubectl -n rutas-norte-pro get pvc
# Quins taints tenen els nodes?
kubectl get nodes -o custom-columns=NOM:.metadata.name,TAINTS:.spec.taintsUn cas que despista molt: si la ResourceQuota està exhaurida, el pod ni tan sols es crea. No veuràs un pod en Pending: no veuràs cap pod. L'error és al ReplicaSet:
Warning FailedCreate 1m replicaset-controller Error creating: pods "api-reserves-7d9f8c4b5-" is
forbidden: exceeded quota: quota-pro, requested: requests.memory=512Mi, used: requests.memory=15872Mi,
limited: requests.memory=16GiÉs la resposta a la pregunta 1 de l'arbre de decisió: quan el pod no existeix, cal mirar el controlador.
ImagePullBackOff i ErrImagePull
Events:
Warning Failed 45s (x4 over 2m) kubelet Failed to pull image
"registry.rutasnorte.example/api-reserves:2.8.2": rpc error: code = NotFound
desc = failed to pull and unpack image: not found
Warning Failed 45s (x4 over 2m) kubelet Error: ErrImagePull
Normal BackOff 20s (x6 over 2m) kubelet Back-off pulling imageDiferència entre els dos estats: ErrImagePull és el primer error; ImagePullBackOff és l'estat després de diversos intents, amb espera exponencial creixent.
Les quatre causes i els seus missatges distintius:
| Missatge del registre | Causa | Solució |
|---|---|---|
not found / manifest unknown |
Etiqueta o nom incorrecte | Verificar l'etiqueta exacta |
unauthorized / authentication required |
Falta l'imagePullSecrets |
Crear el Secret de tipus docker-registry |
no such host |
El node no resol el registre | DNS del node, no del clúster |
context deadline exceeded |
Xarxa lenta o imatge enorme | Ampliar timeout, precarregar la imatge |
# Verificar el nom exacte de la imatge declarada
kubectl -n rutas-norte-pro get deploy api-reserves \
-o jsonpath='{.spec.template.spec.containers[*].image}'
# Està declarat el secret del registre?
kubectl -n rutas-norte-pro get deploy api-reserves \
-o jsonpath='{.spec.template.spec.imagePullSecrets}'
# Crear el secret si falta
kubectl -n rutas-norte-pro create secret docker-registry registre-rutasnorte \
--docker-server=registry.rutasnorte.example \
--docker-username=desplegaments \
--docker-password="$REGISTRY_PASSWORD"Un cas especialment traïdor: etiquetes mutables. Si algú fa servir :latest (cosa que la convenció de Rutas Norte prohibeix expressament: etiquetes d'imatge immutables), el pod pot funcionar en un node on la imatge està en cau i fallar en un altre on no. Un problema intermitent i inexplicable.
CrashLoopBackOff: arrenca i mor
El més comú i el que més passos requereix.
Pas 1: els logs de l'execució anterior. El contenidor actual acaba d'arrencar i no dirà res; la causa és al que va morir.
{"nivell":"fatal","component":"api-reserves","missatge":"Variable d'entorn DB_PASSWORD no definida"}Diagnòstic immediat. Però moltes vegades els logs són buits, i aleshores:
Pas 2: el motiu i el codi de sortida de la terminació.
kubectl -n rutas-norte-pro get pod api-reserves-7d9f8c4b5-x2klm \
-o jsonpath='{.status.containerStatuses[0].lastState.terminated}' | jq{
"containerID": "containerd://3f8a2c...",
"exitCode": 137,
"finishedAt": "2026-08-06T03:14:22Z",
"reason": "OOMKilled",
"startedAt": "2026-08-06T03:12:58Z"
}Taula de codis de sortida:
| Codi | Significat | Causa habitual |
|---|---|---|
0 |
Terminació neta | El procés va acabar quan no havia de fer-ho |
1 |
Error genèric de l'aplicació | Excepció no capturada |
2 |
Ús incorrecte d'una ordre de shell | Arguments malament |
126 |
L'ordre no és executable | Falta permís d'execució |
127 |
Ordre no trobada | Binari absent a la imatge |
137 |
SIGKILL (128+9) |
OOMKilled o període de gràcia exhaurit |
139 |
SIGSEGV (128+11) |
Violació de segment |
143 |
SIGTERM (128+15) |
Terminació normal per Kubernetes |
Pas 3: descartar la causa de 07-01. Una livenessProbe massa agressiva mata contenidors perfectament sans que simplement triguen a arrencar. L'indici:
Warning Unhealthy 2m (x9 over 5m) kubelet Liveness probe failed: Get "http://10.244.2.17:8080/salut":
context deadline exceeded (Client.Timeout exceeded while awaiting headers)
Normal Killing 2m (x3 over 5m) kubelet Container api failed liveness probe, will be restartedSi l'esdeveniment Killing ve precedit d'Unhealthy i els logs de l'aplicació no mostren cap error, gairebé segur que la sonda és el problema, no l'aplicació. La solució és la startupProbe de 07-01.
Pas 4: el truc per depurar un contenidor que mor a l'instant. Si el contenidor mor en menys d'un segon, no dóna temps a fer exec. Es llança una còpia amb l'ordre substituïda per una cosa que no acabi:
kubectl -n rutas-norte-pro debug pod/api-reserves-7d9f8c4b5-x2klm \
--copy-to=api-depuracio \
--container=api \
-- sleep 3600
# Ara el pod viu i s'hi pot entrar a investigar
kubectl -n rutas-norte-pro exec -it api-depuracio -c api -- shA dins es pot comprovar tot el que l'aplicació necessitava:
env | grep DB_ # hi són, les variables?
ls -la /etc/config/ # està muntat el ConfigMap?
cat /etc/secrets/db # el Secret té el que espera?
node server.js # executar a mà i veure l'error completOOMKilled i el codi 137
# Buscar tots els OOMKilled del namespace
kubectl -n rutas-norte-pro get pods -o json | jq -r '
.items[] |
select(.status.containerStatuses[]?.lastState.terminated.reason == "OOMKilled") |
"\(.metadata.name)\t\(.status.containerStatuses[0].lastState.terminated.finishedAt)"'Diferenciar les dues situacions, que exigeixen solucions diferents:
| Situació | Com distingir-la | Solució |
|---|---|---|
| Límit massa baix | El consum és estable i proper al límit | Pujar limits.memory (07-02) |
| Fuita de memòria | El consum creix linealment fins a morir | Arreglar el codi; el límit només retarda |
La distinció es fa amb la mètrica de 07-03:
container_memory_working_set_bytes{namespace="rutas-norte-pro", pod=~"api-reserves-.*", container="api"}Una serra ascendent que arriba al límit i cau a zero repetidament és la signatura inconfusible d'una fuita de memòria. Un valor estable que un dia frega el límit és un límit curt.
Error i Completed inesperat
Un pod en Completed amb restartPolicy: Always acaba en CrashLoopBackOff, perquè Kubernetes el reinicia i torna a acabar. La causa habitual: el procés principal no és un servei de llarga durada.
kubectl -n rutas-norte-pro get pod X -o jsonpath='{.spec.containers[0].command} {.spec.containers[0].args}'Errors típics: un command que executa un script que acaba, un servidor llançat en segon pla mentre PID 1 surt, o un CMD que arrenca en mode dimoni.
Per al CronJob informes-ocupacio (06-03), en canvi, Completed amb codi 0 és el correcte. El diagnòstic és l'oposat:
kubectl -n rutas-norte-pro get jobs -l app=informes-ocupacio
kubectl -n rutas-norte-pro logs job/informes-ocupacio-28934520
kubectl -n rutas-norte-pro describe job informes-ocupacio-28934520 | grep -A 5 "Pod Statuses"ContainerCreating encallat
El pod està programat però el kubelet no aconsegueix crear el contenidor.
Events:
Warning FailedMount 1m (x8 over 5m) kubelet MountVolume.SetUp failed for volume
"config-api" : configmap "configuracio-api-reserves" not foundLes quatre causes:
| Causa | Missatge | Comprovació |
|---|---|---|
| ConfigMap inexistent | configmap "X" not found |
kubectl get cm X |
| Secret inexistent | secret "X" not found |
kubectl get secret X |
| PVC no enllaçat | PersistentVolumeClaim is not bound |
kubectl get pvc |
| Volum en un altre node | Multi-Attach error for volume |
kubectl get volumeattachment |
L'últim és el més pesat. Un volum ReadWriteOnce (05-03) només pot estar muntat en un node alhora. Si el pod es recrea en un altre node abans que el volum s'alliberi de l'anterior, es queda bloquejat:
Warning FailedAttachVolume 2m attachdetach-controller Multi-Attach error for volume
"pvc-4f8a2c91-..." Volume is already exclusively attached to one node and can't be attached to anotherEs resol sol en uns minuts quan el CSI allibera el volum. Si el node original és mort, pot trigar sis minuts o més. És una raó de pes perquè postgres-reserves sigui un StatefulSet (06-01) amb planificació estable.
Pod Terminating etern
Tres causes, per ordre de freqüència:
Causa 1: finalitzadors. Un finalitzador és un marcador que impedeix l'esborrat fins que un controlador faci la seva feina de neteja. Si aquell controlador està caigut, l'objecte es queda bloquejat per sempre.
El primer és normal (protegeix el PVC). El segon és d'un controlador propi: si aquell controlador està caigut, cal arreglar-lo.
Forçar l'esborrat eliminant el finalitzador és l'última opció, perquè salta la neteja que aquell finalitzador garantia:
# ⚠️ ÚLTIM RECURS: pot deixar recursos orfes
kubectl -n rutas-norte-pro patch pod X -p '{"metadata":{"finalizers":null}}' --type=mergeCausa 2: el procés ignora el SIGTERM. Com vam veure a 07-01, si PID 1 és un shell que no propaga senyals, el procés real no rep mai el SIGTERM i cal esperar al SIGKILL del final del període de gràcia. Si terminationGracePeriodSeconds és 3600, el pod triga una hora a morir.
Causa 3: el node està caigut. Si el kubelet no respon, ningú confirma que el pod ha mort. Kubernetes espera (5 minuts per defecte) abans de donar-lo per perdut.
0/3 Ready: les sondes fallen
Running amb 0/1 és la signatura exacta d'una readiness fallant. El contenidor és viu, però no entra als Endpoints.
Warning Unhealthy 30s (x48 over 8m) kubelet Readiness probe failed:
HTTP probe failed with statuscode: 503El pas següent és executar la sonda a mà des de dins del pod, que és el que dóna la resposta real:
kubectl -n rutas-norte-pro exec -it api-reserves-7d9f8c4b5-x2klm -c api -- \
curl -s -w "\nHTTP %{http_code}\n" localhost:8080/preparat{
"estat": "no-preparat",
"motiu": "pool de connexions exhaurit",
"comprovacions": { "poolLliure": false }
}Allà és la causa, i és exactament l'escenari amb què vam obrir 07-01. La readiness està fent la seva feina: apartar el pod fins que pugui servir. El problema no és la sonda, és PostgreSQL.
503 i 502 de l'Ingress
Són errors diferents amb causes diferents, i confondre'ls costa temps.
| Codi | Significat | Causa a Kubernetes |
|---|---|---|
| 503 Service Unavailable | L'Ingress no té a qui enviar | Service sense Endpoints; cap pod Ready |
| 502 Bad Gateway | El backend existeix però ha fallat | El pod va tancar la connexió; timeout; cursa del SIGTERM |
Diagnòstic del 503:
# Hi ha Endpoints? Si està buit, allà és la resposta
kubectl -n rutas-norte-pro get endpointslices -l kubernetes.io/service-name=botiga-web
# Coincideix el selector del Service amb les etiquetes dels pods?
kubectl -n rutas-norte-pro get svc botiga-web -o jsonpath='{.spec.selector}'
kubectl -n rutas-norte-pro get pods -l app=botiga-web --show-labelsUna font clàssica d'error amb la nostra convenció: el Service selecciona app i entorn, i algú desplega els pods amb entorn: produccio en lloc d'entorn: pro. Els pods estan perfectes, el Service està perfecte, i no es troben.
Diagnòstic del 502: és el cas de la secció 8.
- Les eines d'inspecció
kubectl describe: la vista completa
Les seccions que cal llegir, per ordre d'utilitat:
| Secció | Què buscar |
|---|---|
Events (al final) |
Comença sempre per aquí |
Status / Conditions |
Ready, ContainersReady, PodScheduled i els seus motius |
Containers → State / Last State |
Estat actual i motiu de la terminació anterior |
Containers → Restart Count |
Quantes vegades ha mort |
Node |
On és: permet correlacionar amb problemes del node |
Mounts / Volumes |
Què munta i d'on |
QoS Class |
Guaranteed, Burstable o BestEffort (03-05) |
kubectl logs, exec i port-forward
Ja els coneixem de 01-05 i 07-05. Els usos específics per depurar:
# Logs de l'execució anterior: L'ordre davant d'un CrashLoopBackOff
kubectl -n rutas-norte-pro logs POD --previous -c api
# Entrar al contenidor
kubectl -n rutas-norte-pro exec -it POD -c api -- sh
# Comprovacions habituals un cop a dins
env | sort
cat /etc/config/aplicacio.yaml
nslookup postgres-reserves.rutas-norte-pro.svc.cluster.local
wget -qO- http://localhost:8080/preparat
# port-forward: SALTAR-SE l'Ingress i el Service per aïllar el problema
kubectl -n rutas-norte-pro port-forward pod/api-reserves-7d9f8c4b5-x2klm 8080:8080El port-forward directe al pod és la prova d'aïllament més valuosa que existeix. Si funciona, has descartat de cop: l'Ingress, el certificat TLS, el Service, el kube-proxy i les NetworkPolicies. El problema és a la capa de xarxa, no a l'aplicació. I si no funciona, el problema és a l'aplicació i no cal mirar la xarxa.
Un pod netshoot per a problemes de xarxa
Les imatges de producció són mínimes i no porten eines de xarxa. Un pod exhaurible amb tot l'instrumental:
kubectl -n rutas-norte-pro run netshoot --rm -it \
--image=nicolaka/netshoot:latest \
--labels="app=depuracio,entorn=pro" \
--restart=Never -- bash# Resol el DNS del clúster? (04-03)
nslookup postgres-reserves.rutas-norte-pro.svc.cluster.local
dig +short api-reserves.rutas-norte-pro.svc.cluster.local
# S'arriba al port? Confirma o descarta NetworkPolicy (04-06)
nc -zv postgres-reserves 5432
curl -v http://api-reserves/salut
# Quina és la ruta i on es perd?
traceroute 10.244.2.17
mtr --report --report-cycles 10 api-reserves
# Capturar trànsit
tcpdump -i any -n port 8080Avís important: les etiquetes del pod importen. A rutas-norte-pro hi ha una NetworkPolicy deny-all, així que un pod netshoot sense les etiquetes correctes no podrà parlar amb res, i confondràs un problema de política amb un problema d'aplicació. Per això el llancem amb --labels.
kubectl debug: contenidors efímers, còpies i nodes
kubectl debug: contenidors efímers, còpies i nodeskubectl debug és l'eina més potent i menys coneguda, estable des de Kubernetes 1.25. Té tres modes molt diferents.
Mode 1: contenidors efímers
Un contenidor efímer s'afegeix a un pod que ja està corrent, compartint el seu namespace de xarxa i opcionalment el de processos. És la solució al problema de les imatges distroless, que no tenen ni shell:
kubectl -n rutas-norte-pro debug -it api-reserves-7d9f8c4b5-x2klm \
--image=nicolaka/netshoot:latest \
--target=api \
-- bash| Flag | Funció |
|---|---|
--image |
Imatge amb les eines que necessites |
--target=api |
Comparteix el namespace de processos amb aquell contenidor |
-it |
Sessió interactiva |
Amb --target, des del contenidor efímer veus els processos del contenidor original:
PID USER COMMAND
1 node node /app/server.js ← el procés d'api-reserves
28 root bash ← la nostra sessió de depuracióI des d'allà:
# Veure els descriptors de fitxer oberts del procés real
ls -l /proc/1/fd | head -20
# Veure les seves variables d'entorn
cat /proc/1/environ | tr '\0' '\n'
# Bolcar la pila de la JVM o fer un perfilat
kill -QUIT 1
# Comprovar la xarxa DES DE dins del pod, amb la seva mateixa IP
curl -v localhost:8080/preparat
netstat -tulpnPunts importants:
- El contenidor efímer no es pot eliminar un cop afegit: es queda fins que el pod mori.
- No té sondes ni
resources, i no pot modificar el pod. - No reinicia res: el pod continua servint trànsit mentre investigues. És el seu gran avantatge.
Mode 2: --copy-to, depurar una còpia sense tocar producció
Crea un pod nou, còpia de l'original, amb les modificacions que li demanis. El pod original no es toca.
# Còpia amb un contenidor de depuració afegit
kubectl -n rutas-norte-pro debug api-reserves-7d9f8c4b5-x2klm \
--copy-to=api-depuracio \
--image=nicolaka/netshoot:latest \
--share-processes \
-it -- bash
# Còpia amb l'ordre substituïda: per depurar un CrashLoopBackOff
kubectl -n rutas-norte-pro debug api-reserves-7d9f8c4b5-x2klm \
--copy-to=api-depuracio \
--container=api \
-- sleep 7200
# Còpia amb una altra imatge: provar si una versió anterior funciona
kubectl -n rutas-norte-pro debug api-reserves-7d9f8c4b5-x2klm \
--copy-to=api-versio-anterior \
--set-image=api=registry.rutasnorte.example/api-reserves:2.8.0Per què és tan valuós: el pod copiat no porta les etiquetes del selector del Service, així que no rep trànsit de producció. Pots rebentar-lo, reiniciar-lo, modificar-lo i experimentar sense que cap client ho noti. Quan acabes:
Aquest és el mode que resol el dilema clàssic de "necessito depurar en producció però no puc tocar producció".
Mode 3: kubectl debug node/, entrar al node
Quan el problema és del node i no del pod:
Crea un pod en aquell node amb el sistema de fitxers de l'amfitrió muntat a /host:
chroot /host
# Espai al disc: la causa més comuna de desallotjaments
df -h
# Què està passant al node?
top
journalctl -u kubelet --since "1 hour ago" | tail -50
crictl ps -a | head
crictl logs <id-contenidor>
# Els logs dels contenidors, tal com els vam veure a 07-05
ls -la /var/log/containers/ | grep api-reservesAdvertiment de seguretat:
kubectl debug node/crea un pod privilegiat amb accés complet al node. Qui pugui executar-lo controla efectivament aquella màquina i pot llegir els secrets de tots els pods que hi corren. S'ha de restringir per RBAC a un grup molt reduït. El mòdul 8 desenvolupa aquests controls.
Taula comparativa dels tres modes
| Mode | Toca el pod original | Rep trànsit | Per a què |
|---|---|---|---|
| Contenidor efímer | Sí (afegeix contenidor) | Sí, continua servint | Inspeccionar un pod viu sense shell |
--copy-to |
No | No | Experimentar sense risc; depurar CrashLoopBackOff |
node/ |
No (pod nou) | No | Problemes del node: disc, kubelet, runtime |
- Cas real: la botiga retorna 502 durant els desplegaments
Ara ho ajuntem tot. Aquest és l'incident que ha estat obert des de 02-04, i el tancarem fent servir les tres senyals del mòdul.
El símptoma
Cada vegada que es desplega una versió nova d'api-reserves a rutas-norte-pro, durant uns 90 segons alguns clients reben un error 502 en intentar comprar. No tots, i no sempre els mateixos. Fora dels desplegaments, tot va perfecte.
L'equip ho porta ignorant mesos: "només passa en desplegar, i se'n va sol".
Pas 1 — La mètrica confirma que el problema existeix i l'acota
Al quadre de comandament de 07-04, panell d'errors d'api-reserves, amb el rang posat al desplegament d'ahir:
sum(rate(rutasnorte_peticions_total{entorn="pro", codi=~"5.."}[1m]))
/ sum(rate(rutasnorte_peticions_total{entorn="pro"}[1m]))El gràfic mostra tres pics d'error del 4-6 %, d'uns 25 segons cadascun, separats per uns 40 segons. La taxa d'error és zero abans i després.
Primera pista important: tres pics, no un. El Deployment té 6 rèpliques amb maxSurge: 2, així que el desplegament avança en tres tandes. Un pic per tanda. Això descarta un problema puntual d'arrencada de la versió nova: és un patró que es repeteix amb cada substitució de pods.
I una segona consulta descarta el proveïdor extern:
sum(rate(rutasnorte_passarela_pagaments_crides_total{resultat=~"error|timeout"}[1m]))
/ sum(rate(rutasnorte_passarela_pagaments_crides_total[1m]))Plana a zero durant tot el desplegament. El problema és nostre.
Pas 2 — Els logs diuen qui falla i qui no
A Kibana, acotant a la finestra del desplegament:
Resultat: 4 812 documents, tots de botiga-web (que actua de proxy cap a l'API), cap d'api-reserves.
Això és molt revelador i cal aturar-se a pensar-ho. Si api-reserves estigués generant errors, els seus propis logs mostrarien codis 500. No n'hi ha ni un. api-reserves no va veure mai aquelles peticions. El 502 el genera qui intenta parlar amb ella.
Consulta següent, sobre els logs del controlador d'Ingress:
upstream prematurely closed connection while reading response header from upstream,
upstream: "http://10.244.2.17:8080/api/reserves""Upstream prematurely closed connection": el backend va tancar la connexió mentre l'Ingress esperava la resposta. No la va rebutjar (això donaria connection refused): la va acceptar i la va tancar a mitges.
Pas 3 — Els esdeveniments donen la cronologia exacta
Aquí és on l'exportador d'esdeveniments de la secció 3 demostra el seu valor: el desplegament va ser ahir, així que els esdeveniments originals van caducar fa 20 hores. Però són a Elasticsearch:
component: "kubernetes-esdeveniments" and namespace: "rutas-norte-pro"
and objecte_nom: api-reserves-*Ordenat cronològicament:
14:32:01 Normal Killing api-reserves-7d9f8c4b5-x2klm Stopping container api
14:32:01 Normal Scheduled api-reserves-9f2c1a8b6-kp3nm Successfully assigned to worker-1
14:32:04 Normal Started api-reserves-9f2c1a8b6-kp3nm Started container api
14:32:19 Normal Killing api-reserves-7d9f8c4b5-mn8pq Stopping container apiI ara l'observació decisiva, que exigeix mirar la IP:
El pod 10.244.2.17 (api-reserves-7d9f8c4b5-x2klm) va rebre "Killing" a les 14:32:01.
Els errors 502 cap a 10.244.2.17 als logs de l'Ingress estan datats
entre les 14:32:01,340 i les 14:32:03,180.L'Ingress va continuar enviant trànsit a aquell pod durant 1,8 segons després que el kubelet comencés a acabar-lo.
Aquest és exactament el fenomen que vam estudiar a 07-01: la cursa entre la retirada de l'Endpoint i el SIGTERM. Quan Kubernetes decideix acabar un pod, dos camins avancen en paral·lel sense coordinar-se: el kubelet mana SIGTERM gairebé a l'instant, mentre que la retirada de l'endpoint s'ha de propagar pel controlador d'endpoints, kube-proxy i el controlador d'Ingress. El procés mor abans que deixin d'enviar-li trànsit.
Pas 4 — Confirmar la hipòtesi amb el manifest
kubectl -n rutas-norte-pro get deploy api-reserves -o yaml | \
grep -A 6 -E "terminationGracePeriodSeconds|lifecycle|preStop|readinessProbe" terminationGracePeriodSeconds: 30
containers:
- name: api
readinessProbe:
httpGet:
path: /preparat
port: httpConfirmat: no hi ha lifecycle.preStop. El manifest que va arribar a producció es va quedar sense aquella part del que vam dissenyar a 07-01.
I una comprovació addicional que refina el diagnòstic:
Un segon problema: maxUnavailable: 1 permet que durant el desplegament hi hagi només 5 pods servint en lloc de 6, cosa que agreuja l'impacte de cada pic.
Pas 5 — Reproduir en preproducció
No s'arregla producció a cegues. Es reprodueix a rutas-norte-pre amb càrrega sintètica:
# Terminal 1: generar càrrega constant
kubectl -n rutas-norte-pre run carrega --rm -it --image=williamyeh/hey --restart=Never -- \
-z 300s -c 20 http://api-reserves/api/rutes
# Terminal 2: observar els Endpoints en temps real
kubectl -n rutas-norte-pre get endpointslices -l kubernetes.io/service-name=api-reserves -w
# Terminal 3: provocar el desplegament
kubectl -n rutas-norte-pre rollout restart deployment/api-reservesResultat: 1,4 % d'errors durant el desplegament. Reproduït.
Pas 6 — La correcció
# k8s/entorns/pro/api-reserves-desplegament.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-reserves
namespace: rutas-norte-pro
spec:
replicas: 6
minReadySeconds: 15
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2
maxUnavailable: 0 # CORRECCIÓ 2: mai menys de 6 servint
template:
spec:
# CORRECCIÓ 3: 45 s en total. Amb el preStop de 10 s, queden 35 s
# perquè l'aplicació tanqui netament després del SIGTERM.
terminationGracePeriodSeconds: 45
containers:
- name: api
# CORRECCIÓ 1 (la principal): guanyar la cursa contra el SIGTERM.
# Durant aquests 10 segons el contenidor CONTINUA SERVINT amb
# normalitat, mentre la retirada de l'endpoint es propaga pel
# controlador d'endpoints, kube-proxy i l'Ingress.
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
readinessProbe:
httpGet:
path: /preparat
port: http
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 2I una correcció complementària al controlador d'Ingress, perquè reaccioni més ràpid als canvis d'endpoints:
# Anotacions a l'Ingress d'api-reserves
metadata:
annotations:
# Reintentar en un altre backend si aquest tanca la connexió
nginx.ingress.kubernetes.io/proxy-next-upstream: "error timeout http_502"
nginx.ingress.kubernetes.io/proxy-next-upstream-tries: "2"Pas 7 — Verificar
kubectl -n rutas-norte-pre apply -f k8s/entorns/pre/api-reserves-desplegament.yaml
# Repetir la prova de càrrega del pas 5Resultat: 0 errors durant el desplegament. El mateix escenari que abans produïa un 1,4 %.
Es porta a producció i es verifica al quadre de comandament de 07-04:
Plana a zero durant el desplegament.
Pas 8 — Prevenir la recurrència
L'incident no està tancat fins que no pugui repetir-se:
- Alerta específica (07-04): detectar errors durant els desplegaments, en lloc d'esperar que un client es queixi.
- alert: ErrorsDurantDesplegament
expr: |
(
sum(rate(rutasnorte_peticions_total{entorn="pro", codi=~"5.."}[2m]))
/ sum(rate(rutasnorte_peticions_total{entorn="pro"}[2m]))
) > 0.01
and on()
(changes(kube_deployment_status_observed_generation{
namespace="rutas-norte-pro"}[10m]) > 0)
for: 2m
labels:
severity: avis
equip: plataforma
annotations:
summary: "Hi ha errors 5xx coincidint amb un desplegament"
description: >
Taxa d'error del {{ $value | humanizePercentage }} mentre un
Deployment s'està actualitzant. Revisar preStop, readinessProbe i
maxUnavailable del component afectat.
runbook_url: "https://runbooks.rutasnorte.example/errors-desplegament"-
Comprovació automàtica a la integració contínua: rebutjar qualsevol manifest d'un component amb Service que no declari
preStopireadinessProbe. -
Prova de càrrega durant el desplegament com a pas obligatori a
rutas-norte-preabans de promocionar a producció.
La lliçó del cas
Cap senyal per si sola resolia el problema:
| Senyal | Què va aportar | Què no podia aportar |
|---|---|---|
| Mètriques (07-03/04) | Que existia, quan i amb quin patró de tres pics | Per què |
| Logs (07-05) | Que el 502 el genera l'Ingress, no l'API; el missatge exacte | Quan es va acabar cada pod |
| Esdeveniments (07-06) | La cronologia exacta al mil·lisegon | L'impacte en clients |
| Sondes (07-01) | El marc conceptual: la cursa del SIGTERM |
L'evidència |
I un detall que no és menor: els esdeveniments originals havien caducat 20 hores abans. Sense l'exportador d'esdeveniments a Elasticsearch de la secció 3, el pas 3 —el que va donar la resposta— hauria estat impossible, i l'equip hauria continuat amb teories.
- Recollir proves abans de reiniciar
Aquest apartat curt és el consell més important de la lliçó.
Quan alguna cosa falla, l'impuls universal és reiniciar-la. kubectl delete pod, kubectl rollout restart, reiniciar el node. I moltes vegades funciona: el símptoma desapareix.
Però reiniciar destrueix l'evidència. I si no saps per què va fallar, tornarà a fallar, probablement en pitjor moment.
Què es perd exactament en esborrar un pod:
| Evidència | Es perd? |
|---|---|
| Logs del contenidor actual | Sí, si no estan centralitzats (07-05) |
Logs de l'execució anterior (--previous) |
Sí, irrecuperablement |
| Estat de la memòria i els processos | Sí |
| Descriptors de fitxer i connexions obertes | Sí |
Contingut de volums emptyDir |
Sí |
lastState.terminated amb el codi de sortida |
Sí |
| Esdeveniments del pod | Sí, tan bon punt passa el TTL d'1 hora |
| Mètriques | No (Prometheus les conserva) |
| Contingut de PVC | No |
La llista de comprovació de captura
Executa-la abans de tocar res. Són 30 segons i salva la investigació:
#!/bin/bash
# capturar-evidencies.sh <namespace> <pod>
# Executar SEMPRE abans d'esborrar o reiniciar un pod problemàtic.
NS=$1
POD=$2
DIR="/tmp/incident-$(date +%Y%m%d-%H%M%S)-${POD}"
mkdir -p "$DIR"
echo "Capturant evidències a $DIR ..."
# 1. Definició i estat complet del pod
kubectl -n "$NS" get pod "$POD" -o yaml > "$DIR/pod.yaml"
# 2. describe: inclou els esdeveniments, que CADUQUEN EN 1 HORA
kubectl -n "$NS" describe pod "$POD" > "$DIR/describe.txt"
# 3. Logs actuals i anteriors, de TOTS els contenidors
for C in $(kubectl -n "$NS" get pod "$POD" \
-o jsonpath='{.spec.containers[*].name} {.spec.initContainers[*].name}'); do
kubectl -n "$NS" logs "$POD" -c "$C" > "$DIR/logs-$C.txt" 2>&1
kubectl -n "$NS" logs "$POD" -c "$C" --previous > "$DIR/logs-$C-previ.txt" 2>&1
done
# 4. Esdeveniments del namespace complet: la cronologia de l'incident
kubectl -n "$NS" get events --sort-by=.lastTimestamp -o yaml > "$DIR/esdeveniments.yaml"
kubectl get events -A --sort-by=.lastTimestamp > "$DIR/esdeveniments-cluster.txt"
# 5. Estat del node on viu el pod
NODE=$(kubectl -n "$NS" get pod "$POD" -o jsonpath='{.spec.nodeName}')
kubectl describe node "$NODE" > "$DIR/node-$NODE.txt"
# 6. Estat del Service i els seus Endpoints
APP=$(kubectl -n "$NS" get pod "$POD" -o jsonpath='{.metadata.labels.app}')
kubectl -n "$NS" get svc,endpointslices -l app="$APP" -o yaml > "$DIR/xarxa.yaml"
# 7. Consum actual (07-02)
kubectl -n "$NS" top pod "$POD" --containers > "$DIR/consum.txt" 2>&1
# 8. Estat general del namespace, per context
kubectl -n "$NS" get all -o wide > "$DIR/namespace.txt"
tar czf "$DIR.tar.gz" -C /tmp "$(basename "$DIR")"
echo "Evidències empaquetades a $DIR.tar.gz"
echo "ARA ja pots reiniciar."Quan sí que cal reiniciar de seguida
Hi ha una excepció legítima, i cal ser honest amb ella: quan el servei està caigut i restaurar-lo és més urgent que entendre'l.
En aquest cas, l'ordre correcte és:
- Executar l'script de captura (30 segons, no més).
- Restaurar el servei.
- Investigar amb les evidències capturades.
Mai "reiniciar i ja investigarem", perquè a les nou del matí no quedarà res per investigar.
L'alternativa: aïllar en lloc de reiniciar
Si el problema afecta un sol pod de diverses rèpliques, hi ha una opció molt millor que esborrar-lo: treure'l del Service sense matar-lo.
# Canviar una etiqueta del selector: el pod surt dels Endpoints
# però continua viu, amb tota la seva memòria i el seu estat intactes.
kubectl -n rutas-norte-pro label pod api-reserves-7d9f8c4b5-x2klm app=api-reserves-aillat --overwriteEfectes, tots desitjables:
- El pod deixa de rebre trànsit immediatament: els clients deixen de patir-lo.
- El ReplicaSet veu que li falta una rèplica i crea un pod nou i sa: el servei es restaura.
- El pod problemàtic continua viu amb tot el seu estat: pots fer
exec, bolcar la memòria, mirar els descriptors oberts, investigar amb calma.
És la millor eina per depurar un problema que només es manifesta en producció i només en una rèplica. Quan acabis:
Errors Comuns i Consells
1. Començar per kubectl logs sense mirar l'estat. Si el pod està en Pending, no hi ha logs. Segueix l'arbre de decisió en ordre.
2. Oblidar --sort-by=.lastTimestamp als esdeveniments. Sense això surten en ordre arbitrari i la cronologia, que és el valuós, es perd.
3. No saber que els esdeveniments caduquen en una hora. És el fet més important d'aquesta lliçó. Exporta els esdeveniments a la pila de logs.
4. Oblidar --previous davant d'un CrashLoopBackOff. Els logs del contenidor actual no diuen res: acaba d'arrencar. La causa és al que va morir.
5. Reiniciar abans de capturar. Destrueix --previous, l'estat de memòria i els esdeveniments. Trenta segons de captura estalvien hores.
6. No fer servir port-forward per aïllar la capa. És la prova que descarta de cop Ingress, Service, kube-proxy i NetworkPolicies.
7. Confondre 502 amb 503. El 503 és "no hi ha backend" (Endpoints buits); el 502 és "el backend ha fallat" (la cursa del SIGTERM). Causes i solucions diferents.
8. Llançar un pod de depuració sense les etiquetes correctes. Amb deny-all a rutas-norte-pro, un netshoot sense etiquetes no parla amb res, i confondràs una NetworkPolicy amb un problema d'aplicació.
9. Depurar en producció modificant el pod real. Fes servir kubectl debug --copy-to: la còpia no porta les etiquetes del selector, no rep trànsit i pots fer-hi el que vulguis.
10. Ignorar els esdeveniments Normal. En el cas de la secció 8, l'esdeveniment decisiu va ser un Killing de tipus Normal. L'absència d'un esdeveniment esperat també informa.
11. No correlacionar les tres senyals. Una mètrica diu quan, un log diu què i un esdeveniment diu en quin ordre. Cap no basta sola.
12. Tancar l'incident en restaurar el servei. Sense causa arrel identificada i sense mesura preventiva, tornarà a passar. El pas 8 de la secció 8 no és opcional.
Exercicis
Exercici 1 — Diagnosticar tres pods amb la taula mestra
Després d'un desplegament a rutas-norte-pre, aquest és l'estat:
NAME READY STATUS RESTARTS AGE
api-reserves-9f2c1a8b6-kp3nm 0/1 CrashLoopBackOff 7 (30s ago) 12m
worker-notificacions-5f7c8b9d4-tz2mv 0/1 ContainerCreating 0 12m
botiga-web-6c8b9d7f4-hj3ks 1/1 Running 0 3d
redis-cache-0 0/1 Pending 0 12mI aquests esdeveniments:
LAST SEEN TYPE REASON OBJECT MESSAGE
30s Warning BackOff pod/api-reserves-9f2c1a8b6-kp3nm Back-off restarting failed container api
2m Warning FailedMount pod/worker-notificacions-5f7c8b9d4-tz2mv MountVolume.SetUp failed for volume "credencials-smtp": secret "credencials-smtp-v2" not found
11m Warning FailedScheduling pod/redis-cache-0 0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaimsA més, botiga-web retorna 503 als clients tot i estar 1/1 Running.
Per a cadascun dels quatre problemes:
- Identifica la causa probable.
- Escriu la seqüència d'ordres que la confirma.
- Proposa la correcció.
- Explica per què
botiga-webdóna 503 estantRunningi llest.
Exercici 2 — Depurar un contenidor sense shell
api-reserves s'ha migrat a una imatge distroless (sense shell, sense curl, sense ps). En producció, un pod concret de sis respon amb latències de 8 segons mentre els altres cinc van a 40 ms. La mètrica ho confirma, els logs no mostren errors, i el pod està 1/1 Running.
- Per què no pots fer servir
kubectl exec -it POD -- sh? - Escriu les ordres per investigar el pod sense reiniciar-lo i sense treure'l de producció.
- Escriu l'alternativa que el treu de producció sense matar-lo, explicant què hi guanyes.
- Un cop a dins, quines tres comprovacions concretes faries per explicar la latència?
Exercici 3 — Reconstruir un incident passat
El diumenge a les 04:12, informes-ocupacio (el CronJob de 06-03) va fallar. El dilluns a les 10:00 et demanen que expliquis què va passar. Comproves:
$ kubectl -n rutas-norte-pro get jobs -l app=informes-ocupacio
NAME STATUS COMPLETIONS DURATION AGE
informes-ocupacio-28936960 Failed 0/1 14m 30h
$ kubectl -n rutas-norte-pro get events --field-selector involvedObject.name=informes-ocupacio-28936960
No resources found in rutas-norte-pro namespace.
$ kubectl -n rutas-norte-pro logs job/informes-ocupacio-28936960
Error from server (BadRequest): pod for job "informes-ocupacio-28936960" not found- Per què no hi ha esdeveniments ni logs?
- Enumera totes les fonts d'informació que sí que continuen disponibles, amb l'ordre o consulta concreta de cadascuna.
- Reconstrueix què va poder passar si Prometheus mostra que el node
rutas-norte-worker-2va estar amb pressió de memòria entre les 04:05 i les 04:30, i que el volumdades-postgres-reserves-0estava al 96 %. - Proposa tres mesures perquè el proper incident nocturn sí que sigui investigable.
Solucions
Solució 1
Problema 1: api-reserves en CrashLoopBackOff.
Causa probable: el contenidor arrenca i mor. Les candidates, per ordre: error de configuració, OOMKilled, o una liveness massa agressiva (07-01).
Confirmació:
NS=rutas-norte-pre
POD=api-reserves-9f2c1a8b6-kp3nm
# 1. La causa sol ser aquí
kubectl -n $NS logs $POD --previous
# 2. Motiu i codi de sortida de la terminació
kubectl -n $NS get pod $POD \
-o jsonpath='{.status.containerStatuses[0].lastState.terminated}' | jq
# 3. Hi ha esdeveniments Unhealthy abans dels Killing? (liveness)
kubectl -n $NS describe pod $POD | grep -B 3 -A 12 Events
# 4. Si els logs són buits: llançar una còpia amb l'ordre substituïda
kubectl -n $NS debug pod/$POD --copy-to=api-depuracio --container=api -- sleep 3600
kubectl -n $NS exec -it api-depuracio -c api -- shCorrecció, segons el que retorni el pas 2:
reason / exitCode |
Causa | Correcció |
|---|---|---|
OOMKilled / 137 |
Límit de memòria curt o fuita | Pujar limits.memory (07-02) o arreglar la fuita |
Error / 1 amb log de config |
Falta una variable o un Secret | Corregir el ConfigMap o el Secret |
Error / 127 |
Binari no trobat | Corregir el command o la imatge |
Sense log, precedit d'Unhealthy |
Liveness massa agressiva | Afegir startupProbe (07-01) |
Problema 2: worker-notificacions en ContainerCreating.
Causa: l'esdeveniment la dóna directament: secret "credencials-smtp-v2" not found. El manifest referencia un Secret que no existeix en aquest namespace. Patró típic: el Secret es va reanomenar a -v2 a pro però no es va crear a pre.
Confirmació:
# Existeix el Secret que espera?
kubectl -n rutas-norte-pre get secret credencials-smtp-v2
# Quin existeix realment?
kubectl -n rutas-norte-pre get secrets | grep smtp
# Quin nom referencia el Deployment?
kubectl -n rutas-norte-pre get deploy worker-notificacions \
-o jsonpath='{.spec.template.spec.volumes}' | jqCorrecció: crear el Secret que falta, o corregir la referència del manifest:
kubectl -n rutas-norte-pre create secret generic credencials-smtp-v2 \
[email protected] \
--from-literal=password="$SMTP_PASSWORD"El pod passa a Running automàticament tan bon punt el Secret existeixi: el kubelet reintenta el muntatge periòdicament. No cal esborrar-lo.
Prevenció: aquest error és un símptoma de gestionar els manifests per entorn a mà. És exactament el problema que resolen Kustomize (10-04) i GitOps (10-05).
Problema 3: redis-cache-0 en Pending.
Causa: l'esdeveniment ho diu: 3 pod has unbound immediate PersistentVolumeClaims. El PVC del StatefulSet no està enllaçat.
Confirmació:
# Per què no s'aprovisiona?
kubectl -n rutas-norte-pre describe pvc dades-redis-cache-0 | tail -10
# Existeix la StorageClass? (05-04)
kubectl get storageclassLes tres causes típiques:
| Causa | Comprovació |
|---|---|
| La StorageClass no existeix en aquest clúster | kubectl get sc rutasnorte-rapida |
| L'aprovisionador CSI està caigut | kubectl -n kube-system get pods | grep csi |
| No queda capacitat al backend | Esdeveniments del PVC |
Correcció: si és la primera (el més probable a pre, on pot no existir la classe ràpida), crear la StorageClass o canviar el volumeClaimTemplates a rutasnorte-estandar.
Compte amb un detall de 06-01: volumeClaimTemplates és immutable. No pots canviar la StorageClass d'un StatefulSet existent; cal esborrar-lo amb --cascade=orphan, recrear el manifest i tornar-lo a crear.
Problema 4: botiga-web dóna 503 estant 1/1 Running.
Aquesta és la part més instructiva de l'exercici, perquè el pod sembla perfecte.
Causa: un 503 de l'Ingress significa "no hi ha backend a qui enviar". Si el pod està Running i Ready, la causa és entre el Service i el pod:
- El selector del Service no coincideix amb les etiquetes del pod.
- El port del Service apunta a un
targetPortincorrecte. - L'Ingress apunta a un Service amb nom o port equivocat.
Confirmació, en aquest ordre exacte:
NS=rutas-norte-pre
# 1. LA COMPROVACIÓ CLAU: hi ha Endpoints?
kubectl -n $NS get endpointslices -l kubernetes.io/service-name=botiga-webEndpoints buit amb un pod Ready: el selector no casa.
Allà està. El Service busca entorn: preproduccio i el pod té entorn: pre. Els dos objectes són vàlids, estan sans, i no es troben.
# 3. Confirmar que el pod funciona, saltant-se el Service
kubectl -n $NS port-forward pod/botiga-web-6c8b9d7f4-hj3ks 8080:80
curl -s -o /dev/null -w "%{http_code}\n" localhost:8080/
# → 200: el pod està perfecte, el problema és el selectorCorrecció: alinear amb la convenció de Rutas Norte, que fa servir dev/pre/pro:
kubectl -n rutas-norte-pre patch svc botiga-web \
-p '{"spec":{"selector":{"app":"botiga-web","entorn":"pre"}}}'
# Verificar que apareixen els Endpoints
kubectl -n rutas-norte-pre get endpointslices -l kubernetes.io/service-name=botiga-webLa lliçó: l'estat del pod no diu res sobre si rep trànsit. Un pod pot estar 1/1 Running durant dies sense que li arribi ni una sola petició. La pregunta 4 de l'arbre de decisió (el Service té Endpoints?) és la que cal fer-se, i es respon en dos segons.
Solució 2
1. Per què falla kubectl exec -it POD -- sh.
Una imatge distroless conté només el runtime del llenguatge i l'aplicació: no hi ha /bin/sh, ni /bin/bash, ni cap binari d'utilitat. És una decisió de seguretat excel·lent (superfície d'atac mínima, que veurem a 08-05), però impedeix l'exec clàssic:
$ kubectl -n rutas-norte-pro exec -it api-reserves-9f2c1a8b6-kp3nm -- sh
OCI runtime exec failed: exec failed: unable to start container process:
exec: "sh": executable file not found in $PATH: unknown2. Investigar sense reiniciar i sense treure'l de producció: contenidor efímer.
NS=rutas-norte-pro
POD=api-reserves-9f2c1a8b6-kp3nm
kubectl -n $NS debug -it $POD \
--image=nicolaka/netshoot:latest \
--target=api \
--profile=general \
-- bashClaus d'aquesta ordre:
--target=apicomparteix el namespace de processos amb el contenidorapi, cosa que permet veure i inspeccionar el seu procés.- El contenidor efímer comparteix a més el namespace de xarxa: mateixa IP, mateixos ports, mateixa vista de la xarxa.
- El pod continua servint trànsit durant tota la sessió. No es reinicia res.
Advertiment: el contenidor efímer no es pot treure. Es queda al pod fins que el pod mori. Amb sis rèpliques, això és acceptable.
3. L'alternativa que el treu de producció sense matar-lo.
# Canviar l'etiqueta del selector
kubectl -n rutas-norte-pro label pod api-reserves-9f2c1a8b6-kp3nm \
app=api-reserves-quarantena --overwriteQuè hi guanyes exactament:
| Efecte | Benefici |
|---|---|
| Surt dels Endpoints a l'instant | Els clients deixen de patir els 8 segons de latència |
| El ReplicaSet crea un pod nou | El servei torna a tenir 6 rèpliques sanes |
| El pod problemàtic continua viu | Conserves memòria, connexions, descriptors i estat |
| Sense trànsit entrant | Pots fer bolcats i perfilats sense afectar ningú |
És estrictament superior a kubectl delete pod: restaures el servei igual de ràpid i conserves tota l'evidència.
Alternativa complementària amb --copy-to, si vols experimentar amb canvis:
kubectl -n rutas-norte-pro debug $POD \
--copy-to=api-analisi \
--image=nicolaka/netshoot:latest \
--share-processes -it -- bash4. Les tres comprovacions per explicar la latència.
Comprovació A — Està el procés saturat o bloquejat?
# Quanta CPU consumeix realment?
top -b -n 1 -p 1
# En què està bloquejat? Estat D = espera d'E/S ininterrompible
cat /proc/1/status | grep -E "State|Threads|VmRSS"
# Quines crides al sistema fa? (si hi ha permís de traça)
strace -p 1 -c -f -T 2>&1 | head -25Un procés amb CPU al 100 % apunta a un bucle o a un GC descontrolat. Un procés en estat D amb CPU baixa apunta a espera de xarxa o de disc: la resposta més probable en aquest cas.
Comprovació B — On van les connexions i quantes n'hi ha?
# Connexions establertes, agrupades per destinació
ss -tanp state established | awk '{print $5}' | sort | uniq -c | sort -rn 20 10.244.3.88:5432 ← postgres-reserves: 20 connexions (el pool PLE)
1 10.244.1.45:6379 ← redis-cacheVint connexions a PostgreSQL amb un pool de màxim 20: el pool està exhaurit. És l'escenari exacte de 07-01. Cada petició nova espera que s'alliberi una connexió, i d'aquí els 8 segons.
# Hi ha connexions acumulades a la cua d'escolta?
ss -tln | grep 8080
# La columna Recv-Q mostra peticions esperant a ser acceptadesComprovació C — És la latència al nostre costat o al de PostgreSQL?
# Mesurar la latència real cap a la base de dades des d'AQUEST pod
for i in $(seq 1 10); do
time nc -zv postgres-reserves.rutas-norte-pro.svc.cluster.local 5432
done
# Resol el DNS amb normalitat? Un DNS lent explica latències estranyes
time nslookup postgres-reserves.rutas-norte-pro.svc.cluster.local
# L'endpoint de preparació de 07-01 respon amb el diagnòstic
curl -s -w "\ntemps: %{time_total}s\n" localhost:8080/preparatConfirmat. I queda la pregunta de per què només aquest pod de sis. Dues hipòtesis a contrastar amb les mètriques de 07-03:
# Rep aquest pod més trànsit que els altres?
sum by (pod) (rate(rutasnorte_peticions_total{entorn="pro"}[5m]))
# Està el pod en un node amb problemes?
sum by (pod) (rate(container_cpu_cfs_throttled_periods_total{
namespace="rutas-norte-pro", pod=~"api-reserves-.*"}[5m]))
/ sum by (pod) (rate(container_cpu_cfs_periods_total{
namespace="rutas-norte-pro", pod=~"api-reserves-.*"}[5m]))Si el throttling de CPU és alt només en aquest pod, l'explicació és que comparteix node amb informes-ocupacio o amb un altre procés pesat, i el seu alliberament de connexions va més lent que el dels altres. Solució: una regla d'antiafinitat (06-05) o revisar els límits (07-02).
Solució 3
1. Per què no hi ha esdeveniments ni logs.
No hi ha esdeveniments perquè caduquen en una hora. L'error va ser a les 04:12 del diumenge; són les 10:00 del dilluns: han passat 30 hores. L'API Server els va esborrar a les 05:12 del diumenge, 29 hores abans.
No hi ha logs perquè el pod ja no existeix. Un CronJob amb failedJobsHistoryLimit (per defecte 1) conserva l'objecte Job, però els pods es recullen quan se supera el límit o quan actua el recol·lector d'escombraries. kubectl logs job/... necessita que el pod existeixi: només llegeix del fitxer del node.
I encara que el pod existís, la rotació del kubelet (07-05) probablement ja s'hauria emportat el fitxer.
2. Fonts que sí que continuen disponibles.
| Font | Què aporta | Ordre o consulta |
|---|---|---|
| L'objecte Job | Estat, temps, nombre d'intents | kubectl -n rutas-norte-pro get job informes-ocupacio-28936960 -o yaml |
| Logs centralitzats (07-05) | El log complet del pod | component: "informes-ocupacio" and @timestamp >= "2026-08-02T04:00:00Z" |
| Esdeveniments exportats (07-06) | La cronologia, supervivent al TTL | component: "kubernetes-esdeveniments" and objecte_nom: informes-ocupacio-* |
| Prometheus (07-03) | Estat del job, del node i dels recursos | kube_job_status_failed, container_memory_working_set_bytes |
| Grafana (07-04) | Els quadres de comandament amb el rang posat a l'incident | Panell d'informes-ocupacio |
| Alertmanager | Quines alertes van disparar i quan | Històric d'alertes o el canal de Slack |
| kube-state-metrics | Motiu de l'última terminació | kube_pod_container_status_last_terminated_reason |
| Git | Si hi va haver un canvi de configuració | git log --since="2026-08-01" -- k8s/ |
Ordres i consultes concretes:
# L'objecte Job conserva molta informació
kubectl -n rutas-norte-pro get job informes-ocupacio-28936960 -o yaml | \
yq '.status, .spec.backoffLimit, .spec.activeDeadlineSeconds'status:
conditions:
- type: Failed
status: "True"
reason: BackoffLimitExceeded
message: Job has reached the specified backoff limit
lastTransitionTime: "2026-08-02T04:26:11Z"
failed: 3
startTime: "2026-08-02T04:12:00Z"Ja ho sabem: va començar a les 04:12, va fallar tres vegades i es va donar per perdut a les 04:26.
A Kibana:
component: "informes-ocupacio"
and @timestamp >= "2026-08-02T04:00:00.000Z"
and @timestamp <= "2026-08-02T04:30:00.000Z"I a Prometheus:
# Memòria del node durant l'incident
node_memory_MemAvailable_bytes{instance=~"rutas-norte-worker-2.*"}
# Espai del volum de PostgreSQL
kubelet_volume_stats_available_bytes{persistentvolumeclaim="dades-postgres-reserves-0"}
/ kubelet_volume_stats_capacity_bytes{persistentvolumeclaim="dades-postgres-reserves-0"}
# Motiu de l'última terminació dels pods del job
kube_pod_container_status_last_terminated_reason{
namespace="rutas-norte-pro", pod=~"informes-ocupacio-.*"}
# Desallotjaments en aquell node
increase(kube_pod_status_reason{reason="Evicted", node="rutas-norte-worker-2"}[1h])3. Reconstrucció de l'incident.
Amb les dues dades aportades (pressió de memòria a worker-2 entre 04:05 i 04:30, i el volum de PostgreSQL al 96 %), la reconstrucció més probable és:
~04:00 La còpia de seguretat amb pg_dump programada (05-06) arrenca i escriu
el bolcat al volum de postgres-reserves, que ja estava molt ple.
El volum arriba al 96%.
04:05 L'escriptura intensiva satura el cau de pàgina del node worker-2.
La memòria disponible del node comença a caure.
04:12 El CronJob informes-ocupacio arrenca el seu primer pod, planificat a
worker-2 (el node amb més CPU lliure en aquell moment). L'informe
necessita ~800 Mi de memòria i fa consultes pesades contra
postgres-reserves.
04:14 El kubelet de worker-2 detecta pressió de memòria i comença a
desallotjar pods, començant pels de menor QoS. El pod del job,
amb QoS Burstable, és candidat preferent davant dels pods
Guaranteed de la plataforma.
→ El pod és desallotjat (Evicted) o mor OOMKilled.
04:15 El controlador de Job reintenta (intent 2 de 3).
El node continua sota pressió. Torna a fallar.
04:20 Tercer intent. A més, ara és possible que les consultes contra
PostgreSQL també fallin o vagin lentíssimes, perquè el volum
al 96% degrada el rendiment d'escriptura dels fitxers
temporals que necessita la consulta d'agregació.
04:26 BackoffLimitExceeded: el Job es marca com a Failed.
No hi ha informe d'ocupació del diumenge.Confirmació de la hipòtesi (les ordres que la validen o la descarten):
# Hi va haver desallotjaments a worker-2? Confirmaria la teoria del desallotjament
increase(kube_pod_status_reason{reason="Evicted", node="rutas-norte-worker-2"}[2h])
# O va ser OOMKilled? Seria una hipòtesi alternativa
kube_pod_container_status_last_terminated_reason{
pod=~"informes-ocupacio-.*", reason="OOMKilled"}I als logs:
component: "kubernetes-esdeveniments"
and objecte_nom: informes-ocupacio-*
and motiu: ("Evicted" or "OOMKilling" or "Failed")Un esdeveniment Evicted amb el missatge The node was low on resource: memory confirmaria la reconstrucció completa.
Nota metodològica important: això és una hipòtesi coherent amb les dades, no un fet provat. La diferència importa. Amb les mesures del punt 4, el proper incident permetrà afirmar-ho amb certesa en lloc de deduir-ho.
4. Tres mesures perquè el proper sigui investigable.
Mesura A — Exportar els esdeveniments a la pila de logs. És la mancança que ha causat el 80 % de la dificultat d'aquest exercici.
apiVersion: apps/v1
kind: Deployment
metadata:
name: event-exporter
namespace: registre
spec:
replicas: 1
selector:
matchLabels:
app: event-exporter
template:
metadata:
labels:
app: event-exporter
app.kubernetes.io/part-of: rutas-norte
spec:
serviceAccountName: event-exporter # RBAC de només lectura sobre events
containers:
- name: exporter
image: ghcr.io/resmoio/kubernetes-event-exporter:v1.7
args: ["-conf=/etc/config/config.yaml"]
volumeMounts:
- name: config
mountPath: /etc/config
resources:
requests:
cpu: "20m"
memory: "64Mi"
limits:
memory: "128Mi"
volumes:
- name: config
configMap:
name: event-exporter-configAmb la configuració de la secció 3, que emet a stdout i per tant hereta tota la pila de 07-05, amb els seus 30 dies de retenció. Cost: uns 64 Mi de memòria. Benefici: no tornar a perdre mai la cronologia d'un incident.
Mesura B — Conservar l'històric de Jobs i evitar la recollida de pods.
# k8s/base/informes-ocupacio/cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: informes-ocupacio
namespace: rutas-norte-pro
spec:
schedule: "12 4 * * *"
timeZone: "Europe/Madrid"
concurrencyPolicy: Forbid
# Conservar els últims 7 errors i 3 èxits, amb ELS SEUS PODS.
# Per defecte és 1 i 3: es perd gairebé tot.
failedJobsHistoryLimit: 7
successfulJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 3
activeDeadlineSeconds: 3600
# Mantenir els pods completats 48 h abans de recollir-los:
# temps suficient per investigar un error del cap de setmana.
ttlSecondsAfterFinished: 172800
template:
spec:
restartPolicy: Never
# QoS Guaranteed: el pod ja NO és candidat preferent al desallotjament
# quan el node té pressió de memòria.
containers:
- name: informes
image: registry.rutasnorte.example/informes-ocupacio:1.9.2
resources:
requests:
cpu: "1"
memory: "1Gi"
limits:
cpu: "1"
memory: "1Gi"
# Evitar el node on viu postgres-reserves: l'informe fa
# consultes pesades i no ha de competir amb la base de dades per
# la memòria del mateix node (06-05).
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: postgres-reserves
topologyKey: kubernetes.io/hostnameMesura C — Alertes que avisin en el moment, no 30 hores després.
# Ja la teníem a 07-04, però verifiquem que el for no la retardi de més
- alert: CronJobInformesNoExecutat
expr: |
time() - max(kube_job_status_completion_time{job_name=~"informes-ocupacio.*"}) > 100000
for: 30m
labels:
severity: avis
component: informes-ocupacio
equip: dades
# NOVA: avisar de l'error en el moment, no de l'absència acumulada
- alert: JobInformesFallit
expr: |
kube_job_status_failed{namespace="rutas-norte-pro", job_name=~"informes-ocupacio.*"} > 0
for: 5m
labels:
severity: avis
component: informes-ocupacio
equip: dades
annotations:
summary: "El job {{ $labels.job_name }} ha fallat"
description: >
Investigar ARA, mentre els esdeveniments i els logs del pod encara
existeixen. Executar capturar-evidencies.sh abans de tocar res.
runbook_url: "https://runbooks.rutasnorte.example/cronjob-informes"
# NOVA: la causa arrel probable, avisada amb antelació (07-04)
- alert: NodeAmbPressioDeMemoria
expr: kube_node_status_condition{condition="MemoryPressure", status="true"} == 1
for: 5m
labels:
severity: avis
equip: plataforma
annotations:
summary: "El node {{ $labels.node }} té pressió de memòria"
description: >
El kubelet està desallotjant pods. Els de QoS Burstable i BestEffort
són els primers candidats. Revisar què s'hi està executant.Amb aquestes tres mesures, el mateix incident el proper diumenge produiria: una alerta a les 04:17 (error del job) i una altra a les 04:10 (pressió de memòria al node), els esdeveniments complets a Elasticsearch, els logs del pod conservats 48 hores i el pod fallit encara al clúster el dilluns al matí. De reconstruir amb hipòtesis a confirmar amb evidència.
Conclusió
Tanquem el mòdul 7. Hem passat d'una plataforma completament opaca a una que es veu, es mesura i s'explica.
En aquesta última lliçó hem construït el que faltava: el mètode.
- Una metodologia de cinc preguntes en ordre —existeix?, està programat?, va arrencar?, està llest?, rep trànsit?— que evita l'error més car de la depuració: mirar on no és la resposta.
- Els esdeveniments com la font principal i pitjor aprofitada, amb
--sort-by=.lastTimestampcom a ordre imprescindible, i sobretot amb el fet crític que caduquen en una hora, cosa que obliga a exportar-los si es vol investigar alguna cosa que va passar ahir a la nit. - La taula mestra de símptoma → causa probable → ordre que ho confirma, cobrint
Pending,ImagePullBackOff,CrashLoopBackOff,OOMKilledi el codi 137,ContainerCreatingencallat,Terminatingetern,0/n Readyi la distinció entre el502i el503de l'Ingress. - Les eines d'inspecció, amb
port-forwardcom la prova d'aïllament més valuosa ikubectl debugen els seus tres modes: contenidors efímers per depurar un poddistrolesssense reiniciar-lo,--copy-toper experimentar sense tocar producció, inode/per entrar a la màquina. - I hem tancat, per fi, l'incident dels 502 durant els desplegaments que arrossegàvem des de 02-04: la mètrica va dir quan i amb quin patró, els logs van dir que el 502 el generava l'Ingress i no l'API, els esdeveniments exportats van donar la cronologia al mil·lisegon, i les sondes de 07-01 van donar el marc per entendre-ho. Cap senyal sola bastava.
- Amb el consell que més incidents salva: recollir proves abans de reiniciar, perquè un
kubectl delete poddestrueix--previous, l'estat de memòria i els esdeveniments; i l'alternativa millor, aïllar canviant una etiqueta per restaurar el servei sense perdre l'evidència.
Rutas Norte es veu. Cada component declara si està sa, cada consum queda registrat, cada petició deixa rastre, cada alerta arriba a qui pot actuar i cada incident pot reconstruir-se.
I aquí apareix el problema següent, que és incòmode precisament perquè l'hem construït nosaltres.
Tot el que hem muntat en aquest mòdul assumeix que qui accedeix al clúster és algú de confiança. Però avui, a rutas-norte-pro, qualsevol amb un kubectl configurat pot llegir el Secret amb la contrasenya de postgres-reserves i, amb ella, el nom, el DNI, el telèfon i el correu de tots els clients de Rutas Norte. Pot desplegar un contenidor privilegiat que munti el sistema de fitxers del node i llegeixi els secrets dels altres pods. Pot executar kubectl debug node/ i controlar la màquina. Pot desplegar una imatge que ningú ha revisat i que executa el que vulgui com a root. I el sistema d'auditoria que ens diria qui va fer què, senzillament, no existeix.
Al mòdul 8 tanquem aquesta escletxa: RBAC perquè cada persona i cada ServiceAccount tinguin exactament els permisos que necessiten i ni un més; contextos de seguretat perquè cap contenidor no corri com a root ni pugui escalar privilegis; els Pod Security Standards perquè aquestes regles s'apliquin a nivell de namespace sense dependre de la bona voluntat de qui escriu el manifest; seguretat de xarxa, seguretat d'imatges i, per fi, auditoria. Perquè una plataforma que es veu però que qualsevol pot vulnerar no està llesta per a producció.
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
