Aquesta és l'última lliçó del curs i funciona d'una manera diferent de totes les anteriors: no es llegeix, es fa. És un examen de pràctiques complet, amb quinze tasques cronometrades a l'estil de les certificacions, sobre la plataforma Rutas Norte que has construït al llarg dels onze mòduls anteriors. Té la seva puntuació, el seu solucionari i una taula d'autoavaluació que tradueix el teu resultat en un veredicte clar: a punt per reservar data, repassar dos dominis concrets, o tornar a determinats mòduls.

La regla més important: fes el simulacre abans de llegir les solucions. El valor d'aquesta lliçó no és al solucionari —és a descobrir, amb un cronòmetre corrent, què saps fer sense ajuda i què no. Llegir-lo primer converteix un diagnòstic en una lectura agradable i sense utilitat.

Prepara el clúster, posa el cronòmetre a 120 minuts i comença.

Contingut

  1. Instruccions del simulacre
  2. Preparació de l'entorn
  3. Les quinze tasques
  4. Repartiment del temps i estratègia

  1. Instruccions del simulacre

1.1 Regles

Aquestes regles repliquen les condicions reals de l'examen. Respectar-les és el que fa que el resultat signifiqui alguna cosa.

Regla Detall
Temps total 120 minuts, cronometrats des de la primera tasca
Sense pauses Res d'aturar el rellotge. Ves al bany abans.
Documentació Només kubernetes.io/docs. Una pestanya. Res més.
Prohibit Cercadors, fòrums, GitHub, notes pròpies, aquesta lliçó, eines d'IA
Editor vim o nano al terminal. No un IDE gràfic.
Mòbil En una altra habitació
Puntuació 100 punts repartits entre les 15 tasques
Correcció Al final, amb el solucionari. Mai durant.

1.2 Com puntuar

Cada tasca indica el seu criteri d'acceptació. Puntua així:

  • Puntuació completa si es compleix tot el criteri.
  • Puntuació parcial (la meitat) si es compleix una part substancial però falta algun requisit.
  • Zero si l'objecte no existeix, està al namespace equivocat o no funciona.

Sigues honest amb tu mateix. Inflar la nota només et perjudica.

1.3 Què anotar mentre treballes

Tingues un fitxer de notes obert (o paper, aquí sí que val) i registra per a cada tasca:

T1  5p  ✔  3 min
T2  7p  ½  9 min   - no em va sortir l Ingress a la primera
T3  6p  ✔  4 min
T4  8p  ⏸  saltada - PVC en Pending i no vaig saber per que
...

Aquesta informació és tan valuosa com la nota final: et diu on se't va el temps.


  1. Preparació de l'entorn

2.1 Opció recomanada: kind amb tres nodes

kind aixeca un clúster multinode en un minut i suporta gairebé tot el que necessita el simulacre. Necessites Docker o Podman instal·lat.

# kind-simulacre.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30080
    hostPort: 30080
- role: worker
- role: worker
networking:
  disableDefaultCNI: true          # instal·larem Calico per a les NetworkPolicies
kind create cluster --name simulacre --config kind-simulacre.yaml

# CNI amb suport de NetworkPolicies (imprescindible per a la tasca 9)
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml

# Esperar que tots els nodes estiguin Ready
kubectl wait --for=condition=Ready nodes --all --timeout=300s
kubectl get nodes
NAME                      STATUS   ROLES           AGE   VERSION
simulacre-control-plane   Ready    control-plane   2m    v1.30.0
simulacre-worker          Ready    <none>          2m    v1.30.0
simulacre-worker2         Ready    <none>          2m    v1.30.0

2.2 Alternativa: minikube

minikube start --nodes=3 --cni=calico --kubernetes-version=v1.30.0
minikube addons enable ingress
minikube addons enable metrics-server

Minikube té l'avantatge de portar el controlador d'Ingress i el metrics-server com a addons, que a kind cal instal·lar a part.

2.3 Components addicionals

Perquè totes les tasques siguin resolubles, instal·la això abans d'engegar el cronòmetre:

# Controlador d'Ingress (necessari per a la tasca 2)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.2/deploy/static/provider/kind/deploy.yaml
kubectl wait --namespace ingress-nginx --for=condition=ready pod \
  --selector=app.kubernetes.io/component=controller --timeout=180s

# metrics-server (necessari per a la tasca 11)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl patch deployment metrics-server -n kube-system --type='json' \
  -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

2.4 Script de preparació (executa'l abans del cronòmetre)

Aquest script crea els namespaces, les càrregues de partida i les tres avaries de les tasques de resolució de problemes. Desa'l com a preparar-simulacre.sh i executa'l. No el llegeixis amb detall: les avaries han de ser una sorpresa.

#!/bin/bash
set -e

for ns in rutas-norte-dev rutas-norte-pre rutas-norte-pro; do
  kubectl create namespace $ns --dry-run=client -o yaml | kubectl apply -f -
done

# Etiquetes de node per a la tasca 5
kubectl label node simulacre-worker2 tipus=batch --overwrite
kubectl taint node simulacre-worker2 dedicat=batch:NoSchedule --overwrite

# Càrrega de partida per a les tasques 7, 11 i 12
kubectl create deployment api-reserves --image=nginx:1.27-alpine \
  --replicas=2 -n rutas-norte-pre
kubectl create deployment botiga-web --image=nginx:1.27-alpine \
  --replicas=3 -n rutas-norte-pro

# --- AVARIA 1 (tasca 13) ---
kubectl create deployment worker-notificacions --image=busybox:1.36 \
  -n rutas-norte-dev
kubectl set env deployment/worker-notificacions -n rutas-norte-dev \
  --from=secret/credencials-cua --prefix=CUA_ 2>/dev/null || \
kubectl patch deployment worker-notificacions -n rutas-norte-dev --type='json' \
  -p='[{"op":"add","path":"/spec/template/spec/containers/0/envFrom",
       "value":[{"secretRef":{"name":"credencials-cua"}}]}]'

# --- AVARIA 2 (tasca 14) ---
kubectl create deployment redis-cache --image=redis:7-alpine -n rutas-norte-pre
kubectl expose deployment redis-cache --name=redis-cache-svc --port=6379 \
  -n rutas-norte-pre
kubectl patch svc redis-cache-svc -n rutas-norte-pre \
  -p '{"spec":{"selector":{"app":"redis"}}}'

# --- AVARIA 3 (tasca 15) ---
kubectl run informes-ocupacio --image=busybox:1.36 -n rutas-norte-pro \
  --overrides='{"spec":{"nodeSelector":{"disc":"nvme"}}}' \
  --command -- sleep 3600

echo "Entorn preparat. Engega el cronometre."
chmod +x preparar-simulacre.sh
./preparar-simulacre.sh

2.5 Ara sí: engega el cronòmetre

Abans de llegir la tasca 1, tecleja el teu bloc d'arrencada (lliçó 12-04):

alias k=kubectl
export do='--dry-run=client -o yaml'
export now='--force --grace-period=0'
source <(kubectl completion bash)
complete -o default -F __start_kubectl k

cat <<'EOF' > ~/.vimrc
set number
set expandtab
set tabstop=2
set shiftwidth=2
set softtabstop=2
set autoindent
set paste
EOF

120 minuts. Comença.


  1. Les quinze tasques


Tasca 1 — Desplegament bàsic i escalat

