Aurora Libros escala sola i aguanta el pic. Queda el moment més delicat de tots: publicar una versió nova. Entre la vella i la nova sempre hi ha un període en què conviuen, i decidir com es gestiona aquell període és la diferència entre una publicació que ningú no nota i un quart d'hora sense poder comprar un llibre.
Contingut
- El problema: la finestra de coexistència
- Les cinc estratègies, comparades
- Rolling update a Kubernetes:
maxSurgeimaxUnavailable update_configa Swarm i la taula d'equivalències- La readiness i
minReadySeconds - Veure el desplegament en marxa
- Rollback:
undo, revisions irevisionHistoryLimit - Rollback a Swarm
- Assaig d'un desplegament fallit:
aurora-api:2.1.0 - Blue-green amb dos Deployments
- Canari per rèpliques i per Ingress
- Què es vigila durant el canari
- Argo Rollouts, Flagger i GitOps
- Migracions de base de dades: expand/contract
- Feature flags
- El problema: la finestra de coexistència
Durant un desplegament progressiu, el trànsit es reparteix entre versions diferents. Això obliga que la versió 2.1.0 sigui compatible amb la 2.0.0 en tres fronts:
| Front | Què exigeix |
|---|---|
| Esquema de la base de dades | Que totes dues versions puguin llegir i escriure la mateixa taula |
| Contracte de l'API | Que un client que va començar amb la vella pugui acabar amb la nova |
| Memòria cau compartida | Que una entrada escrita per una versió l'entengui l'altra |
El tercer és el que més sorprèn i el més fàcil de provocar: si la 2.1.0 canvia el format del que desa a aurora-cache amb la mateixa clau, les rèpliques antigues llegiran objectes que no entenen i retornaran errors intermitents que apareixen i desapareixen segons a quin Pod caigui cada petició. La solució és tan simple com versionar la clau: llibres:v2:*.
- Les cinc estratègies, comparades
| Estratègia | Com | Recursos extra | Tall | Rollback | Risc | Quan |
|---|---|---|---|---|---|---|
| Recreate | Mata tot i arrenca el nou | Cap | Sí, total | Redesplegar (lent) | Alt | Canvis incompatibles, entorns interns |
| Rolling update | Substitueix de N en N | +maxSurge |
No | Rodant cap enrere | Mitjà | El valor per defecte assenyat |
| Blue-green | Dos entorns complets, es commuta | ×2 | No | Instantani | Baix | Canvis grans, es pot pagar el doble |
| Canari | Un % del trànsit a la nova | +1 rèplica | No | Ràpid | El més baix | Canvis sensibles amb mètriques fiables |
| Shadow | Còpia del trànsit real sense retornar resposta | ×2 en còmput | No | No aplica | Nul per a l'usuari | Validar rendiment abans d'exposar |
flowchart TB
subgraph R[Rolling update]
R1["v1 v1 v1"] --> R2["v2 v1 v1"] --> R3["v2 v2 v1"] --> R4["v2 v2 v2"]
end
subgraph B[Blue-green]
B1["blau v1 ← 100%"] --> B2["blau v1 + verd v2 (0%)"] --> B3["verd v2 ← 100%"]
end
subgraph C[Canari]
C1["v1 100%"] --> C2["v1 90% / v2 10%"] --> C3["v1 50% / v2 50%"] --> C4["v2 100%"]
end
El shadow mereix una nota: duplica el trànsit real cap a la versió nova però descarta la seva resposta, així que l'usuari no la veu mai. És magnífic per validar rendiment amb càrrega autèntica, i té un parany perillós: si la petició duplicada escriu a la base de dades o cobra una targeta, l'operació passa dues vegades. Només serveix amb trànsit de lectura o amb dependències aïllades.
- Rolling update a Kubernetes:
maxSurge i maxUnavailable
maxSurge i maxUnavailablespec:
replicas: 6
minReadySeconds: 10 # 10 s sa abans de comptar com a disponible
progressDeadlineSeconds: 300 # si en 5 min no avança, es marca com a fallit
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2 # fins a 8 Pods alhora (6 + 2)
maxUnavailable: 0 # mai menys de 6 disponiblesmaxSurge |
maxUnavailable |
Capacitat durant el desplegament | Velocitat | Recursos extra |
|---|---|---|---|---|
0 |
1 |
5 de 6 (83 %) | Lenta | Cap |
1 |
1 |
6 de 6 | Mitjana | +1 Pod |
2 |
0 |
6 de 6 sempre | Mitjana | +2 Pods |
100% |
0 |
Doble durant la transició | Màxima | ×2 (blue-green de facto) |
25% |
25% |
75 % mínim | Mitjana | +25 % |
La combinació maxSurge: 2 / maxUnavailable: 0 és la correcta per a Aurora Libros: mai hi ha menys de sis Pods servint, així que la capacitat no baixa ni un instant durant la publicació. Costa dos Pods de més durant uns minuts, que és un preu ridícul. maxUnavailable: 0 és també una assegurança: si els Pods nous no arriben a estar preparats, el desplegament no pot avançar, perquè avançar exigiria treure Pods vells.
Compte amb un cas concret: maxSurge: 0 i maxUnavailable: 0 alhora és una configuració invàlida —el desplegament no podria fer ni un pas—, i Kubernetes la rebutja.
update_config a Swarm i la taula d'equivalències
update_config a Swarm i la taula d'equivalències deploy:
replicas: 6
update_config:
parallelism: 2 # 2 tasques alhora
delay: 15s # espera entre lots
order: start-first # arrencar la nova abans d'aturar la vella
failure_action: rollback
monitor: 30s # finestra de vigilància després de cada tasca
max_failure_ratio: 0.2
rollback_config:
parallelism: 3
order: stop-first| Concepte | Kubernetes | Swarm |
|---|---|---|
| Quantes alhora | maxSurge / maxUnavailable |
parallelism |
| Pausa entre lots | minReadySeconds |
delay |
| Ordre | maxSurge > 0 = start-first |
order: start-first / stop-first |
| Què fer si falla | S'atura (amb maxUnavailable: 0) |
failure_action: rollback |
| Finestra de vigilància | progressDeadlineSeconds |
monitor |
| Llindar de fallada | Implícit a les sondes | max_failure_ratio |
| Desfer | kubectl rollout undo |
docker service rollback |
| Historial | revisionHistoryLimit |
Només la versió anterior |
Dues diferències importants. Swarm pot desfer sol amb failure_action: rollback, mentre que Kubernetes es limita a aturar-se i esperar que decideixis —cosa defensable: aturar deixa el sistema en un estat conegut i no amaga el problema—. En canvi, Swarm guarda únicament la versió immediatament anterior, mentre que Kubernetes conserva tantes revisions com diguis.
- La readiness i
minReadySeconds
minReadySecondsAquí és on el que vas fer a 06-01 rendeix de debò. Sense sonda de readiness, un rolling update és una ruleta: Kubernetes considera "preparat" un Pod tan bon punt el seu contenidor arrenca, li envia trànsit i la teva aplicació encara està establint el pool de PostgreSQL. Cada rèplica substituïda produeix uns segons d'errors.
Sense readiness: el contenidor arrenca → rep trànsit → encara connectant → 502
Amb readiness: el contenidor arrenca → /salut/preparat 503 → connecta → 200 → rep trànsitminReadySeconds: 10 hi afegeix una segona assegurança: exigeix que el Pod porti deu segons seguits preparat abans de comptar-lo com a disponible i passar al següent. Sense això, un Pod que passa la readiness i cau dos segons després faria avançar el desplegament igualment, i acabaries amb sis rèpliques trencades. Amb això, el desplegament s'atura al primer Pod inestable.
I el preStop amb sleep 5 de 06-05 tanca l'altre extrem: els Pods que se'n van deixen de rebre trànsit abans de tancar les seves connexions. Readiness en entrar, preStop en sortir: entre totes dues, zero errors durant la publicació.
- Veure el desplegament en marxa
kubectl set image deploy/aurora-api api=ghcr.io/auroralibros/aurora-api:2.1.0 -n aurora
# o, amb Kustomize: editar l'overlay i kubectl apply -k
kubectl rollout status deploy/aurora-api -n aurora --timeout=300s
kubectl get pods -n aurora -l app.kubernetes.io/name=aurora-api -w
kubectl get rs -n aurora -l app.kubernetes.io/name=aurora-api
# Waiting for deployment "aurora-api" rollout to finish: 4 of 6 updated replicas are available...
# deployment "aurora-api" successfully rolled out
# NAME DESIRED CURRENT READY AGE
# aurora-api-6d4f8b7c9 0 0 0 3d ← v2.0.0, conservat
# aurora-api-8f2a1c5e7 6 6 6 2m ← v2.1.0, actiuEls dos ReplicaSets són la clau de tot el que ve després. L'antic es queda a zero rèpliques, no s'esborra: és l'historial que fa possible l'undo. El rollout status retorna codi 0 si acaba i diferent de zero si falla, cosa que el converteix en la porta natural de l'últim pas del pipeline de 06-02.
- Rollback:
undo, revisions i revisionHistoryLimit
undo, revisions i revisionHistoryLimitkubectl rollout history deploy/aurora-api -n aurora --revision=4 # veure una revisió
kubectl rollout undo deploy/aurora-api -n aurora # a l'anterior
kubectl rollout undo deploy/aurora-api -n aurora --to-revision=3 # a una de concreta
kubectl rollout pause deploy/aurora-api -n aurora # congelar a mitges
kubectl rollout resume deploy/aurora-api -n aurora
# REVISION CHANGE-CAUSE
# 3 kubectl apply -k k8s/overlays/produccio (2.0.0)
# 4 kubectl set image ... aurora-api:2.1.0
# 5 kubectl rollout undo (tornada a 2.0.0)Observa la revisió 5: el rollback no esborra la 4, crea una revisió nova amb el contingut de la 3. L'historial sempre va cap endavant, així que pots tornar a avançar quan corregeixis el problema.
revisionHistoryLimit: 5 al teu manifest controla quants ReplicaSets antics es conserven. Un valor de 0 fa impossible qualsevol undo; un valor enorme omple el namespace d'objectes buits. Entre 5 i 10 és el raonable.
I la regla d'or, que no és una comanda: el rollback ha d'estar provat abans de necessitar-lo. Un undo que ningú no ha executat mai és una suposició, no un pla. Assaja'l a staging cada vegada que canviïs res rellevant del desplegament, perquè el dia que calgui serà a les tres de la matinada i amb clients esperant.
- Rollback a Swarm
docker service update --image ghcr.io/auroralibros/aurora-api:2.1.0 aurora_aurora-api
docker service rollback aurora_aurora-api
docker service inspect aurora_aurora-api --format '{{.UpdateStatus.State}} {{.UpdateStatus.Message}}'
# rollback_completed rollback: service rolled back to previous specificationAmb failure_action: rollback, Swarm ho fa sol: si durant la finestra de monitor una tasca nova falla i se supera max_failure_ratio, reverteix sense que ningú hi intervingui. És còmode i té un límit clar: només guarda l'especificació anterior, així que un rollback sobre un altre rollback et torna a on vas començar.
- Assaig d'un desplegament fallit:
aurora-api:2.1.0
aurora-api:2.1.0Simulem el cas real: la 2.1.0 té una errada d'escriptura al nom de la variable de la memòria cau, així que la readiness no passa mai.
kubectl set image deploy/aurora-api api=ghcr.io/auroralibros/aurora-api:2.1.0 -n aurora
kubectl rollout status deploy/aurora-api -n aurora --timeout=120s
# Waiting for deployment "aurora-api" rollout to finish: 2 out of 6 new replicas updated...
# error: deployment "aurora-api" exceeded its progress deadlinekubectl get pods -n aurora -l app.kubernetes.io/name=aurora-api
# NAME READY STATUS RESTARTS AGE
# aurora-api-6d4f8b7c9-2xkpq 1/1 Running 0 3d ← v2.0.0 servint (×6)
# aurora-api-8f2a1c5e7-b7kmd 0/1 Running 0 2m ← v2.1.0 mai preparada
# aurora-api-8f2a1c5e7-n9xwp 0/1 Running 0 2mAquí hi ha el mecanisme complet: sis Pods vells servint i dos de nous que no arriben mai a 1/1. El desplegament es va aturar sol al segon Pod perquè maxUnavailable: 0 prohibeix retirar cap dels antics mentre els nous no estiguin preparats. Cap client no ha notat res; els dos Pods trencats no reben trànsit perquè la seva readiness retorna 503 i kube-proxy no els té als endpoints.
kubectl logs -n aurora aurora-api-8f2a1c5e7-b7kmd --tail=2
kubectl rollout undo deploy/aurora-api -n aurora
kubectl rollout status deploy/aurora-api -n auroraAquest és l'escenari que justifica totes les decisions del mòdul: la sonda de readiness va detectar el problema, maxUnavailable: 0 va impedir que es propagués i l'historial de revisions va permetre desfer-ho en una comanda.
- Blue-green amb dos Deployments
La idea és tenir dos Deployments complets i un Service el selector del qual decideix quin rep el trànsit.
# Dos Deployments idèntics llevat de l'etiqueta version i la imatge
metadata: { name: aurora-api-blau }
template:
metadata: { labels: { app.kubernetes.io/name: aurora-api, version: "2.0.0" } }
---
metadata: { name: aurora-api-verd }
template:
metadata: { labels: { app.kubernetes.io/name: aurora-api, version: "2.1.0" } }
---
apiVersion: v1
kind: Service
metadata: { name: aurora-api }
spec:
selector:
app.kubernetes.io/name: aurora-api
version: "2.0.0" # ← el commutador
ports: [{ port: 3000, targetPort: http }]# 1. Desplegar el verd i validar-lo sense exposar-lo, per un Service secundari
kubectl apply -f k8s/blue-green/verd.yaml
kubectl port-forward -n aurora deploy/aurora-api-verd 8081:3000 &
curl -s localhost:8081/salut/preparat && curl -s localhost:8081/llibres | jq '.llibres|length'
# 2. Commutar: una sola comanda, efecte immediat
kubectl patch svc aurora-api -n aurora -p '{"spec":{"selector":{"version":"2.1.0"}}}'
# 3. Si alguna cosa va malament, tornar: la mateixa comanda a l'inrevés
kubectl patch svc aurora-api -n aurora -p '{"spec":{"selector":{"version":"2.0.0"}}}'Això és el que permetia l'acoblament per etiquetes de 06-04. El rollback és instantani —un canvi de selector, sense arrencar res— i aquesta és la seva gran virtut. El cost també és evident: durant la transició pagues el doble de recursos, i amb dotze rèpliques d'API no és trivial. A més, les connexions ja obertes contra els Pods blaus continuen allà fins que acabin; el tall no és tan atòmic com sembla.
Regla pràctica: mantén l'entorn blau viu almenys un cicle complet de negoci —una hora punta, un dia— abans d'eliminar-lo. El rollback barat només existeix mentre l'altre entorn continua dret.
- Canari per rèpliques i per Ingress
Per rèpliques, sense eines: dos Deployments amb la mateixa etiqueta de selector i el Service repartint per nombre de Pods.
aurora-api-estable |
aurora-api-canari |
Trànsit al canari |
|---|---|---|
| 9 | 1 | ~10 % |
| 8 | 2 | ~20 % |
| 5 | 5 | ~50 % |
| 0 | 10 | 100 % (promocionat) |
És aproximat —el repartiment de kube-proxy és aleatori— i de granularitat gruixuda: per servir un 1 % caldrien 99 rèpliques estables. Però no requereix instal·lar res.
Per Ingress, amb precisió real:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: aurora-canari
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10" # 10 % exacte
nginx.ingress.kubernetes.io/canary-by-header: "x-aurora-canari"
spec:
ingressClassName: nginx
rules:
- host: llibres.aurora.example
http:
paths:
- { path: /llibres, pathType: Prefix, backend: { service: { name: aurora-api-canari, port: { number: 3000 } } } }L'anotació canary-by-header és més útil del que sembla: permet que l'equip forci el seu propi trànsit cap al canari amb una capçalera, i validar la versió nova amb usuaris reals coneguts abans d'obrir el percentatge al públic.
- Què es vigila durant el canari
Un canari sense mètriques és només un desplegament lent. El que el converteix en una estratègia és comparar les dues versions amb els mateixos indicadors i a la mateixa finestra.
| Mètrica | Llindar típic d'avortament | D'on surt (05-06) |
|---|---|---|
| Taxa d'error 5xx | > 1 %, o el doble que l'estable | Prometheus sobre els logs amb req_id |
| Latència p95 | > 1,2 × l'estable | Histograma de l'API |
| Latència p99 | > 1,5 × l'estable | Histograma de l'API |
| Reinicis de Pods | Qualsevol | kube_pod_container_status_restarts |
| Ús de memòria | > 1,3 × l'estable | cAdvisor |
| Errors de l'aplicació | Qualsevol tipus nou | Logs estructurats |
Progressió típica: 10 % → 25 % → 50 % → 100 %
Espera a cada pas: almenys 10 min o 1 000 peticions (el que arribi més tard)El criteri d'espera importa tant com els llindars. A les tres de la matinada, un 10 % de trànsit durant cinc minuts són trenta peticions: no diuen absolutament res. Per això la condició ha de ser doble, de temps i de volum. I la promoció, tan bon punt sigui possible, automàtica amb aquests llindars: si depèn que algú miri un panell, acabarà promocionant-se sense mirar.
- Argo Rollouts, Flagger i GitOps
| Eina | Què aporta |
|---|---|
| Argo Rollouts | Substitueix el Deployment per un Rollout amb passos de canari i blue-green declaratius, i AnalysisTemplate per promoure o avortar segons Prometheus |
| Flagger | Automatitza el canari sobre el Deployment existent, integrat amb malles de serveis i Ingress |
| Argo CD / Flux | GitOps: el clúster se sincronitza amb el que hi ha a Git; desplegar és un merge |
# Argo Rollouts: la progressió del canari, declarada
strategy:
canary:
steps:
- setWeight: 10
- pause: { duration: 10m }
- analysis: { templates: [{ templateName: taxa-d-exit }] }
- setWeight: 50
- pause: { duration: 10m }El model GitOps canvia la direcció del desplegament i mereix entendre's: en comptes que el pipeline faci kubectl apply contra el clúster (push), un agent dins del clúster observa el repositori i aplica el que hi troba (pull). Els avantatges són concrets: CI no necessita credencials del clúster —desapareix el risc de l'últim pas de 06-02—, l'estat desitjat és a Git amb el seu historial i les seves revisions, i qualsevol canvi manual es detecta com a drift i es corregeix. El rollback passa a ser git revert.
- Migracions de base de dades: expand/contract
Cap estratègia de desplegament no resol això tota sola, perquè l'esquema és compartit per totes les versions alhora.
Imagina't que la 2.1.0 afegeix idioma a la taula llibres. L'enfocament ingenu —desplegar codi i esquema junts— falla en les dues direccions: si migres primer, les rèpliques velles no coneixen la columna; si desplegues primer, les noves fallen perquè la columna no existeix. I si fas rollback del codi, què fas amb l'esquema?
La resposta és el patró expand/contract, en tres desplegaments separats:
-- FASE 1 — EXPAND: additiu i compatible cap enrere
ALTER TABLE llibres ADD COLUMN idioma VARCHAR(8) DEFAULT 'es';La 2.0.0 continua funcionant: ignora una columna que no coneix. Es desplega només l'esquema, sense tocar el codi.
-- FASE 2 — MIGRATE: la 2.1.0 escriu als dos llocs i llegeix del nou
UPDATE llibres SET idioma = 'es' WHERE idioma IS NULL;Ara sí que es desplega la 2.1.0, que ja fa servir idioma. Durant el rolling update conviuen les dues versions sense problema: la vella no toca la columna, la nova l'omple. I el rollback és segur, perquè tornar a la 2.0.0 no trenca res.
-- FASE 3 — CONTRACT: només quan ja ningú no fa servir el que és vell
ALTER TABLE llibres ALTER COLUMN idioma SET NOT NULL;
ALTER TABLE llibres DROP COLUMN idioma_antic;| Canvi | Compatible cap enrere? | Com fer-ho |
|---|---|---|
Afegir columna amb DEFAULT |
Sí | Directe |
Afegir columna NOT NULL sense valor per defecte |
No | Expand amb defecte → omplir → SET NOT NULL |
| Reanomenar columna | No | Afegir la nova → escriure a totes dues → migrar → esborrar |
| Esborrar columna | No | Deixar de fer-la servir → esperar un cicle → esborrar |
| Canviar el tipus | No | Columna nova, escriptura doble, migrar, esborrar |
| Afegir índex | Sí | CREATE INDEX CONCURRENTLY (no bloqueja) |
La regla que se'n dedueix: l'esquema i el codi no es despleguen mai acoblats. L'esquema va sempre al davant i de manera additiva; la retirada del que és vell va sempre al darrere i amb un cicle de retard. Així, en tot moment, la base de dades és compatible amb la versió anterior i amb la següent, i qualsevol rollback de codi és segur.
Advertència. Una migració sobre dades reals pot bloquejar taules, esgotar el disc pel WAL o fer irreversible un desplegament. Un
ALTER TABLEque reescriu una taula gran deixa el servei bloquejat mentre dura. Valida sempre el pla de migració, la finestra i el procediment de tornada enrere amb el responsable de dades de la teva organització, i assaja'l abans sobre una còpia del volum de producció.
- Feature flags
L'última peça separa dues coses que solem confondre: desplegar codi i activar una funcionalitat.
// api/src/flags.js — la bandera arriba per configuració, com tota la resta (06-01)
const actives = new Set((config.flags ?? '').split(',').filter(Boolean));
const activa = (nom) => actives.has(nom);
app.get('/llibres', async (req, res) => {
const llibres = await cataleg.llistar();
if (activa('recomanacions')) llibres.recomanats = await recomanador.per(req);
res.json(llibres);
});Amb la bandera apagada, el codi de la 2.1.0 es desplega fins a producció i no fa res. S'activa després, quan vulguis, canviant un ConfigMap i sense desplegar res; i si va malament, s'apaga en segons, cosa infinitament més ràpida que qualsevol rollback d'imatge.
| Aspecte | Rollback de desplegament | Apagar una bandera |
|---|---|---|
| Temps | 1-5 min | Segons |
| Abast | Tota la versió | Només aquella funcionalitat |
| Risc | Recrea Pods | Cap |
| Segmentació | No | Per usuari, percentatge o regió |
El preu és el deute tècnic: cada bandera és una branca viva al codi i dos camins per provar. Es posen amb data de caducitat i es retiren tan bon punt la funcionalitat està consolidada, o acabes amb vint banderes i cap combinació provada.
Errors Habituals i Consells
- Desplegar sense sonda de readiness. Cada rèplica substituïda produeix uns segons d'errors. És el requisit de tota la resta.
maxUnavailable > 0amb capacitat justa. Publiques justament quan menys capacitat tens. Fes servirmaxSurgeimaxUnavailable: 0.- Esquema i codi al mateix desplegament. Bloqueja el rollback: pots tornar el codi, però no l'esquema. Expand/contract, sempre.
- Canari sense mètriques ni criteri. És un desplegament lent amb més passos. Defineix llindars i finestra abans de començar.
revisionHistoryLimit: 0. Deixakubectl rollout undosense res a què tornar.- Blue-green i esborrar el blau de seguida. Perds l'única cosa que feia barat el rollback. Espera un cicle complet.
- Canviar el format de la memòria cau sense versionar la clau. Errors intermitents impossibles de reproduir durant la coexistència.
- Consell: assaja el rollback a
stagingperiòdicament. Un procediment no provat no és un procediment. - Consell: fes que el pipeline falli si
kubectl rollout statusfalla. Un desplegament en verd amb Pods trencats és el pitjor dels dos mons.
Exercicis
Exercici 1. Assaja un desplegament fallit d'aurora-api:2.1.0 que no passi la readiness: observa com s'atura sol, comprova que cap client no rep errors durant tot el procés i desfés-lo verificant l'historial de revisions.
Exercici 2. Implementa un blue-green complet: desplega la versió verda, valida-la sense exposar-la, commuta el Service, mesura el temps de commutació i torna enrere.
Exercici 3. Aplica expand/contract per afegir la columna idioma a llibres sense tall i demostrant que el rollback del codi continua sent segur a cada fase.
Solucions
Solució 1.
# Càrrega contínua en segon pla durant tot l'exercici
kubectl run vigia --image=curlimages/curl -n aurora --restart=Never -- \
sh -c 'while true; do curl -s -o /dev/null -w "%{http_code}\n" http://aurora-api:3000/llibres; sleep 0.2; done' &
kubectl set image deploy/aurora-api api=ghcr.io/auroralibros/aurora-api:2.1.0 -n aurora
kubectl rollout status deploy/aurora-api -n aurora --timeout=180s; echo "codi: $?"
# error: deployment "aurora-api" exceeded its progress deadline
# codi: 1
kubectl get pods -n aurora -l app.kubernetes.io/name=aurora-api \
-o custom-columns=POD:.metadata.name,PREPARAT:.status.containerStatuses[0].ready | sort -k2
kubectl get endpoints aurora-api -n aurora -o jsonpath='{.subsets[0].addresses[*].ip}' | wc -w
kubectl logs -n aurora vigia --tail=2000 | sort | uniq -caurora-api-8f2a1c5e7-b7kmd false ← v2.1.0
aurora-api-8f2a1c5e7-n9xwp false ← v2.1.0
aurora-api-6d4f8b7c9-2xkpq true ← v2.0.0 (×6)
6
1983 200Tres números ho demostren tot. Hi ha vuit Pods en marxa però només sis endpoints: els dos Pods de la 2.1.0 existeixen i no van entrar mai al balanceig perquè la seva readiness no va retornar mai 200. I el vigia va registrar 1 983 respostes, totes 200, durant un desplegament que ha fallat completament.
El mecanisme té tres peces encadenades, i totes tres es van decidir en lliçons anteriors. La readiness de 06-01 va detectar que la 2.1.0 no podia servir; kube-proxy la va excloure dels endpoints, així que no va rebre ni una petició; i maxUnavailable: 0 va impedir retirar cap Pod vell, perquè fer-ho hauria baixat de sis disponibles. El desplegament va quedar congelat en un estat segur.
kubectl rollout undo deploy/aurora-api -n aurora
kubectl rollout status deploy/aurora-api -n aurora
kubectl rollout history deploy/aurora-api -n aurora
kubectl logs -n aurora vigia --tail=3000 | sort | uniq -c
# deployment "aurora-api" successfully rolled out
# REVISION CHANGE-CAUSE
# 3 kubectl apply -k k8s/overlays/produccio (2.0.0)
# 4 kubectl set image ... aurora-api:2.1.0
# 5 kubectl rollout undo
# 2874 200Zero errors en tot l'exercici: desplegament fallit, espera i rollback inclosos. Fixa't que l'undo va ser pràcticament instantani, i la raó és que no va arrencar res: els sis Pods de la 2.0.0 no van deixar d'existir mai, així que desfer va consistir a esborrar els dos Pods trencats i tornar el ReplicaSet antic a sis rèpliques desitjades. Un rollback després d'un desplegament reeixit sí que triga, perquè cal recrear els Pods vells.
I el codi: 1 del rollout status és la peça que faltava al pipeline de 06-02: n'hi ha prou de no ignorar aquest codi de sortida perquè un desplegament així posi el job en vermell i dispari l'undo automàticament.
Solució 2.
kubectl apply -f k8s/blue-green/verd.yaml
kubectl wait --for=condition=available deploy/aurora-api-verd -n aurora --timeout=120s
kubectl get endpoints aurora-api -n aurora -o jsonpath='{.subsets[0].addresses[*].ip}' | wc -w
kubectl port-forward -n aurora deploy/aurora-api-verd 8081:3000 &
curl -s localhost:8081/llibres | jq -r '"\(.origen) \(.llibres|length)"'
# 6
# db 9Detall important del primer número: encara que el Deployment verd ja està preparat, el Service continua tenint sis endpoints, els del blau. La versió nova és viva, validada per port-forward i servint els nou títols, i no rep ni una petició d'usuari. Aquesta separació entre "desplegat" i "exposat" és l'essència del blue-green.
inici=$(date +%s%3N)
kubectl patch svc aurora-api -n aurora -p '{"spec":{"selector":{"version":"2.1.0"}}}'
kubectl get endpoints aurora-api -n aurora -o jsonpath='{.subsets[0].addresses[*].ip}' | wc -w
echo "commutacio: $(( $(date +%s%3N) - inici )) ms"
kubectl logs -n aurora vigia --tail=200 | sort | uniq -c
# 6
# commutacio: 412 ms
# 200 200
kubectl patch svc aurora-api -n aurora -p '{"spec":{"selector":{"version":"2.0.0"}}}' # tornada enrere| Estratègia | Temps de commutació | Temps de rollback | Recursos durant |
|---|---|---|---|
| Rolling update (6 rèpliques) | ~90 s | ~90 s | 6 + 2 Pods |
| Blue-green | 0,4 s | 0,4 s | 12 Pods |
Els 412 mil·lisegons són l'argument sencer del blue-green, i el 12 de l'última columna és la seva factura. La commutació és tan ràpida perquè no arrenca ni atura res: només reescriu un selector, i el controlador d'endpoints recalcula quins Pods pertanyen al Service.
Dos matisos que la taula no mostra. El primer: les connexions HTTP keep-alive ja establertes contra Pods blaus continuen servint-se des de la versió vella fins que es tanquen, així que la commutació no és atòmica des del punt de vista de l'usuari. El segon, més seriós: mentre els dos entorns conviuen, totes dues versions estan escrivint a la mateixa base de dades i a la mateixa memòria cau, amb la qual cosa la compatibilitat de la secció 1 continua sent obligatòria. Blue-green resol l'encaminament, no la compatibilitat de dades.
Solució 3.
-- FASE 1 (EXPAND) — es desplega SOLA, sense tocar el codi
ALTER TABLE llibres ADD COLUMN idioma VARCHAR(8) DEFAULT 'es';kubectl exec -n aurora aurora-db-0 -- psql -U aurora -d aurora_llibres -f /tmp/expand.sql
curl -s localhost:8080/llibres | jq -r '"\(.origen) \(.llibres|length)"' # amb la 2.0.0 encara
# db 9La 2.0.0 continua retornant els nou títols amb una columna nova que ignora completament, perquè el seu SELECT anomena les columnes que necessita. Aquesta és la propietat que fa segura la fase 1: un canvi additiu amb valor per defecte és invisible per al codi antic.
# FASE 2 (MIGRATE) — ara sí, el codi nou, amb rolling update normal
kubectl set image deploy/aurora-api api=ghcr.io/auroralibros/aurora-api:2.1.0 -n aurora
kubectl rollout status deploy/aurora-api -n aurora
curl -s localhost:8080/llibres | jq -r '.llibres[1] | "\(.titol) [\(.idioma)]"'
kubectl rollout undo deploy/aurora-api -n aurora # rollback de prova, a mitges
curl -s localhost:8080/llibres | jq -r '.llibres[1].titol'
# Rayuela [es] ← amb la 2.1.0
# Rayuela ← després del rollback a la 2.0.0Aquestes dues línies són la demostració demanada. Amb la 2.1.0, /llibres inclou l'idioma; després del rollback a la 2.0.0, el camp desapareix de la resposta i tota la resta continua funcionant. La base de dades no ha hagut de canviar en cap moment, perquè la columna era compatible amb les dues versions.
-- FASE 3 (CONTRACT) — dies després, quan cap versió antiga no queda viva
ALTER TABLE llibres ALTER COLUMN idioma SET NOT NULL;| Fase | Què es desplega | Rollback del codi segur? | Cal rollback de l'esquema? |
|---|---|---|---|
| 1. Expand | Només esquema (additiu) | Sí (no ha canviat) | No |
| 2. Migrate | Només codi (2.1.0) | Sí | No |
| 3. Contract | Només esquema (restrictiu) | Sí, si ja no hi ha versions velles | No |
La columna decisiva és la tercera: en cap de les tres fases no cal desfer l'esquema, i aquest és exactament l'objectiu del patró. Comparat amb l'enfocament acoblat, la diferència és abismal: si haguessis desplegat l'ALTER TABLE ... NOT NULL juntament amb la 2.1.0, el rollback del codi hauria deixat la 2.0.0 inserint files sense idioma contra una columna obligatòria, i cada alta hauria fallat.
La fase 3 exigeix una disciplina que s'oblida amb facilitat: no es pot executar fins a estar segur que cap versió antiga continua viva, i això inclou treballs per lots, processos d'informes o integracions de tercers que potser ningú no recorda. Per això el prudent és deixar passar un cicle complet —dies, no minuts— entre la fase 2 i la 3. I per això el pla de migració, la finestra i el procediment de tornada enrere s'acorden amb el responsable de dades abans de tocar producció.
Conclusió
Ja saps publicar sense que ningú deixi de poder comprar un llibre. Tens clar el problema de fons —durant tot desplegament conviuen dues versions sobre la mateixa base de dades i la mateixa memòria cau— i les cinc estratègies amb el seu cost real: recreate i el seu tall, el rolling update com a valor per defecte assenyat, blue-green amb la seva commutació de 412 mil·lisegons a canvi del doble de recursos, el canari com l'opció de menor risc quan tens mètriques fiables, i shadow amb la seva advertència sobre els efectes secundaris duplicats.
Domines el rolling update per dins: maxSurge: 2 amb maxUnavailable: 0 per no perdre capacitat ni un instant, minReadySeconds per no donar per bo un Pod que cau dos segons després, progressDeadlineSeconds com a límit, i la taula d'equivalències amb l'update_config de Swarm. Sobretot, has comprovat per què la sonda de readiness de 06-01 és la peça que sosté tota la resta: l'assaig del desplegament fallit d'aurora-api:2.1.0 va acabar amb vuit Pods en marxa, només sis endpoints i 1 983 respostes, totes 200, amb el desplegament congelat en un estat segur i un rollout status retornant codi 1 perquè el pipeline ho detecti. L'undo va ser instantani perquè no hi havia res a arrencar, i l'historial de revisions sempre avança, mai no esborra.
Has muntat el blue-green commutant un selector —l'acoblament per etiquetes de 06-04 rendint—, el canari per rèpliques i per anotacions de l'Ingress amb la seva capçalera per a l'equip, i saps què es vigila durant el canari i amb quin criteri doble de temps i volum es promociona o s'avorta. Coneixes Argo Rollouts i Flagger per automatitzar-ho, i GitOps amb Argo CD o Flux invertint la direcció del desplegament perquè CI no necessiti credencials del clúster. I has resolt el que cap estratègia no arregla tota sola: el patró expand/contract en tres desplegaments, aplicat a afegir idioma a llibres, amb la demostració que el rollback del codi és segur a totes les fases perquè l'esquema no es desplega mai acoblat. Tanquen el quadre els feature flags, que separen desplegar d'activar i converteixen una tornada enrere de minuts en una de segons.
I amb això es tanca el mòdul 6. Aurora Libros hi va entrar sent una pila de Compose al teu portàtil i en surt convertida en una plataforma: una imatge 2.0.0 apta per a producció, amb la configuració fora, validació en arrencar que falla en 0,4 segons, aturada ordenada sense perdre ni una petició i tres sondes amb semàntiques separades; un pipeline que a cada git push prova contra PostgreSQL i Redis reals, construeix per a dues arquitectures en 41 segons gràcies a la memòria cau remota, escaneja, signa amb Cosign i publica; un clúster —primer Swarm amb les seves xarxes overlay i la seva malla d'encaminament, després Kubernetes amb els quatre serveis en manifestos reals, StatefulSet per a les dades, Kustomize per entorn i /llibres retornant els nou títols—; un HorizontalPodAutoscaler que va portar el p95 de 1 840 ms a 88 ms sense que ningú toqués res, amb PgBouncer resolent el coll d'ampolla que era on no miraves; i, ara, publicacions sense tall amb tornada enrere provada. Aurora Libros ja no viu a la teva màquina: viu en un clúster, es desplega sola des d'un commit, creix amb la càrrega i s'actualitza sense que cap client ho noti.
Al mòdul 7 aixequem la vista del projecte per mirar al voltant. Veuràs com s'aprovisionen els hosts que sostenen tot això, quan convé Compose i quan Kubernetes amb una comparació honesta de tots dos, què aporta i què costa Docker Desktop, quines eines i plugins de tercers mereixen un lloc al teu flux de treball, com hi encaixen Podman, containerd i l'estàndard OCI —aquestes imatges teves que ja saps que no són "de Docker"— i cap a on va l'ecosistema de contenidors.
Docker: De Principiant a Avançat
Mòdul 1: Introducció a Docker
- Què és Docker?
- Instal·lant Docker
- Arquitectura de Docker
- Comandes Bàsiques de Docker
- Entenent les Imatges de Docker
- Creant el teu Primer Contenidor Docker
- El Projecte del Curs: la Plataforma Aurora Libros
Mòdul 2: Treballant amb Imatges Docker
- Docker Hub i Repositoris
- Construint Imatges Docker
- Conceptes Bàsics de Dockerfile
- Instruccions Avançades del Dockerfile
- Gestionant Imatges Docker
- Etiquetatge i Publicació d'Imatges
Mòdul 3: Contenidors Docker
- Executant Contenidors
- Cicle de Vida del Contenidor
- Gestionant Contenidors
- Inspecció i Depuració de Contenidors
- Xarxes a Docker
- Persistència de Dades amb Volums
- Límits de Recursos i Polítiques de Reinici
Mòdul 4: Docker Compose
- Introducció a Docker Compose
- Definint Serveis a Docker Compose
- Comandes de Docker Compose
- Aplicacions Multi-Contenidor
- Variables d'Entorn a Docker Compose
- Perfils, Overrides i Múltiples Entorns
- Desenvolupament Local amb Docker Compose
Mòdul 5: Conceptes Avançats de Docker
- Aprofundiment en Xarxes Docker
- Opcions d'Emmagatzematge Docker
- Millors Pràctiques de Seguretat a Docker
- Optimitzant Imatges Docker
- Builds Avançades amb BuildKit i Buildx
- Registre i Monitoratge a Docker
- El Runtime per Dins: Namespaces, Cgroups i Capes
Mòdul 6: Docker en Producció
- Preparar una Imatge per a Producció
- CI/CD amb Docker
- Orquestrant Contenidors amb Docker Swarm
- Introducció a Kubernetes
- Desplegant Contenidors Docker a Kubernetes
- Escalat i Balanceig de Càrrega
- Estratègies de Desplegament i Rollback
