Tot el mòdul s'ha ocupat fins ara de créixer: més pods, de la mida correcta, sobre més nodes, i abans que arribi el trànsit. Però hi ha una pregunta que no hem fet: què passa quan alguna cosa es trenca, o quan algú ha de tocar el clúster?
El proper dimarts toca actualitzar la versió de Kubernetes dels nodes de Rutas Norte. Algú executarà kubectl drain sobre el primer node per buidar-lo. Si en aquest node hi ha tres de les quatre rèpliques d'api-reserves —perquè el planificador les hi va posar i ningú li va dir que no ho fes—, aquest drenatge s'endurà el 75 % de la capacitat de l'API en un instant, i la quarta rèplica rebrà quatre vegades la seva càrrega habitual fins a caure també.
I hi ha una variant pitjor: que el node no el dreni ningú, sinó que s'apagui sol per una fallada de maquinari a la zona de disponibilitat B, a les tres de la matinada, sense previ avís.
Els dos escenaris produeixen el mateix dany, però Kubernetes només et pot protegir d'un d'ells. Entendre per què, i què fer amb l'altre, és el contingut d'aquesta lliçó.
Veurem la distinció entre interrupcions voluntàries i involuntàries, el PodDisruptionBudget i l'API de desallotjament que el respecta, els PDB impossibles que bloquegen el manteniment per sempre, la distribució topològica amb topologySpreadConstraints davant de la podAntiAffinity de 06-05, què significa l'alta disponibilitat a les altres capes del sistema, el procediment complet de manteniment d'un node, i una prova de caos per comprovar que tot funciona de debò.
Contingut
- Interrupcions voluntàries i involuntàries
- El PodDisruptionBudget: què és i què protegeix
minAvailabledavant demaxUnavailable- L'API de desallotjament i
kubectl drain - Demostració: un drenatge que es queda esperant
- El PDB impossible que bloqueja el manteniment
unhealthyPodEvictionPolicy- Els PDB de Rutas Norte, component a component
topologySpreadConstraintsa fonstopologySpreadConstraintsdavant depodAntiAffinity- Repartir Rutas Norte entre zones i nodes
- Alta disponibilitat a les altres capes
- El procediment de manteniment d'un node
- Interacció amb el Cluster Autoscaler i els desplegaments
- Una prova de caos senzilla
- Errors comuns i consells
- Exercicis
- Conclusió
- Interrupcions voluntàries i involuntàries
Kubernetes distingeix formalment dues classes d'interrupció, i la distinció no és acadèmica: determina quins mecanismes de protecció existeixen.
Interrupcions involuntàries
Passen sense que ningú les hagi demanat. Són fallades:
| Causa | Exemple a Rutas Norte |
|---|---|
| Fallada de maquinari del node | La font d'alimentació del servidor que allotja tres rèpliques |
| Caiguda de la zona de disponibilitat | Un tall elèctric al centre de dades de la zona B |
| El kernel mata un procés per falta de memòria | OOMKilled d'api-reserves (mòdul 3) |
| Desallotjament del kubelet per pressió de recursos | El node es queda sense disc i desallotja pods BestEffort |
| Partició de xarxa | El node perd connectivitat amb el pla de control |
| Esborrat accidental d'una màquina virtual | Algú s'equivoca a la consola del proveïdor |
Kubernetes no pot impedir cap d'aquestes. Quan el maquinari falla, falla. L'única cosa que es pot fer és limitar el dany: si les rèpliques estan repartides, la caiguda d'un node se'n porta una fracció; si estan amuntegades, se'n porta tot.
La protecció contra interrupcions involuntàries és arquitectònica: rèpliques suficients, repartides entre dominis de fallada independents. Això és l'apartat 9 endavant.
Interrupcions voluntàries
Les provoca algú deliberadament, a través de l'API:
| Causa | Exemple a Rutas Norte |
|---|---|
| Drenar un node per manteniment | Actualitzar el kernel o la versió de Kubernetes |
| Reducció del clúster | El Cluster Autoscaler retira un node infrautilitzat (09-03) |
| Redesplegar una aplicació | Una actualització progressiva d'api-reserves (02-04) |
| El VPA ajusta recursos | L'actualitzador desallotja un pod per recrear-lo (09-02) |
| Esborrar un pod a mà | kubectl delete pod |
| Reprogramació per un descheduler | Una eina que reequilibra pods entre nodes |
Aquí sí que pot intervenir Kubernetes, perquè aquestes accions passen pel servidor d'API i poden ser avaluades abans d'executar-se. El mecanisme s'anomena PodDisruptionBudget, i és l'objecte que diu: «pots fer manteniment, però no a costa de deixar-me sense servei».
flowchart TD
subgraph INVOL["Interrupcions INVOLUNTARIES"]
I1[Fallada de maquinari]
I2[Caiguda de zona]
I3[OOMKilled]
I4[Particio de xarxa]
end
subgraph VOL["Interrupcions VOLUNTARIES"]
V1[kubectl drain]
V2[Reduccio del Cluster Autoscaler]
V3[Actualitzacio progressiva]
V4[Desallotjament del VPA]
end
INVOL -->|Kubernetes NO les pot impedir| ARQ["Proteccio ARQUITECTONICA:<br/>repliques repartides entre<br/>dominis de fallada<br/>topologySpreadConstraints"]
VOL -->|Passen per l'API de desallotjament| PDB["Proteccio per POLITICA:<br/>PodDisruptionBudget"]
style INVOL fill:#fdd,stroke:#c00
style VOL fill:#ffd,stroke:#c90
style ARQ fill:#cde,stroke:#369
style PDB fill:#cfc,stroke:#393
Les dues proteccions són complementàries i calen totes dues. Un PDB perfecte no et salva que caigui la zona on tens totes les rèpliques. I una distribució topològica perfecta no impedeix que un kubectl drain descurat s'endugui tres rèpliques de cop.
Un matís important sobre el maxUnavailable del Deployment
Convé aclarir una confusió freqüent. El Deployment té el seu propi control d'interrupció durant les actualitzacions:
Això governa només el desplegament progressiu (02-04). No protegeix d'un kubectl drain, ni de la reducció del Cluster Autoscaler, ni d'un desallotjament del VPA. Són mecanismes diferents amb àmbits diferents, i necessites tots dos.
strategy.rollingUpdate.maxUnavailable |
PodDisruptionBudget | |
|---|---|---|
| Àmbit | Només actualitzacions del Deployment | Qualsevol desallotjament per l'API |
| L'aplica | El controlador de Deployments | El servidor d'API, en la petició de desallotjament |
| Protegeix de | Que la mateixa actualització deixi sense servei | drain, CA, VPA, descheduler |
| Es defineix a | El Deployment | Un objecte PodDisruptionBudget a part |
- El PodDisruptionBudget: què és i què protegeix
Un PodDisruptionBudget (PDB) és un objecte que declara: «d'aquest conjunt de pods, sempre n'hi ha d'haver almenys N de disponibles» (o «com a molt N de no disponibles»).
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-reserves
namespace: rutas-norte-pro
spec:
maxUnavailable: 25%
selector:
matchLabels:
app: api-reservesTres camps i res més:
selector: quins pods cobreix. Igual que el selector d'un Service o un Deployment (02-07).minAvailableomaxUnavailable: la restricció. Mútuament excloents: un dels dos, mai tots dos.- (Opcional)
unhealthyPodEvictionPolicy: apartat 7.
Què fa exactament
Quan algú demana desallotjar un pod a través de l'API de desallotjament, el servidor d'API:
- Busca els PDB el
selectordels quals coincideixi amb aquest pod. - Per a cadascun, calcula quants pods estan actualment disponibles (
Ready). - Comprova si desallotjar aquest pod violaria la restricció.
- Si la violaria, rebutja la petició amb un error HTTP 429 (
TooManyRequests). - Si no, l'accepta i el pod s'elimina.
És una comprovació en el moment de la petició, no una reserva. El client que rep el 429 normalment reintenta, i així el desallotjament espera fins que sigui segur.
Què NO fa un PDB
Aquesta llista és tan important com l'anterior:
| El PDB no | Explicació |
|---|---|
| Impedeix que un node caigui | És una interrupció involuntària: ningú demana permís |
Impedeix kubectl delete pod |
L'esborrat directe no passa per l'API de desallotjament |
| Garanteix que hi hagi N rèpliques | Això ho fa el Deployment/ReplicaSet |
| Crea pods nous | No és un controlador de rèpliques |
Protegeix d'un OOMKilled |
Involuntari |
| Protegeix que el kubelet desallotgi per pressió de recursos | Involuntari |
El segon punt sorprèn molta gent i mereix èmfasi: kubectl delete pod ignora els PDB per complet. L'esborrat directe és una operació diferent del desallotjament. Si vols respectar els PDB des de la línia d'ordres, fes servir kubectl drain (que sí que fa servir l'API de desallotjament) o la mateixa API:
# Aixo IGNORA el PDB
kubectl delete pod api-reserves-7c9d4f8b6d-2mk8p -n rutas-norte-pro
# Aixo RESPECTA el PDB
kubectl drain <node> --pod-selector=app=api-reserves
minAvailable davant de maxUnavailable
minAvailable davant de maxUnavailableEls dos expressen el mateix des de costats oposats, però es comporten de manera molt diferent quan el nombre de rèpliques canvia, que és exactament el que passa quan hi ha un HPA.
minAvailable
«Sempre hi ha d'haver almenys N pods disponibles.»
Amb 4 rèpliques: se'n pot desallotjar 1 (en queden 3). Amb 10 rèpliques: se'n poden desallotjar 7.
maxUnavailable
«Com a molt hi pot haver N pods no disponibles.»
Amb 4 rèpliques: se'n pot desallotjar 1. Amb 10 rèpliques: també només 1.
La diferència crítica amb els percentatges
Aquí hi ha el detall que gairebé ningú coneix i que produeix comportaments sorprenents: l'arrodoniment és diferent en cada cas.
| Camp | Arrodoniment del percentatge | Motiu |
|---|---|---|
minAvailable: 50% |
Cap amunt | Més conservador: garanteix més pods |
maxUnavailable: 50% |
Cap avall | Més conservador: permet menys desallotjaments |
En tots dos casos l'arrodoniment afavoreix la disponibilitat. Vegem-ho amb números:
Amb 5 rèpliques:
| PDB | Càlcul | Resultat | Desallotjaments permesos |
|---|---|---|---|
minAvailable: 50% |
sostre(5 × 0,5) = 3 |
Mínim 3 disponibles | 2 |
maxUnavailable: 50% |
terra(5 × 0,5) = 2 |
Màxim 2 no disponibles | 2 |
Aquí coincideixen. Però amb 7 rèpliques:
| PDB | Càlcul | Resultat | Desallotjaments permesos |
|---|---|---|---|
minAvailable: 50% |
sostre(7 × 0,5) = 4 |
Mínim 4 disponibles | 3 |
maxUnavailable: 50% |
terra(7 × 0,5) = 3 |
Màxim 3 no disponibles | 3 |
Coincideixen un altre cop. Amb 3 rèpliques:
| PDB | Càlcul | Resultat | Desallotjaments permesos |
|---|---|---|---|
minAvailable: 50% |
sostre(3 × 0,5) = 2 |
Mínim 2 disponibles | 1 |
maxUnavailable: 50% |
terra(3 × 0,5) = 1 |
Màxim 1 no disponible | 1 |
Per al 50 % el resultat és equivalent. La diferència apareix amb altres percentatges. Amb 10 rèpliques i un 20 %:
| PDB | Càlcul | Resultat | Desallotjaments permesos |
|---|---|---|---|
minAvailable: 20% |
sostre(10 × 0,2) = 2 |
Mínim 2 disponibles | 8 |
maxUnavailable: 20% |
terra(10 × 0,2) = 2 |
Màxim 2 no disponibles | 2 |
Radicalment diferent. minAvailable: 20% permet desallotjar el 80 % dels pods; maxUnavailable: 20% permet desallotjar el 20 %.
Regla mental: minAvailable parla del que queda; maxUnavailable parla del que se'n va.
Quin fer servir: la regla de l'HPA
I ara la raó per la qual això importa tant en aquest mòdul. Amb un HPA, el nombre de rèpliques canvia sol. Un PDB en número absolut que era raonable amb 30 rèpliques es torna impossible amb 4.
Recordem el cas de l'exercici 2 de 09-03:
Durant el pont: 30 repliques. Algu posa minAvailable: 25.
Desallotjaments permesos: 5. Raonable.
Despres del pont: l'HPA baixa a 4 repliques. El PDB continua dient minAvailable: 25.
Pods disponibles: 4. Minim exigit: 25.
JA s'esta per sota del minim.
Desallotjaments permesos: ZERO. Per sempre.
El node que allotgi qualsevol replica d'api-reserves queda ANCORAT.
El Cluster Autoscaler no el pot retirar. El manteniment es bloqueja.Per això:
| Situació | Recomanació |
|---|---|
| Càrrega amb HPA o KEDA | maxUnavailable en percentatge |
| Nombre de rèpliques fix i conegut | Qualsevol; minAvailable és més explícit |
| Càrrega amb estat i quòrum (etcd, bases de dades) | maxUnavailable: 1 sempre |
| Una sola rèplica | Cap dels dos funciona bé (apartat 6) |
maxUnavailable: 25% és l'elecció per defecte assenyada per a una càrrega amb HPA. S'adapta sola: amb 4 rèpliques permet desallotjar-ne 1; amb 30, en permet desallotjar 7. La proporció de servei protegida és constant.
Una nota sobre el denominador: el percentatge es calcula sobre el nombre de rèpliques desitjades pel controlador (el spec.replicas del Deployment), no sobre els pods Ready en aquell moment. Això importa quan hi ha pods arrencant: durant un escalat de l'HPA de 4 a 12, el denominador ja és 12 encara que només 5 estiguin llestos.
- L'API de desallotjament i
kubectl drain
kubectl drainEl PDB només funciona si qui vol treure un pod demana permís. Aquest mecanisme és l'API de desallotjament (Eviction API).
Com funciona
És un subrecurs del pod:
Amb aquest cos:
{
"apiVersion": "policy/v1",
"kind": "Eviction",
"metadata": {
"name": "api-reserves-7c9d4f8b6d-2mk8p",
"namespace": "rutas-norte-pro"
}
}Respostes possibles:
| Codi | Significat |
|---|---|
200 OK |
Desallotjament acceptat; el pod s'elimina amb el seu període de gràcia |
429 TooManyRequests |
Un PDB ho impedeix. Reintentar més tard |
500 Internal Server Error |
Configuració incoherent (un PDB mal format) |
El 429 inclou un missatge explicatiu:
{
"kind": "Status",
"status": "Failure",
"message": "Cannot evict pod as it would violate the pod's disruption budget.",
"reason": "TooManyRequests",
"details": {
"causes": [
{
"reason": "DisruptionBudget",
"message": "The disruption budget api-reserves needs 3 healthy pods and has 3 currently"
}
]
}
}Aquest missatge —«necessita 3 pods sans i en té 3 actualment»— és el que veuràs a la pràctica i el que cal saber llegir.
Qui fa servir l'API de desallotjament
| Client | Respecta els PDB? |
|---|---|
kubectl drain |
Sí |
| Cluster Autoscaler (09-03) | Sí |
| Actualitzador del VPA (09-02) | Sí |
| Descheduler | Sí |
| Karpenter (consolidació) | Sí |
kubectl delete pod |
No. Esborrat directe |
| Controlador de Deployments (actualització progressiva) | No. Fa servir maxUnavailable propi |
| El kubelet desallotjant per pressió de recursos | No. És involuntari |
| Un node que s'apaga | No. És involuntari |
Les quatre primeres files són les que importen: tots els mecanismes automàtics que hem anat afegint en aquest mòdul respecten els PDB. És la garantia que l'autoescalat no s'endugui per davant la disponibilitat.
El camp disruptionsAllowed
El PDB publica al seu estat quants desallotjaments permet en aquest moment:
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
api-reserves N/A 25% 1 12d
botiga-web N/A 1 1 12d
postgres-reserves 1 N/A 0 12d
worker-notificacions N/A 50% 3 12dALLOWED DISRUPTIONS és el número més útil de tota la lliçó. Si és 0, cap pod d'aquest conjunt no es pot desallotjar ara mateix, i qualsevol drenatge que els afecti es quedarà esperant.
Un 0 pot ser correcte (postgres-reserves amb una rèplica i minAvailable: 1) o símptoma d'un problema (un PDB impossible, o pods que no estan Ready).
Vista detallada:
Name: api-reserves
Namespace: rutas-norte-pro
Max unavailable: 25%
Selector: app=api-reserves
Status:
Allowed disruptions: 2
Current: 8
Desired: 6
Total: 8| Camp | Significat |
|---|---|
Current |
Pods Ready ara mateix |
Desired |
Mínim que exigeix el PDB (calculat) |
Total |
Pods que cobreix el selector |
Allowed disruptions |
Current - Desired |
- Demostració: un drenatge que es queda esperant
Res no ensenya millor que veure-ho. Preparem l'escenari a minikube.
Preparació
minikube start -p rutas-norte --nodes=3 --cpus=2 --memory=4096
kubectl create namespace rutas-norte-dev
# Un Deployment amb 3 repliques
kubectl -n rutas-norte-dev create deployment demo-pdb \
--image=registry.k8s.io/pause:3.9 --replicas=3
kubectl -n rutas-norte-dev set resources deployment/demo-pdb \
--requests=cpu=100m,memory=64MiUn PDB deliberadament restrictiu:
# /tmp/pdb-demo.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: demo-pdb
namespace: rutas-norte-dev
spec:
# Amb 3 repliques, exigir-ne 3 de disponibles significa ZERO desallotjaments permesos.
minAvailable: 3
selector:
matchLabels:
app: demo-pdbALLOWED DISRUPTIONS: 0. Ja sabem què passarà.
El drenatge
NAME READY STATUS NODE
demo-pdb-6c9f8d7b5-2mkjp 1/1 Running rutas-norte
demo-pdb-6c9f8d7b5-7wqzx 1/1 Running rutas-norte-m02
demo-pdb-6c9f8d7b5-9nvtr 1/1 Running rutas-norte-m03node/rutas-norte-m02 cordoned
evicting pod rutas-norte-dev/demo-pdb-6c9f8d7b5-7wqzx
error when evicting pods/"demo-pdb-6c9f8d7b5-7wqzx" -n "rutas-norte-dev"
(will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod rutas-norte-dev/demo-pdb-6c9f8d7b5-7wqzx
error when evicting pods/"demo-pdb-6c9f8d7b5-7wqzx" -n "rutas-norte-dev"
(will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod rutas-norte-dev/demo-pdb-6c9f8d7b5-7wqzx
error when evicting pods/"demo-pdb-6c9f8d7b5-7wqzx" -n "rutas-norte-dev"
(will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
...El drenatge es queda en bucle infinit, reintentant cada 5 segons.
Observacions importants:
- El node JA està acordonat (
cordoned). Encara que el drenatge no avanci, el node no accepta pods nous. És el primer que fadrain. - El missatge és explícit:
Cannot evict pod as it would violate the pod's disruption budget. drainno es rendeix. Reintenta indefinidament fins que l'hi permetin o fins que l'interrompis.
Aquest és exactament el comportament desitjat: el PDB ha impedit que el manteniment trenqui el servei. Però també és exactament el comportament que bloqueja el manteniment per sempre si el PDB està malament, i aquesta és la lliçó de l'apartat 6.
Desbloquejar-ho
Tres formes:
# Opcio A: mes repliques. Amb 4 repliques i minAvailable: 3, es permet 1 desallotjament.
kubectl -n rutas-norte-dev scale deployment demo-pdb --replicas=4Tan bon punt la quarta rèplica estigui Ready, el drenatge que estava reintentant avança sol. És molt satisfactori de veure.
# Opcio B: corregir el PDB
kubectl -n rutas-norte-dev patch pdb demo-pdb --type merge \
-p '{"spec":{"minAvailable":null,"maxUnavailable":"25%"}}'Compte: minAvailable i maxUnavailable són mútuament excloents, així que cal anul·lar-ne un en posar l'altre.
# Opcio C (ULTIM RECURS, perillos): forcar el drenatge
kubectl drain rutas-norte-m02 --ignore-daemonsets --delete-emptydir-data \
--disable-eviction--disable-eviction fa servir esborrat directe en lloc de l'API de desallotjament, saltant-se els PDB. És l'opció d'emergència, i cal saber que pot deixar el servei sense cap rèplica disponible. Fes-la servir només quan entenguis exactament què caurà i hagis decidit que és acceptable.
Neteja
kubectl uncordon rutas-norte-m02
kubectl delete -f /tmp/pdb-demo.yaml
kubectl delete deployment demo-pdb -n rutas-norte-devNo oblidis mai l'uncordon. Un node acordonat que ningú descordona és capacitat pagada i inutilitzable, i el símptoma (pods Pending mentre hi ha nodes aparentment lliures) desconcerta moltíssim.
- El PDB impossible que bloqueja el manteniment
Formalitzem el problema, perquè és la trampa més perillosa d'aquesta lliçó.
La definició
Un PDB impossible és aquell la restricció del qual no es pot satisfer desallotjant ni un sol pod. El seu ALLOWED DISRUPTIONS és 0 de forma permanent.
Els dos casos:
Cas 1: minAvailable igual (o major) al nombre de rèpliques.
Desallotjar qualsevol pod deixaria 2 disponibles, per sota del mínim de 3. Zero desallotjaments, sempre.
I la variant insidiosa: minAvailable: 25 sobre un Deployment amb HPA que ha baixat a 4 rèpliques. El PDB era raonable quan es va escriure; l'HPA l'ha tornat impossible sense que ningú el toqués.
Cas 2: una sola rèplica amb qualsevol PDB restrictiu.
Amb una rèplica, desallotjar-la deixa zero disponibles. Zero desallotjaments, sempre.
Les conseqüències
| Conseqüència | Detall |
|---|---|
| El manteniment de nodes es bloqueja | kubectl drain reintenta per sempre |
| El Cluster Autoscaler no pot reduir | Nodes infrautilitzats que ningú retira, pagant-se (09-03) |
| El VPA no pot aplicar recomanacions | L'actualitzador falla en desallotjar (09-02) |
| Les actualitzacions del clúster s'encallen | Un proveïdor gestionat pot avortar l'actualització |
| El diagnòstic és difícil | El símptoma (nodes zombis) és lluny de la causa (un PDB) |
Aquest últim punt és el que fa perillós el problema: ningú relaciona un node que no es retira amb un PDB escrit fa tres mesos. Els logs del Cluster Autoscaler ho diuen (pdb-blocked), però cal saber mirar-hi.
Detectar-los
NAMESPACE NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS
rutas-norte-pro api-reserves N/A 25% 2
rutas-norte-pro botiga-web N/A 1 1
rutas-norte-pro postgres-reserves 1 N/A 0
rutas-norte-pro redis-cache 1 N/A 0
rutas-norte-pro worker-notificacions N/A 50% 4
monitoratge prometheus 1 N/A 0Els tres zeros cal analitzar-los un per un:
postgres-reserves: una rèplica primària.ALLOWED DISRUPTIONS: 0és deliberat i correcte: no volem que cap mecanisme automàtic mogui la base de dades. El preu és que el seu node requereix intervenció manual per al manteniment.redis-cache: mateix cas, encara que aquí és més discutible: perdre la memòria cau uns minuts és molest però no catastròfic. Hi hauria arguments per permetre el desallotjament.prometheus: si Prometheus té una sola rèplica (habitual), és el mateix patró.
Una alerta útil per detectar els impossibles no deliberats:
# PDB que porten mes d'una hora sense permetre cap desallotjament
kube_poddisruptionbudget_status_pod_disruptions_allowed == 0Amb una anotació als PDB deliberats per excloure'ls de l'alerta.
Què fer amb les càrregues d'una sola rèplica
És una pregunta que cal respondre de forma explícita, i hi ha tres respostes legítimes:
Opció A: no posar PDB. Sense PDB, el desallotjament es permet sempre. El servei tindrà un tall durant el manteniment (els segons que trigui a arrencar en un altre node), però el manteniment flueix. És l'opció correcta per a serveis no crítics.
Opció B: posar maxUnavailable: 1 (equivalent a minAvailable: 0). Permet sempre el desallotjament. Sembla inútil, però documenta explícitament que s'hi ha pensat i s'accepta la interrupció. Millor que l'absència de PDB, que és ambigua.
Opció C: posar dues rèpliques. Si el servei de debò no es pot interrompre, la solució no és un PDB: és tenir més d'una rèplica. Un PDB no crea disponibilitat, només la protegeix.
Per a postgres-reserves, la resposta correcta no és cap de les tres: és un operador (06-07) que gestioni un clúster de PostgreSQL amb primari i rèpliques, i que sàpiga promocionar una rèplica quan el primari s'hagi de moure. Ho desenvolupem a l'apartat 12.
unhealthyPodEvictionPolicy
unhealthyPodEvictionPolicyUn camp relativament recent que resol un problema molt concret i molt molest.
El problema
Imagina que api-reserves té 6 rèpliques i un PDB de maxUnavailable: 1. Un desplegament defectuós fa que 4 de les 6 rèpliques no passin la sonda de readiness (07-01): estan Running però no Ready.
Repliques totals: 6
Repliques Ready: 2
Repliques no Ready: 4
PDB: maxUnavailable: 1 -> minim 5 disponibles.
Disponibles actuals: 2. Ja estem PER SOTA del minim.
ALLOWED DISRUPTIONS: 0.Ara vols desallotjar un dels pods trencats —precisament perquè està trencat i vols que es recreï en un altre node— i el PDB no t'ho deixa. El PDB està protegint pods que no estan servint trànsit.
És una situació circular i absurda: el sistema està trencat, i el mecanisme de protecció impedeix arreglar-lo.
La solució
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-reserves
namespace: rutas-norte-pro
spec:
maxUnavailable: 25%
selector:
matchLabels:
app: api-reserves
# Permet desallotjar SEMPRE els pods que no estan Ready, sense consumir
# pressupost d'interrupcio.
unhealthyPodEvictionPolicy: AlwaysAllowEls dos valors:
| Valor | Comportament |
|---|---|
IfHealthyBudget (defecte) |
Els pods no sans només es poden desallotjar si el PDB té pressupost disponible |
AlwaysAllow |
Els pods no sans (que no estan Ready) es poden desallotjar sempre |
Amb AlwaysAllow, el raonament és directe: un pod que no està Ready no està servint trànsit, així que desallotjar-lo no redueix la disponibilitat. Al contrari: permet que es recreï, possiblement en un node sa.
Quan fer-lo servir
| Situació | Recomanació |
|---|---|
Aplicacions sense estat (api-reserves, botiga-web, workers) |
AlwaysAllow. Sempre |
| Aplicacions amb quòrum (etcd, Kafka, Zookeeper) | IfHealthyBudget (el defecte). Un pod no Ready pot estar recuperant-se i continuar comptant per al quòrum |
| Bases de dades amb rèpliques | Depèn del sistema; generalment IfHealthyBudget |
La distinció és important: en un sistema de quòrum, un membre que no respon a la sonda pot continuar participant en el consens. Desallotjar-lo alegrement pot perdre el quòrum. En una aplicació sense estat, un pod no Ready és pur llast.
Per a Rutas Norte, AlwaysAllow en tots els components sense estat. Evita l'encallament circular i no té contrapartides.
- Els PDB de Rutas Norte, component a component
Escrivim els manifests definitius, amb la justificació de cada decisió.
api-reserves
# k8s/entorns/pro/pdb-api-reserves.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-reserves
namespace: rutas-norte-pro
labels:
app: api-reserves
app.kubernetes.io/part-of: rutas-norte
spec:
# PERCENTATGE, no numero absolut. api-reserves te KEDA/HPA (09-01, 09-04)
# i oscil·la entre 4 i 30 repliques al llarg del dia. Un minAvailable
# absolut quedaria obsolet tan bon punt l'HPA es mogues, i amb la vall
# nocturna es convertiria en un PDB IMPOSSIBLE que bloquejaria el
# manteniment i la reduccio de nodes.
#
# Amb 25 %:
# 4 repliques -> permet desallotjar-ne 1 (terra(4 x 0,25) = 1)
# 12 repliques -> permet desallotjar-ne 3
# 30 repliques -> permet desallotjar-ne 7
# La PROPORCIO de servei protegida es constant: sempre el 75 %.
maxUnavailable: 25%
selector:
matchLabels:
app: api-reserves
# Els pods que no passen la readiness no estan servint transit: desallotjar-los
# no redueix la disponibilitat i evita l'encallament circular d'un desplegament
# defectuos que impedeix la seva propia reparacio.
unhealthyPodEvictionPolicy: AlwaysAllowbotiga-web
# k8s/entorns/pro/pdb-botiga-web.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: botiga-web
namespace: rutas-norte-pro
labels:
app: botiga-web
app.kubernetes.io/part-of: rutas-norte
spec:
# 34 %, mes permissiu que api-reserves. Rao: botiga-web es nginx servint
# estatic. Arrenca en ~1 segon i no te estat ni cache que escalfar,
# aixi que una replica desallotjada torna gairebe a l'instant en un altre node.
# Permetre mes desallotjaments simultanis accelera el manteniment sense risc real.
#
# Amb 3 repliques -> permet desallotjar-ne 1 (terra(3 x 0,34) = 1)
# Amb 15 repliques -> permet desallotjar-ne 5
maxUnavailable: 34%
selector:
matchLabels:
app: botiga-web
unhealthyPodEvictionPolicy: AlwaysAllowworker-notificacions
# k8s/entorns/pro/pdb-worker-notificacions.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: worker-notificacions
namespace: rutas-norte-pro
labels:
app: worker-notificacions
app.kubernetes.io/part-of: rutas-norte
spec:
# 50 %, el mes permissiu de la plataforma. Justificacio:
#
# 1. NINGU ESPERA. Un correu de confirmacio enviat 30 segons mes tard
# no te cap consequencia per a l'usuari.
# 2. LA CUA ES L'AMORTIDOR. Si es desallotja la meitat dels workers, la
# feina no es perd: s'acumula a la cua i es processa despres.
# El missatge en vol es reentrega automaticament.
# 3. KEDA REACCIONA. Si la cua creix perque hi ha menys workers, KEDA (09-04)
# escalara per compensar.
#
# Amb escalat a ZERO (minReplicaCount: 0) hi ha un matis: quan hi ha 0
# repliques, el PDB no cobreix cap pod i ALLOWED DISRUPTIONS sera 0, pero
# aixo es irrellevant perque no hi ha res a desallotjar.
maxUnavailable: 50%
selector:
matchLabels:
app: worker-notificacions
unhealthyPodEvictionPolicy: AlwaysAllowredis-cache
# k8s/entorns/pro/pdb-redis-cache.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: redis-cache
namespace: rutas-norte-pro
labels:
app: redis-cache
app.kubernetes.io/part-of: rutas-norte
annotations:
# Anotacio per excloure aquest PDB de l'alerta de "PDB impossible":
# el seu ALLOWED DISRUPTIONS es 0 de forma DELIBERADA.
rutasnorte.example/pdb-bloqueig-intencionat: >
redis-cache es un StatefulSet d'una replica. Desallotjar-lo buida la cache
i descarrega tota la carrega de consultes de disponibilitat sobre
postgres-reserves. Requereix intervencio manual i finestra de manteniment.
spec:
# minAvailable: 1 amb UNA replica = ZERO desallotjaments permesos.
# Es DELIBERAT. Consequencies assumides:
# - El Cluster Autoscaler no retirara el node que allotgi redis-cache.
# - kubectl drain sobre aquest node es quedara esperant.
# - El manteniment d'aquest node requereix decisio humana explicita.
#
# Es un compromis ACCEPTAT: preferim que el manteniment sigui manual a
# que un mecanisme automatic buidi la cache en el pitjor moment possible.
minAvailable: 1
selector:
matchLabels:
app: redis-cache
# IfHealthyBudget (el defecte). No hi posem AlwaysAllow: si redis-cache no
# esta Ready, pot estar carregant dades o recuperant-se. Desallotjar-lo
# empitjoraria les coses.
unhealthyPodEvictionPolicy: IfHealthyBudgetpostgres-reserves
# k8s/entorns/pro/pdb-postgres-reserves.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: postgres-reserves
namespace: rutas-norte-pro
labels:
app: postgres-reserves
app.kubernetes.io/part-of: rutas-norte
annotations:
rutasnorte.example/pdb-bloqueig-intencionat: >
postgres-reserves es la base de dades primaria de la plataforma amb una
unica replica. CAP mecanisme automatic no l'ha de moure. El manteniment
del node que l'allotgi requereix una finestra planificada, amb parada
controlada de l'aplicacio i copia de seguretat previa. Veure 05-06.
La solucio definitiva es un operador de PostgreSQL: veure 06-07.
spec:
# ZERO desallotjaments. Es la proteccio mes forta possible, i es intencionada.
#
# Un desallotjament automatic de postgres-reserves significaria:
# - Tall de TOTES les connexions d'api-reserves.
# - Perdua de la cache de pagines de PostgreSQL (minuts de latencia dolenta).
# - Risc d'inconsistencia si hi ha transaccions en vol.
# - La plataforma sencera caiguda durant l'arrencada.
#
# Guarda dades personals de clients: qualsevol risc es inacceptable.
minAvailable: 1
selector:
matchLabels:
app: postgres-reserves
unhealthyPodEvictionPolicy: IfHealthyBudgetTaula resum
| Component | PDB | Desallotjaments amb rèpliques mín. | unhealthyPodEvictionPolicy |
Justificació |
|---|---|---|---|---|
api-reserves |
maxUnavailable: 25% |
1 de 4 | AlwaysAllow |
Amb HPA: percentatge obligatori. 75 % de servei garantit |
botiga-web |
maxUnavailable: 34% |
1 de 3 | AlwaysAllow |
Arrenca en 1 s; més permissiu accelera el manteniment |
worker-notificacions |
maxUnavailable: 50% |
1 de 2 | AlwaysAllow |
La cua amortitza; ningú espera |
redis-cache |
minAvailable: 1 |
0 | IfHealthyBudget |
Una rèplica; buidar la memòria cau és car. Bloqueig deliberat |
postgres-reserves |
minAvailable: 1 |
0 | IfHealthyBudget |
Base de dades primària. Bloqueig deliberat |
informes-ocupacio |
Cap | — | — | És un CronJob. Es protegeix amb safe-to-evict: false (09-03) |
Fixa't en l'última fila: els Jobs no porten PDB. No té sentit: un Job que es desallotja es reinicia i perd la feina. La seva protecció és l'anotació cluster-autoscaler.kubernetes.io/safe-to-evict: "false" que vam veure a 09-03.
topologySpreadConstraints a fons
topologySpreadConstraints a fonsEls PDB protegeixen de les interrupcions voluntàries. Per a les involuntàries, la protecció és repartir les rèpliques entre dominis de fallada independents. I aquí entra el camp que vam esmentar a 06-05 i vam prometre desenvolupar aquí.
El problema que resol
Sense cap restricció, el planificador de Kubernetes col·loca els pods on caben, optimitzant l'ús de recursos. Res no li impedeix posar les quatre rèpliques d'api-reserves al mateix node, si és on hi ha forat.
node-1 (zona A): api-reserves x4, botiga-web x2
node-2 (zona A): postgres-reserves
node-3 (zona B): redis-cache, worker x2
node-4 (zona B): (gairebe buit)
Cau node-1 -> ZERO repliques d'api-reserves. Plataforma caiguda.
Cau la zona A -> ZERO repliques d'api-reserves I la base de dades.topologySpreadConstraints diu al planificador: «reparteix aquests pods de forma equilibrada entre aquests dominis».
Els camps
spec:
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: api-reserves
minDomains: 3
nodeAffinityPolicy: Honor
nodeTaintsPolicy: Honor
matchLabelKeys:
- pod-template-hashtopologyKey — l'etiqueta de node que defineix el domini. Els valors estàndard:
| Etiqueta | Domini | Protegeix de |
|---|---|---|
kubernetes.io/hostname |
Un node | Fallada d'un servidor |
topology.kubernetes.io/zone |
Una zona de disponibilitat | Fallada d'un centre de dades |
topology.kubernetes.io/region |
Una regió geogràfica | Catàstrofe regional |
Etiquetes pròpies (rack, bastidor) |
El que tu defineixis | Fallada d'un bastidor o un commutador |
maxSkew — la diferència màxima permesa entre el domini amb més pods i el que en té menys. És el cor del mecanisme.
Amb maxSkew: 1, la diferència entre qualsevol parell de dominis no pot superar 1. És el repartiment més equilibrat possible.
whenUnsatisfiable — què fer si no es pot complir:
| Valor | Comportament | Quan fer-lo servir |
|---|---|---|
DoNotSchedule |
El pod es queda Pending abans que violar la restricció |
Distribució obligatòria |
ScheduleAnyway |
Es col·loca igualment, però el planificador prefereix complir-la | Distribució preferida |
Aquesta és la decisió més conseqüent del bloc, i la desenvolupem a l'apartat 11.
labelSelector — quins pods es compten per calcular el biaix. Normalment els del mateix Deployment.
minDomains — el nombre mínim de dominis que han d'existir. Sense ell, hi ha un forat subtil:
Sense minDomains, amb 3 repliques i nomes 1 zona amb nodes disponibles:
Zona A: 3 pods. Zona B: no hi ha nodes. Zona C: no hi ha nodes.
Dominis EXISTENTS: 1
Biaix: 3 - 3 = 0 -> la restriccio ES COMPLEIX.
Resultat: les 3 repliques a la mateixa zona. NO es el que volies.
Amb minDomains: 3:
El planificador considera que hi ha 3 dominis, dos d'ells amb 0 pods.
Biaix: 3 - 0 = 3 > maxSkew 1 -> NO es compleix.
Amb DoNotSchedule: els pods queden Pending fins que hi hagi nodes en altres zones.minDomains només té efecte amb whenUnsatisfiable: DoNotSchedule. És la garantia que el repartiment entre zones és real i no una il·lusió.
nodeAffinityPolicy i nodeTaintsPolicy — si tenir en compte les afinitats i taints en calcular els dominis:
| Valor | Comportament |
|---|---|
Honor (defecte) |
Només compta els nodes on el pod podria anar (respectant afinitat i taints) |
Ignore |
Compta tots els nodes |
El valor per defecte Honor és gairebé sempre el correcte. Si api-reserves té afinitat cap al grup de nodes generals, no volem que el node de dades (amb el seu taint) compti com un domini buit que mai es podrà omplir.
matchLabelKeys — el camp més subtil i el que resol un problema real durant els desplegaments.
matchLabelKeys i el problema de les actualitzacions
Durant una actualització progressiva (02-04) coexisteixen pods de la versió vella i de la nova. Sense matchLabelKeys, el càlcul del biaix els compta tots junts:
Actualitzacio d'api-reserves de v1.14 a v1.15. 6 repliques.
Estat intermedi:
zona A: 2 pods v1.14 + 1 pod v1.15 = 3 pods
zona B: 2 pods v1.14 = 2 pods
zona C: 1 pod v1.14 = 1 pod
Biaix calculat sobre TOTS: 3 - 1 = 2 > maxSkew 1
-> El planificador NO pot col·locar mes pods v1.15 a la zona A.
-> L'actualitzacio s'encalla o es distribueix malament.Amb matchLabelKeys: ["pod-template-hash"], el planificador compta només els pods de la mateixa versió (el pod-template-hash és una etiqueta que el controlador de Deployments afegeix automàticament i que identifica el ReplicaSet):
Amb matchLabelKeys: ["pod-template-hash"]:
Per col·locar un pod v1.15, nomes compten els pods v1.15:
zona A: 1, zona B: 0, zona C: 0
Biaix: 1 - 0 = 1 <= maxSkew 1 -> es pot col·locar a B o C.
La nova versio es distribueix correctament pel seu compte.Regla pràctica: inclou sempre matchLabelKeys: ["pod-template-hash"] a les restriccions d'un Deployment. Evita encallaments durant els desplegaments i no té contrapartida.
El càlcul del biaix, pas a pas
Practiquem amb un cas concret. Tres zones, maxSkew: 1, 7 rèpliques d'api-reserves.
Col·locacio de la replica 1:
A: 0, B: 0, C: 0. Qualsevol zona val. -> A
Estat: A:1, B:0, C:0
Col·locacio de la replica 2:
Si va a A: A:2, B:0, C:0. Biaix = 2-0 = 2 > 1. NO PERMES.
Si va a B: A:1, B:1, C:0. Biaix = 1-0 = 1 <= 1. PERMES.
-> B (o C)
Estat: A:1, B:1, C:0
Col·locacio de la replica 3:
Nomes C mante el biaix en 1 o menys. -> C
Estat: A:1, B:1, C:1 (biaix 0)
Repliques 4, 5, 6: es reparteixen una per zona.
Estat: A:2, B:2, C:2 (biaix 0)
Col·locacio de la replica 7:
Qualsevol zona: el biaix passaria a 1. Permes a les tres.
-> A (arbitrari)
Estat FINAL: A:3, B:2, C:2 (biaix 1)Resultat: 3-2-2. El repartiment més equilibrat possible amb 7 rèpliques en 3 zones.
I ara la comprovació de disponibilitat:
Cau la zona A: queden 4 de 7 repliques (57 %).
Cau la zona B: queden 5 de 7 repliques (71 %).
Sense topologySpreadConstraints, en el pitjor cas les 7 podrien ser en una zona:
Cau aquesta zona: queden 0 de 7 (0 %). Plataforma caiguda.
topologySpreadConstraints davant de podAntiAffinity
topologySpreadConstraints davant de podAntiAffinityA 06-05 vam veure l'antiafinitat entre pods per separar rèpliques. Ara tenim dues eines que semblen fer el mateix. No són equivalents, i convé saber quan fer servir cadascuna.
L'antiafinitat, recordada
affinity:
podAntiAffinity:
# Versio DURA: no es pot violar
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: api-reserves
topologyKey: kubernetes.io/hostnameSignifica: «no col·loquis aquest pod en un node on ja hi hagi un altre pod amb app: api-reserves».
És una restricció binària: o hi ha un pod del mateix tipus al domini, o no n'hi ha. No hi ha noció d'equilibri.
La diferència fonamental
podAntiAffinity (requerida) |
topologySpreadConstraints |
|
|---|---|---|
| Semàntica | «Com a molt un per domini» | «Reparteix equitativament entre dominis» |
| Granularitat | Binària: hi ha o no hi ha | Numèrica: controla la diferència |
| Amb més rèpliques que dominis | Els sobrants queden Pending per sempre |
Es reparteixen equilibradament |
| Cost computacional | Alt: O(n²) en clústers grans | Baix |
| Versió «preferida» | preferredDuringScheduling amb weight |
whenUnsatisfiable: ScheduleAnyway |
| Multinivell | Difícil d'expressar | Natural: diverses restriccions alhora |
| Reequilibrat després d'una fallada | No n'hi ha | Tampoc, però el repartiment inicial és millor |
El cas que ho decideix
api-reserves escala de 4 a 30 rèpliques i el clúster té 9 nodes.
Amb podAntiAffinity requerida sobre hostname:
Regla: com a molt 1 replica d'api-reserves per node.
Nodes: 9.
Repliques maximes col·locables: 9.
L'HPA demana 30 repliques.
Se'n col·loquen 9. Les altres 21 queden en Pending PER SEMPRE.
El Cluster Autoscaler veu pods pendents i arrenca nodes... i amb cada node
nou hi cap UNA replica mes. Per a 30 repliques calarien 30 NODES.
Cost: absurd.Amb topologySpreadConstraints:
Regla: maxSkew 1 entre nodes.
Nodes: 9. Repliques: 30.
Repartiment: 30 / 9 = 3,33
Resultat: 3 nodes amb 4 repliques, 6 nodes amb 3 repliques.
Biaix: 4 - 3 = 1. Compleix.
Les 30 repliques es col·loquen. Cap Pending.Aquesta és la raó per la qual topologySpreadConstraints és l'eina correcta per a càrregues amb autoescalat. L'antiafinitat requerida i l'HPA són incompatibles a la pràctica.
Quan continua sent millor l'antiafinitat
| Cas | Per què |
|---|---|
| Components amb poques rèpliques fixes i separació estricta | Etcd, un pla de control: 3 rèpliques, una per node, sense excepció |
| Antiafinitat entre aplicacions diferents | «api-reserves no ha de compartir node amb informes-ocupacio»: això topologySpread no ho expressa |
Regles de coubicació (podAffinity) |
«Posa aquest pod a prop de la memòria cau»: no té equivalent a topologySpread |
Aquest segon cas mereix un exemple, perquè és un ús legítim i freqüent:
# api-reserves NO ha de compartir node amb el CronJob d'informes, que consumeix
# 4 nuclis de cop i estrangularia l'API durant 40 minuts.
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: informes-ocupacio # UNA ALTRA aplicacio, no la mateixa
topologyKey: kubernetes.io/hostnameLa combinació recomanada
Per a api-reserves a Rutas Norte fem servir les dues, cadascuna per al seu:
spec:
template:
spec:
# 1. Repartiment equilibrat entre zones i nodes (disponibilitat)
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: api-reserves
matchLabelKeys: ["pod-template-hash"]
- maxSkew: 2
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: api-reserves
matchLabelKeys: ["pod-template-hash"]
# 2. Antiafinitat amb UNA ALTRA aplicacio (aillament de recursos)
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: informes-ocupacio
topologyKey: kubernetes.io/hostname
- Repartir Rutas Norte entre zones i nodes
Apliquem-ho a la plataforma, amb els càlculs de biaix.
api-reserves: dos nivells de distribució
# k8s/base/deployment-api-reserves.yaml (fragment)
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-reserves
namespace: rutas-norte-pro
spec:
# Sense replicas: ho governa KEDA/HPA (09-01, 09-04)
selector:
matchLabels:
app: api-reserves
template:
metadata:
labels:
app: api-reserves
app.kubernetes.io/part-of: rutas-norte
spec:
topologySpreadConstraints:
# NIVELL 1: entre ZONES DE DISPONIBILITAT. Restriccio DURA.
#
# Aquest es el domini de fallada gran: si cau una zona sencera, cal
# garantir que sobrevisquin repliques a les altres dues. Es una
# garantia que NO estem disposats a negociar, aixi que DoNotSchedule:
# preferim un pod Pending a un repartiment desequilibrat.
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: api-reserves
# 3 zones sempre, encara que una no tingui nodes ara mateix. Sense aixo,
# si el Cluster Autoscaler nomes te nodes en 2 zones, el repartiment
# "equilibrat" entre aquestes 2 compliria la restriccio i perdriem
# la garantia de les 3 zones.
minDomains: 3
matchLabelKeys: ["pod-template-hash"]
# NIVELL 2: entre NODES. Restriccio TOVA.
#
# Repartir entre nodes es desitjable pero NO al preu de deixar pods
# Pending. Durant el pont de maig, amb 30 repliques i el Cluster
# Autoscaler arrencant nodes, un DoNotSchedule aqui bloquejaria
# l'escalat justament quan mes falta fa.
#
# maxSkew 2 (no 1): amb 30 repliques i 9 nodes, exigir maxSkew 1 seria
# innecessariament rigid. Un 4-3-3-4-3-3-4-3-3 es perfectament sa.
- maxSkew: 2
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: api-reserves
matchLabelKeys: ["pod-template-hash"]
containers:
- name: api
image: registry.rutasnorte.example/api-reserves:1.14.2
resources:
requests: {cpu: 412m, memory: 800Mi}
limits: {cpu: "1", memory: 1600Mi}L'asimetria entre els dos nivells és la decisió clau, i mereix explicar-se bé:
| Nivell | whenUnsatisfiable |
Raonament |
|---|---|---|
| Zona | DoNotSchedule |
La caiguda d'una zona és un esdeveniment catastròfic i plausible. Un pod Pending és preferible a perdre la garantia |
| Node | ScheduleAnyway |
La caiguda d'un node és freqüent però d'impacte limitat. Bloquejar l'escalat seria pitjor que un repartiment imperfecte |
El càlcul amb 4 rèpliques (vall)
3 zones, maxSkew 1, minDomains 3.
Repartiment: 4 / 3 = 1,33
Resultat: A:2, B:1, C:1. Biaix = 2-1 = 1. Compleix.
Caiguda de la zona A: queden 2 de 4 (50 %).
Caiguda de la zona B o C: queden 3 de 4 (75 %).
Amb maxUnavailable: 25% al PDB, 4 repliques permeten 1 desallotjament.
La caiguda d'una zona (2 repliques) es INVOLUNTARIA: el PDB no aplica.
Sobreviuen 2 repliques -> el servei continua, degradat.
KEDA/HPA detectara la carrega per replica duplicada i escalara.El càlcul amb 30 rèpliques (pic del pont)
Nivell ZONA (maxSkew 1, DoNotSchedule):
30 / 3 = 10 exactes.
Resultat: A:10, B:10, C:10. Biaix 0. Perfecte.
Nivell NODE (maxSkew 2, ScheduleAnyway), amb 9 nodes (3 per zona):
Dins de cada zona, 10 repliques entre 3 nodes: 4-3-3.
Biaix global entre nodes: 4 - 3 = 1 <= 2. Compleix.
Repartiment final:
zona A: node-a1:4, node-a2:3, node-a3:3
zona B: node-b1:4, node-b2:3, node-b3:3
zona C: node-c1:4, node-c2:3, node-c3:3
Caiguda d'un NODE: es perden 3 o 4 repliques de 30 (10-13 %). Imperceptible.
Caiguda d'una ZONA: es perden 10 de 30 (33 %). El servei aguanta amb 20.botiga-web
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: botiga-web
minDomains: 3
matchLabelKeys: ["pod-template-hash"]
- maxSkew: 1
topologyKey: kubernetes.io/hostname
# maxSkew 1 i ScheduleAnyway: amb nomes 3-15 repliques, un repartiment
# d'1 per node es assolible i desitjable. Pero continuem sense
# bloquejar l'escalat.
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: botiga-web
matchLabelKeys: ["pod-template-hash"]Amb 3 rèpliques i minDomains: 3, el repartiment és exactament una per zona. És el mínim acceptable per a la porta d'entrada de la plataforma.
postgres-reserves: el cas especial
Amb una sola rèplica, topologySpreadConstraints no aporta res: no hi ha res a repartir. El que sí que importa és on viu:
# k8s/base/statefulset-postgres-reserves.yaml (fragment)
spec:
template:
spec:
# Viu al grup de nodes de dades (09-03), amb el seu taint i la seva afinitat.
tolerations:
- key: rol
operator: Equal
value: dades
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: rol
operator: In
values: ["dades"]
# NO compartir node amb redis-cache: si cau aquest node, no volem
# perdre la base de dades I la cache alhora, perque l'arrencada de
# la base de dades amb la cache buida es el pitjor escenari possible.
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: redis-cache
topologyKey: kubernetes.io/hostnameI una consideració crucial que connecta amb el mòdul 5: el volum persistent de postgres-reserves està ancorat a una zona. Un PV de disc de bloc d'un proveïdor de núvol només és accessible des de la seva zona. Això significa que:
- El pod només es pot planificar en nodes d'aquesta zona.
- Si cau la zona, no es pot recuperar en una altra zona sense restaurar d'una còpia de seguretat (05-06).
Això no és un problema de Kubernetes, és una propietat de l'emmagatzematge de blocs. I és la raó fonamental per la qual l'alta disponibilitat d'una base de dades no es resol amb objectes de Kubernetes (apartat 12).
- Alta disponibilitat a les altres capes
Fins ara hem parlat de les aplicacions. Però l'alta disponibilitat és una propietat del sistema complet, i hi ha tres capes més que convé revisar.
El pla de control i el quòrum d'etcd
Recordem 01-02: el pla de control el formen el servidor d'API, el planificador, el gestor de controladors i etcd, la base de dades que guarda tot l'estat del clúster.
etcd fa servir l'algoritme de consens Raft, que necessita majoria estricta per operar. D'aquí surt la regla del nombre senar:
| Membres | Quòrum (majoria) | Fallades tolerades |
|---|---|---|
| 1 | 1 | 0 |
| 2 | 2 | 0 |
| 3 | 2 | 1 |
| 4 | 3 | 1 |
| 5 | 3 | 2 |
| 6 | 4 | 2 |
| 7 | 4 | 3 |
Fixa't que 4 membres toleren les mateixes fallades que 3, i 6 les mateixes que 5. Un nombre parell afegeix cost i latència de consens sense afegir tolerància. D'aquí que la configuració estàndard sigui 3 o 5 membres, mai parell.
Què passa en perdre el quòrum:
Cluster de 3 membres d'etcd. En cauen 2.
Quorum necessari: 2. Membres vius: 1. NO HI HA QUORUM.
Consequencies:
- etcd passa a NOMES LECTURA. No accepta escriptures.
- El servidor d'API no pot persistir canvis.
- kubectl apply falla. No es poden crear, modificar ni esborrar objectes.
- L'HPA no pot canviar repliques.
- El Cluster Autoscaler no pot fer res.
PERO, i aixo es l'important:
- Els pods que JA estaven corrent CONTINUEN CORRENT.
- Els kubelets continuen executant els seus contenidors.
- Els Services continuen enrutant (kube-proxy te el seu estat local).
- LA PLATAFORMA CONTINUA ATENENT TRANSIT.El pla de control caigut no tomba les aplicacions en marxa. És una propietat de disseny molt valuosa de Kubernetes: el pla de dades sobreviu al pla de control. El que es perd és la capacitat de canviar coses: escalar, desplegar, recuperar-se d'una fallada de pod.
Distribució recomanada del pla de control:
3 nodes de pla de control, un per zona de disponibilitat:
zona A: control-1 (etcd, api-server, scheduler, controller-manager)
zona B: control-2
zona C: control-3
Caiguda d'una zona: queden 2 de 3. Quorum = 2. ES MANTE.A Kubernetes gestionat (10-06) això ho fa el proveïdor i no ho veus. És un dels arguments més forts a favor d'un clúster gestionat: l'alta disponibilitat del pla de control és difícil de fer bé i no aporta valor diferencial.
El controlador d'Ingress
El controlador d'Ingress (04-04) és la porta d'entrada de tot el trànsit extern. Si cau, la plataforma és inabastable encara que tots els pods estiguin sans.
# k8s/entorns/pro/deployment-ingress-nginx.yaml (fragment)
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
replicas: 4 # Mai 1. Mai 2 al mateix domini de fallada.
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
minDomains: 3
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule # Aqui SI que es dur
labelSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
minAvailable: 2 # Numero absolut: les repliques de l'Ingress son FIXES
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
unhealthyPodEvictionPolicy: AlwaysAllowDues decisions que s'aparten de l'anterior:
DoNotScheduletambé entre nodes. L'Ingress no té HPA (les seves rèpliques són fixes), així que no hi ha risc de bloquejar un escalat. I dues rèpliques de l'Ingress al mateix node són dues rèpliques que es perden juntes.minAvailable: 2en números absoluts. Amb rèpliques fixes, el número absolut és més explícit i més fàcil de raonar que un percentatge.
CoreDNS
CoreDNS (04-03) resol tots els noms del clúster. Si falla, api-reserves no troba postgres-reserves, worker-notificacions no troba RabbitMQ, i tot cau amb errors de DNS que despisten moltíssim.
És un component que gairebé ningú revisa i que és un punt únic de fallada silenciosa.
# Fragment del Deployment de CoreDNS a kube-system
spec:
replicas: 3
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
k8s-app: kube-dns
priorityClassName: system-cluster-criticalI el seu PDB:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: coredns
namespace: kube-system
spec:
maxUnavailable: 1
selector:
matchLabels:
k8s-app: kube-dns
unhealthyPodEvictionPolicy: AlwaysAllowUn consell addicional molt rendible: NodeLocal DNSCache, un DaemonSet que posa una memòria cau de DNS a cada node. Redueix la latència de resolució, descarrega CoreDNS, i fa que una fallada temporal de CoreDNS no afecti les consultes cachejades. Hi tornarem a 09-06 en parlar del ndots: 5.
postgres-reserves: la disponibilitat la dona l'operador
I arribem al cas més important, el que resumeix la lliçó de tot el mòdul sobre les càrregues amb estat.
Kubernetes no pot fer que PostgreSQL sigui d'alta disponibilitat. El que Kubernetes ofereix:
- Reiniciar el pod si el procés mor.
- Reprogramar-lo en un altre node si el node falla (si el volum és accessible).
- Un PDB que impedeix desallotjaments automàtics.
El que Kubernetes no ofereix:
| Capacitat necessària | Per què Kubernetes no la dona |
|---|---|
| Replicació de dades entre instàncies | És un protocol de PostgreSQL, no de Kubernetes |
| Promoció automàtica d'una rèplica a primària | Requereix conèixer l'estat de la replicació |
| Redirigir les escriptures a la nova primària | Requereix canviar el Service en el moment just |
| Evitar el «cervell dividit» (dues primàries) | Requereix una lògica de consens específica |
| Garantir que no es perden transaccions confirmades | Requereix entendre el WAL de PostgreSQL |
Tot això ho aporta un operador (06-07): CloudNativePG, Zalando Postgres Operator, Crunchy PGO. Un operador de PostgreSQL:
flowchart TD
OP[Operador de PostgreSQL<br/>controlador amb coneixement del domini]
P[(postgres-primari<br/>zona A<br/>ESCRIPTURES)]
R1[(postgres-replica-1<br/>zona B<br/>lectures)]
R2[(postgres-replica-2<br/>zona C<br/>lectures)]
SVCW[Service: postgres-rw<br/>apunta al PRIMARI]
SVCR[Service: postgres-ro<br/>apunta a les REPLIQUES]
OP -->|vigila salut i replicacio| P
OP -->|vigila| R1
OP -->|vigila| R2
P -->|replicacio en streaming| R1
P -->|replicacio en streaming| R2
OP -.->|si el primari cau:<br/>PROMOCIONA i reapunta| SVCW
SVCW --> P
SVCR --> R1
SVCR --> R2
style OP fill:#cde,stroke:#369,stroke-width:2px
style P fill:#cfc,stroke:#393
Amb un operador, el model canvia per complet:
# Exemple amb CloudNativePG
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgres-reserves
namespace: rutas-norte-pro
spec:
instances: 3 # 1 primaria + 2 repliques
primaryUpdateStrategy: unsupervised
storage:
size: 100Gi
storageClass: rutasnorte-rapida # De 05-04
# L'operador reparteix les instancies entre zones automaticament
affinity:
enablePodAntiAffinity: true
topologyKey: topology.kubernetes.io/zone
podAntiAffinityType: required
postgresql:
parameters:
max_connections: "200"
shared_buffers: "1GB"
monitoring:
enablePodMonitor: true # S'integra amb Prometheus (07-03)I l'operador crea, manté i gestiona:
- Tres StatefulSets amb els seus PVC en tres zones.
- Els Services
postgres-reserves-rw(primari) ipostgres-reserves-ro(rèpliques). - La replicació en streaming entre instàncies.
- La commutació automàtica: si el primari cau, promociona una rèplica i reapunta el Service
-rwen uns segons. - Els seus propis PDB, correctament configurats per al seu model de quòrum.
- Còpies de seguretat contínues al magatzem d'objectes.
La lliçó: per a les càrregues amb estat, l'alta disponibilitat es delega en un operador que entengui el sistema. Els PDB i topologySpreadConstraints són eines genèriques de Kubernetes; un operador aporta el coneixement específic del domini que cap eina genèrica no pot tenir.
Aplicar això a Rutas Norte és la feina pendent que s'aborda al mòdul 11 (11-02).
- El procediment de manteniment d'un node
Reunim-ho tot en el procediment operatiu complet. Això és el que s'executa el dimarts al matí.
El flux
flowchart TD
A[1. Verificar l'estat previ<br/>PDB, repliques, alertes] --> B{Tots els PDB<br/>permeten desallotjaments?}
B -->|No| C[Corregir els PDB impossibles<br/>o planificar intervencio manual]
C --> A
B -->|Si| D[2. cordon: deixar d'acceptar pods nous]
D --> E[3. drain: desallotjar els pods existents<br/>respectant els PDB]
E --> F{S'ha completat?}
F -->|No, bloquejat| G[Diagnosticar: quin PDB ho impedeix?]
G --> H[Escalar repliques o corregir]
H --> E
F -->|Si| I[4. Verificar: node buit<br/>i servei sa]
I --> J[5. MANTENIMENT<br/>actualitzar kernel, kubelet, reiniciar]
J --> K[6. Verificar que el node torna Ready]
K --> L[7. uncordon: tornar a acceptar pods]
L --> M[8. Verificar la distribucio<br/>i descordonar el seguent]
style D fill:#ffd,stroke:#c90
style E fill:#ffd,stroke:#c90
style L fill:#cfc,stroke:#393
Pas 1: verificació prèvia
NAMESPACE NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS
rutas-norte-pro api-reserves N/A 25% 1
rutas-norte-pro botiga-web N/A 34% 1
rutas-norte-pro postgres-reserves 1 N/A 0 <- ATENCIO
rutas-norte-pro redis-cache 1 N/A 0 <- ATENCIO
rutas-norte-pro worker-notificacions N/A 50% 1
ingress-nginx ingress-nginx 2 N/A 2
kube-system coredns N/A 1 1Els dos zeros són els deliberats de l'apartat 8. Cal saber en quins nodes són:
NAME READY STATUS NODE ZONA
postgres-reserves-0 2/2 Running node-dades-1 eu-west-1a
redis-cache-0 1/1 Running node-dades-2 eu-west-1bAquests dos nodes requereixen un procediment diferent, amb finestra de manteniment planificada. Els altres es poden drenar sense més.
# Que hi ha al node que drenarem?
kubectl get pods --all-namespaces -o wide --field-selector spec.nodeName=node-a2NAMESPACE NAME READY STATUS AGE
rutas-norte-pro api-reserves-7c9d4f8b6d-2mkjp 2/2 Running 4h
rutas-norte-pro api-reserves-7c9d4f8b6d-9nvtr 2/2 Running 4h
rutas-norte-pro botiga-web-6f7d9c4b58-4kjnx 1/1 Running 2d
kube-system fluentd-x7kqp 1/1 Running 12d <- DaemonSet
kube-system falco-2wxkp 1/1 Running 12d <- DaemonSet
kube-system kube-proxy-mn2vp 1/1 Running 12d <- DaemonSetTres pods d'aplicació i tres DaemonSets. Els DaemonSets no es drenen (un per node per definició).
# Hi ha alguna alerta activa? No es fa manteniment amb incidents oberts.
kubectl get events --all-namespaces --field-selector type=Warning \
--sort-by=.lastTimestamp | tail -20Pas 2: cordon
Què fa exactament: posa spec.unschedulable: true al node i li afegeix el taint node.kubernetes.io/unschedulable:NoSchedule.
NAME STATUS ROLES AGE VERSION
node-a1 Ready <none> 12d v1.30.2
node-a2 Ready,SchedulingDisabled <none> 12d v1.30.2
node-a3 Ready <none> 12d v1.30.2cordon NO mou res. Els pods que ja hi són continuen corrent. Només impedeix que n'arribin de nous. És una operació segura i reversible a l'instant.
Pas 3: drain
kubectl drain node-a2 \
--ignore-daemonsets \
--delete-emptydir-data \
--grace-period=60 \
--timeout=300sLes opcions, una a una:
| Opció | Què fa | És necessària? |
|---|---|---|
--ignore-daemonsets |
No intenta desallotjar pods de DaemonSet | Sí, sempre. Sense ella drain falla |
--delete-emptydir-data |
Accepta perdre el contingut dels emptyDir |
Sí si hi ha pods amb emptyDir (memòria cau de nginx) |
--grace-period=60 |
Segons perquè el pod acabi netament | Recomanat: dona temps a l'apagada ordenada |
--timeout=300s |
Abandona després de 5 minuts | Molt recomanable. Evita esperar per sempre |
--force |
Esborra també pods sense controlador | Perillós: aquests pods no tornen |
--disable-eviction |
Ignora els PDB fent servir esborrat directe | Només emergències |
--pod-selector |
Drena només els pods que coincideixin | Útil per a drenatges parcials |
--dry-run=client |
Mostra què faria sense fer-ho | Excel·lent per verificar abans |
Sortida esperada:
node/node-a2 already cordoned
Warning: ignoring DaemonSet-managed Pods: kube-system/fluentd-x7kqp,
kube-system/falco-2wxkp, kube-system/kube-proxy-mn2vp
evicting pod rutas-norte-pro/api-reserves-7c9d4f8b6d-2mkjp
evicting pod rutas-norte-pro/botiga-web-6f7d9c4b58-4kjnx
evicting pod rutas-norte-pro/api-reserves-7c9d4f8b6d-9nvtr
error when evicting pods/"api-reserves-7c9d4f8b6d-9nvtr" -n "rutas-norte-pro"
(will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
pod/botiga-web-6f7d9c4b58-4kjnx evicted
pod/api-reserves-7c9d4f8b6d-2mkjp evicted
evicting pod rutas-norte-pro/api-reserves-7c9d4f8b6d-9nvtr
pod/api-reserves-7c9d4f8b6d-9nvtr evicted
node/node-a2 drainedFixa't en el segon pod d'api-reserves: el primer intent va fallar amb el missatge del PDB, va esperar 5 segons, i en reintentar va tenir èxit. Què va passar en aquests 5 segons? El primer pod desallotjat es va recrear en un altre node i va passar a Ready, alliberant pressupost d'interrupció.
Aquest és el PDB funcionant exactament com ha de: no ha impedit el manteniment, l'ha serialitzat perquè mai hi hagi més d'un pod caigut alhora.
Pas 4: verificar
# El node ha d'estar buit de pods d'aplicacio
kubectl get pods --all-namespaces -o wide --field-selector spec.nodeName=node-a2NAMESPACE NAME READY STATUS AGE
kube-system fluentd-x7kqp 1/1 Running 12d
kube-system falco-2wxkp 1/1 Running 12d
kube-system kube-proxy-mn2vp 1/1 Running 12dNomés DaemonSets. Correcte.
# I el SERVEI ha d'estar sa: aixo es el que de debo importa
kubectl get pods -n rutas-norte-pro -l app=api-reserves
kubectl get pdb -n rutas-norte-proComprovació a Grafana (07-04): latència p95 estable, taxa d'error a zero. Si el drenatge ha degradat el servei, no continuïs amb el següent node.
Pas 5: el manteniment
# Exemple: actualitzar el kubelet en un cluster amb kubeadm (10-02)
ssh node-a2
sudo apt-get update
sudo apt-get install -y kubeadm=1.30.3-1.1
sudo kubeadm upgrade node
sudo apt-get install -y kubelet=1.30.3-1.1 kubectl=1.30.3-1.1
sudo systemctl daemon-reload
sudo systemctl restart kubelet
exitPas 6: verificar el node
Ready amb la versió nova. Continua acordonat, que és el correcte.
Pas 7: uncordon
No oblidis mai aquest pas. Un node que queda acordonat és capacitat pagada i invisible: el Cluster Autoscaler pot arrencar nodes nous mentre aquest està buit i disponible però rebutjant pods.
Un detall que sorprèn: uncordon no reequilibra els pods existents. El node està disponible, però els pods que es van moure no tornen. Kubernetes no reprograma pods en marxa. La redistribució passa de forma natural amb el següent escalat o desplegament.
Si vols forçar el reequilibrat, l'eina és el descheduler, un component que desallotja pods que violen les polítiques de distribució perquè es replanifiquin millor. Respecta els PDB, naturalment.
Pas 8: el següent node
# Verificar la distribucio abans de continuar
kubectl get pods -n rutas-norte-pro -l app=api-reserves -o wide \
| awk '{print $7}' | sort | uniq -cDesprés del drenatge, node-a2 està buit i els seus pods s'han repartit. La distribució continua sent raonable entre zones (3 a A, 3 a B... encara que falta C: caldria revisar si minDomains s'està complint).
Regla d'or: un node cada vegada, amb verificació entre cadascun. Drenar dos nodes en paral·lel pot violar els PDB de formes que no vas anticipar.
Què es trenca si no hi ha PDB
Val la pena fer l'exercici mental. Sense cap PDB:
node/node-a2 cordoned
evicting pod rutas-norte-pro/api-reserves-7c9d4f8b6d-2mkjp
evicting pod rutas-norte-pro/api-reserves-7c9d4f8b6d-9nvtr
evicting pod rutas-norte-pro/botiga-web-6f7d9c4b58-4kjnx
pod/api-reserves-7c9d4f8b6d-2mkjp evicted
pod/api-reserves-7c9d4f8b6d-9nvtr evicted
pod/botiga-web-6f7d9c4b58-4kjnx evicted
node/node-a2 drainedInstantani. Els tres pods desallotjats alhora. I en aquest instant:
api-reserves tenia 4 repliques: 2 a node-a2, 1 a node-a1, 1 a node-b1.
Despres del drenatge: 2 repliques vives, 2 arrencant (25-30 segons).
Durant 30 segons: LA MEITAT de la capacitat de l'API.
Cada replica viva rep el DOBLE de carrega.
Latencia p95: de 180 ms a 900 ms.
Algunes peticions: timeout.
I si haguessin estat 3 de les 4 repliques en aquest node: 25 % de capacitat.
La replica supervivent se satura i cau. CAIGUDA TOTAL.El PDB converteix aquest desastre en un desallotjament serialitzat de 90 segons sense impacte perceptible. És la diferència entre un manteniment i un incident.
- Interacció amb el Cluster Autoscaler i els desplegaments
Dues interaccions que convé tenir presents.
Amb el Cluster Autoscaler (09-03)
El CA fa servir l'API de desallotjament, així que respecta els PDB. Conseqüències:
Conseqüència 1: un PDB impossible ancora el node permanentment. Ja ho vam veure a 09-03: els logs diuen pdb-blocked i el node no es retira mai. Amb postgres-reserves és intencionat; amb un PDB mal configurat, són diners llençats.
Conseqüència 2: la reducció és més lenta amb PDB. El CA ha de desallotjar els pods d'un en un, esperant que es recreïn. Un node amb 8 pods d'api-reserves i maxUnavailable: 25% amb 12 rèpliques totals triga uns quants minuts a buidar-se. És correcte i desitjable, però cal saber-ho per no pensar que el CA està trencat.
Conseqüència 3: cal revisar els PDB quan canvien els maxReplicas. Si puges maxReplicas d'api-reserves de 30 a 60, el maxUnavailable: 25% permetrà 15 desallotjaments simultanis al pic. És acceptable perdre 15 rèpliques alhora? Probablement sí, però és una decisió que mereix pensar-se.
I una interacció especialment subtil: el coixí de sobreaprovisionament no ha de portar PDB. Els pods de farciment de 09-03 hi són per ser desallotjats; un PDB els protegiria i trencaria tot el mecanisme. Si de cas, un PDB explícitament permissiu:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: farciment-capacitat
namespace: rutas-norte-pro
spec:
# 100 % d'indisponibilitat permesa: aquests pods hi son PER ser desallotjats.
# Documenta la intencio millor que l'absencia de PDB.
maxUnavailable: 100%
selector:
matchLabels:
app: farciment-capacitatAmb els desplegaments (02-04)
El controlador de Deployments no fa servir l'API de desallotjament durant una actualització progressiva: esborra pods directament i aplica el seu propi maxUnavailable. Els PDB no intervenen.
Això significa que necessites configurar els dos coherentment:
# Al Deployment: governa l'ACTUALITZACIO
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25% # Coherent amb el PDB
maxSurge: 25%
---
# Al PDB: governa els DESALLOTJAMENTS (drain, CA, VPA)
spec:
maxUnavailable: 25%Si el Deployment permet el 50 % d'indisponibilitat i el PDB només el 25 %, tindràs una incoherència: durant un desplegament el servei baixarà al 50 %, però un drenatge només el podrà baixar al 75 %. No és un error, però és una inconsistència de criteri que convé resoldre.
Recomanació: fes servir el mateix valor en tots dos, perquè el nivell de servei garantit sigui el mateix passi el que passi.
I un escenari a evitar: un desplegament i un manteniment alhora. Durant una actualització, la meitat dels pods estan arrencant i no compten com a disponibles. Si a més drenes un node, el PDB bloquejarà el drenatge (correctament) i el manteniment s'encallarà. Mai facis manteniment durant un desplegament, i viceversa.
- Una prova de caos senzilla
Tot l'anterior és teoria fins que es comprova. Matarem un node i veurem què passa.
Preparació a minikube
minikube start -p rutas-norte --nodes=4 --cpus=2 --memory=4096
# Etiquetar els nodes amb zones ficticies
kubectl label node rutas-norte topology.kubernetes.io/zone=zona-a
kubectl label node rutas-norte-m02 topology.kubernetes.io/zone=zona-a
kubectl label node rutas-norte-m03 topology.kubernetes.io/zone=zona-b
kubectl label node rutas-norte-m04 topology.kubernetes.io/zone=zona-bDesplegament amb distribució i PDB:
# /tmp/caos-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: caos-api
namespace: rutas-norte-dev
spec:
replicas: 6
selector:
matchLabels:
app: caos-api
template:
metadata:
labels:
app: caos-api
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: caos-api
matchLabelKeys: ["pod-template-hash"]
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: caos-api
matchLabelKeys: ["pod-template-hash"]
containers:
- name: web
image: nginx:1.27-alpine
resources:
requests: {cpu: 50m, memory: 32Mi}
readinessProbe:
httpGet: {path: /, port: 80}
initialDelaySeconds: 2
periodSeconds: 3
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: caos-api
namespace: rutas-norte-dev
spec:
maxUnavailable: 25%
selector:
matchLabels:
app: caos-api
unhealthyPodEvictionPolicy: AlwaysAllow
---
apiVersion: v1
kind: Service
metadata:
name: caos-api
namespace: rutas-norte-dev
spec:
selector:
app: caos-api
ports:
- port: 80NAME READY STATUS NODE ZONA
caos-api-7d9c5f8b6b-2mkjp 1/1 Running rutas-norte zona-a
caos-api-7d9c5f8b6b-4nqzx 1/1 Running rutas-norte-m02 zona-a
caos-api-7d9c5f8b6b-7wtrv 1/1 Running rutas-norte-m02 zona-a
caos-api-7d9c5f8b6b-9hbcx 1/1 Running rutas-norte-m03 zona-b
caos-api-7d9c5f8b6b-kp3ln 1/1 Running rutas-norte-m04 zona-b
caos-api-7d9c5f8b6b-tv6ws 1/1 Running rutas-norte-m03 zona-bDistribució perfecta: 3 a zona-a, 3 a zona-b. El maxSkew: 1 entre nodes també es compleix: 1-2-2-1.
Experiment 1: el manteniment planificat (voluntari)
# Terminal 1: generar transit continu i comptar errors
kubectl -n rutas-norte-dev run client --rm -it --restart=Never \
--image=busybox:1.36 -- /bin/sh -c \
'ok=0; err=0; while true; do
if wget -q -T2 -O- http://caos-api > /dev/null 2>&1; then
ok=$((ok+1)); else err=$((err+1)); fi;
echo "OK=$ok ERR=$err";
sleep 0.2;
done'Resultat esperat al terminal 1:
Zero errors. El PDB va serialitzar el desallotjament dels 2 pods d'aquest node, i el Service (04-02) va treure dels seus endpoints cada pod tan bon punt va deixar d'estar Ready.
Això és el que es busca: el manteniment és invisible per a l'usuari.
Experiment 2: la caiguda d'un node (involuntària)
# Simular una fallada de maquinari: apagar el node bruscament
minikube -p rutas-norte node stop rutas-norte-m03Observem la seqüència completa:
NAME STATUS ROLES AGE
rutas-norte-m03 Ready <none> 1h
rutas-norte-m03 NotReady <none> 1h <- als ~40 segonsNAME READY STATUS NODE
caos-api-7d9c5f8b6b-9hbcx 1/1 Running rutas-norte-m03
caos-api-7d9c5f8b6b-tv6ws 1/1 Running rutas-norte-m03
...
(passen ~5 minuts)
...
caos-api-7d9c5f8b6b-9hbcx 1/1 Terminating rutas-norte-m03
caos-api-7d9c5f8b6b-tv6ws 1/1 Terminating rutas-norte-m03
caos-api-7d9c5f8b6b-x2klm 0/1 Pending <none>
caos-api-7d9c5f8b6b-p9wnz 0/1 Pending <none>
caos-api-7d9c5f8b6b-x2klm 0/1 ContainerCreating rutas-norte-m04
caos-api-7d9c5f8b6b-p9wnz 1/1 Running rutas-norte-m04Els temps són l'ensenyament de l'experiment:
| Moment | Esdeveniment | Temps acumulat |
|---|---|---|
| t=0 | El node s'apaga | 0 |
| t=40 s | El pla de control el marca NotReady (node-monitor-grace-period) |
40 s |
| t=40 s | El Service treu els seus pods dels endpoints. El trànsit deixa d'anar-hi | 40 s |
| t=5 min | Els taints NoExecute desallotgen els pods (tolerationSeconds: 300) |
5 min |
| t=5 min | El ReplicaSet crea els pods substituts | 5 min |
| t=5 min 20 s | Els nous pods estan Ready |
5 min 20 s |
El punt crític són aquests primers 40 segons: el node és mort però Kubernetes encara no ho sap, i el Service continua enviant-li trànsit. Durant aquests 40 segons, un terç de les peticions fallen.
Al terminal 1 veuries una cosa així:
OK=1204 ERR=0
OK=1210 ERR=3 <- comencen els errors
OK=1218 ERR=11
...
OK=1301 ERR=68 <- ~40 segons d'errors
OK=1409 ERR=68 <- s'aturen: el Service ja va excloure el node mort
OK=1520 ERR=68Aquesta és la diferència fonamental entre les interrupcions voluntàries i les involuntàries, mesurada amb un cronòmetre:
Voluntària (drain) |
Involuntària (node mort) | |
|---|---|---|
| El Service exclou els pods | Immediatament (la readiness falla en rebre SIGTERM) | Als ~40 segons |
| Errors per a l'usuari | Zero | ~40 segons d'errors |
| Protecció possible | PDB | Cap; només limitar el dany repartint |
Què fer amb aquests 40 segons
No es poden eliminar, però es poden mitigar:
| Mesura | Efecte |
|---|---|
Repartir bé les rèpliques (topologySpreadConstraints) |
Amb 2 de 6 rèpliques al node mort, només falla el 33 % de les peticions, no el 100 % |
| Reintents al client | botiga-web reintenta amb un altre pod; l'usuari no veu l'error |
| Malla de serveis (08-04) | Detecció de fallades i reintents automàtics, amb expulsió d'instàncies que fallen |
Baixar node-monitor-grace-period |
Detecta abans... però produeix falsos positius amb latència de xarxa |
L'última opció és temptadora i gairebé sempre mala idea: baixar el llindar de detecció fa que una latència temporal de xarxa s'interpreti com un node caigut, i desallotja pods sans. Els 40 segons per defecte són un compromís ben triat.
Neteja
Escalant la prova de caos
Aquest experiment manual és el nivell més bàsic. En un entorn seriós, s'automatitza amb eines d'enginyeria del caos (Chaos Mesh, LitmusChaos) que permeten:
- Matar pods aleatoris de forma contínua.
- Injectar latència de xarxa entre serveis.
- Simular la caiguda d'una zona completa.
- Omplir el disc d'un node.
- Executar-ho periòdicament i alertar si el sistema no aguanta.
Però l'experiment manual és on cal començar, perquè és el que ensenya els temps reals. Fes-ho una vegada a rutas-norte-pre abans del pont de maig i sabràs exactament què esperar.
Errors Comuns i Consells
Error 1: PDB amb minAvailable absolut sobre una càrrega amb HPA. L'error més freqüent d'aquest mòdul. L'HPA baixa les rèpliques a la vall, el minAvailable es torna inassolible, i el PDB passa a bloquejar tot el manteniment i tota la reducció de nodes. Amb autoescalat, sempre percentatges.
Error 2: creure que un PDB protegeix de la caiguda d'un node. No ho fa. Els PDB només intervenen en desallotjaments que passen per l'API. Un node que s'apaga no demana permís. La protecció contra fallades és la distribució topològica.
Error 3: podAntiAffinity requerida sobre hostname en una càrrega amb HPA. Limita les rèpliques al nombre de nodes. L'HPA en demanarà 30, se'n col·locaran 9, i 21 quedaran Pending per sempre mentre el Cluster Autoscaler arrenca nodes que només caben d'un en un. Fes servir topologySpreadConstraints.
Error 4: DoNotSchedule entre nodes en una càrrega amb autoescalat. Durant el pont de maig, amb el CA arrencant nodes, una restricció dura entre nodes bloqueja l'escalat justament quan més falta fa. DoNotSchedule per a zones, ScheduleAnyway per a nodes.
Error 5: oblidar matchLabelKeys: ["pod-template-hash"]. Sense ell, el càlcul del biaix barreja les versions vella i nova durant un desplegament, i l'actualització s'encalla o es distribueix malament. Inclou-lo sempre en Deployments.
Error 6: oblidar l'uncordon. Un node acordonat i oblidat és capacitat pagada i inutilitzable. El símptoma —pods Pending mentre hi ha nodes aparentment sans— és especialment desconcertant. Posa-ho a la llista de comprovació del manteniment.
Error 7: --disable-eviction per «desencallar» un drenatge. Salta els PDB fent servir esborrat directe. Pot deixar un servei sense cap rèplica. Si un drenatge està bloquejat, la resposta correcta és diagnosticar per què, no desactivar la protecció.
Error 8: no posar PDB als components d'infraestructura. CoreDNS, el controlador d'Ingress i Prometheus són tan crítics com l'aplicació. Un drenatge que s'endugui les dues rèpliques de CoreDNS tomba la resolució de noms de tot el clúster, amb símptomes que ningú relaciona amb el manteniment.
Consell 1: revisa ALLOWED DISRUPTIONS abans de cada manteniment. Un kubectl get pdb --all-namespaces de dos segons et diu si el manteniment fluirà o s'encallarà. És la comprovació prèvia més rendible que existeix.
Consell 2: anota els PDB deliberadament bloquejants. postgres-reserves i redis-cache tenen ALLOWED DISRUPTIONS: 0 a propòsit. Una anotació que ho expliqui evita que algú els «arregli» sense entendre per què estan així, i permet excloure'ls de les alertes.
Consell 3: alerta sobre PDB bloquejats no deliberats.
# PDB sense desallotjaments permesos, excloent els intencionats
kube_poddisruptionbudget_status_pod_disruptions_allowed == 0
unless on(namespace, poddisruptionbudget)
kube_poddisruptionbudget_annotations{annotation_rutasnorte_example_pdb_bloqueig_intencionat!=""}Consell 4: verifica la distribució real, no la configurada.
# Repliques per zona
kubectl get pods -n rutas-norte-pro -l app=api-reserves \
-o custom-columns=NODE:.spec.nodeName --no-headers | \
while read n; do kubectl get node "$n" -o jsonpath='{.metadata.labels.topology\.kubernetes\.io/zone}{"\n"}'; done | \
sort | uniq -cUna restricció ben escrita que no es compleix (perquè no hi ha nodes en una zona, per exemple) és pitjor que cap: dona falsa sensació de seguretat.
Consell 5: assaja el manteniment a rutas-norte-pre. Drena un node de preproducció amb trànsit de prova i mesura l'impacte. Si a pre hi ha errors, a pro n'hi haurà de multiplicats.
Consell 6: fes la prova de caos almenys una vegada. Matar un node i cronometrar la recuperació ensenya més que qualsevol documentació. Els 40 segons de detecció són un número que cal haver vist amb els propis ulls.
Consell 7: coordina el maxUnavailable del Deployment amb el del PDB. Fes servir el mateix valor perquè el nivell de servei garantit sigui el mateix durant un desplegament i durant un manteniment.
Exercicis
Exercici 1: diagnosticar un manteniment bloquejat
És dimarts al matí i cal actualitzar els nodes. El drenatge del primer node porta 20 minuts encallat:
node/node-b1 already cordoned
Warning: ignoring DaemonSet-managed Pods: kube-system/fluentd-2wxkp, kube-system/falco-x7kqp
evicting pod monitoratge/prometheus-server-0
evicting pod rutas-norte-pro/api-reserves-7c9d4f8b6d-mn2vp
evicting pod rutas-norte-pro/worker-notificacions-5f8d9c-4kjnx
pod/worker-notificacions-5f8d9c-4kjnx evicted
error when evicting pods/"prometheus-server-0" -n "monitoratge"
(will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
error when evicting pods/"api-reserves-7c9d4f8b6d-mn2vp" -n "rutas-norte-pro"
(will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.Estat dels PDB:
NAMESPACE NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS
rutas-norte-pro api-reserves 8 N/A 0
rutas-norte-pro botiga-web N/A 34% 1
rutas-norte-pro worker-notificacions N/A 50% 1
monitoratge prometheus 1 N/A 0Estat dels pods:
NAMESPACE NAME READY STATUS NODE
rutas-norte-pro api-reserves-7c9d4f8b6d-2mkjp 2/2 Running node-a1
rutas-norte-pro api-reserves-7c9d4f8b6d-9nvtr 2/2 Running node-a2
rutas-norte-pro api-reserves-7c9d4f8b6d-mn2vp 2/2 Running node-b1
rutas-norte-pro api-reserves-7c9d4f8b6d-tv6ws 2/2 Running node-b2
rutas-norte-pro api-reserves-7c9d4f8b6d-x2klm 1/2 Running node-a1
monitoratge prometheus-server-0 1/1 Running node-b1Analitza els dos bloquejos per separat. Per a cadascun: és legítim o és un error? Quina és la causa exacta? Com el desbloqueges ara i com evites que torni a passar? Presta especial atenció al pod x2klm.
Exercici 2: calcular la distribució i l'impacte
api-reserves té aquesta configuració:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
minDomains: 3
labelSelector:
matchLabels:
app: api-reserves
- maxSkew: 2
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: api-reservesEl clúster té 9 nodes: 3 a eu-west-1a, 3 a eu-west-1b, 3 a eu-west-1c.
Calcula per a 11 rèpliques i per a 26 rèpliques:
(a) El repartiment entre zones i el biaix resultant. (b) El repartiment entre nodes dins de cada zona. (c) Quantes rèpliques es perden si cau un node i quin percentatge de capacitat queda. (d) Quantes rèpliques es perden si cau una zona sencera i quin percentatge queda. (e) Quants desallotjaments simultanis permet el PDB en cada cas. (f) Si en drenar un node amb 26 rèpliques desplegades es pot buidar aquest node d'una sola vegada, o el PDB ho serialitza.
A més: què passaria amb 2 rèpliques i minDomains: 3? I amb 2 rèpliques si una zona es queda sense nodes disponibles?
Exercici 3: dissenyar l'alta disponibilitat d'un component nou
Rutas Norte incorpora passarela-pagaments, el component que parla amb el banc per cobrar les reserves. Perfil:
- És un servei HTTP sense estat, al camí crític: si falla, no es pot comprar.
- Cada transacció triga entre 2 i 8 segons (esperant el banc), i no es pot interrompre a mitges: una transacció tallada pot deixar un cobrament sense reserva associada.
- El banc limita a 50 connexions simultànies des de la IP de Rutas Norte.
- Trànsit: 5-40 transaccions per segon un dia normal; fins a 300 al pic del pont de maig.
- Arrencada: 20 segons (cal establir la sessió TLS mútua amb el banc i validar certificats).
- L'equip de compliment exigeix que cada transacció quedi registrada abans de respondre a l'usuari.
- Ha de funcionar encara que caigui una zona de disponibilitat completa.
Dissenya l'estratègia completa d'alta disponibilitat: nombre de rèpliques, PDB, topologySpreadConstraints, terminationGracePeriodSeconds, i qualsevol altre mecanisme que consideris necessari. Justifica cada decisió i escriu els manifests. Presta atenció especial al límit de 50 connexions del banc i a l'exigència de no interrompre transaccions.
Solucions
Solució 1
Hi ha tres troballes, no dues.
Bloqueig 1: api-reserves amb minAvailable: 8 — ERROR de configuració.
Repliques totals d'api-reserves: 5
- 4 amb READY 2/2 (sanes)
- 1 amb READY 1/2 (el pod x2klm: un contenidor no esta llest)
Repliques DISPONIBLES (Ready): 4
El PDB exigeix: minAvailable: 8
4 < 8 -> JA estem per sota del minim.
ALLOWED DISRUPTIONS: 0. I continuara sent 0 passi el que passi.És el cas de llibre del PDB impossible amb HPA: algú va posar minAvailable: 8 quan hi havia 12 rèpliques durant una punta, i ara l'HPA ha baixat a 5. El PDB no es va ajustar perquè és un número absolut.
Tercera troballa: el pod x2klm està en 1/2.
Aquest és el detall que cal caçar. api-reserves té dos contenidors (l'API i l'exportador de mètriques). 1/2 significa que un d'ells no està Ready.
kubectl describe pod api-reserves-7c9d4f8b6d-x2klm -n rutas-norte-pro
kubectl logs api-reserves-7c9d4f8b6d-x2klm -n rutas-norte-pro -c exportador-metriquesUn pod que no està completament Ready no compta com a disponible per al PDB. Així que encara que corregíssim el PDB, aquest pod és un problema a part que cal investigar: pot ser el símptoma d'alguna cosa més greu.
Desbloqueig immediat:
kubectl patch pdb api-reserves -n rutas-norte-pro --type merge \
-p '{"spec":{"minAvailable":null,"maxUnavailable":"25%"}}'Efecte immediat:
Repliques desitjades: 5
maxUnavailable: 25% -> terra(5 x 0,25) = 1
Minim disponible: 5 - 1 = 4
Disponibles actuals: 4
ALLOWED DISRUPTIONS: 0
...Continua sent 0, per culpa del pod x2klm que no esta Ready.El pedaç sol no basta. Cal resoldre també el pod trencat. Dues vies:
# Via A: si el pod esta trencat sense remei, esborrar-lo perque es recrei
kubectl delete pod api-reserves-7c9d4f8b6d-x2klm -n rutas-norte-pro
# (kubectl delete NO respecta PDB: aqui ens va be)
# Via B: afegir unhealthyPodEvictionPolicy perque els pods no sans
# no consumeixin pressupost
kubectl patch pdb api-reserves -n rutas-norte-pro --type merge \
-p '{"spec":{"unhealthyPodEvictionPolicy":"AlwaysAllow"}}'La via B és la correcta a llarg termini, i és exactament el problema que resol unhealthyPodEvictionPolicy (apartat 7): un pod no sa estava bloquejant la reparació del sistema.
Manifest corregit:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-reserves
namespace: rutas-norte-pro
annotations:
rutasnorte.example/nota: >
PERCENTATGE obligatori: api-reserves te KEDA/HPA i oscil·la entre
4 i 30 repliques. Un minAvailable absolut es torna impossible a la
vall i bloqueja tot el manteniment. Veure 09-05.
spec:
maxUnavailable: 25%
selector:
matchLabels:
app: api-reserves
unhealthyPodEvictionPolicy: AlwaysAllowBloqueig 2: prometheus amb minAvailable: 1 i una rèplica — LEGÍTIM però mal resolt.
prometheus-server-0: 1 replica (es un StatefulSet).
PDB: minAvailable: 1.
Desallotjar-la deixaria 0 disponibles.
ALLOWED DISRUPTIONS: 0. Permanent i per disseny.El bloqueig és coherent amb la configuració, però la configuració és discutible. Cal decidir què es vol:
Opció A: acceptar la interrupció. Prometheus amb una rèplica i emmagatzematge local. Perdre uns minuts de mètriques durant un manteniment és molest però no crític: no afecta el servei als usuaris.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: prometheus
namespace: monitoratge
spec:
# Una sola replica: permetem el desallotjament. Perdem uns minuts de
# metriques durant el manteniment, cosa que es acceptable: Prometheus
# no esta al cami critic de la venda.
maxUnavailable: 1
selector:
matchLabels:
app: prometheusOpció B: dues rèpliques. Prometheus en alta disponibilitat es fa amb dues instàncies independents que recol·lecten el mateix, més Thanos o Mimir per desduplicar. És més complex i només es justifica si les alertes són crítiques.
Decisió pragmàtica: opció A. Prometheus no és al camí de la venda, i l'observabilitat pot tenir un forat de tres minuts durant un manteniment planificat.
Nota important i contraintuïtiva: perdre Prometheus durant el manteniment té un efecte secundari del mòdul anterior. Si api-reserves escala amb KEDA per una mètrica de Prometheus (09-04), KEDA entrarà en mode fallback mentre Prometheus no hi sigui. Per això el fallback de 09-04 és tan important: cobreix exactament aquest cas.
Procediment complet per desencallar:
# 1. Corregir el PDB d'api-reserves
kubectl patch pdb api-reserves -n rutas-norte-pro --type merge \
-p '{"spec":{"minAvailable":null,"maxUnavailable":"25%","unhealthyPodEvictionPolicy":"AlwaysAllow"}}'
# 2. Corregir el PDB de prometheus
kubectl patch pdb prometheus -n monitoratge --type merge \
-p '{"spec":{"minAvailable":null,"maxUnavailable":1}}'
# 3. Verificar
kubectl get pdb --all-namespacesNAMESPACE NAME MAX UNAVAILABLE ALLOWED DISRUPTIONS
rutas-norte-pro api-reserves 25% 1
monitoratge prometheus 1 1El drenatge, que continuava reintentant en segon pla, avança sol.
# 4. Investigar el pod x2klm (no urgent, pero no ho oblidis)
kubectl describe pod api-reserves-7c9d4f8b6d-x2klm -n rutas-norte-proPrevenció estructural:
- Comprovació prèvia obligatòria al procediment de manteniment:
kubectl get pdb --all-namespaces \
-o custom-columns='NS:.metadata.namespace,NOM:.metadata.name,PERMESOS:.status.disruptionsAllowed' \
| awk 'NR==1 || $3=="0"'-
Política de Kyverno (08-03) que rebutgi PDB amb
minAvailableabsolut en càrregues que tinguin HPA oScaledObject. -
Alerta permanent:
Amb l'anotació d'exclusió per als bloquejos deliberats (postgres-reserves, redis-cache).
Solució 2
Cas A: 11 rèpliques
(a) Repartiment entre zones (maxSkew: 1, DoNotSchedule, 3 zones):
11 / 3 = 3,67
Repartiment mes equilibrat possible:
eu-west-1a: 4
eu-west-1b: 4
eu-west-1c: 3
Biaix = 4 - 3 = 1 <= maxSkew 1. COMPLEIX.(b) Repartiment entre nodes (maxSkew: 2, ScheduleAnyway, 3 nodes per zona):
Zona a (4 repliques, 3 nodes): 2-1-1
Zona b (4 repliques, 3 nodes): 2-1-1
Zona c (3 repliques, 3 nodes): 1-1-1
Biaix global entre nodes: max 2 - min 1 = 1 <= maxSkew 2. COMPLEIX.(c) Caiguda d'un node:
(d) Caiguda d'una zona:
(e) Desallotjaments permesos:
Cas B: 26 rèpliques
(a) Repartiment entre zones:
26 / 3 = 8,67
Repartiment:
eu-west-1a: 9
eu-west-1b: 9
eu-west-1c: 8
Biaix = 9 - 8 = 1 <= maxSkew 1. COMPLEIX.(b) Repartiment entre nodes:
Zona a (9 repliques, 3 nodes): 3-3-3
Zona b (9 repliques, 3 nodes): 3-3-3
Zona c (8 repliques, 3 nodes): 3-3-2
Biaix global entre nodes: 3 - 2 = 1 <= maxSkew 2. COMPLEIX.(c) Caiguda d'un node:
(d) Caiguda d'una zona:
(e) Desallotjaments permesos:
(f) Es pot buidar un node d'una vegada amb 26 rèpliques?
Un node te 3 repliques d'api-reserves.
El PDB permet 6 desallotjaments simultanis.
3 <= 6 -> SI, es poden desallotjar les 3 d'una vegada.
El drenatge NO se serialitzara per culpa del PDB d'api-reserves.
Sera gairebe instantani per a aquest component.Comparem amb el cas d'11 rèpliques:
Amb 11 repliques, un node te 2 repliques i el PDB permet 2 desallotjaments.
2 <= 2 -> tambe hi caben d'una vegada, encara que just al limit.
Si el node tingues 3 repliques (possible amb maxSkew 2), el PDB serialitzaria:
desallotja 2, espera que es recreïn, desallotja la tercera.Observació important: el maxUnavailable: 25% escala amb les rèpliques, així que el manteniment es torna MÉS ràpid amb més rèpliques, no més lent. És exactament el que es vol: al pic hi ha més capacitat i es pot permetre perdre més rèpliques simultàniament.
Cas especial: 2 rèpliques amb minDomains: 3
minDomains: 3 significa que el planificador considera 3 dominis,
encara que algun estigui buit.
Col·locacio de la replica 1: zona a. Estat: a:1, b:0, c:0.
Biaix = 1 - 0 = 1 <= maxSkew 1. Compleix.
Col·locacio de la replica 2:
Si va a la zona a: a:2, b:0, c:0. Biaix = 2 - 0 = 2 > 1. NO PERMES.
Si va a la zona b: a:1, b:1, c:0. Biaix = 1 - 0 = 1 <= 1. PERMES.
Resultat: 1 replica a la zona a, 1 a la zona b, 0 a la c.
La zona c queda buida, i aixo ES CORRECTE: amb 2 repliques no es poden
cobrir 3 zones.Biaix final: 1. Compleix. La restricció funciona bé amb menys rèpliques que zones.
Cas especial: 2 rèpliques si una zona es queda sense nodes
Escenari: la zona c perd tots els seus nodes (caiguda completa).
Nodes disponibles: nomes a les zones a i b.
minDomains: 3 fa que el planificador CONTINUI COMPTANT 3 dominis.
Estat despres de la caiguda: a:1, b:1, c:0 (la zona c no te nodes).
Biaix = 1 - 0 = 1 <= maxSkew 1. COMPLEIX. Res a fer.
Pero si l'HPA volgues escalar a 4 repliques:
Col·locacio de la replica 3:
Si va a la zona a: a:2, b:1, c:0. Biaix = 2 - 0 = 2 > 1. NO PERMES.
Si va a la zona b: a:1, b:2, c:0. Biaix = 2 - 0 = 2 > 1. NO PERMES.
Si va a la zona c: NO HI HA NODES.
-> La replica 3 es queda en PENDING PER SEMPRE.Aquest és el perill de minDomains combinat amb DoNotSchedule. La restricció, pensada per garantir el repartiment, impedeix escalar quan una zona no està disponible: exactament el moment en què més falta fa escalar, perquè has perdut un terç de la capacitat.
Mitigacions possibles:
| Opció | Efecte | Contrapartida |
|---|---|---|
Treure minDomains |
Amb 2 zones vives, el repartiment entre elles compleix | Perds la garantia de les 3 zones en operació normal |
whenUnsatisfiable: ScheduleAnyway a zona |
Mai bloqueja | Perds la garantia dura |
Pujar maxSkew a 2 |
Amb a:2, b:1, c:0, el biaix és 2 <= 2. Permet | Repartiment menys equilibrat |
| Acceptar-ho i tenir un procediment | Davant d'una caiguda de zona, es relaxa la restricció a mà | Requereix intervenció humana sota pressió |
Recomanació per a Rutas Norte: maxSkew: 1 amb minDomains: 3 i un procediment documentat per relaxar-lo davant d'una caiguda de zona:
# PROCEDIMENT D'EMERGENCIA: caiguda de zona
# Relaxar la restriccio topologica per permetre escalar a les zones vives.
kubectl patch deployment api-reserves -n rutas-norte-pro --type json -p '[
{"op": "replace", "path": "/spec/template/spec/topologySpreadConstraints/0/whenUnsatisfiable",
"value": "ScheduleAnyway"}
]'
# REVERTIR quan la zona torni.Tenir-ho escrit per endavant, provat, i al runbook. És molt millor que improvisar-ho a les tres de la matinada.
Solució 3
Anàlisi dels requisits i les seves conseqüències:
| Requisit | Conseqüència de disseny |
|---|---|
| Camí crític, sense estat | Mínim 3 rèpliques, repartides entre 3 zones |
| Transacció de 2-8 s no interrompible | terminationGracePeriodSeconds alt + apagada ordenada |
| 50 connexions màximes del banc | Límit dur al nombre de rèpliques |
| 5-300 transaccions/s | Autoescalat necessari, però acotat pel límit del banc |
| Arrencada de 20 s | Objectiu d'escalat conservador; no escalar a zero |
| Registre abans de respondre | L'apagada ha d'esperar que s'escrigui el registre |
| Ha de sobreviure a una zona | DoNotSchedule entre zones, minDomains: 3 |
El límit de 50 connexions és la restricció dominant, i cal calcular-lo abans que res.
Pas 1: calcular el sostre real de rèpliques
El banc permet 50 connexions simultanies des de la IP de Rutas Norte.
Si cada replica mante un pool de N connexions:
Repliques maximes = 50 / N
Amb N = 5 connexions per replica: 10 repliques maximes
Amb N = 3 connexions per replica: 16 repliques maximes
Amb N = 2 connexions per replica: 25 repliques maximes
Quantes transaccions per segon sosté una replica?
Cada transaccio triga 2-8 s, mitjana ~5 s.
Amb un pool de N connexions concurrents:
transaccions/s = N / 5
Amb N = 5: 1 transaccio/s per replica
Amb N = 3: 0,6 transaccions/s per replica
Per a 300 transaccions/s al pic:
Amb N = 5: 300 repliques. IMPOSSIBLE (excedeix el limit del banc per 30x).
Amb N = 3: 500 repliques. Pitjor.Aquí hi ha un problema greu que cap configuració de Kubernetes no resol:
Capacitat TEORICA MAXIMA del sistema:
50 connexions concurrents / 5 segons per transaccio = 10 transaccions/s
OBJECTIU DEL PIC: 300 transaccions/s
EL SISTEMA NO POT PROCESSAR MES DE 10 TRANSACCIONS PER SEGON.
El limit es el BANC, no Kubernetes.Aquesta és la lliçó de 09-01 sobre el coll d'ampolla real, en la seva forma més pura. Escalar passarela-pagaments a 30 rèpliques no augmenta la capacitat ni una engruna: les 30 rèpliques es barallarien per les mateixes 50 connexions.
Pas 2: l'arquitectura que sí que funciona
La solució no és d'escalat, és d'arquitectura:
1. NEGOCIAR amb el banc un limit mes gran. 50 connexions es ridicul per a
300 transaccions/s. Es l'accio mes important i no es tecnica.
2. Mentrestant: DESACOBLAR amb una cua.
- api-reserves encua la peticio de cobrament i respon a l'usuari
"processant el seu pagament".
- passarela-pagaments consumeix de la cua a la velocitat que el banc permet.
- L'usuari rep la confirmacio per notificacio push o correu.
Aixo converteix una restriccio dura en una latencia acceptable, i encaixa
perfectament amb KEDA (09-04): la cua es el senyal d'escalat natural.
3. Un POOL COMPARTIT de connexions al banc, en lloc d'un pool per replica.Com que l'enunciat demana dissenyar l'alta disponibilitat, assumim l'opció 3 amb un nombre de rèpliques acotat.
Pas 3: els manifests
# k8s/entorns/pro/deployment-passarela-pagaments.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: passarela-pagaments
namespace: rutas-norte-pro
labels:
app: passarela-pagaments
app.kubernetes.io/part-of: rutas-norte
spec:
# SENSE camp replicas: ho governa l'HPA/KEDA.
selector:
matchLabels:
app: passarela-pagaments
strategy:
type: RollingUpdate
rollingUpdate:
# Coherent amb el PDB (veure mes avall). Mai perdre mes d'1 replica.
maxUnavailable: 1
# maxSurge 1, no mes: cada replica nova obre connexions al banc i
# tenim un limit dur de 50. Un maxSurge alt podria esgotar-lo
# durant el desplegament.
maxSurge: 1
template:
metadata:
labels:
app: passarela-pagaments
app.kubernetes.io/part-of: rutas-norte
spec:
# PERIODE DE GRACIA DE 90 SEGONS.
#
# Una transaccio triga fins a 8 segons i NO es pot interrompre:
# tallar-la podria deixar un cobrament sense reserva associada, que es el pitjor
# error possible en una passarel·la de pagaments.
#
# 90 s = 8 s (transaccio mes llarga) + marge ampli per:
# - buidar la cua interna de transaccions en curs
# - escriure el registre de compliment de cadascuna
# - tancar netament les connexions TLS amb el banc
#
# El proces HA de gestionar SIGTERM: deixar d'acceptar peticions noves,
# acabar les en curs, escriure els registres, i sortir.
terminationGracePeriodSeconds: 90
topologySpreadConstraints:
# ZONES: restriccio DURA. El requisit de sobreviure a la caiguda d'una
# zona es explicit i no negociable.
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
minDomains: 3
labelSelector:
matchLabels:
app: passarela-pagaments
matchLabelKeys: ["pod-template-hash"]
# NODES: restriccio DURA tambe, i aqui SI que ens la podem permetre.
# Amb un maxim de 9 repliques i 9 nodes, exigir-ne 1 per node es
# assolible. I dues repliques de la passarel·la al mateix node son
# dues repliques que es perden juntes: inacceptable al cami
# critic del pagament.
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: passarela-pagaments
matchLabelKeys: ["pod-template-hash"]
containers:
- name: passarela
image: registry.rutasnorte.example/passarela-pagaments:2.4.1
# PARADA ORDENADA: el hook preStop dona marge perque el Service
# tregui aquest pod dels seus endpoints ABANS que comenci a apagar-se.
# Sense aixo, hi ha una finestra d'1-2 segons en que el pod rep
# peticions noves mentre ja s'esta apagant.
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
resources:
requests: {cpu: 200m, memory: 256Mi}
limits: {cpu: 500m, memory: 512Mi}
env:
# POOL PETIT I EXPLICIT: 5 connexions per replica.
# Amb maxReplicas 9: 9 x 5 = 45 < 50. Deixa marge de 5 connexions
# per al desplegament (maxSurge 1) i per a reintents.
# AQUEST NUMERO ES CRITIC: pujar-lo esgota el limit del banc.
- name: BANC_POOL_MAX
value: "5"
readinessProbe:
httpGet: {path: /preparat, port: 8080}
initialDelaySeconds: 20 # L'arrencada triga 20 s (TLS mutu)
periodSeconds: 5
failureThreshold: 2
livenessProbe:
httpGet: {path: /salut, port: 8080}
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
# STARTUP PROBE: dona fins a 60 s per a l'arrencada sense que la
# livenessProbe mati el pod a mitja negociacio TLS.
startupProbe:
httpGet: {path: /salut, port: 8080}
periodSeconds: 5
failureThreshold: 12# k8s/entorns/pro/pdb-passarela-pagaments.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: passarela-pagaments
namespace: rutas-norte-pro
labels:
app: passarela-pagaments
app.kubernetes.io/part-of: rutas-norte
spec:
# maxUnavailable: 1 EN NUMERO ABSOLUT, no en percentatge.
#
# Es l'excepcio a la regla general de l'apartat 3, i es deliberada:
#
# 1. El rang de repliques es ESTRET (3 a 9), no com api-reserves (4 a 30).
# Un percentatge del 25 % donaria: 3 repliques -> 0 desallotjaments (BLOQUEIG), i
# 9 repliques -> 2 desallotjaments. El cas de 3 repliques seria un PDB impossible.
#
# 2. Cada replica que cau pot endur-se fins a 5 transaccions en vol,
# cadascuna amb diners reals d'un client. Volem que els desallotjaments es
# SERIALITZIN sempre, sigui quin sigui el nombre de repliques.
#
# 3. Amb el terminationGracePeriodSeconds de 90 s, cada desallotjament triga fins a
# un minut i mig. Serialitzar-los fa el manteniment lent pero segur,
# que es exactament el compromis que volem en una passarel·la de pagaments.
maxUnavailable: 1
selector:
matchLabels:
app: passarela-pagaments
# AlwaysAllow: un pod que no passa la readiness no esta processant pagaments
# (el Service ja no li envia transit), aixi que desallotjar-lo no posa en
# risc cap transaccio.
unhealthyPodEvictionPolicy: AlwaysAllow# k8s/entorns/pro/scaledobject-passarela-pagaments.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: passarela-pagaments
namespace: rutas-norte-pro
spec:
scaleTargetRef:
name: passarela-pagaments
pollingInterval: 15
cooldownPeriod: 600
# 3 repliques de terra: UNA PER ZONA. Es el minim per sobreviure a la
# caiguda d'una zona conservant 2 repliques operatives. MAI escalar a zero:
# l'arrencada de 20 s (negociacio TLS mutua amb el banc) es inacceptable
# amb un usuari a la pantalla de pagament.
minReplicaCount: 3
# 9 REPLIQUES MAXIM. AQUEST NUMERO NO SURT DEL TRANSIT, SURT DEL BANC:
# 9 repliques x 5 connexions = 45 connexions < 50 del limit.
# Marge de 5 connexions per al desplegament (maxSurge 1) i reintents.
#
# ADVERTENCIA CRITICA: pujar aquest numero SENSE renegociar el limit amb el
# banc NO augmenta la capacitat. Nomes fa que les repliques competeixin per
# les mateixes 50 connexions i comencin a rebre errors de connexio
# rebutjada. El coll d'ampolla es el BANC (veure 09-01).
maxReplicaCount: 9
fallback:
failureThreshold: 3
replicas: 6
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleUp:
# 30 s d'estabilitzacio (no 0): cada replica nova triga 20 s a
# estar llesta i obre 5 connexions al banc. No volem crear
# repliques a la babala i esgotar el limit de connexions.
stabilizationWindowSeconds: 30
selectPolicy: Max
policies:
- type: Pods
value: 2
periodSeconds: 60
scaleDown:
# 15 minuts. Cada replica que s'apaga triga fins a 90 s a acabar
# les seves transaccions. Baixar a poc a poc es essencial.
stabilizationWindowSeconds: 900
selectPolicy: Min
policies:
- type: Pods
value: 1
periodSeconds: 300
triggers:
# Escalat per CONNEXIONS ACTIVES AL BANC, no per CPU ni per peticions.
# Es la metrica que reflecteix el recurs escas real.
- type: prometheus
metadata:
serverAddress: http://prometheus-operated.monitoratge.svc.cluster.local:9090
metricName: passarela_connexions_banc_actives
query: |
sum(passarela_pagaments_connexions_banc_actives{namespace="rutas-norte-pro"})
# Objectiu: 3,5 connexions actives per replica (d'un pool de 5).
# Al 70 % d'ocupacio del pool, escalem.
threshold: "3.5"
activationThreshold: "1"
authenticationRef:
kind: ClusterTriggerAuthentication
name: prometheus-lectura
# Xarxa de seguretat: transaccions encuades esperant una connexio lliure.
- type: prometheus
metadata:
serverAddress: http://prometheus-operated.monitoratge.svc.cluster.local:9090
metricName: passarela_transaccions_en_espera
query: |
sum(passarela_pagaments_transaccions_en_espera{namespace="rutas-norte-pro"})
threshold: "3"
activationThreshold: "1"
authenticationRef:
kind: ClusterTriggerAuthentication
name: prometheus-lecturaResum de decisions i justificacions:
| Decisió | Elecció | Justificació |
|---|---|---|
minReplicaCount |
3 | Una per zona; sobreviu a la caiguda d'una zona amb 2 operatives |
maxReplicaCount |
9 | 9 × 5 = 45 < 50 connexions del banc. No surt del trànsit |
| PDB | maxUnavailable: 1 absolut |
Rang estret (3-9); un 25 % donaria 0 desallotjaments amb 3 rèpliques |
topologySpread zona |
DoNotSchedule + minDomains: 3 |
Requisit explícit de sobreviure a una zona |
topologySpread node |
DoNotSchedule, maxSkew: 1 |
Amb 9 rèpliques i 9 nodes és assolible; dues rèpliques per node és risc innecessari |
terminationGracePeriodSeconds |
90 s | 8 s de transacció + registre + tancament TLS, amb marge ampli |
preStop |
sleep 10 |
Que el Service tregui el pod abans que comenci a apagar-se |
startupProbe |
60 s de marge | El TLS mutu triga 20 s; evita que la liveness mati l'arrencada |
| Mètrica d'escalat | Connexions actives al banc | És el recurs escàs real, no la CPU ni les peticions |
maxSurge |
1 | Cada rèplica extra consumeix connexions del límit del banc |
| Escalat a zero | No | 20 s d'arrencada amb un usuari a la pantalla de pagament |
La conclusió que cal endur-se de l'exercici, i que va més enllà de la configuració:
L'alta disponibilitat de passarela-pagaments està limitada pel banc, no per Kubernetes. Cap quantitat de PDB, topologySpreadConstraints o autoescalat no permet processar més de 10 transaccions per segon amb 50 connexions i 5 segons per transacció.
Amb 300 transaccions per segon al pic del pont de maig, el sistema se saturarà irremeiablement. Les accions que de debò resolen el problema són:
- Renegociar el límit amb el banc. No és tècnica, és comercial, i és la més important.
- Desacoblar amb una cua, convertint una restricció dura en una latència acceptable comunicada a l'usuari.
- Un pool compartit en lloc d'un pool per rèplica, amb un component intermedi que multiplexi.
- Un límit de taxa explícit a
api-reservesque rebutgi educadament les peticions de pagament quan la passarel·la està saturada, en lloc de deixar que s'acumulin fins al timeout.
Dissenyar l'alta disponibilitat d'un component sense entendre el seu coll d'ampolla real és exactament l'error que 09-01 advertia: escalar la capa web quan el límit és en una altra banda multiplica el cost sense vendre ni un bitllet més.
Conclusió
L'alta disponibilitat no és un objecte de Kubernetes que s'activa: és una propietat del sistema que es construeix entenent què pot fallar i quina protecció existeix per a cada cosa.
L'essencial:
- Kubernetes distingeix interrupcions voluntàries i involuntàries, i només et pot protegir de les primeres. Un
kubectl drainpassa per l'API i es pot negociar; un servidor que s'apaga, no. - El PodDisruptionBudget declara quant servei ha de romandre disponible davant dels desallotjaments. El respecten
kubectl drain, el Cluster Autoscaler, l'actualitzador del VPA i el descheduler; no el respectenkubectl delete podni les actualitzacions progressives del Deployment. minAvailableparla del que queda;maxUnavailabledel que se'n va. Els percentatges arrodoneixen en direccions oposades, sempre afavorint la disponibilitat. Amb HPA o KEDA, sempre percentatges: un número absolut es torna impossible a la vall i bloqueja tot el manteniment.- El PDB impossible (
minAvailableigual a les rèpliques, o una sola rèplica) ancora nodes, bloqueja drenatges i impedeix al Cluster Autoscaler reduir. De vegades és deliberat (postgres-reserves); documenta'l amb una anotació perquè ningú l'«arregli». unhealthyPodEvictionPolicy: AlwaysAllowtrenca l'encallament circular d'un desplegament defectuós els pods trencats del qual impedeixen la seva pròpia reparació. Fes-lo servir en tot el que no tingui quòrum.topologySpreadConstraintsreparteix equitativament entre dominis de fallada. Davant de lapodAntiAffinityde 06-05, guanya clarament en càrregues amb autoescalat: l'antiafinitat requerida limita les rèpliques al nombre de nodes, i amb 30 rèpliques i 9 nodes deixa 21 podsPendingper sempre.- L'asimetria entre nivells és la decisió clau:
DoNotScheduleentre zones (la caiguda d'una zona és catastròfica) iScheduleAnywayentre nodes (bloquejar l'escalat seria pitjor). ImatchLabelKeys: ["pod-template-hash"]sempre, perquè els desplegaments no s'encallin. - L'alta disponibilitat de les altres capes importa igual: tres o cinc membres d'etcd (mai parell) repartits entre zones, diverses rèpliques del controlador d'Ingress i de CoreDNS amb els seus PDB, i per a
postgres-reserves, un operador que sàpiga promocionar una rèplica: això Kubernetes no ho fa i no ho pot fer. - El procediment de manteniment és
cordon→drain→ actualitzar →uncordon, amb verificació de PDB abans i de servei entre cada node. Sense PDB, un drenatge s'endú totes les rèpliques d'un node en un instant; amb PDB, les serialitza i l'usuari no nota res. - La prova de caos revela el que la teoria amaga: un drenatge produeix zero errors; un node que mor produeix uns 40 segons d'errors mentre el pla de control se n'assabenta. Aquesta diferència, mesurada amb cronòmetre, és la definició pràctica de voluntari davant d'involuntari.
Rutas Norte té ara la plataforma repartida entre tres zones amb garanties dures, PDB coherents amb l'autoescalat, i un procediment de manteniment que no interromp la venda. Les rèpliques creixen quan cal, tenen la mida correcta, els nodes apareixen quan no hi caben, l'escalat s'anticipa als esdeveniments coneguts, i res d'això es trenca quan algú actualitza un node o quan cau un servidor.
I tanmateix, queda una pregunta que no hem fet en tot el mòdul: és que cal tant?
Hem donat per bons els números que anàvem arrossegant: que una rèplica d'api-reserves sosté 85 peticions per segon, que cal escalar a 30 al pic, que el requests.cpu ha de ser 412m. Però ningú ha preguntat per què una rèplica només aguanta 85 peticions per segon, ni si podria aguantar-ne 200 amb els mateixos recursos. Ningú ha mirat si el pool de connexions a PostgreSQL està ben dimensionat, si les consultes tenen els índexs que necessiten, si redis-cache s'està fent servir de debò, o si l'estrangulament de CPU que vam veure al mòdul 3 apareix molt abans del que crèiem.
Escalar és multiplicar la ineficiència pel nombre de rèpliques. Si cada rèplica malgasta la meitat de la seva capacitat, trenta rèpliques en malgasten quinze.
A la propera lliçó, Ajust de Rendiment, tanquem el mòdul mirant cap endins: la metodologia de mesurar, trobar el coll d'ampolla real i canviar una sola cosa cada vegada; proves de càrrega amb k6 simulant el pont de maig i com llegir-ne els resultats de debò; l'ajust de la capa d'aplicació, que és on gairebé sempre és el problema; com es tradueix un limit de CPU en quota del kernel i per què l'estrangulament apareix molt abans del 100 %; l'arrencada ràpida com a requisit de l'autoescalat; l'ajust del clúster, inclòs l'efecte del ndots: 5 que vam deixar pendent a 04-03; i un pressupost de latència que converteix l'objectiu de negoci en objectius per component.
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