Namespace: rutas-norte-dev · Pes: 5 punts · Objectiu: 4 min

Crea un Deployment anomenat botiga-web amb la imatge nginx:1.27-alpine i 4 rèpliques. Tots els seus pods han de portar l'etiqueta entorn=dev a més de les que posi el controlador. Després, escala'l a 6 rèpliques.

Criteri d'acceptació: kubectl get deploy botiga-web -n rutas-norte-dev mostra 6/6 i tots els pods tenen l'etiqueta entorn=dev.


Tasca 2 — Exposició: Service i Ingress

Namespace: rutas-norte-dev · Pes: 7 punts · Objectiu: 8 min

Exposa el Deployment botiga-web de la tasca anterior amb un Service ClusterIP anomenat botiga-web-svc al port 80. Crea a més un Ingress botiga-ingress amb la IngressClass nginx que encamini l'amfitrió www.rutasnorte.es, ruta /, cap a aquest Service al port 80.

Criteri d'acceptació: el Service té endpoints poblats i kubectl describe ingress botiga-ingress mostra la regla amb el backend correcte.


Tasca 3 — Configuració i secrets

Namespace: rutas-norte-dev · Pes: 6 punts · Objectiu: 6 min

Crea:

  • Un ConfigMap config-api amb les claus nivell_log=info i max_connexions=100.
  • Un Secret credencials-postgres amb usuari=reserves i password=N0rt3-2026.

Crea un Pod api-reserves amb la imatge nginx:1.27-alpine que rebi totes les claus del ConfigMap com a variables d'entorn i munti el Secret com a fitxers a /etc/secrets en mode només lectura.

Criteri d'acceptació: kubectl exec api-reserves -- env | grep nivell_log retorna nivell_log=info i kubectl exec api-reserves -- cat /etc/secrets/usuari retorna reserves.


Tasca 4 — Emmagatzematge persistent

Namespace: rutas-norte-pre · Pes: 8 punts · Objectiu: 9 min

Crea un PersistentVolume pv-reserves de 3Gi, mode d'accés ReadWriteOnce, storageClassName: manual, política de reclamació Retain, amb hostPath a /mnt/dades-reserves.

Crea un PersistentVolumeClaim pvc-reserves de 2Gi que s'enllaci a aquest PV, i un Pod postgres-reserves amb imatge busybox:1.36 que executi sleep 3600 i munti el volum a /var/lib/dades.

Criteri d'acceptació: el PVC apareix com a Bound al PV pv-reserves i el pod està Running amb el volum muntat.


Tasca 5 — Planificació amb taints i etiquetes de node

Namespace: rutas-norte-pro · Pes: 6 punts · Objectiu: 6 min

El node simulacre-worker2 té el taint dedicat=batch:NoSchedule i l'etiqueta tipus=batch.

Crea un Deployment worker-notificacions amb imatge busybox:1.36, comanda sleep 3600, 2 rèpliques, que s'executi exclusivament en aquell node.

Criteri d'acceptació: els 2 pods estan Running a simulacre-worker2 (comprova-ho amb -o wide).


Tasca 6 — Treball programat

Namespace: rutas-norte-pro · Pes: 5 punts · Objectiu: 5 min

Crea un CronJob informes-nocturns que s'executi cada dia a les 2:30, amb imatge busybox:1.36 i comanda echo informe ocupacio generat. Requisits: no ha de permetre execucions solapades, cada Job pot reintentar com a màxim 2 vegades, i només es conserven 3 Jobs reeixits a l'historial.

Criteri d'acceptació: el CronJob existeix amb schedule: "30 2 * * *", concurrencyPolicy: Forbid, backoffLimit: 2 i successfulJobsHistoryLimit: 3. Una execució manual produeix la sortida esperada.


Tasca 7 — Sondes i qualitat de servei

Namespace: rutas-norte-pre · Pes: 7 punts · Objectiu: 7 min

El Deployment api-reserves ja existeix. Modifica'l perquè:

  • No rebi trànsit fins que respongui GET / al port 80 (readinessProbe).
  • Es reiniciï si GET / falla 3 vegades seguides (livenessProbe).
  • Tingui classe de QoS Guaranteed amb 200m de CPU i 256Mi de memòria.

Criteri d'acceptació: kubectl describe pod -l app=api-reserves -n rutas-norte-pre mostra QoS Class: Guaranteed i les dues sondes configurades.


Tasca 8 — RBAC i ServiceAccount

Namespace: rutas-norte-pro · Pes: 7 punts · Objectiu: 7 min

Crea la ServiceAccount suport-n1 a rutas-norte-pro. Concedeix-li permís per llistar i veure pods i llegir-ne els logs, únicament en aquell namespace. No ha de poder esborrar res ni accedir a altres namespaces.

Assigna aquesta ServiceAccount al Deployment botiga-web de rutas-norte-pro.

Criteri d'acceptació: kubectl auth can-i list pods --as=system:serviceaccount:rutas-norte-pro:suport-n1 -n rutas-norte-pro retorna yes; la mateixa consulta amb delete o en un altre namespace retorna no.


Tasca 9 — Política de xarxa

Namespace: rutas-norte-pro · Pes: 7 punts · Objectiu: 8 min

Crea a rutas-norte-pro una NetworkPolicy anomenada denegar-tot-entrada que denegui tot el trànsit d'entrada a tots els pods del namespace.

Afegeix una segona política permetre-botiga-des-dingress que permeti el trànsit d'entrada al port 80 dels pods amb etiqueta app=botiga-web únicament des de pods del namespace ingress-nginx.

Criteri d'acceptació: totes dues polítiques existeixen; un pod de prova dins de rutas-norte-pro no pot arribar a botiga-web al port 80.


Tasca 10 — Enduriment i admissió

Namespace: rutas-norte-pre · Pes: 7 punts · Objectiu: 7 min

Etiqueta el namespace rutas-norte-pre perquè apliqui l'estàndard restricted de Pod Security Admission en mode enforce, amb la versió v1.30.

Després, crea un Pod auditor-segur (imatge busybox:1.36, comanda sleep 3600) que compleixi aquell estàndard: usuari no root, sense escalada de privilegis, totes les capacitats descartades i perfil seccomp per defecte.

Criteri d'acceptació: el namespace té l'etiqueta d'enforce; el pod auditor-segur està Running; un kubectl run insegur --image=nginx -n rutas-norte-pre és rebutjat.


Tasca 11 — Autoescalat

Namespace: rutas-norte-pre · Pes: 5 punts · Objectiu: 4 min

Crea un HorizontalPodAutoscaler per al Deployment api-reserves que mantingui entre 2 i 8 rèpliques, amb un objectiu d'utilització de CPU del 65 %.

Criteri d'acceptació: kubectl get hpa -n rutas-norte-pre mostra l'HPA amb MINPODS 2, MAXPODS 8 i l'objectiu del 65 %, i amb mètriques llegides (no <unknown>).


Tasca 12 — Actualització i reversió

Namespace: rutas-norte-pro · Pes: 6 punts · Objectiu: 6 min

Sobre el Deployment botiga-web de rutas-norte-pro:

  1. Configura l'estratègia perquè cap pod no deixi d'estar disponible durant l'actualització (maxUnavailable: 0, maxSurge: 1).
  2. Actualitza la imatge a nginx:1.27.2-alpine i anota la causa del canvi com a pujada a 1.27.2.
  3. Espera que acabi el desplegament i després reverteix a la revisió anterior.

