La lliçó anterior va acabar amb una mancança molt concreta: un ReplicaSet manté amb vida N còpies d'una plantilla, però no sap canviar de versió. Canviaves la imatge del template i els pods existents continuaven tan tranquils amb la imatge antiga. El Deployment és la peça que tanca aquell forat i, amb diferència, l'objecte que més escriuràs a la teva vida professional amb Kubernetes. Governa ReplicaSets, i a través d'ells, pods: et dona rèpliques, autoreparació, escalat, actualitzacions controlades, historial de revisions i marxa enrere. En aquesta lliçó entendràs la cadena Deployment → ReplicaSet → Pod i què aporta exactament cada baula, convertiràs per fi el pod solt de botiga-web en un Deployment de 3 rèpliques, crearàs el d'api-reserves, aprendràs a llegir amb precisió les columnes de kubectl get deploy i les conditions de l'estat, escalaràs de dues maneres diferents i observaràs què passa al clúster en canviar una imatge. Acabaràs sabent també quan un Deployment no és la càrrega de treball adequada.
Contingut
- La cadena Deployment → ReplicaSet → Pod
- De pod solt a Deployment:
botiga-web - El manifest complet, comentat
- El Deployment d'
api-reserves - Llegir
kubectl get deploy:READY,UP-TO-DATE,AVAILABLE status,conditionsikubectl rollout status- Escalat:
kubectl scalei manifest - Observació: què passa en canviar la imatge
- Quan un Deployment no és la càrrega adequada
- La cadena Deployment → ReplicaSet → Pod
Kubernetes hauria pogut ficar tota la funcionalitat en un sol objecte. No ho va fer, i la raó és bona: cada nivell té una responsabilitat i només una.
flowchart TD
D["<b>Deployment</b> botiga-web<br/>Gestiona VERSIONS<br/>historial, actualització, rollback"]
RS1["<b>ReplicaSet</b> botiga-web-7d9f8c6b4<br/>versió 1.27.0 · rèpliques: 3<br/>Gestiona QUANTITAT"]
RS2["<b>ReplicaSet</b> botiga-web-5c8b7a2d1<br/>versió 1.26.0 · rèpliques: 0<br/>revisió anterior, conservada"]
P1["Pod botiga-web-7d9f8c6b4-4kx7d"]
P2["Pod botiga-web-7d9f8c6b4-9wq2m"]
P3["Pod botiga-web-7d9f8c6b4-pv6cl"]
D --> RS1
D --> RS2
RS1 --> P1
RS1 --> P2
RS1 --> P3
La divisió de la feina, en una taula:
| Nivell | La seva única pregunta | Què sap fer | Què no sap fer |
|---|---|---|---|
| Pod | Són vius els meus contenidors? | Reiniciar contenidors caiguts (via kubelet i restartPolicy) |
Recrear-se si desapareix |
| ReplicaSet | Hi ha N pods amb aquestes etiquetes? | Crear i esborrar pods fins a quadrar el número | Canviar de versió |
| Deployment | Quina versió ha d'estar servint, i com hi arribo? | Crear ReplicaSets, traspassar rèpliques entre ells, guardar historial, desfer | Res més: delega tota la resta |
La clau per entendre els Deployments és aquesta frase: un Deployment no gestiona pods, gestiona ReplicaSets. Cada cop que canvies alguna cosa del template, el Deployment crea un ReplicaSet nou i va movent rèpliques del vell al nou. Un ReplicaSet per revisió, i cadascun sap mantenir la seva pròpia quantitat de pods. El Deployment orquestra el traspàs.
Aquell sufix hexadecimal dels noms (botiga-web-7d9f8c6b4) no és aleatori: és el pod-template-hash, un hash calculat sobre el contingut del template. El Deployment l'afegeix com a etiqueta a cada pod i al selector de cada ReplicaSet, i així aconsegueix que dos ReplicaSets del mateix Deployment no es barallin mai pels mateixos pods. És exactament la mena de selector de conjunts que, com hem vist, el vell ReplicationController no podia expressar.
- De pod solt a Deployment:
botiga-web
botiga-webAl final del mòdul 1 vam deixar botiga-web com un pod solt, i vam comprovar que en esborrar-lo no tornava. Ha arribat el moment de saldar aquell deute.
Partim del namespace buit, tal com va quedar al final de la lliçó anterior:
La conversió d'un pod en un Deployment segueix sempre el mateix patró mecànic:
| Pod solt | Deployment |
|---|---|
apiVersion: v1 |
apiVersion: apps/v1 |
kind: Pod |
kind: Deployment |
metadata |
metadata (del Deployment) |
| — | spec.replicas |
| — | spec.selector |
spec: (els contenidors) |
spec.template.spec |
metadata.labels |
spec.template.metadata.labels |
És a dir: el pod sencer s'enfonsa un nivell i es converteix en el template, i per sobre apareixen replicas i selector.
- El manifest complet, comentat
# k8s/base/botiga-web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: botiga-web
namespace: rutas-norte-dev
labels:
app: botiga-web
app.kubernetes.io/part-of: rutas-norte
entorn: dev
annotations:
kubernetes.io/change-cause: "Desplegament inicial de botiga-web 1.27.0"
spec:
replicas: 3
revisionHistoryLimit: 5
selector:
matchLabels:
app: botiga-web
entorn: dev
template:
metadata:
labels:
app: botiga-web
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
ports:
- name: http
containerPort: 80
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"El que és nou respecte del ReplicaSet de la lliçó anterior, camp per camp:
kind: Deployment, mateix grupapps/v1.metadata.annotationsambkubernetes.io/change-cause: text lliure que quedarà registrat a l'historial de revisions com el "motiu" d'aquest desplegament. L'explotarem a fons a la lliçó següent.spec.replicas: 3: tres rèpliques debotiga-web. El Deployment no les crea ell; ho encarrega al seu ReplicaSet.spec.revisionHistoryLimit: 5: quants ReplicaSets antics (amb 0 rèpliques) es conserven per poder desfer. Per defecte en són 10.spec.selector: obligatori i immutable, igual que al ReplicaSet. I amb el mateix advertiment: que sigui precís,app+entorn.spec.template: idèntic al del ReplicaSet. Aquí hi ha la clau conceptual: qualsevol canvi sotatemplatedispara una revisió nova; els canvis fora d'ell (com arareplicas) no.
No apareix spec.strategy perquè el valor per defecte, RollingUpdate, és el que volem. Aquell camp i tots els seus paràmetres són el contingut íntegre de la lliçó següent, Actualitzacions, Rollbacks i Estratègies de Desplegament.
Aplica'l:
deployment.apps/botiga-web created
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/botiga-web 3/3 3 3 9s
NAME DESIRED CURRENT READY AGE
replicaset.apps/botiga-web-7d9f8c6b4 3 3 3 9s
NAME READY STATUS RESTARTS AGE
pod/botiga-web-7d9f8c6b4-4kx7d 1/1 Running 0 9s
pod/botiga-web-7d9f8c6b4-9wq2m 1/1 Running 0 9s
pod/botiga-web-7d9f8c6b4-pv6cl 1/1 Running 0 9sAquí hi ha la cadena completa, materialitzada en tres blocs de sortida. Has creat un objecte i han aparegut tres nivells. Comprova'n la genealogia:
kubectl get rs botiga-web-7d9f8c6b4 -o jsonpath='{.metadata.ownerReferences[0].kind}/{.metadata.ownerReferences[0].name}{"\n"}'
kubectl get pod botiga-web-7d9f8c6b4-4kx7d -o jsonpath='{.metadata.ownerReferences[0].kind}/{.metadata.ownerReferences[0].name}{"\n"}'I mira l'etiqueta que ha afegit el Deployment pel seu compte als pods:
NAME READY STATUS RESTARTS AGE LABELS
botiga-web-7d9f8c6b4-4kx7d 1/1 Running 0 2m app=botiga-web,app.kubernetes.io/part-of=rutas-norte,entorn=dev,pod-template-hash=7d9f8c6b4Tu no vas escriure pod-template-hash. L'hi va afegir el Deployment, i és el que li permetrà distingir els pods d'una revisió dels d'una altra.
Verifica que ara sí que s'autorepara:
pod "botiga-web-7d9f8c6b4-4kx7d" deleted
NAME READY STATUS RESTARTS AGE
botiga-web-7d9f8c6b4-9wq2m 1/1 Running 0 3m
botiga-web-7d9f8c6b4-hs4bd 1/1 Running 0 2s
botiga-web-7d9f8c6b4-pv6cl 1/1 Running 0 3mEl pod solt del mòdul 1 no tornava. Aquest torna en dos segons, i qui el retorna no és el Deployment sinó el seu ReplicaSet, exactament com vas aprendre a la lliçó anterior.
- El Deployment d'
api-reserves
api-reservesSegon component. Mateix esquema, amb les particularitats de l'API.
# k8s/base/api-reserves-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-reserves
namespace: rutas-norte-dev
labels:
app: api-reserves
app.kubernetes.io/part-of: rutas-norte
entorn: dev
annotations:
kubernetes.io/change-cause: "Desplegament inicial d'api-reserves 2.4.0"
spec:
replicas: 2
revisionHistoryLimit: 5
selector:
matchLabels:
app: api-reserves
entorn: dev
template:
metadata:
labels:
app: api-reserves
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
terminationGracePeriodSeconds: 30
containers:
- name: api
image: node:20-alpine
command: ["node", "-e"]
args:
- |
const http = require('http');
const pod = process.env.HOSTNAME;
http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({servei: 'api-reserves', version: '2.4.0', pod}));
}).listen(3000, () => console.log(`api-reserves 2.4.0 a punt a ${pod}`));
ports:
- name: http
containerPort: 3000
env:
- name: ENTORN
value: "dev"
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"Detalls deliberats d'aquest manifest:
replicas: 2: en desenvolupament n'hi ha prou amb dues. En producció el número el decidirà l'autoescalat del mòdul 9.terminationGracePeriodSeconds: 30dins deltemplate.spec: recorda de la lliçó de Pods que és el que permet que una reserva en curs no es talli durant un desplegament.- La resposta inclou el nom del pod (
process.env.HOSTNAME, que en un pod és el seu nom). Això ens servirà a la lliçó de Serveis per demostrar el balanceig de càrrega: cada petició respondrà amb un pod diferent.
deployment.apps/api-reserves created
NAME READY UP-TO-DATE AVAILABLE AGE
api-reserves 2/2 2 2 14s
botiga-web 3/3 3 3 9mComprova que l'API respon, fent servir un pod efímer com els de la lliçó de Pods:
kubectl run test --rm -it --image=curlimages/curl:8.8.0 --restart=Never -- \
curl -s http://$(kubectl get pod -l app=api-reserves -o jsonpath='{.items[0].status.podIP}'):3000/disponibilitatFunciona, però fixa't en la incomoditat: hem hagut d'esbrinar la IP d'un pod concret per parlar-hi. Aquella IP canviarà al pròxim desplegament. El problema està servit per a la lliçó de Serveis.
- Llegir
kubectl get deploy: READY, UP-TO-DATE, AVAILABLE
kubectl get deploy: READY, UP-TO-DATE, AVAILABLEAquestes tres columnes són la informació més densa de tot Kubernetes, i confondre-les porta a diagnòstics equivocats. Les tres compten pods, però compten coses diferents.
| Columna | Què compta exactament | Camp del status |
|---|---|---|
READY |
pods llestos / pods desitjats. "Llest" = tots els seus contenidors passen les seves comprovacions |
readyReplicas / replicas |
UP-TO-DATE |
Pods creats amb la revisió actual del template |
updatedReplicas |
AVAILABLE |
Pods que porten llestos com a mínim minReadySeconds seguits |
availableReplicas |
La manera útil de llegir-les és preguntar-se què significa cada discrepància:
| Situació | Interpretació |
|---|---|
READY 3/3, UP-TO-DATE 3, AVAILABLE 3 |
Tot correcte i estable |
READY 2/3 |
Un pod no està llest: arrencant, o fallant |
UP-TO-DATE 1 de 3 |
Actualització en curs: només 1 pod té la versió nova |
READY 3/3 però AVAILABLE 2 |
Un pod està llest però encara no ha complert minReadySeconds; es considera "jove" |
READY 0/3 durant minuts |
Desplegament trencat: mira els pods amb describe |
Un exemple d'actualització a mitges, que veuràs molt:
Això es llegeix així: hi ha 4 pods llestos encara que el desitjat en siguin 4, però només 2 tenen la versió nova, i només 3 porten prou temps per comptar-se com a disponibles. Som a mitja actualització progressiva, i encara hi conviuen pods de dues revisions.
Per veure'n més, -o wide afegeix imatges i selector:
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
api-reserves 2/2 2 2 14m api node:20-alpine app=api-reserves,entorn=dev
botiga-web 3/3 3 3 23m nginx nginx:1.27-alpine app=botiga-web,entorn=dev
status, conditions i kubectl rollout status
status, conditions i kubectl rollout statusCom tot objecte de Kubernetes, un Deployment té un spec (el que demanes) i un status (el que hi ha). L'status complet:
{
"availableReplicas": 3,
"conditions": [
{
"lastTransitionTime": "2026-08-05T12:03:11Z",
"lastUpdateTime": "2026-08-05T12:03:11Z",
"message": "Deployment has minimum availability.",
"reason": "MinimumReplicasAvailable",
"status": "True",
"type": "Available"
},
{
"lastTransitionTime": "2026-08-05T12:03:05Z",
"lastUpdateTime": "2026-08-05T12:03:11Z",
"message": "ReplicaSet \"botiga-web-7d9f8c6b4\" has successfully progressed.",
"reason": "NewReplicaSetAvailable",
"status": "True",
"type": "Progressing"
}
],
"observedGeneration": 1,
"readyReplicas": 3,
"replicas": 3,
"updatedReplicas": 3
}Les conditions són el mecanisme estàndard de Kubernetes per expressar estats parcials, i un Deployment en té tres tipus:
| Condition | status: True significa |
status: False significa |
|---|---|---|
Available |
Hi ha prou rèpliques disponibles per donar servei | El Deployment no està servint amb garanties |
Progressing |
El desplegament avança (o ha acabat bé) | S'ha encallat: ha superat progressDeadlineSeconds |
ReplicaFailure |
(només apareix si hi ha problema) No es poden crear pods: quota, permisos, límits | — |
La combinació Available: True + Progressing: True amb motiu NewReplicaSetAvailable és la signatura d'un Deployment sa i acabat.
Un altre camp que mereix atenció és observedGeneration. Cada canvi al spec incrementa metadata.generation; el controlador copia aquell número a status.observedGeneration quan ha processat el canvi. Si veus generation: 5 i observedGeneration: 4, el controlador encara no ha reaccionat al teu últim canvi.
En el dia a dia no llegeixes el JSON: fas servir l'ordre que l'interpreta per tu.
kubectl rollout status bloqueja la terminal fins que el desplegament acaba i retorna codi de sortida 0 si va bé, diferent de 0 si falla. Per això és la instrucció que es posa en qualsevol canalització de CI/CD just darrere del kubectl apply: converteix "he enviat el canvi" en "el canvi està funcionant". Admet --timeout:
kubectl apply -f k8s/base/botiga-web-deployment.yaml
kubectl rollout status deployment/botiga-web --timeout=120s
- Escalat:
kubectl scale i manifest
kubectl scale i manifestRutas Norte té un pont a la vista i cal apujar rèpliques d'api-reserves. Dues vies, les mateixes que amb el ReplicaSet.
Imperativa, per reaccionar ja:
Observa un detall importantíssim:
No ha aparegut cap ReplicaSet nou. Escalar no canvia el template, així que no és una revisió nova: el mateix ReplicaSet passa de 2 a 4. Aquesta distinció —canvis que creen revisió enfront de canvis que no— és l'eix de la lliçó següent.
Declarativa, la correcta segons les convencions del projecte: edita replicas: 4 a k8s/base/api-reserves-deployment.yaml, fes commit i aplica.
I un advertiment pràctic molt comú: si escales amb kubectl scale i no portes el canvi al fitxer, el següent kubectl apply tornarà les rèpliques al valor del manifest. Perdràs la capacitat extra en el pitjor moment possible. És la raó de la regla del projecte: el clúster reflecteix Git, no a l'inrevés.
Un tercer camí, útil per automatitzar sense editar fitxers a mà:
Tornem a 2 abans de continuar:
- Observació: què passa en canviar la imatge
Aquesta és l'observació que motiva tota la lliçó següent. Canvia la versió d'nginx de botiga-web i mira l'estructura que apareix, sense preocupar-te encara del mecanisme.
deployment.apps/botiga-web image updated
NAME DESIRED CURRENT READY AGE
botiga-web-5f6d8b9c7 3 3 3 25s
botiga-web-7d9f8c6b4 0 0 0 31mHi ha dos ReplicaSets. El nou (5f6d8b9c7) té les 3 rèpliques; el vell (7d9f8c6b4) s'ha quedat a 0 però no s'ha esborrat. I aquí hi ha la clau de l'historial: aquell ReplicaSet buit conserva la definició completa de la versió anterior, així que tornar enrere consisteix simplement a retornar-li les seves rèpliques.
Compara-ho amb el que passava a la lliçó anterior en fer kubectl set image sobre un ReplicaSet: res, els pods continuaven amb la imatge vella. Aquí, en canvi, els pods són nous i tenen la imatge nova:
kubectl get pods -l app=botiga-web -o custom-columns=NOM:.metadata.name,IMATGE:.spec.containers[0].imageNOM IMATGE
botiga-web-5f6d8b9c7-2xk4m nginx:1.27.1-alpine
botiga-web-5f6d8b9c7-7q9wd nginx:1.27.1-alpine
botiga-web-5f6d8b9c7-nb3rt nginx:1.27.1-alpineI l'historial ja té dues entrades:
deployment.apps/botiga-web
REVISION CHANGE-CAUSE
1 Desplegament inicial de botiga-web 1.27.0
2 <none>De moment, queda't amb aquestes quatre observacions:
- Canviar el
templatecrea un ReplicaSet nou, amb unpod-template-hashdiferent. - El Deployment traspassa rèpliques del vell al nou de forma progressiva, no de cop.
- Els ReplicaSets antics es conserven a 0 rèpliques com a historial (fins a
revisionHistoryLimit). - Durant el traspàs conviuen pods de dues versions, que és el que fa possible desplegar sense tall de servei.
Com es controla aquell traspàs pas a pas —maxSurge, maxUnavailable, Recreate, pause, undo— és exactament el contingut d'Actualitzacions, Rollbacks i Estratègies de Desplegament. I les variants avançades, blue-green i canary, esperen a 11-04.
Torna botiga-web a la imatge original per començar la lliçó següent en un punt conegut:
- Quan un Deployment no és la càrrega adequada
El Deployment és l'opció per defecte, però no l'única. Està dissenyat per a càrregues sense estat, intercanviables i de llarga durada: els seus pods són bestiar, no mascotes. S'anomenen amb sufixos aleatoris, es creen i es destrueixen en qualsevol ordre i cap no té identitat pròpia.
Quan això no encaixa, hi ha un altre objecte:
| Necessitat | Objecte correcte | Per què falla el Deployment | Lliçó |
|---|---|---|---|
| Identitat i emmagatzematge estables per rèplica (bases de dades, cues, clústers amb quòrum) | StatefulSet | Noms aleatoris, ordre d'arrencada no garantit, totes les rèpliques compartirien el mateix volum | 06-01 |
| Exactament un pod a cada node (agents de logs, mètriques, xarxa) | DaemonSet | Un Deployment reparteix N rèpliques on càpiguen, sense garantir cobertura de nodes | 06-02 |
| Tasca que s'executa, acaba i ja està | Job | Un Deployment reiniciaria el pod eternament en acabar (restartPolicy: Always obligatòria) |
06-03 |
| Tasca programada per horari | CronJob | Un Deployment no té noció de calendari | 06-03 |
Aplicat als sis components de Rutas Norte:
| Component | Càrrega de treball | Motiu |
|---|---|---|
botiga-web |
Deployment | Sense estat, rèpliques idèntiques i intercanviables |
api-reserves |
Deployment | Sense estat; tota la persistència és a PostgreSQL i Redis |
postgres-reserves |
StatefulSet | Necessita disc propi, nom de xarxa estable i arrencada ordenada |
redis-cache |
Deployment (una rèplica) o StatefulSet | Amb una sola instància i estat prescindible, un Deployment ja fa el fet |
worker-notificacions |
Deployment | Procés de llarga durada sense estat; no rep trànsit, així que no portarà Service |
informes-ocupacio |
CronJob | S'executa a les 02:30, acaba i no torna fins l'endemà |
Un advertiment que evita un error clàssic: redis-cache com a Deployment només és acceptable amb una rèplica. Si escalessis a 3, tindries tres memòries cau independents darrere del mateix nom, i cada petició d'api-reserves aniria a una cau diferent amb contingut diferent. El resultat serien encerts de memòria cau erràtics i impossibles de diagnosticar. Escalar no és gratis: només funciona si el component és realment sense estat.
Errors Comuns i Consells
- Oblidar
spec.selector. És obligatori aapps/v1. Sense ell, l'API rebutja el manifest. - Intentar canviar el
selectord'un Deployment existent. És immutable. Si el necessites, cal esborrar el Deployment (amb--cascade=orphansi no vols tallar el servei) i crear-ne un de nou. - Posar les etiquetes només al
metadatade dalt. Les que governen els pods són les despec.template.metadata.labels. És l'error de principiant més repetit. - Escalar amb
kubectl scalei no tocar el fitxer. El següentapplyrevertirà el canvi, probablement en mal moment. - Confondre
READYambAVAILABLE.READYés "pot atendre";AVAILABLEés "pot atendre i porta estable el temps mínim exigit". - Creure que
kubectl applyespera que acabi el desplegament. No espera: només envia l'objecte. Per esperar de debò, encadenakubectl rollout status. - Fer servir un Deployment per a PostgreSQL. Amb més d'una rèplica, diverses instàncies intentarien muntar el mateix volum i escriure alhora. Corrupció garantida.
- Escalar
redis-cachea diverses rèpliques amb un Deployment. Tindràs N memòries cau incoherents, no una cau replicada. - Consell: genera l'esquelet amb
kubectl create deployment api-reserves --image=node:20-alpine --replicas=2 --dry-run=client -o yaml > deploy.yamli afegeix-hi després etiquetes, recursos i anotacions. - Consell: per veure la cadena completa d'un component d'un cop d'ull,
kubectl get deploy,rs,pods -l app=botiga-web. Tres nivells en una sola ordre.
Exercicis
Exercici 1: Deployment de worker-notificacions
worker-notificacions és un procés de fons que consumeix una cua i envia correus. No rep trànsit entrant, així que no porta ports ni Service.
Escriu k8s/base/worker-notificacions-deployment.yaml amb 2 rèpliques a rutas-norte-dev, imatge busybox:1.36, una ordre que escrigui cada 10 segons enviant lot de confirmacions des de <nom del pod>, les tres etiquetes del projecte, terminationGracePeriodSeconds: 45 i requests de 50m/64Mi. Després:
- Aplica'l i espera amb l'ordre que bloqueja fins que acaba el desplegament.
- Mostra la cadena Deployment → ReplicaSet → Pod amb una sola ordre.
- Comprova als logs que tots dos pods treballen.
- Explica per què aquest component porta un termini de gràcia més gran que
botiga-web.
Exercici 2: Llegir l'estat d'un desplegament
Un company t'envia aquesta captura d'un clúster de preproducció i et demana diagnòstic:
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing False ProgressDeadlineExceededRespon, justificant cada resposta:
- Quants pods haurien d'existir i quants n'hi ha de llestos?
- Quants tenen la versió nova?
- Està el servei caigut?
- Què ha passat amb el desplegament?
- Quines dues ordres executaries, en aquest ordre, per trobar la causa arrel?
Exercici 3: De pod solt a Deployment, i decidir la càrrega correcta
- Crea a mà un pod solt
redis-cachearutas-norte-devambredis:7.2-alpinei les tres etiquetes del projecte. - Converteix-lo en un Deployment d'1 rèplica anomenat
redis-cache, escrivint el manifest i esborrant el pod solt. Verifica que la memòria cau respon aredis-cli ping. - Escala el Deployment a 3 rèpliques i explica per què això és un error de disseny per a Rutas Norte, encara que tècnicament funcioni. Torna a 1.
- Per a cadascun dels sis components de Rutas Norte, indica l'objecte correcte i una frase de justificació.
Solucions
Solució 1
# k8s/base/worker-notificacions-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker-notificacions
namespace: rutas-norte-dev
labels:
app: worker-notificacions
app.kubernetes.io/part-of: rutas-norte
entorn: dev
annotations:
kubernetes.io/change-cause: "Desplegament inicial de worker-notificacions 1.2.0"
spec:
replicas: 2
selector:
matchLabels:
app: worker-notificacions
entorn: dev
template:
metadata:
labels:
app: worker-notificacions
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
terminationGracePeriodSeconds: 45
containers:
- name: worker
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
trap 'echo "tancant: acabant enviaments pendents"; sleep 5; exit 0' TERM
while true; do
echo "enviant lot de confirmacions des de $HOSTNAME"
sleep 10
done
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"kubectl apply -f k8s/base/worker-notificacions-deployment.yaml
kubectl rollout status deployment/worker-notificacions
kubectl get deploy,rs,pods -l app=worker-notificacions
kubectl logs -l app=worker-notificacions --tail=2 --prefixdeployment.apps/worker-notificacions created
deployment "worker-notificacions" successfully rolled out
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/worker-notificacions 2/2 2 2 12s
NAME DESIRED CURRENT READY AGE
replicaset.apps/worker-notificacions-8c7b5d94f 2 2 2 12s
NAME READY STATUS RESTARTS AGE
pod/worker-notificacions-8c7b5d94f-k2xrt 1/1 Running 0 12s
pod/worker-notificacions-8c7b5d94f-p9mzq 1/1 Running 0 12s
[pod/worker-notificacions-8c7b5d94f-k2xrt/worker] enviant lot de confirmacions des de worker-notificacions-8c7b5d94f-k2xrt
[pod/worker-notificacions-8c7b5d94f-p9mzq/worker] enviant lot de confirmacions des de worker-notificacions-8c7b5d94f-p9mzq- El termini de gràcia és més gran perquè
worker-notificacionspot estar a mig enviament de correu quan rep elSIGTERM. Si el kubelet el mata abans d'acabar, un client que ha pagat el bitllet es queda sense justificant i el missatge pot quedar en un estat ambigu (consumit de la cua però no enviat).botiga-webnomés serveix fitxers estàtics: les seves peticions duren mil·lisegons i 30 segons li sobren.
Solució 2
- Haurien d'existir 5 pods (
spec.replicas = 5, denominador deREADY) i n'hi ha 3 de llestos. En falten 2. - Només 2 tenen la versió nova (
UP-TO-DATE). Els altres pods llestos són de la revisió anterior. - No està caigut.
Available: Trueamb motiuMinimumReplicasAvailableindica que hi ha prou rèpliques disponibles per atendre trànsit. Sí que està degradat: serveix amb 3 pods en lloc de 5. - El desplegament s'ha encallat:
Progressing: Falseamb motiuProgressDeadlineExceededsignifica que ha superatprogressDeadlineSeconds(600 s per defecte) sense avançar. Els 2 pods de la versió nova no arriben a estar llestos, així que el Deployment no pot continuar retirant els vells. Causa típica: imatge inexistent,CrashLoopBackOffper configuració o una sonda mal ajustada. - Les dues ordres, en aquest ordre:
# 1. Veure quins pods estan fallant i amb quin motiu
kubectl get pods -l app=api-reserves -n rutas-norte-pre
# 2. La causa arrel del pod problematic
kubectl describe pod <pod-que-no-arrenca> -n rutas-norte-pre
# i, si el contenidor reinicia:
kubectl logs <pod-que-no-arrenca> -n rutas-norte-pre --previousSolució 3
# 1. Pod solt
kubectl run redis-cache --image=redis:7.2-alpine \
--labels="app=redis-cache,app.kubernetes.io/part-of=rutas-norte,entorn=dev"# 2. k8s/base/redis-cache-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-cache
namespace: rutas-norte-dev
labels:
app: redis-cache
app.kubernetes.io/part-of: rutas-norte
entorn: dev
annotations:
kubernetes.io/change-cause: "Desplegament inicial de redis-cache 7.2"
spec:
replicas: 1
selector:
matchLabels:
app: redis-cache
entorn: dev
template:
metadata:
labels:
app: redis-cache
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
containers:
- name: redis
image: redis:7.2-alpine
ports:
- name: redis
containerPort: 6379
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "256Mi"kubectl delete pod redis-cache
kubectl apply -f k8s/base/redis-cache-deployment.yaml
kubectl rollout status deployment/redis-cache
kubectl exec deploy/redis-cache -- redis-cli pingpod "redis-cache" deleted
deployment.apps/redis-cache created
deployment "redis-cache" successfully rolled out
PONGNota pràctica: kubectl exec deploy/redis-cache tria automàticament un dels pods del Deployment, així no cal copiar el nom amb el seu sufix aleatori.
# 3. Escalar a 3 i tornar
kubectl scale deployment redis-cache --replicas=3
kubectl get pods -l app=redis-cache
kubectl scale deployment redis-cache --replicas=1És un error de disseny perquè les tres rèpliques no formen una memòria cau replicada: són tres memòries cau independents i buides, cadascuna amb la seva pròpia memòria. Quan a la lliçó següent hi posem un Service al davant, cada consulta d'api-reserves caurà en una rèplica diferent, així que la disponibilitat de places que es va escriure a la rèplica A no es trobarà en llegir de la B. El resultat seria una taxa d'encerts de memòria cau al voltant del 33 %, consultes extra a postgres-reserves i, el pitjor, respostes incoherents segons a quina rèplica caigui la petició. Replicar Redis de debò requereix un StatefulSet amb configuració de rèplica o clúster, tema del mòdul 6.
- Objecte correcte per component:
| Component | Objecte | Justificació |
|---|---|---|
botiga-web |
Deployment | Sense estat; qualsevol rèplica serveix qualsevol petició |
api-reserves |
Deployment | Sense estat; ho persisteix tot a PostgreSQL i Redis |
postgres-reserves |
StatefulSet | Necessita volum propi, identitat de xarxa estable i arrencada ordenada |
redis-cache |
Deployment d'1 rèplica | Estat prescindible i reconstruïble; amb més rèpliques caldria un StatefulSet |
worker-notificacions |
Deployment | Procés de llarga durada, sense estat i sense trànsit entrant |
informes-ocupacio |
CronJob | Execució programada i finita cada nit a les 02:30 |
Conclusió
Ja domines l'objecte que sosté la major part de qualsevol plataforma a Kubernetes. Has vist la cadena Deployment → ReplicaSet → Pod amb una responsabilitat nítida a cada baula: el pod manté vius els seus contenidors, el ReplicaSet manté la quantitat, i el Deployment gestiona les versions. Saps que un Deployment no mana sobre pods sinó sobre ReplicaSets, i que el pod-template-hash és el truc que permet que dues revisions convisquin sense barallar-se pels mateixos pods.
Has saldat el deute que arrossegàvem des del mòdul 1: botiga-web ja no és un pod fràgil sinó un Deployment de 3 rèpliques que es recupera sol en dos segons, i api-reserves té el seu amb 2 rèpliques. Saps llegir les tres columnes de kubectl get deploy sense confondre-les —READY és el que atén, UP-TO-DATE el que té la versió actual, AVAILABLE el que a més porta estable el temps mínim— i interpretar les conditions Available i Progressing, que són les que et diran en producció si un desplegament va bé, va degradat o s'ha quedat encallat. Escales amb kubectl scale per apagar focs i amb el manifest perquè el canvi perduri, i saps que el clúster reflecteix Git i no a l'inrevés. A més, tens el criteri per no forçar l'eina: PostgreSQL demana StatefulSet, un agent per node demana DaemonSet, l'informe nocturn demana CronJob, i escalar redis-cache a tres rèpliques trencaria la coherència de la memòria cau.
Queda per explicar el més interessant del que has vist: en canviar la imatge va aparèixer un segon ReplicaSet, el vell es va quedar a 0 rèpliques sense esborrar-se i durant uns segons hi van conviure pods de dues versions. Això no és un efecte secundari, és el mecanisme amb què Rutas Norte aconseguirà per fi el seu objectiu de zero talls als desplegaments, davant dels un o dos minuts d'aturada dels dimarts de matinada. A la lliçó següent, Actualitzacions, Rollbacks i Estratègies de Desplegament, controlarem aquell traspàs pod a pod amb maxSurge i maxUnavailable, veurem per què postgres-reserves no admetria una actualització progressiva, i aprendrem a diagnosticar i desfer un desplegament trencat amb kubectl rollout undo.
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
