A la lliçó anterior vam canviar la imatge de botiga-web i vam observar una cosa que vam deixar deliberadament sense explicar: va aparèixer un segon ReplicaSet, el vell es va quedar a zero rèpliques sense esborrar-se, i durant uns segons hi van conviure pods de dues versions. Aquell mecanisme és la raó de ser del Deployment i la resposta directa al primer dels quatre objectius de Rutas Norte: zero talls als desplegaments, davant del minut o dos d'aturada que avui obliga a desplegar els dimarts de matinada. En aquesta lliçó prenem el control d'aquell traspàs. Veuràs les dues estratègies natives del Deployment i quan exigeix cadascuna, calcularàs pod a pod el que passa durant una actualització d'api-reserves, ajustaràs el ritme amb maxSurge i maxUnavailable, distingiràs quins canvis generen una revisió nova i quins no, i faràs servir el joc complet de kubectl rollout: status, history, undo, pause i resume. I acabaràs trencant un desplegament a propòsit amb una imatge inexistent, diagnosticant-lo i desfent-lo.
Contingut
- Les dues estratègies natives:
RollingUpdateiRecreate - Per què
postgres-reservesno admetRollingUpdate maxSurgeimaxUnavailable, pas a pas ambapi-reserves- Ritme i tolerància:
minReadySeconds,progressDeadlineSeconds,revisionHistoryLimit - Què dispara una revisió nova i què no
- El joc complet de
kubectl rollout - L'anotació
kubernetes.io/change-cause - Desplegament sense tall: què cal de debò
- Diagnòstic i rollback d'un desplegament encallat
- Les dues estratègies natives:
RollingUpdate i Recreate
RollingUpdate i Recreatespec.strategy.type només admet dos valors. No hi ha més estratègies natives a Kubernetes: blue-green i canary, que veuràs a 11-04, es construeixen combinant aquests maons amb Serveis i Ingress.
RollingUpdate (per defecte)
Substitueix els pods de forma progressiva: aixeca pods de la versió nova i retira els de la vella a poc a poc, mantenint sempre un nombre mínim de pods servint.
flowchart LR
subgraph T0["Inici"]
A1["v1"]; A2["v1"]; A3["v1"]; A4["v1"]
end
subgraph T1["A mitges"]
B1["v1"]; B2["v1"]; B3["v2"]; B4["v2"]
end
subgraph T2["Final"]
C1["v2"]; C2["v2"]; C3["v2"]; C4["v2"]
end
T0 --> T1 --> T2
Recreate
Mata tots els pods vells, espera que desapareguin, i només llavors crea els nous. Hi ha un interval, normalment de segons a un parell de minuts, sense cap pod servint.
flowchart LR
subgraph R0["Inici"]
D1["v1"]; D2["v1"]
end
subgraph R1["TALL DE SERVEI"]
X["Cap pod"]
end
subgraph R2["Final"]
E1["v2"]; E2["v2"]
end
R0 --> R1 --> R2
Comparativa
| Aspecte | RollingUpdate |
Recreate |
|---|---|---|
| Tall de servei | No | Sí, mentre dura la substitució |
| Conviuen dues versions | Sí, és inevitable | No, mai |
| Capacitat extra necessària | Sí, si maxSurge > 0 |
No |
| Velocitat | Més lenta (progressiva) | Més ràpida |
| Consum de recursos al pic | Fins a replicas + maxSurge |
Mai no supera replicas |
| Rollback | Progressiu, també sense tall | Amb un altre tall |
| Quan fer-la servir | Càrregues sense estat: botiga-web, api-reserves, worker-notificacions |
Quan dues versions no poden coexistir |
Com es declara explícitament:
- Per què
postgres-reserves no admet RollingUpdate
postgres-reserves no admet RollingUpdateÉs la pregunta clau d'aquest apartat, i la resposta ensenya més que l'estratègia en si. Imagina que postgres-reserves fos un Deployment d'1 rèplica amb RollingUpdate i maxSurge: 1. Durant l'actualització, això és el que passaria:
- Kubernetes crea el pod nou abans de retirar el vell, perquè
maxSurge: 1ho permet. - Durant uns segons hi ha dos processos PostgreSQL simultanis.
- Tots dos intenten muntar i escriure al mateix directori de dades.
Conseqüències reals, en ordre de gravetat:
- Bloqueig de l'arrencada: PostgreSQL detecta el fitxer
postmaster.pidde la instància viva i es nega a arrencar. El desplegament s'encalla (el cas benigne). - Corrupció de dades si el bloqueig falla o l'emmagatzematge no ho garanteix: dos processos escrivint als mateixos fitxers WAL.
- Escriptures perdudes: reserves confirmades al client que no queden registrades.
I hi ha un segon motiu, independent de l'emmagatzematge: la migració d'esquema. Si la versió nova d'api-reserves requereix una columna que la vella no coneix, tenir les dues versions parlant alhora amb la mateixa base de dades trenca una de les dues. Amb RollingUpdate aquella coexistència dura minuts; amb Recreate, no existeix.
| Component | Estratègia | Motiu |
|---|---|---|
botiga-web |
RollingUpdate |
Sense estat; dues versions d'una SPA conviuen sense problema |
api-reserves |
RollingUpdate |
Sense estat; el contracte de l'API es manté compatible entre versions consecutives |
worker-notificacions |
RollingUpdate |
Sense estat; consumeix d'una cua, dues versions poden consumir alhora |
redis-cache |
Recreate |
Una sola rèplica amb un volum; a més la memòria cau es repobla sola |
postgres-reserves |
Ni l'una ni l'altra: StatefulSet | Necessita identitat i emmagatzematge estables (06-01) |
La conclusió honesta: per a postgres-reserves, la resposta correcta no és Recreate, és no fer servir un Deployment. Recreate és l'estratègia adequada per a càrregues d'una sola rèplica que no toleren duplicitat però tampoc no necessiten identitat estable, com el nostre redis-cache.
Aplica-ho a redis-cache, que ja vas desplegar a la lliçó anterior:
kubectl patch deployment redis-cache --type=merge \
-p '{"spec":{"strategy":{"type":"Recreate","rollingUpdate":null}}}'
kubectl get deploy redis-cache -o jsonpath='{.spec.strategy.type}{"\n"}'Recorda portar també aquell canvi al manifest a Git.
maxSurge i maxUnavailable, pas a pas amb api-reserves
maxSurge i maxUnavailable, pas a pas amb api-reservesAquests dos paràmetres governen completament el ritme d'una actualització progressiva.
| Paràmetre | Què limita | Valor per defecte | Efecte d'apujar-lo |
|---|---|---|---|
maxSurge |
Quants pods de més hi pot haver per sobre de replicas |
25 % | Actualització més ràpida, més recursos al pic |
maxUnavailable |
Quants pods poden estar no disponibles per sota de replicas |
25 % | Actualització més ràpida, menys capacitat durant el procés |
Tots dos admeten un nombre enter o un percentatge. Els percentatges s'arrodoneixen així: maxSurge cap amunt i maxUnavailable cap avall, per pecar sempre per excés de capacitat. I hi ha una restricció: no poden valer 0 tots dos alhora, perquè aleshores el desplegament no podria fer ni un pas.
L'escenari
api-reserves amb 4 rèpliques, maxSurge: 1 i maxUnavailable: 1. Actualitzem de la versió 2.4.0 a la 2.5.0. Els números que regeixen tot el procés:
- Màxim de pods simultanis =
replicas + maxSurge= 4 + 1 = 5 - Mínim de pods disponibles =
replicas - maxUnavailable= 4 − 1 = 3
Prepara l'escenari:
kubectl scale deployment api-reserves --replicas=4
kubectl patch deployment api-reserves --type=merge \
-p '{"spec":{"strategy":{"type":"RollingUpdate","rollingUpdate":{"maxSurge":1,"maxUnavailable":1}}}}'
kubectl get deploy api-reservesEl recompte exacte, pas a pas
| Pas | Acció del controlador | Pods v2.4.0 | Pods v2.5.0 | Total | Disponibles | Respecta els límits? |
|---|---|---|---|---|---|---|
| 0 | Estat inicial | 4 llestos | 0 | 4 | 4 | Sí |
| 1 | Crea 1 pod nou (fa servir el maxSurge) |
4 llestos | 1 creant-se | 5 | 4 | Total = 5 = màxim |
| 2 | Retira 1 pod vell (fa servir el maxUnavailable) |
3 llestos | 1 creant-se | 4 | 3 | Disponibles = 3 = mínim |
| 3 | El pod nou passa a Ready |
3 llestos | 1 llest | 4 | 4 | Sí |
| 4 | Crea un altre pod nou | 3 llestos | 1 llest + 1 creant-se | 5 | 4 | Total = 5 |
| 5 | Retira un altre de vell | 2 llestos | 1 llest + 1 creant-se | 4 | 3 | Disponibles = 3 |
| 6 | El segon nou passa a Ready |
2 llestos | 2 llestos | 4 | 4 | Sí |
| 7 | Crea el tercer | 2 llestos | 2 llestos + 1 creant-se | 5 | 4 | Total = 5 |
| 8 | Retira el tercer vell | 1 llest | 2 llestos + 1 creant-se | 4 | 3 | Disponibles = 3 |
| 9 | El tercer passa a Ready |
1 llest | 3 llestos | 4 | 4 | Sí |
| 10 | Crea el quart | 1 llest | 3 llestos + 1 creant-se | 5 | 4 | Total = 5 |
| 11 | Retira l'últim vell | 0 | 3 llestos + 1 creant-se | 4 | 3 | Disponibles = 3 |
| 12 | El quart passa a Ready. Final |
0 | 4 llestos | 4 | 4 | Completat |
Les dues lectures importants d'aquella taula:
- En cap moment no hi ha menys de 3 pods servint. Rutas Norte no perd mai capacitat total, només el 25 % durant uns segons.
- En cap moment no hi ha més de 5 pods. El clúster necessita lloc per a un pod extra, no per a vuit.
flowchart TD
P0["Pas 0<br/>4 vells · 0 nous<br/>total 4"] --> P1["Pas 1<br/>4 vells · 1 nou<br/>total 5 ← sostre de maxSurge"]
P1 --> P2["Pas 2<br/>3 vells · 1 nou<br/>disponibles 3 ← terra de maxUnavailable"]
P2 --> P3["Pas 3<br/>el nou passa a Ready<br/>disponibles 4"]
P3 --> PN["Es repeteix el cicle<br/>crear · retirar · esperar Ready"]
PN --> PF["Pas 12<br/>0 vells · 4 nous<br/>desplegament completat"]
Veure-ho en directe
En una terminal:
En una altra, llança l'actualització:
NAME READY STATUS RESTARTS AGE
api-reserves-6b4c9d7f5-x2jkp 1/1 Running 0 41m
api-reserves-6b4c9d7f5-k7wpd 1/1 Running 0 41m
api-reserves-6b4c9d7f5-m4rzt 1/1 Running 0 41m
api-reserves-6b4c9d7f5-q8vnc 1/1 Running 0 41m
api-reserves-9f2a7c531-t3xkb 0/1 Pending 0 0s
api-reserves-9f2a7c531-t3xkb 0/1 ContainerCreating 0 0s
api-reserves-6b4c9d7f5-x2jkp 1/1 Terminating 0 41m
api-reserves-9f2a7c531-t3xkb 1/1 Running 0 3s
api-reserves-9f2a7c531-w9djm 0/1 ContainerCreating 0 0s
api-reserves-6b4c9d7f5-k7wpd 1/1 Terminating 0 41m
...I el recompte de rèpliques de cada ReplicaSet durant el procés:
El Deployment està traspassant rèpliques d'un ReplicaSet a un altre. Això és, literalment, tot el que fa un desplegament progressiu.
Combinacions habituals
maxSurge |
maxUnavailable |
Comportament | Quan fer-la servir |
|---|---|---|---|
25% |
25% |
Per defecte: equilibri entre velocitat i seguretat | La majoria dels casos |
1 |
0 |
Mai no baixa de la capacitat nominal. Primer aixeca, després retira | Producció amb capacitat crítica: api-reserves a rutas-norte-pro |
0 |
1 |
Mai no supera la capacitat nominal. Primer retira, després aixeca | Clústers amb recursos molt justos o quotes ajustades |
100% |
0 |
Aixeca tots els nous alhora i després retira tots els vells | Desplegament molt ràpid, exigeix el doble de recursos |
0 |
0 |
Invàlid: l'API ho rebutja | — |
Per a producció de Rutas Norte, l'elecció recomanada és maxSurge: 1 i maxUnavailable: 0: en un pont amb el trànsit multiplicat per sis, perdre un 25 % de capacitat durant un desplegament no és acceptable.
- Ritme i tolerància:
minReadySeconds, progressDeadlineSeconds, revisionHistoryLimit
minReadySeconds, progressDeadlineSeconds, revisionHistoryLimitTres camps que completen el control del desplegament.
| Camp | Per defecte | Què fa | Per què importa |
|---|---|---|---|
minReadySeconds |
0 |
Segons que un pod ha de portar llest sense caure abans de comptar-se com a disponible | Evita que el desplegament avanci sobre pods que arrenquen i moren als 5 segons |
progressDeadlineSeconds |
600 |
Segons sense progrés després dels quals el desplegament es declara fallit | Converteix un encallament silenciós en una condició Progressing: False detectable |
revisionHistoryLimit |
10 |
Quants ReplicaSets antics (a 0 rèpliques) es conserven | És la teva capacitat de rollback; a 0, no pots desfer res |
minReadySeconds mereix una explicació a part perquè el seu efecte és subtil però molt valuós. Amb el valor per defecte de 0, tan bon punt un pod nou diu "estic llest", el controlador retira un altre de vell i tira endavant. Si aquell pod cau dos segons després, el dany ja està fet: el desplegament avança sobre una versió trencada. Amb minReadySeconds: 15, cada pod nou ha de sobreviure 15 segons llest abans de comptar. Si cau abans, el desplegament es frena.
progressDeadlineSeconds és la diferència entre un desplegament que falla en silenci i un que avisa. Sense ell, un desplegament encallat per una imatge inexistent es quedaria intentant-ho eternament sense que cap sistema d'alertes se n'assabentés.
Manifest d'api-reserves amb els tres camps i l'estratègia de producció:
# k8s/base/api-reserves-deployment.yaml (fragment actualitzat)
spec:
replicas: 4
revisionHistoryLimit: 5
minReadySeconds: 15
progressDeadlineSeconds: 300
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0Amb aquesta configuració, una actualització d'api-reserves a rutas-norte-pro triga com a mínim 4 × 15 = 60 segons, i si alguna cosa falla es declara fallida als 5 minuts en lloc d'als 10. Lentitud a canvi de seguretat: en un component que gestiona pagaments de bitllets, és un canvi excel·lent.
- Què dispara una revisió nova i què no
Aquesta és la regla més important de la lliçó i la més oblidada:
Només els canvis sota
spec.templatedisparen una revisió nova. Tota la resta, no.
La lògica és transparent: una revisió és un ReplicaSet, i un ReplicaSet es distingeix d'un altre pel hash de la seva plantilla de pod. Si la plantilla no canvia, el hash no canvia, i no hi ha cap ReplicaSet nou a crear.
| Canvi | Crea revisió? | Què passa |
|---|---|---|
spec.template.spec.containers[].image |
Sí | ReplicaSet nou; substitució progressiva de pods |
spec.template.spec.containers[].env |
Sí | Ídem: els pods es recreen amb les variables noves |
spec.template.spec.containers[].resources |
Sí | Ídem |
spec.template.metadata.labels o annotations |
Sí | Ídem, encara que el contenidor sigui idèntic |
spec.template.spec.terminationGracePeriodSeconds |
Sí | Ídem |
spec.replicas |
No | El ReplicaSet actual apuja o abaixa el seu nombre de pods |
spec.strategy |
No | S'aplicarà a la pròxima actualització |
spec.minReadySeconds |
No | Ídem |
spec.revisionHistoryLimit |
No | Només neteja ReplicaSets antics |
metadata.annotations del Deployment |
No | Inclosa change-cause: és metadada del Deployment, no del pod |
Comprova-ho tu mateix:
kubectl rollout history deployment/api-reserves | tail -3
kubectl scale deployment api-reserves --replicas=5
kubectl rollout history deployment/api-reserves | tail -3REVISION CHANGE-CAUSE
1 Desplegament inicial d'api-reserves 2.4.0
2 <none>
deployment.apps/api-reserves scaled
REVISION CHANGE-CAUSE
1 Desplegament inicial d'api-reserves 2.4.0
2 <none>Mateix historial: escalar no és desplegar.
Una conseqüència pràctica que sorprèn molta gent: canviar un ConfigMap no reinicia els pods que el consumeixen, perquè el ConfigMap no és al template, només s'hi referencia des d'ell. El truc habitual, que veuràs al mòdul 3, és forçar un canvi al template amb una anotació calculada, o fer servir directament:
rollout restart no canvia res funcional: escriu una anotació kubectl.kubernetes.io/restartedAt dins del template, cosa que altera el hash i provoca una substitució progressiva de tots els pods. És la manera correcta i sense tall de "reiniciar" un Deployment.
- El joc complet de
kubectl rollout
kubectl rolloutkubectl set image: actualitzar la imatge
La sintaxi és <nom-del-contenidor>=<imatge>. El nom del contenidor és el de spec.template.spec.containers[].name, no el del pod. Amb diversos contenidors es poden actualitzar alhora separant-los per espais.
Advertiment de disciplina: en un projecte declaratiu, la via correcta és editar el manifest i aplicar-lo. set image és per a emergències, i el seu canvi es perd al següent apply.
kubectl rollout status: esperar
Waiting for deployment "api-reserves" rollout to finish: 1 out of 4 new replicas have been updated...
Waiting for deployment "api-reserves" rollout to finish: 2 out of 4 new replicas have been updated...
Waiting for deployment "api-reserves" rollout to finish: 3 out of 4 new replicas have been updated...
Waiting for deployment "api-reserves" rollout to finish: 1 old replicas are pending termination...
deployment "api-reserves" successfully rolled outkubectl rollout history: l'historial
deployment.apps/api-reserves
REVISION CHANGE-CAUSE
1 Desplegament inicial d'api-reserves 2.4.0
2 Actualitzacio a node 20.15 per correccio de seguretat
3 Actualitzacio a node 20.16I el detall complet d'una revisió concreta:
deployment.apps/api-reserves with revision #2
Pod Template:
Labels: app=api-reserves
app.kubernetes.io/part-of=rutas-norte
entorn=dev
pod-template-hash=9f2a7c531
Annotations: kubernetes.io/change-cause: Actualitzacio a node 20.15 per correccio de seguretat
Containers:
api:
Image: node:20.15-alpine
Port: 3000/TCP
Limits: cpu: 500m, memory: 256Mi
Requests: cpu: 100m, memory: 128MiAquella informació surt íntegrament del ReplicaSet a 0 rèpliques que va quedar guardat. Per això revisionHistoryLimit: 0 et deixaria sense historial i sense rollback.
kubectl rollout undo: desfer
Torna a la revisió immediatament anterior. Per anar a una de concreta:
Dues precisions importants:
- Un rollback és un desplegament més: fa servir la mateixa estratègia, respecta
maxSurgeimaxUnavailable, i per tant tampoc no talla el servei. - Desfer no esborra la revisió dolenta: crea una revisió nova amb el contingut de l'antiga. Si eres a la 3 i fas
undo, apareixes a la 4, el contingut de la qual és el de la 2. Els números no retrocedeixen mai.
REVISION CHANGE-CAUSE
1 Desplegament inicial d'api-reserves 2.4.0
3 Actualitzacio a node 20.16
4 Actualitzacio a node 20.15 per correccio de seguretatFixa't que la revisió 2 ha desaparegut de la llista: el seu ReplicaSet s'ha reaprofitat com a revisió 4.
kubectl rollout pause i resume: congelar a mitges
Amb el Deployment pausat, els canvis que facis no s'apliquen. És l'eina per agrupar diverses modificacions en un sol desplegament:
kubectl rollout pause deployment/api-reserves
kubectl set image deployment/api-reserves api=node:20.17-alpine
kubectl set resources deployment/api-reserves -c=api --limits=cpu=600m,memory=384Mi
kubectl scale deployment api-reserves --replicas=5
# fins aqui no ha passat RES al cluster
kubectl rollout resume deployment/api-reserves
kubectl rollout status deployment/api-reservesdeployment.apps/api-reserves paused
deployment.apps/api-reserves image updated
deployment.apps/api-reserves resource requirements updated
deployment.apps/api-reserves scaled
deployment.apps/api-reserves resumed
Waiting for deployment "api-reserves" rollout to finish: 3 of 5 updated replicas are available...
deployment "api-reserves" successfully rolled outSense pause, aquelles tres ordres haurien provocat tres desplegaments encadenats, cadascun interrompent l'anterior. Amb pause, un de sol i una sola revisió.
L'altre ús de pause és d'emergència: si veus que un desplegament està traient pods trencats, kubectl rollout pause el congela a l'acte, deixant-te els pods vells que queden servint mentre decideixes si arreglar o desfer.
Taula resum del joc complet:
| Ordre | Per a què |
|---|---|
rollout status |
Esperar i verificar; retorna codi != 0 si falla |
rollout history |
Veure revisions; amb --revision=N, el detall |
rollout undo |
Tornar enrere, amb o sense --to-revision |
rollout pause / resume |
Congelar i reprendre; agrupar canvis |
rollout restart |
Recrear tots els pods sense canviar res |
- L'anotació
kubernetes.io/change-cause
kubernetes.io/change-causeHauràs notat els <none> a la columna CHANGE-CAUSE. Un historial de revisions sense motius és gairebé inútil: al cap de tres setmanes ningú no recorda què era la revisió 7.
kubernetes.io/change-cause és una anotació del Deployment el valor de la qual es copia al ReplicaSet d'aquella revisió i es mostra a l'historial. Tres maneres d'omplir-la, de pitjor a millor:
Amb kubectl annotate (ràpida, imperativa):
kubectl annotate deployment/api-reserves \
kubernetes.io/change-cause="Actualitzacio a node 20.17 i pujada de limits pels pics d'agost" \
--overwriteAl manifest (la correcta segons les convencions del projecte):
metadata:
name: api-reserves
annotations:
kubernetes.io/change-cause: "v2.5.0 - cache de disponibilitat per expedicio (RN-482)"Automatitzada a CI/CD, que és l'ideal:
kubectl annotate deployment/api-reserves \
kubernetes.io/change-cause="${CI_COMMIT_SHA} · ${CI_COMMIT_MESSAGE} · ${CI_USER}" \
--overwriteUn detall que ja coneixes per l'apartat 5, però que convé repetir perquè genera confusió: canviar només aquesta anotació no dispara una revisió nova, perquè viu al metadata del Deployment i no al template. Anota-la juntament amb el canvi real, no després.
Bones pràctiques per al text de Rutas Norte: versió de la imatge, resum del canvi en llenguatge planer i referència al tiquet. Exemple real del projecte:
REVISION CHANGE-CAUSE
1 v2.4.0 - desplegament inicial
2 v2.4.1 - correccio de calcul de places lliures (RN-455)
3 v2.5.0 - cache de disponibilitat per expedicio (RN-482)
4 v2.4.1 - ROLLBACK: la 2.5.0 retornava 500 en reservar (RN-489)
- Desplegament sense tall: què cal de debò
Aquí arriba l'advertiment més important de la lliçó. Podries pensar que amb RollingUpdate ben configurat ja tens desplegaments sense tall de servei. No és així, i aquesta és la causa número u d'errors 502 durant desplegaments suposadament perfectes.
El problema és que, amb el que sabem fins ara, Kubernetes considera que un pod està "llest" tan bon punt el procés arrenca. Però un procés de Node.js triga uns segons a carregar dependències, connectar a PostgreSQL i quedar operatiu. Durant aquella finestra:
- El pod ja compta com a
Ready. - El Deployment retira un pod vell, confiat.
- El Service comença a enviar-li trànsit real.
- El pod nou encara no pot respondre: errors 502.
flowchart TD
A["El pod nou arrenca"] --> B["Kubernetes el marca Ready<br/>només perquè el procés viu"]
B --> C["El Service li envia transit"]
C --> D{"L'aplicacio esta<br/>realment operativa?"}
D -->|Sí| E["Tot correcte"]
D -->|No: encara connectant a PostgreSQL| F["502 a clients reals<br/>durant diversos segons"]
La peça que falta s'anomena sonda de disponibilitat (readinessProbe): una comprovació que el kubelet executa contra la teva aplicació i que decideix si el pod està realment llest per rebre trànsit. Amb ella, el pas 1 de l'esquema no passa fins que l'aplicació respon de debò.
La llista de comprovació completa d'un desplegament realment sense tall a Rutas Norte:
| Requisit | Estat en aquest moment | On es resol |
|---|---|---|
Estratègia RollingUpdate ben parametritzada |
Fet en aquesta lliçó | 02-04 |
| Més d'una rèplica | Fet | 02-03 |
minReadySeconds com a coixí |
Fet | 02-04 |
readinessProbe que reflecteixi la disponibilitat real |
Pendent | 07-01 |
livenessProbe per detectar pods penjats |
Pendent | 07-01 |
Aturada ordenada que capturi SIGTERM |
Fet | 02-01 |
| Un Service que enruti als pods llestos | Pendent | 02-05 |
| Compatibilitat entre versions consecutives de l'API | Responsabilitat del desenvolupament | — |
És a dir: RollingUpdate és necessari però no suficient. Sense sondes, el desplegament progressiu avança a cegues. Fins que arribem al mòdul 7, minReadySeconds és un substitut pobre però útil.
- Diagnòstic i rollback d'un desplegament encallat
Exercici final i el més realista de tots. Un divendres a la tarda, algú desplega a rutas-norte-dev una versió que no existeix al registre.
El desastre
kubectl annotate deployment/api-reserves \
kubernetes.io/change-cause="v2.6.0 - versio que no existeix (RN-501)" --overwrite
kubectl set image deployment/api-reserves api=node:99.99-inexistent
kubectl rollout status deployment/api-reserves --timeout=90sdeployment.apps/api-reserves annotated
deployment.apps/api-reserves image updated
Waiting for deployment "api-reserves" rollout to finish: 1 out of 5 new replicas have been updated...
error: timed out waiting for the conditionPas 1: l'estat general
Lectura: hi ha 5 pods llestos i disponibles —el servei continua funcionant amb la versió anterior— però només 1 té la versió nova. El desplegament ha fet un pas i s'ha aturat. Això és el sistema funcionant bé: maxUnavailable: 0 ha impedit que es retirés cap pod vell fins que el nou estigués llest, i com que no ho estarà mai, no se n'ha retirat cap.
Pas 2: els ReplicaSets
El ReplicaSet nou té 1 pod que no arriba a READY.
Pas 3: el pod culpable
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulling 2m kubelet Pulling image "node:99.99-inexistent"
Warning Failed 2m kubelet Failed to pull image: manifest for node:99.99-inexistent not found
Warning Failed 2m kubelet Error: ErrImagePull
Normal BackOff 30s (x6 over 2m) kubelet Back-off pulling image "node:99.99-inexistent"Causa arrel identificada, exactament amb el procediment de la lliçó de Pods.
Pas 4: les conditions
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing False ProgressDeadlineExceededAvailable: True (continuem servint) + Progressing: False amb ProgressDeadlineExceeded (el desplegament és mort) és la signatura exacta d'un desplegament encallat. En un clúster monitorat, aquella combinació ha de generar una alerta.
Pas 5: decidir i actuar
Dues opcions, segons el moment:
# Opcio A: congelar mentre s'investiga (no reverteix, nomes atura)
kubectl rollout pause deployment/api-reserves
# Opcio B: desfer ja (el normal un divendres a la tarda)
kubectl rollout undo deployment/api-reserves
kubectl rollout status deployment/api-reservesAdvertiment important: si el Deployment està pausat, undo no té cap efecte. Cal fer resume abans.
Pas 6: verificar i documentar
kubectl get deploy,rs -l app=api-reserves
kubectl annotate deployment/api-reserves \
kubernetes.io/change-cause="ROLLBACK a v2.5.0: la imatge de v2.6.0 no existeix al registre (RN-501)" \
--overwrite
kubectl rollout history deployment/api-reservesNAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/api-reserves 5/5 5 5 1h
NAME DESIRED CURRENT READY AGE
replicaset.apps/api-reserves-3d8e1f602 0 0 0 6m
replicaset.apps/api-reserves-c5a9b47d8 5 5 5 22m
REVISION CHANGE-CAUSE
1 Desplegament inicial d'api-reserves 2.4.0
4 v2.6.0 - versio que no existeix (RN-501)
5 ROLLBACK a v2.5.0: la imatge de v2.6.0 no existeix al registre (RN-501)El ReplicaSet trencat es queda a 0 rèpliques, sense consumir res, com a testimoni de l'incident. I l'historial explica la història completa a qui la llegeixi d'aquí a sis mesos.
La moralitat de tot l'apartat: durant els sis passos anteriors, api-reserves no va deixar d'atendre ni una sola reserva. Amb el Docker Compose de partida, aquell mateix error hauria deixat la plataforma caiguda fins que algú se n'adonés. Això és el que compra un Deployment ben configurat.
Deixa el clúster endreçat abans de continuar:
NAME READY UP-TO-DATE AVAILABLE AGE
api-reserves 2/2 2 2 1h
botiga-web 3/3 3 3 1h
redis-cache 1/1 1 1 50m
worker-notificacions 2/2 2 2 45mErrors Comuns i Consells
- Creure que
RollingUpdategaranteix zero talls per si sol. SensereadinessProbe, el trànsit arriba a pods que encara no poden respondre. És la causa número u de 502 durant desplegaments. - Posar
maxSurge: 0imaxUnavailable: 0. L'API ho rebutja: el desplegament no podria avançar. - Fer servir
RollingUpdateamb una càrrega que no tolera dues versions simultànies. Bases de dades d'una rèplica, processos amb bloqueig exclusiu o migracions d'esquema incompatibles: allà hi vaRecreate, o directament un StatefulSet. - Posar
revisionHistoryLimit: 0per "estalviar". Els ReplicaSets a 0 rèpliques no consumeixen ni CPU ni memòria, només uns quilobytes a etcd. A canvi, perds completament la capacitat de desfer. - Esperar que canviar
spec.strategyospec.replicasdispari un desplegament. No són altemplate. Només s'apliquen a l'actualització real següent. - Esperar que modificar un ConfigMap reiniciï els pods. No ho fa. Fes servir
kubectl rollout restart. - Oblidar el
change-cause. Un historial de<none>no serveix de res quan cal decidir a quina revisió tornar a les tres de la matinada. - Anotar el
change-causedesprés del desplegament. Com que no dispara revisió, queda associat al Deployment però pot no reflectir el que va fer aquella revisió. Anota'l al mateixapply. - Fer
undosobre un Deployment pausat. No passa res fins que facisresume. És un clàssic de la depuració a cegues. - Confiar en
kubectl applycom a verificació.applynomés diu que l'API ha acceptat l'objecte. Encadena semprekubectl rollout status --timeout=...als teus scripts. - Consell: en producció,
maxSurge: 1imaxUnavailable: 0. Costa un pod extra de capacitat i garanteix que mai no baixes de la capacitat nominal. - Consell:
kubectl rollout statusretorna codi de sortida diferent de 0 si el desplegament falla. És el que converteix una canalització de CI/CD en una cosa fiable:kubectl apply -f k8s/ && kubectl rollout status deploy/api-reserves --timeout=300s || kubectl rollout undo deploy/api-reserves.
Exercicis
Exercici 1: Calcular un desplegament sobre el paper
botiga-web és a rutas-norte-pro amb 6 rèpliques, maxSurge: 2 i maxUnavailable: 1.
- Quin és el nombre màxim de pods simultanis i el mínim de pods disponibles?
- Construeix una taula amb els primers cinc passos de l'actualització, indicant a cadascun: pods vells, pods nous, total i disponibles.
- Si l'equip de plataforma exigeix que mai no es baixi de 6 pods servint, quins valors hi posaries i quins recursos extra necessita el clúster?
- Amb la configuració del punt 3 i
minReadySeconds: 20, quina és la durada mínima teòrica del desplegament si cada pod triga 5 segons a arrencar?
Exercici 2: Cicle complet d'actualització i rollback
Sobre botiga-web a rutas-norte-dev:
- Configura
RollingUpdateambmaxSurge: 1,maxUnavailable: 0,minReadySeconds: 10irevisionHistoryLimit: 5, aplicant-ho des del manifest. - Actualitza a
nginx:1.27.1-alpineamb el seuchange-causecorresponent i espera que acabi. - Actualitza a
nginx:1.26-alpineamb un altrechange-cause. - Mostra l'historial i el detall de la revisió 2.
- Simula que la 1.26 té una fallada greu: torna a la 1.27.1 i verifica que les tres rèpliques la serveixen.
- Explica per què l'historial no mostra la numeració que esperaries.
Exercici 3: Diagnosticar un desplegament encallat a worker-notificacions
- Trenca
worker-notificacionsa propòsit desplegant la imatgebusybox:9.9.9-inexistent, ambchange-causeinclòs. - Amb quatre ordres, diagnostica el problema seguint l'ordre correcte: estat del Deployment, ReplicaSets, pod culpable i conditions.
- Respon: continuen enviant-se els correus de confirmació durant l'encallament? Justifica-ho amb la sortida de les ordres.
- Congela el desplegament, comprova que
undono fa res mentre està pausat, reprèn-lo i desfés. - Documenta el rollback amb una anotació i mostra l'historial final.
Solucions
Solució 1
-
Màxim de pods =
replicas + maxSurge= 6 + 2 = 8. Mínim disponibles =replicas - maxUnavailable= 6 − 1 = 5. -
Primers cinc passos:
| Pas | Acció | Vells | Nous | Total | Disponibles |
|---|---|---|---|---|---|
| 0 | Estat inicial | 6 llestos | 0 | 6 | 6 |
| 1 | Crea 2 pods nous (sostre de maxSurge) |
6 llestos | 2 creant-se | 8 | 6 |
| 2 | Retira 1 de vell (sostre de maxUnavailable) |
5 llestos | 2 creant-se | 7 | 5 |
| 3 | Els 2 nous passen a Ready |
5 llestos | 2 llestos | 7 | 7 |
| 4 | Crea 1 nou més (7 + 1 = 8, sostre) | 5 llestos | 2 llestos + 1 creant-se | 8 | 7 |
| 5 | Retira 2 de vells (disponibles 7 − 2 = 5, terra) | 3 llestos | 2 llestos + 1 creant-se | 6 | 5 |
-
Per no baixar mai de 6 servint:
maxUnavailable: 0, imaxSurgea 1 o 2 segons com de ràpid es vulgui anar. AmbmaxSurge: 2, el clúster ha de tenir lloc per a 8 pods debotiga-webalhora: ambrequestsde 50m de CPU i 64Mi per pod, això són 100m de CPU i 128Mi de memòria lliures de més durant el desplegament. AmbmaxUnavailable: 0el clúster ha de tenir aquella capacitat; si no la té, els pods nous es queden enPendingi el desplegament no avança mai. -
Amb
maxSurge: 2imaxUnavailable: 0, els pods se substitueixen en tandes de 2. Cada tanda triga 5 s (arrencada) + 20 s (minReadySeconds) = 25 s. Sis pods en tandes de dos són 3 tandes: 75 segons com a mínim teòric, sense comptar descàrregues d'imatge ni el temps de terminació dels vells.
Solució 2
# k8s/base/botiga-web-deployment.yaml (fragment)
spec:
replicas: 3
revisionHistoryLimit: 5
minReadySeconds: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0kubectl apply -f k8s/base/botiga-web-deployment.yaml
# 2. Actualitzacio a 1.27.1
kubectl annotate deployment/botiga-web \
kubernetes.io/change-cause="v1.27.1 - correccio de seguretat de nginx (RN-470)" --overwrite
kubectl set image deployment/botiga-web nginx=nginx:1.27.1-alpine
kubectl rollout status deployment/botiga-web
# 3. Actualitzacio a 1.26
kubectl annotate deployment/botiga-web \
kubernetes.io/change-cause="v1.26 - tornada a branca anterior per compatibilitat (RN-473)" --overwrite
kubectl set image deployment/botiga-web nginx=nginx:1.26-alpine
kubectl rollout status deployment/botiga-web# 4. Historial i detall
kubectl rollout history deployment/botiga-web
kubectl rollout history deployment/botiga-web --revision=2deployment.apps/botiga-web
REVISION CHANGE-CAUSE
1 Desplegament inicial de botiga-web 1.27.0
2 v1.27.1 - correccio de seguretat de nginx (RN-470)
3 v1.26 - tornada a branca anterior per compatibilitat (RN-473)
deployment.apps/botiga-web with revision #2
Pod Template:
Labels: app=botiga-web
pod-template-hash=6f9c4b8d7
Annotations: kubernetes.io/change-cause: v1.27.1 - correccio de seguretat de nginx (RN-470)
Containers:
nginx:
Image: nginx:1.27.1-alpine# 5. Rollback a la revisio 2
kubectl rollout undo deployment/botiga-web --to-revision=2
kubectl rollout status deployment/botiga-web
kubectl get pods -l app=botiga-web \
-o custom-columns=NOM:.metadata.name,IMATGE:.spec.containers[0].imagedeployment.apps/botiga-web rolled back
deployment "botiga-web" successfully rolled out
NOM IMATGE
botiga-web-6f9c4b8d7-42kxr nginx:1.27.1-alpine
botiga-web-6f9c4b8d7-8vnwq nginx:1.27.1-alpine
botiga-web-6f9c4b8d7-t9mzd nginx:1.27.1-alpineREVISION CHANGE-CAUSE
1 Desplegament inicial de botiga-web 1.27.0
3 v1.26 - tornada a branca anterior per compatibilitat (RN-473)
4 v1.27.1 - correccio de seguretat de nginx (RN-470)- La numeració sorprèn perquè les revisions no retrocedeixen mai. En desfer cap a la revisió 2, Kubernetes no hi "torna": reaprofita el seu ReplicaSet i el renumera com a revisió 4, la més recent. Per això la 2 desapareix de la llista i n'apareix una 4 amb el seu mateix
change-cause. El número de revisió identifica l'ordre en què es van aplicar els canvis, no el contingut: un rollback és un canvi més.
Solució 3
# 1. Trencar-lo
kubectl annotate deployment/worker-notificacions \
kubernetes.io/change-cause="v1.3.0 - imatge que no existeix (RN-512)" --overwrite
kubectl set image deployment/worker-notificacions worker=busybox:9.9.9-inexistent# 2. Diagnostic en quatre passos
kubectl get deploy worker-notificacions
kubectl get rs -l app=worker-notificacions
kubectl get pods -l app=worker-notificacions
kubectl describe pod <pod-en-ImagePullBackOff> | tail -6
kubectl describe deployment worker-notificacions | grep -A4 ConditionsNAME READY UP-TO-DATE AVAILABLE AGE
worker-notificacions 2/2 1 2 1h
NAME DESIRED CURRENT READY AGE
worker-notificacions-5a3f9d21c 1 1 0 90s
worker-notificacions-8c7b5d94f 2 2 2 1h
NAME READY STATUS RESTARTS AGE
worker-notificacions-5a3f9d21c-r6bkt 0/1 ImagePullBackOff 0 90s
worker-notificacions-8c7b5d94f-k2xrt 1/1 Running 0 1h
worker-notificacions-8c7b5d94f-p9mzq 1/1 Running 0 1h
Warning Failed 80s kubelet Failed to pull image "busybox:9.9.9-inexistent": manifest unknown
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing False ProgressDeadlineExceeded- Sí, els correus se segueixen enviant. La prova és a tres llocs:
READY 2/2iAVAILABLE 2al Deployment, els 2 pods de la revisió anterior enRunning, i sobretotAvailable: TrueambMinimumReplicasAvailablea les conditions. NomésUP-TO-DATE 1delata que hi ha una versió nova intentant entrar sense aconseguir-ho. Confirmació directa:
[pod/worker-notificacions-8c7b5d94f-k2xrt/worker] enviant lot de confirmacions des de worker-notificacions-8c7b5d94f-k2xrt# 4. Pausar, comprovar que undo no fa res, reprendre i desfer
kubectl rollout pause deployment/worker-notificacions
kubectl rollout undo deployment/worker-notificacions
kubectl get rs -l app=worker-notificacionsdeployment.apps/worker-notificacions paused
deployment.apps/worker-notificacions rolled back
NAME DESIRED CURRENT READY AGE
worker-notificacions-5a3f9d21c 1 1 0 4m
worker-notificacions-8c7b5d94f 2 2 2 1hEl ReplicaSet trencat continua amb 1 rèplica: l'undo s'ha registrat però no s'ha executat, perquè el Deployment està pausat.
kubectl rollout resume deployment/worker-notificacions
kubectl rollout status deployment/worker-notificacions
kubectl get rs -l app=worker-notificacionsdeployment.apps/worker-notificacions resumed
deployment "worker-notificacions" successfully rolled out
NAME DESIRED CURRENT READY AGE
worker-notificacions-5a3f9d21c 0 0 0 5m
worker-notificacions-8c7b5d94f 2 2 2 1h# 5. Documentar
kubectl annotate deployment/worker-notificacions \
kubernetes.io/change-cause="ROLLBACK a v1.2.0: la imatge de v1.3.0 no existeix al registre (RN-512)" \
--overwrite
kubectl rollout history deployment/worker-notificacionsREVISION CHANGE-CAUSE
1 Desplegament inicial de worker-notificacions 1.2.0
2 v1.3.0 - imatge que no existeix (RN-512)
3 ROLLBACK a v1.2.0: la imatge de v1.3.0 no existeix al registre (RN-512)Conclusió
Has tancat el cercle que vam obrir a la lliçó anterior amb aquell segon ReplicaSet que va aparèixer sense explicació. Ara saps que les dues estratègies natives del Deployment són RollingUpdate i Recreate, i per què l'elecció no és de gust sinó de naturalesa de la càrrega: botiga-web, api-reserves i worker-notificacions toleren dues versions convivint i van amb RollingUpdate; redis-cache no, i va amb Recreate; i postgres-reserves no admet cap de les dues perquè el Deployment no és el seu lloc: dos processos PostgreSQL sobre el mateix directori de dades signifiquen bloqueig en el millor cas i corrupció en el pitjor.
Saps calcular un desplegament sobre el paper abans d'executar-lo: replicas + maxSurge és el sostre de pods i replicas - maxUnavailable el terra de disponibles, i amb aquests dos números pots reconstruir pas a pas el que farà el controlador amb les 4 rèpliques d'api-reserves. Coneixes les combinacions útils i la recomanada per a producció, maxSurge: 1 amb maxUnavailable: 0, que protegeix la capacitat nominal en un pont d'agost. Ajustes el ritme amb minReadySeconds, detectes els encallaments amb progressDeadlineSeconds i conserves la teva capacitat de desfer amb revisionHistoryLimit. Tens clara la regla que ho governa tot: només els canvis sota spec.template creen una revisió, i per això escalar no desplega, canviar un ConfigMap no reinicia res i existeix kubectl rollout restart. I fas servir el joc complet —status, history, undo, pause, resume— amb change-cause documentant cada pas, inclòs el rollback d'un desplegament encallat per una imatge inexistent, resolt sense que un sol client deixés de comprar el seu bitllet.
Queda un advertiment pendent i una mancança grossa. L'advertiment: RollingUpdate és necessari però no suficient per desplegar sense tall real; sense una readinessProbe, Kubernetes marca com a llest un pod el procés del qual acaba d'arrencar i li envia trànsit abans que el pugui atendre. Aquella peça arriba a Verificacions de Salut i Sondes. I la mancança: tot el trànsit del qual portem dues lliçons parlant encara no existeix. Els nostres pods tenen IPs que canvien a cada desplegament, i per parlar amb api-reserves hem hagut de buscar a mà la IP d'un pod concret. A la lliçó següent, Serveis, donarem a cada component de Rutas Norte una adreça estable que sobrevisqui als desplegaments, amb balanceig de càrrega entre rèpliques inclòs.
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