Criteri d'acceptació: kubectl rollout history mostra almenys dues revisions amb la causa anotada, i la imatge final torna a ser nginx:1.27-alpine.


Tasca 13 — Resolució de problemes: el worker que no arrenca

Namespace: rutas-norte-dev · Pes: 8 punts · Objectiu: 8 min

El Deployment worker-notificacions de rutas-norte-dev no aconsegueix arrencar cap pod. Troba'n la causa, corregeix-la sense canviar la imatge ni la configuració del contenidor, i deixa el Deployment amb almenys un pod en Running.

Escriu a /opt/diagnostic-13.txt una línia explicant la causa arrel.

Criteri d'acceptació: el Deployment té 1/1 pods a punt i el fitxer de diagnòstic existeix i no està buit.


Tasca 14 — Resolució de problemes: el servei que no respon

Namespace: rutas-norte-pre · Pes: 8 punts · Objectiu: 8 min

El Service redis-cache-svc de rutas-norte-pre no està servint trànsit: les aplicacions que el criden reben un rebuig de connexió, tot i que els pods de redis-cache estan Running. Troba'n la causa i arregla-la sense modificar els pods.

Escriu a /opt/diagnostic-14.txt una línia explicant la causa arrel.

Criteri d'acceptació: kubectl get endpoints redis-cache-svc -n rutas-norte-pre mostra almenys una adreça IP.


Tasca 15 — Resolució de problemes: el pod que mai no es planifica

Namespace: rutas-norte-pro · Pes: 8 punts · Objectiu: 8 min

El Pod informes-ocupacio de rutas-norte-pro fa minuts que està en estat Pending. Esbrina per què i aconsegueix posar-lo en Running. Pots triar entre corregir el pod o adaptar el clúster; justifica la teva decisió.

Escriu a /opt/diagnostic-15.txt una línia explicant la causa arrel i la solució triada.

Criteri d'acceptació: el pod informes-ocupacio està en Running.


  1. Repartiment del temps i estratègia

4.1 Temps objectiu

Tasca Tema Punts Objectiu Acumulat
1 Deployment i escalat 5 4 min 4
2 Service i Ingress 7 8 min 12
3 ConfigMap i Secret 6 6 min 18
4 PV, PVC i Pod 8 9 min 27
5 Taints i nodeSelector 6 6 min 33
6 CronJob 5 5 min 38
7 Sondes i QoS 7 7 min 45
8 RBAC 7 7 min 52
9 NetworkPolicy 7 8 min 60
10 Pod Security 7 7 min 67
11 HPA 5 4 min 71
12 Rollout i rollback 6 6 min 77
13 TS: worker caigut 8 8 min 85
14 TS: Service sense endpoints 8 8 min 93
15 TS: pod Pending 8 8 min 101
Revisió final ~19 min 120

Total: 100 punts en 101 minuts de feina, amb 19 de marge. Aquest marge és deliberat: a l'examen real es consumeix en dubtes, tasques que s'allarguen i la revisió.

4.2 Estratègia suggerida

Aplicant el que s'ha après a la lliçó 12-04:

Min 0-4     Bloc d arrencada + lectura de les 15 tasques
Min 4-30    Fase 1 (barates i rapides): T1, T11, T6, T3, T12   → 27 punts
Min 30-85   Fase 2 (bloc central per pes): T4, T13, T14, T15, T9, T8, T10, T7, T2, T5
Min 85-105  Fase 3: pendents i mig fetes
Min 105-120 Revisio: context, namespace i funcionament de cada objecte

Si t'ajustes a l'ordre numèric també funciona, però fixa't que les tasques 11 i 6 valen 10 punts entre totes dues i es resolen en 9 minuts: són les de millor rendiment del simulacre.

4.3 Recordatori abans de començar

  • Fixa el namespace en començar cada tasca, o fes servir -n a totes les comandes.
  • Verifica cada tasca abans de passar a la següent.
  • Si una tasca supera el seu objectiu en més del 50 %, marca-la i salta.
  • No deixis mai una tasca completament buida: hi ha puntuació parcial.

Quan acabis, encara no llegeixis el solucionari. Primer fes els exercicis integradors de la secció següent si et queden forces, o descansa i torna-hi més tard.


Errors Comuns i Consells

Aquestes són les fallades que més es repeteixen en aquest simulacre concret.

Error Tasca afectada Com evitar-ho
Crear el Deployment sense l'etiqueta demanada als pods (només al Deployment) 1 L'etiqueta va a spec.template.metadata.labels
Ingress amb pathType: Exact en lloc de Prefix 2 Fer servir --rule="host/*=svc:80" amb asterisc
Confondre envFrom amb env 3 "Totes les claus" → envFrom; "una clau" → valueFrom
storageClassName diferent entre PV i PVC 4 Ha de coincidir exactament, o el PVC queda Pending
Posar només la tolerància i oblidar el nodeSelector 5 La tolerància permet, no obliga: calen totes dues coses
backoffLimit al nivell equivocat del CronJob 6 Va a jobTemplate.spec, no a CronJob.spec
QoS Burstable en lloc de Guaranteed 7 requests i limits idèntics en CPU i memòria
Oblidar el subrecurs pods/log 8 Sense ell, kubectl logs falla encara que puguis llistar pods
NetworkPolicy sense policyTypes 9 Sense policyTypes: [Ingress] no hi ha denegació efectiva
Aplicar restricted i oblidar seccompProfile 10 L'estàndard restricted ho exigeix explícitament
HPA amb <unknown> a les mètriques 11 Falta el metrics-server o el Deployment no té requests de CPU
rollout undo sense comprovar la imatge resultant 12 Verificar amb jsonpath la imatge final
Diagnosticar sense mirar els esdeveniments 13, 14, 15 kubectl describe i kubectl get events --sort-by=.lastTimestamp
Esborrar pods d'un Deployment per "arreglar-los" 13 Es recreen idèntics: cal corregir la causa

Tres consells finals per al simulacre:

  1. Tracta les tres tasques de resolució de problemes com l'examen real les tracta: valen 24 dels 100 punts, gairebé una quarta part. Si vas just de temps, prioritza-les abans que les de creació.
  2. Si un objecte queda Pending o CrashLoopBackOff, la tasca no està feta. No n'hi ha prou que el YAML s'hagi aplicat.
  3. Anota el temps real de cada tasca. L'anàlisi posterior d'on se't va anar el temps val tant com la nota.

Exercicis

Aquests tres reptes integradors són de més calat que les tasques del simulacre: cadascun ajunta diversos mòduls del curs en un únic objectiu. Fes-los després del simulacre, sense límit estricte de temps però mesurant-lo.

Exercici 1 — Component complet des de zero

Partint d'un namespace buit rutas-norte-qa, deixa operatiu el component api-reserves complet, a punt per a producció:

  1. Un ConfigMap config-api i un Secret credencials-postgres.
  2. Un Deployment api-reserves amb 3 rèpliques, imatge nginx:1.27-alpine, que consumeixi el ConfigMap per variables i el Secret per fitxer.
  3. Sondes de readiness i liveness, i QoS Guaranteed.
  4. Una ServiceAccount pròpia sa-api amb permís per llegir únicament el ConfigMap config-api, i sense muntatge automàtic del token.
  5. Un Service ClusterIP i un Ingress amb amfitrió qa.rutasnorte.es.
  6. Un HPA entre 3 i 9 rèpliques al 70 % de CPU.
  7. Un PodDisruptionBudget que garanteixi almenys 2 pods disponibles.
  8. NetworkPolicy de denegació d'entrada per defecte, amb excepció per al namespace ingress-nginx.

Objectiu: 30 minuts. Criteri: tot Running, endpoints poblats, auth can-i correcte i el pod sense token muntat.

Exercici 2 — Recuperar un servei caigut

Provoca deliberadament aquesta situació a rutas-norte-pro i després recupera-la com si fos una incidència real (fes servir el runbook mental del mòdul 11):

El símptoma que et donen: "els usuaris reben 503 en entrar a la botiga; l'Ingress respon però no arriba res al darrere".

Preparació de l'avaria (que la faci una altra persona, o fes-la tu i espera un dia per oblidar-ne els detalls):

kubectl scale deployment botiga-web --replicas=0 -n rutas-norte-pro
kubectl patch svc botiga-web-svc -n rutas-norte-pro \
  -p '{"spec":{"ports":[{"port":80,"targetPort":8080}]}}'
kubectl set image deployment/botiga-web botiga-web=nginx:9.9-noexisteix -n rutas-norte-pro

El que se't demana: diagnosticar en ordre (Ingress → Service → endpoints → pods → contenidor), documentar cada troballa, arreglar les tres causes i deixar el servei servint amb 3 rèpliques. Escriu un breu informe d'incidència amb línia temporal, causa arrel i acció preventiva.

Exercici 3 — Migració amb zero talls

A rutas-norte-pro, el Deployment botiga-web serveix trànsit. Migra el seu emmagatzematge de configuració d'un ConfigMap muntat com a fitxer a un ConfigMap diferent amb contingut nou, sense que cap pod deixi d'estar disponible en cap moment i sense que se serveixi contingut inconsistent.

Passos que has de resoldre: crear el nou ConfigMap, ajustar l'estratègia del Deployment, pausar el rollout per agrupar canvis, aplicar la modificació, reprendre, verificar a cada fase que hi ha almenys 3 pods a punt, i tenir preparat el rollout undo si alguna cosa falla.

Demostra amb un bucle de peticions que no hi ha hagut ni una sola fallada durant la migració.


Solucions

Solució a l'Exercici 1

k create namespace rutas-norte-qa
k config set-context --current --namespace=rutas-norte-qa

# 1. Configuració
k create configmap config-api --from-literal=nivell_log=info --from-literal=max_connexions=100
k create secret generic credencials-postgres \
  --from-literal=usuari=reserves --from-literal=password='N0rt3-2026'

# 4a. ServiceAccount i RBAC mínim
k create serviceaccount sa-api
k patch serviceaccount sa-api -p '{"automountServiceAccountToken": false}'
k create role lector-config --verb=get --resource=configmaps --resource-name=config-api
k create rolebinding lector-config-b --role=lector-config \
  --serviceaccount=rutas-norte-qa:sa-api

El Deployment amb tot integrat:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-reserves
  namespace: rutas-norte-qa
spec:
  replicas: 3
  selector:
    matchLabels: { app: api-reserves }
  template:
    metadata:
      labels: { app: api-reserves }
    spec:
      serviceAccountName: sa-api
      automountServiceAccountToken: false
      containers:
      - name: api
        image: nginx:1.27-alpine
        ports:
        - containerPort: 80
        envFrom:
        - configMapRef: { name: config-api }
        volumeMounts:
        - name: secrets
          mountPath: /etc/secrets
          readOnly: true
        resources:
          requests: { cpu: 200m, memory: 256Mi }
          limits:   { cpu: 200m, memory: 256Mi }
        readinessProbe:
          httpGet: { path: /, port: 80 }
          initialDelaySeconds: 3
          periodSeconds: 10
        livenessProbe:
          httpGet: { path: /, port: 80 }
          periodSeconds: 15
          failureThreshold: 3
      volumes:
      - name: secrets
        secret: { secretName: credencials-postgres }
k apply -f api-qa.yaml

# 5. Exposició
k expose deployment api-reserves --name=api-reserves-svc --port=80 --target-port=80
k create ingress api-ingress --class=nginx --rule="qa.rutasnorte.es/*=api-reserves-svc:80"

# 6. HPA
k autoscale deployment api-reserves --min=3 --max=9 --cpu-percent=70

# 7. PDB
k create poddisruptionbudget api-pdb --selector=app=api-reserves --min-available=2
# 8. NetworkPolicies
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: denegar-entrada, namespace: rutas-norte-qa }
spec:
  podSelector: {}
  policyTypes: [Ingress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: permetre-des-dingress, namespace: rutas-norte-qa }
spec:
  podSelector:
    matchLabels: { app: api-reserves }
  policyTypes: [Ingress]
  ingress:
  - from:
    - namespaceSelector:
        matchLabels: { kubernetes.io/metadata.name: ingress-nginx }
    ports:
    - protocol: TCP
      port: 80

Verificació completa:

k get deploy,svc,ing,hpa,pdb,netpol -n rutas-norte-qa
k describe pod -l app=api-reserves -n rutas-norte-qa | grep "QoS Class"     # Guaranteed
k exec deploy/api-reserves -n rutas-norte-qa -- ls /var/run/secrets/kubernetes.io/serviceaccount
# No such file or directory  ← el token NO està muntat
k auth can-i get configmap/config-api --as=system:serviceaccount:rutas-norte-qa:sa-api -n rutas-norte-qa   # yes
k auth can-i list configmaps --as=system:serviceaccount:rutas-norte-qa:sa-api -n rutas-norte-qa            # no

Les dues trampes de l'exercici: automountServiceAccountToken: false cal posar-lo al pod (o a la SA), i el --resource-name=config-api al Role és el que converteix "llegir un ConfigMap concret" en un permís realment mínim.

Solució a l'Exercici 2

Diagnòstic en l'ordre correcte, de fora cap endins:

# 1. L'Ingress: té backend?
k describe ingress botiga-ingress -n rutas-norte-pro | grep -A5 Rules
Host                Path  Backends
www.rutasnorte.es   /     botiga-web-svc:80 (<error: endpoints "botiga-web-svc" not found>)
# 2. El Service: té endpoints?
k get endpoints botiga-web-svc -n rutas-norte-pro
NAME             ENDPOINTS   AGE
botiga-web-svc   <none>      42m
# 3. Hi ha pods?
k get pods -l app=botiga-web -n rutas-norte-pro
k get deploy botiga-web -n rutas-norte-pro
No resources found.
NAME         READY   UP-TO-DATE   AVAILABLE
botiga-web   0/0     0            0

Troballa 1: el Deployment està escalat a 0.

k scale deployment botiga-web --replicas=3 -n rutas-norte-pro
k get pods -l app=botiga-web -n rutas-norte-pro
NAME                          READY   STATUS             RESTARTS
botiga-web-7d9f8c6b4d-abc12   0/1     ImagePullBackOff   0

Troballa 2: imatge inexistent.

k describe pod -l app=botiga-web -n rutas-norte-pro | grep -A3 Events
# Failed to pull image "nginx:9.9-noexisteix": manifest unknown

k set image deployment/botiga-web botiga-web=nginx:1.27-alpine -n rutas-norte-pro
k rollout status deployment/botiga-web -n rutas-norte-pro
k get endpoints botiga-web-svc -n rutas-norte-pro
NAME             ENDPOINTS
botiga-web-svc   <none>

Els pods estan Running però el Service continua sense endpoints: hi ha una tercera causa.

k get svc botiga-web-svc -n rutas-norte-pro -o yaml | grep -A4 ports
  ports:
  - port: 80
    targetPort: 8080     ← els pods escolten al 80

Troballa 3: targetPort equivocat.

k patch svc botiga-web-svc -n rutas-norte-pro \
  -p '{"spec":{"ports":[{"port":80,"targetPort":80,"protocol":"TCP"}]}}'
k get endpoints botiga-web-svc -n rutas-norte-pro
NAME             ENDPOINTS
botiga-web-svc   10.244.1.7:80,10.244.2.5:80,10.244.2.6:80

Informe d'incidència:

INCIDENCIA: 503 a www.rutasnorte.es
Impacte: botiga-web no disponible, 42 minuts.
Linia temporal:
  T+0  Alerta de 503 des de l Ingress
  T+2  Ingress OK, Service sense endpoints
  T+4  Deployment escalat a 0 -> escalat a 3
  T+6  Pods en ImagePullBackOff (nginx:9.9-noexisteix) -> imatge corregida
  T+9  Pods Running pero Service encara sense endpoints
  T+11 targetPort 8080 davant del port real 80 -> corregit
  T+12 Servei restablert
Causa arrel: tres canvis manuals no revisats sobre produccio.
Prevencio: gestionar rutas-norte-pro per GitOps (modul 10), impedir canvis
manuals amb RBAC, i alerta sobre "Service sense endpoints" a Prometheus.

La lliçó d'aquest exercici: un símptoma pot amagar diverses causes encadenades. Arreglar la primera i donar per tancada la incidència és l'error clàssic. La verificació d'extrem a extrem (endpoints poblats) és l'única cosa que demostra que està resolt.

Solució a l'Exercici 3

# 0. Bucle de comprovació en un altre terminal
while true; do
  k run probe-$RANDOM --rm -q --image=busybox:1.36 --restart=Never -n rutas-norte-pro \
    -- wget -qO- --timeout=2 botiga-web-svc >/dev/null 2>&1 \
    && echo -n "." || echo -n "X"
  sleep 1
done
# 1. Nou ConfigMap
k create configmap config-web-v2 \
  --from-literal=index.html='<h1>Rutas Norte v2</h1>' -n rutas-norte-pro

# 2. Estratègia sense talls
k patch deployment botiga-web -n rutas-norte-pro -p '
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0'

# 3. Pausar per agrupar canvis
k rollout pause deployment/botiga-web -n rutas-norte-pro

# 4. Aplicar els canvis (encara no se'n desplega cap)
k patch deployment botiga-web -n rutas-norte-pro --type='json' -p='[
  {"op":"replace","path":"/spec/template/spec/volumes/0/configMap/name","value":"config-web-v2"}
]'
k annotate deployment botiga-web -n rutas-norte-pro \
  kubernetes.io/change-cause="migracio a config-web-v2" --overwrite

# 5. Reprendre: ara sí, un únic rollout
k rollout resume deployment/botiga-web -n rutas-norte-pro
k rollout status deployment/botiga-web -n rutas-norte-pro --timeout=120s

Verificació durant el procés (en un tercer terminal):

watch -n1 'kubectl get deploy botiga-web -n rutas-norte-pro; \
           kubectl get endpoints botiga-web-svc -n rutas-norte-pro'
NAME         READY   UP-TO-DATE   AVAILABLE
botiga-web   4/3     1            3          ← mai no baixa de 3 disponibles

El bucle de peticions ha de mostrar només punts, sense cap X.

Pla de reversió preparat abans de començar:

k rollout undo deployment/botiga-web -n rutas-norte-pro
k rollout status deployment/botiga-web -n rutas-norte-pro

Les tres claus de l'exercici:

  1. maxUnavailable: 0 és el que garanteix que mai no faltin pods; maxSurge: 1 és el que permet que el rollout avanci (amb tots dos a 0 es bloquejaria).
  2. rollout pause / resume evita dos rollouts consecutius quan hi ha diversos canvis: sense pausar, el patch del volum i l'anotació provocarien dues onades de reemplaçament.
  3. La readinessProbe és imprescindible: sense ella, Kubernetes considera "disponible" un pod que encara no serveix, i sí que hi hauria talls encara que maxUnavailable sigui 0.

Solucionari del Simulacre

Ara sí. Corregeix tasca per tasca i anota la teva puntuació.

Tasca 1 (5 punts)

k create deployment botiga-web --image=nginx:1.27-alpine --replicas=4 \
  -n rutas-norte-dev $do > t1.yaml

Afegir l'etiqueta a la plantilla del pod:

  template:
    metadata:
      labels:
        app: botiga-web
        entorn: dev          # ← aquí
k apply -f t1.yaml
k scale deployment botiga-web --replicas=6 -n rutas-norte-dev

Comprovació:

k get deploy botiga-web -n rutas-norte-dev
k get pods -n rutas-norte-dev --show-labels | head -3
NAME         READY   UP-TO-DATE   AVAILABLE
botiga-web   6/6     6            6

botiga-web-6c9d...   1/1  Running  app=botiga-web,entorn=dev,pod-template-hash=...

La trampa: posar entorn=dev només a metadata.labels del Deployment. Els pods no l'hereten: va a spec.template.metadata.labels. Alternativa vàlida i més ràpida: crear el Deployment i després k label pods -l app=botiga-web entorn=dev, però llavors els pods nous de l'escalat no la portarien.

Tasca 2 (7 punts)

k expose deployment botiga-web --name=botiga-web-svc \
  --port=80 --target-port=80 -n rutas-norte-dev

k create ingress botiga-ingress -n rutas-norte-dev --class=nginx \
  --rule="www.rutasnorte.es/*=botiga-web-svc:80"

Comprovació:

k get endpoints botiga-web-svc -n rutas-norte-dev
k describe ingress botiga-ingress -n rutas-norte-dev | grep -A4 Rules
NAME             ENDPOINTS
botiga-web-svc   10.244.1.5:80,10.244.1.6:80,... (6 adreces)

Rules:
  Host                Path  Backends
  www.rutasnorte.es   /     botiga-web-svc:80 (10.244.1.5:80,...)

La trampa: el /* genera pathType: Prefix. Sense l'asterisc obtindries Exact, que només casa l'arrel literal. I si el describe mostra <error: endpoints not found>, el Service està malament.

Tasca 3 (6 punts)

k create configmap config-api --from-literal=nivell_log=info \
  --from-literal=max_connexions=100 -n rutas-norte-dev

k create secret generic credencials-postgres \
  --from-literal=usuari=reserves --from-literal=password='N0rt3-2026' -n rutas-norte-dev

k run api-reserves --image=nginx:1.27-alpine -n rutas-norte-dev $do > t3.yaml
spec:
  containers:
  - name: api-reserves
    image: nginx:1.27-alpine
    envFrom:
    - configMapRef:
        name: config-api
    volumeMounts:
    - name: secrets
      mountPath: /etc/secrets
      readOnly: true
  volumes:
  - name: secrets
    secret:
      secretName: credencials-postgres

Comprovació:

k exec api-reserves -n rutas-norte-dev -- env | grep -E 'nivell_log|max_connexions'
k exec api-reserves -n rutas-norte-dev -- cat /etc/secrets/usuari
nivell_log=info
max_connexions=100
reserves

La trampa: envFrom és germà d'env, no fill. I "muntar el Secret com a fitxers" significa volum, no secretKeyRef.

Tasca 4 (8 punts)

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-reserves
spec:
  capacity: { storage: 3Gi }
  accessModes: [ReadWriteOnce]
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath: { path: /mnt/dades-reserves }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-reserves
  namespace: rutas-norte-pre
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: manual
  resources:
    requests: { storage: 2Gi }
---
apiVersion: v1
kind: Pod
metadata:
  name: postgres-reserves
  namespace: rutas-norte-pre
spec:
  containers:
  - name: postgres
    image: busybox:1.36
    command: ["sleep", "3600"]
    volumeMounts:
    - name: dades
      mountPath: /var/lib/dades
  volumes:
  - name: dades
    persistentVolumeClaim:
      claimName: pvc-reserves

Comprovació:

k get pv pv-reserves
k get pvc,pod -n rutas-norte-pre
NAME          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM
pv-reserves   3Gi        RWO            Retain           Bound    rutas-norte-pre/pvc-reserves

NAME                                 STATUS   VOLUME        CAPACITY
persistentvolumeclaim/pvc-reserves   Bound    pv-reserves   3Gi

La trampa doble: el PV és d'àmbit de clúster (sense namespace), i el storageClassName: manual ha d'estar en tots dos. Si l'omets al PVC, l'aprovisionador dinàmic per defecte intentaria crear un altre volum i el PVC no s'enllaçaria al teu.

Tasca 5 (6 punts)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: worker-notificacions
  namespace: rutas-norte-pro
spec:
  replicas: 2
  selector:
    matchLabels: { app: worker-notificacions }
  template:
    metadata:
      labels: { app: worker-notificacions }
    spec:
      nodeSelector:
        tipus: batch
      tolerations:
      - key: dedicat
        operator: Equal
        value: batch
        effect: NoSchedule
      containers:
      - name: worker
        image: busybox:1.36
        command: ["sleep", "3600"]

Comprovació:

k get pods -l app=worker-notificacions -n rutas-norte-pro -o wide
NAME                     READY   STATUS    NODE
worker-notificacions-x   1/1     Running   simulacre-worker2
worker-notificacions-y   1/1     Running   simulacre-worker2

La trampa: calen les dues peces. La toleration només permet que el pod entri en aquell node; el nodeSelector és el que l'obliga a anar-hi. Amb només la tolerància, els pods anirien a qualsevol node lliure.

Tasca 6 (5 punts)

apiVersion: batch/v1
kind: CronJob
metadata:
  name: informes-nocturns
  namespace: rutas-norte-pro
spec:
  schedule: "30 2 * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: informes
            image: busybox:1.36
            command: ["/bin/sh", "-c", "echo informe ocupacio generat"]

Comprovació:

k get cronjob informes-nocturns -n rutas-norte-pro
k create job prova-t6 --from=cronjob/informes-nocturns -n rutas-norte-pro
k logs job/prova-t6 -n rutas-norte-pro
informe ocupacio generat

La trampa: backoffLimit va a jobTemplate.spec; concurrencyPolicy i successfulJobsHistoryLimit a CronJob.spec. I restartPolicy ha de ser OnFailure o Never: Always fa que l'API rebutgi el manifest.

Tasca 7 (7 punts)

k edit deployment api-reserves -n rutas-norte-pre
      containers:
      - name: nginx
        image: nginx:1.27-alpine
        resources:
          requests: { cpu: 200m, memory: 256Mi }
          limits:   { cpu: 200m, memory: 256Mi }
        readinessProbe:
          httpGet: { path: /, port: 80 }
          initialDelaySeconds: 3
          periodSeconds: 10
        livenessProbe:
          httpGet: { path: /, port: 80 }
          periodSeconds: 10
          failureThreshold: 3

Comprovació:

k rollout status deployment/api-reserves -n rutas-norte-pre
k describe pod -l app=api-reserves -n rutas-norte-pre | grep "QoS Class"
QoS Class:  Guaranteed

La trampa: Guaranteed exigeix requests idèntics a limits en CPU i memòria i a tots els contenidors. Posar només limits també funciona (Kubernetes copia els requests), però posar requests diferents dels limits dona Burstable i la tasca puntua a la meitat.

Tasca 8 (7 punts)

k create serviceaccount suport-n1 -n rutas-norte-pro

k create role suport-lectura --verb=get,list,watch \
  --resource=pods,pods/log -n rutas-norte-pro

k create rolebinding suport-lectura-b --role=suport-lectura \
  --serviceaccount=rutas-norte-pro:suport-n1 -n rutas-norte-pro

k set serviceaccount deployment/botiga-web suport-n1 -n rutas-norte-pro

Comprovació:

SA=system:serviceaccount:rutas-norte-pro:suport-n1
k auth can-i list pods --as=$SA -n rutas-norte-pro          # yes
k auth can-i get pods/log --as=$SA -n rutas-norte-pro       # yes
k auth can-i delete pods --as=$SA -n rutas-norte-pro        # no
k auth can-i list pods --as=$SA -n rutas-norte-dev          # no
k get deploy botiga-web -n rutas-norte-pro \
  -o jsonpath='{.spec.template.spec.serviceAccountName}'    # suport-n1

La trampa: pods/log és un recurs a part. Sense ell, kubectl logs retorna Forbidden encara que puguis llistar pods. I fer servir ClusterRole+ClusterRoleBinding donaria accés a tots els namespaces: violaria el criteri.

Tasca 9 (7 punts)

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: denegar-tot-entrada
  namespace: rutas-norte-pro
spec:
  podSelector: {}
  policyTypes: [Ingress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: permetre-botiga-des-dingress
  namespace: rutas-norte-pro
spec:
  podSelector:
    matchLabels:
      app: botiga-web
  policyTypes: [Ingress]
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: ingress-nginx
    ports:
    - protocol: TCP
      port: 80

Comprovació:

k get netpol -n rutas-norte-pro

# Des de dins del namespace: ha de FALLAR
k run test --rm -it --image=busybox:1.36 --restart=Never -n rutas-norte-pro \
  -- wget -qO- --timeout=3 botiga-web-svc
wget: download timed out

La trampa: sense policyTypes: [Ingress] a la primera política, l'objecte existeix però no denega res. I kubernetes.io/metadata.name és una etiqueta que Kubernetes posa automàticament a tots els namespaces des de la 1.21: és la manera fiable de seleccionar-ne un pel nom.

Tasca 10 (7 punts)

k label namespace rutas-norte-pre \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=v1.30 --overwrite
apiVersion: v1
kind: Pod
metadata:
  name: auditor-segur
  namespace: rutas-norte-pre
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 10001
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: auditor
    image: busybox:1.36
    command: ["sleep", "3600"]
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]

Comprovació:

k get ns rutas-norte-pre --show-labels
k get pod auditor-segur -n rutas-norte-pre
k run insegur --image=nginx -n rutas-norte-pre
Error from server (Forbidden): pods "insegur" is forbidden:
violates PodSecurity "restricted:v1.30": allowPrivilegeEscalation != false,
unrestricted capabilities, runAsNonRoot != true, seccompProfile

La trampa: l'estàndard restricted exigeix quatre coses alhora i la gent oblida seccompProfile: RuntimeDefault. El missatge d'error les enumera totes: fes-lo servir com a llista de comprovació.

Efecte lateral a tenir en compte: en aplicar enforce a rutas-norte-pre, el Deployment api-reserves de la tasca 7 continuarà corrent (PSA no expulsa pods existents), però no podrà crear pods nous. Si fas la tasca 10 abans que la 7 o la 11, el rollout es bloquejarà. És exactament el tipus d'interacció entre tasques que apareix a l'examen real.

Tasca 11 (5 punts)

k autoscale deployment api-reserves --min=2 --max=8 --cpu-percent=65 -n rutas-norte-pre

Comprovació:

k get hpa -n rutas-norte-pre
NAME           REFERENCE                 TARGETS        MINPODS   MAXPODS   REPLICAS
api-reserves   Deployment/api-reserves   cpu: 0%/65%    2         8         2

La trampa: si TARGETS mostra <unknown>/65%, hi ha dues causes possibles: el metrics-server no està instal·lat o no respon, o el Deployment no té requests de CPU (l'HPA calcula el percentatge sobre el request). Si vas fer la tasca 7 abans, els requests ja hi són i això funciona; si no, cal afegir-los.

Tasca 12 (6 punts)

k patch deployment botiga-web -n rutas-norte-pro -p '
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0'

k set image deployment/botiga-web nginx=nginx:1.27.2-alpine -n rutas-norte-pro
k annotate deployment botiga-web -n rutas-norte-pro \
  kubernetes.io/change-cause="pujada a 1.27.2" --overwrite

k rollout status deployment/botiga-web -n rutas-norte-pro
k rollout history deployment/botiga-web -n rutas-norte-pro
k rollout undo deployment/botiga-web -n rutas-norte-pro

Comprovació:

k rollout status deployment/botiga-web -n rutas-norte-pro
k get deploy botiga-web -n rutas-norte-pro \
  -o jsonpath='{.spec.template.spec.containers[0].image}'
nginx:1.27-alpine

La trampa: el nom del contenidor a set image no és lliure. Comprova'l primer:

k get deploy botiga-web -n rutas-norte-pro \
  -o jsonpath='{.spec.template.spec.containers[*].name}'

Si set image no troba el contenidor, la comanda diu error: unable to find container named ... i no fa res.

Tasca 13 (8 punts) — Resolució de problemes

k get pods -n rutas-norte-dev
NAME                                    READY   STATUS                       RESTARTS
worker-notificacions-5d8f...-abc        0/1     CreateContainerConfigError   0
k describe pod -l app=worker-notificacions -n rutas-norte-dev | tail -8
Events:
  Warning  Failed  30s (x9 over 3m)  kubelet
    Error: secret "credencials-cua" not found

Causa arrel: el Deployment referencia amb envFrom un Secret que no existeix al namespace.

k create secret generic credencials-cua \
  --from-literal=host=cua.rutasnorte.es --from-literal=token=abc123 \
  -n rutas-norte-dev

k get pods -n rutas-norte-dev -w
echo "El Deployment referenciava amb envFrom el Secret credencials-cua, inexistent a rutas-norte-dev" \
  > /opt/diagnostic-13.txt

Comprovació:

k get deploy worker-notificacions -n rutas-norte-dev
cat /opt/diagnostic-13.txt
NAME                   READY   UP-TO-DATE   AVAILABLE
worker-notificacions   1/1     1            1

La trampa: CreateContainerConfigError sempre apunta a un ConfigMap o Secret absent, o a una clau inexistent dins d'un que sí que existeix. No ho confonguis amb CrashLoopBackOff (el procés arrenca i mor) ni amb ImagePullBackOff (problema d'imatge). I l'enunciat prohibeix canviar la configuració del contenidor: la solució és crear el Secret, no treure l'envFrom.

Tasca 14 (8 punts) — Resolució de problemes

k get pods,svc,endpoints -n rutas-norte-pre | grep redis
pod/redis-cache-6f8b...-xyz   1/1     Running
service/redis-cache-svc       ClusterIP   10.96.87.4   6379/TCP
endpoints/redis-cache-svc     <none>                    ← el símptoma

Els pods estan bé però el Service no els veu. Això només té una explicació: el selector no casa les etiquetes.

k get svc redis-cache-svc -n rutas-norte-pre -o jsonpath='{.spec.selector}'
k get pods -l app=redis-cache -n rutas-norte-pre --show-labels
{"app":"redis"}
NAME                       LABELS
redis-cache-6f8b...-xyz    app=redis-cache,pod-template-hash=6f8b...

Causa arrel: el Service selecciona app=redis, però els pods porten app=redis-cache.

k patch svc redis-cache-svc -n rutas-norte-pre \
  -p '{"spec":{"selector":{"app":"redis-cache"}}}'

echo "El selector del Service era app=redis i els pods porten app=redis-cache" \
  > /opt/diagnostic-14.txt

Comprovació:

k get endpoints redis-cache-svc -n rutas-norte-pre
NAME              ENDPOINTS
redis-cache-svc   10.244.2.9:6379

La trampa: l'enunciat prohibeix tocar els pods, així que canviar-ne les etiquetes no val (i a més trencaria el ReplicaSet, que els recrearia). Cal corregir el Service. Recorda la regla: Service sense endpoints = selector que no casa en el 90 % dels casos; el 10 % restant són pods no Ready per una readinessProbe que falla.

Tasca 15 (8 punts) — Resolució de problemes

k get pod informes-ocupacio -n rutas-norte-pro
k describe pod informes-ocupacio -n rutas-norte-pro | tail -6
NAME                READY   STATUS    RESTARTS   AGE
informes-ocupacio   0/1     Pending   0          14m

Events:
  Warning  FailedScheduling  2m (x8 over 14m)  default-scheduler
    0/3 nodes are available: 1 node(s) had untolerated taint
    {node-role.kubernetes.io/control-plane: }, 2 node(s) didn't match
    Pod's node affinity/selector.

Causa arrel: el pod té nodeSelector: disc=nvme i cap node no porta aquesta etiqueta.

k get pod informes-ocupacio -n rutas-norte-pro -o jsonpath='{.spec.nodeSelector}'
k get nodes --show-labels | grep disc || echo "cap node te l etiqueta disc"

Dues solucions vàlides. La millor és etiquetar el node, perquè el nodeSelector expressa un requisit real (un disc ràpid per generar informes) i el pod és immutable en aquest camp:

k label node simulacre-worker disc=nvme
k get pod informes-ocupacio -n rutas-norte-pro -o wide
NAME                READY   STATUS    NODE
informes-ocupacio   1/1     Running   simulacre-worker

L'alternativa —recrear el pod sense nodeSelector— també deixa el pod Running, però perd la intenció del disseny:

k get pod informes-ocupacio -n rutas-norte-pro -o yaml > t15.yaml
# eliminar la seccio nodeSelector
k delete pod informes-ocupacio -n rutas-norte-pro
k apply -f t15.yaml
echo "nodeSelector disc=nvme sense cap node etiquetat; solucio: etiquetar simulacre-worker amb disc=nvme per respectar el requisit de planificacio" \
  > /opt/diagnostic-15.txt

La trampa: el missatge de FailedScheduling és la resposta completa i cal saber llegir-lo. didn't match Pod's node affinity/selector és nodeSelector o afinitat; untolerated taint és un taint sense tolerància; Insufficient cpu/memory són recursos; pod has unbound immediate PersistentVolumeClaims és emmagatzematge. Quatre missatges, quatre causes diferents.


Autoavaluació

Suma els punts obtinguts i busca la teva franja.

Puntuació Veredicte Què fer
90-100 A punt per presentar-te. El teu nivell està clarament per damunt del llindar d'aprovat, amb marge per a un mal dia. Reserva la data ja. Dedica els dies previs a mantenir velocitat, no a estudiar temes nous.
75-89 Pràcticament a punt. Aprovaries, però sense coixí. Identifica les 2-3 tasques que vas fallar, repassa'n les lliçons i repeteix el simulacre d'aquí a una setmana. Reserva la data per d'aquí a 2-3 setmanes.
60-74 Al límit. És la franja del "pot caure de qualsevol costat". Repassa els dominis de les teves fallades amb les taules de mapatge de les lliçons 12-01, 12-02 o 12-03. Practica 2 setmanes més i repeteix el simulacre.
40-59 Repassar dominis concrets. El coneixement hi és, però hi ha llacunes i falta velocitat. Torna a les lliçons dels mòduls on vas fallar (veure taula següent). Treballa 3-4 setmanes abans de repetir.
0-39 Tornar als mòduls. Falten fonaments, no només pràctica. Refés el curs des del mòdul corresponent, amb el clúster al davant i fent tots els exercicis.

Diagnòstic per tasca fallada

Cada tasca apunta a un mòdul concret. Si en vas fallar una, saps exactament on tornar.

Tasca fallada Domini Torna a
1 Càrregues de treball 02-03-deployments
2 Serveis i xarxes 04-02-tipus-de-serveis, 04-04-controladors-dingress
3 Configuració 03-01-configmaps, 03-02-secrets, 03-03-variables-dentorn
4 Emmagatzematge 05-02-volums-persistents, 05-03-reclamacions-de-volums-persistents
5 Planificació 06-05-planificacio-afinitat-taints-i-toleracions
6 Càrregues per lots 06-03-treballs-i-cronjobs
7 Observabilitat i recursos 07-01-verificacions-de-salut-i-sondes, 03-05-limitranges-i-classes-de-qos
8 Seguretat i accés 08-01-control-dacces-basat-en-rols, 03-06-serviceaccounts-i-acces-a-la-api
9 Seguretat de xarxa 04-06-politiques-de-xarxa, 08-04-seguretat-de-xarxa
10 Enduriment 08-02-contextos-de-seguretat-i-enduriment, 08-03-politiques-de-seguretat-de-pods
11 Escalat 09-01-autoescalat-horitzontal-de-pods
12 Desplegaments 02-04-actualitzacions-rollbacks-i-estrategies
13, 14, 15 Resolució de problemes 07-06-depuracio-i-esdeveniments-del-cluster, 11-06-operacio-en-produccio

Anàlisi del temps

A més de la nota, revisa els temps que has anotat:

Situació Què significa Remei
Nota alta però sense acabar a temps Ho saps fer, però a poc a poc Àlies, $do, copiar de la documentació (lliçó 12-04)
Vas acabar aviat amb nota baixa Vas ràpid però amb llacunes Repassar els mòduls de la taula anterior
Et vas encallar més de 15 min en una tasca Falta disciplina de temps Entrenar el límit dur i el salt sense remordiments
Vas fallar per namespace o context Problema de procés, no de coneixement Entrenar el cicle context → resoldre → verificar
Vas fallar les tres de troubleshooting El domini de més pes del CKA Trenca el teu clúster a propòsit i arregla'l, una vegada i una altra

Conclusió

Aquí acaba el curs.

Vas començar, al mòdul 1, llançant un Pod solt contra un clúster acabat de muntar i preguntant-te per què dimonis calia tanta maquinària per executar un contenidor. Acabes resolent un examen de quinze tasques contrarellotge sobre una plataforma completa en producció.

Entre aquests dos punts hi ha el recorregut de Rutas Norte. Li vas donar forma amb Deployments i Services, la vas configurar sense ficar contrasenyes al codi, la vas publicar amb Ingress i TLS, li vas donar memòria amb volums persistents i còpies de seguretat, li vas ensenyar a arrencar en l'ordre correcte amb initContainers i a sobreviure al manteniment d'un node amb taints i PodDisruptionBudgets. Li vas posar ulls amb Prometheus i Grafana, la vas tancar amb RBAC, NetworkPolicies, Pod Security i signatura d'imatges, la vas fer créixer sola amb HPA i KEDA, la vas empaquetar amb Helm i Kustomize, la vas desplegar sola des de Git amb Argo CD, i la vas operar de debò: amb incidències reals, runbooks, desplegaments canari i una factura de costos que calia justificar.

Què saps fer ara, dit sense adorns:

  • Dissenyar i desplegar una aplicació completa a Kubernetes, des del manifest fins a la URL pública amb certificat.
  • Triar la primitiva correcta per a cada problema: Deployment o StatefulSet, Job o CronJob, initContainer o sidecar, readiness o liveness.
  • Gestionar configuració i secrets sense exposar-los, i controlar el consum de recursos de tot el que corre.
  • Muntar, actualitzar i reparar un clúster: nodes, pla de control, etcd, certificats.
  • Diagnosticar. I això és el que més et distingirà: davant d'un símptoma —un 503, un pod Pending, un Service sense endpoints— saps per on començar, quina comanda executar i com arribar a la causa arrel en minuts.
  • Assegurar el que desplegues, en les tres fases: construcció, desplegament i execució.
  • Escalar, mesurar i ajustar, amb dades en lloc d'intuïcions.
  • Operar en producció com un professional: amb runbooks, amb procediments de reversió provats i amb consciència del cost.

I si et presentes a una certificació, saps també com funciona l'examen, quins dominis hi entren, on es va estudiar cada objectiu i com es gestionen dues hores de terminal contrarellotge.

Kubernetes continuarà canviant. Apareixeran APIs noves, se'n retiraran d'altres, la Gateway API acabarà desplaçant l'Ingress, i eines que avui són estàndard seran reemplaçades. El que no canvia és el que de debò has après: el model declaratiu, el bucle de reconciliació, la separació entre desig i realitat, i el mètode per esbrinar per què la realitat no coincideix amb el desig. Això es trasllada a qualsevol versió i a qualsevol eina que vingui després.

Munta un clúster propi i no l'apaguis. Trenca coses a propòsit. Desplega-hi els teus projectes encara que no calgui. L'única manera que això no s'oxidi és fer-lo servir.

Bon viatge.

Curs de Kubernetes

Mòdul 1: Introducció a Kubernetes

Mòdul 2: Components Principals de Kubernetes

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

Mòdul 4: Xarxes a Kubernetes

Mòdul 5: Emmagatzematge a Kubernetes

Mòdul 6: Conceptes Avançats de Kubernetes

Mòdul 7: Monitoratge i Registre

Mòdul 8: Seguretat a Kubernetes

Mòdul 9: Escalat i Rendiment

Mòdul 10: Ecosistema i Eines de Kubernetes

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

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

© Copyright 2026. Tots els drets reservats