Fins ara hem decidit què s'executa i com es compon cada pod, però mai on. El kube-scheduler ha anat col·locant els nostres pods als nodes que li ha semblat i no ens hem queixat, perquè en un clúster homogeni de proves qualsevol lloc serveix.
En producció no serveix qualsevol lloc. postgres-reserves necessita el node amb disc SSD, perquè en un disc mecànic les consultes d'ocupació triguen cinc vegades més. Les sis rèpliques d'api-reserves no haurien d'estar totes al mateix node: si aquell node es reinicia, l'API sencera desapareix justament quan algú està pagant un bitllet. I les càrregues d'anàlisi pesades no haurien de competir per CPU amb la venda de bitllets en un pont festiu.
Tot això s'expressa amb tres mecanismes que estudiarem aquí: afinitat (el pod tria node), antiafinitat (el pod evita companyia) i taints amb toleracions (el node rebutja pods). Al final tindrem el pla de planificació complet de Rutas Norte i sabrem diagnosticar el pod que es queda Pending per sempre.
Contingut
- Com decideix el
kube-scheduler: filtratge i puntuació nodeNameinodeSelector: els mecanismes bàsics- Afinitat de node:
requireddavant depreferred - Afinitat i antiafinitat entre pods:
topologyKey - Taints i toleracions: el node rebutja
- Taints automàtics de Kubernetes
PriorityClassi preemptiontopologySpreadConstraints: menció i remissió- El pla de planificació de Rutas Norte
- Diagnòstic del pod eternament
Pending
- Com decideix el
kube-scheduler: filtratge i puntuació
kube-scheduler: filtratge i puntuacióA la lliçó 01-02 vam dir que el scheduler "assigna pods a nodes". Ara veiem com.
Quan un pod es crea, el seu camp spec.nodeName és buit. El scheduler observa aquests pods sense assignar i, per a cadascun, executa un cicle de dues fases.
graph LR P[Pod sense nodeName] --> F[Fase 1: FILTRATGE<br/>quins nodes són viables?] F -->|8 nodes → 3 viables| S[Fase 2: PUNTUACIÓ<br/>quin és el millor?] S -->|node-5: 78 punts| B[Binding:<br/>escriure nodeName] F -->|0 viables| PE[Pod en Pending<br/>+ esdeveniment FailedScheduling]
Fase 1: filtratge
Es descarten els nodes que no poden allotjar el pod. Cada comprovació és un veto absolut: n'hi ha prou amb una per eliminar el node.
| Filtre | Què comprova |
|---|---|
NodeResourcesFit |
Queden CPU i memòria lliures per a les requests del pod? |
NodeAffinity |
Compleix el node l'afinitat required del pod? |
NodeName |
Si el pod va fixar nodeName, és aquest aquell node? |
TaintToleration |
Tolera el pod els taints NoSchedule del node? |
NodePorts |
Si el pod demana un hostPort, està lliure al node? |
VolumeBinding |
Pot el node accedir als volums del pod (zona, topologia)? |
PodTopologySpread |
Respecta les restriccions de distribució topològica? |
InterPodAffinity |
Compleix l'afinitat i l'antiafinitat required entre pods? |
Un detall que connecta amb el mòdul 5: el filtre VolumeBinding és el que fa funcionar volumeBindingMode: WaitForFirstConsumer. Un PVC ja vinculat a un volum de la zona eu-west-1a elimina del filtratge tots els nodes de les altres zones.
I un altre que connecta amb el mòdul 3: NodeResourcesFit mira requests, no l'ús real. Un node amb 8 CPU on els pods han reservat 7,8 CPU però només n'usen 0,5 es considera ple. Per això uns requests inflats malbaraten clúster.
Fase 2: puntuació
Dels nodes viables, cada complement de puntuació atorga entre 0 i 100 punts, i se'n fa una mitjana ponderada.
| Complement | Premia |
|---|---|
NodeResourcesBalancedAllocation |
Nodes on l'ús de CPU i memòria quedaria equilibrat |
NodeResourcesFit (LeastAllocated) |
Nodes amb més recursos lliures: reparteix la càrrega |
ImageLocality |
Nodes que ja tenen descarregada la imatge: arrencada més ràpida |
InterPodAffinity |
Nodes que satisfan l'afinitat preferred entre pods |
NodeAffinity |
Nodes que satisfan l'afinitat preferred de node, amb el seu weight |
TaintToleration |
Nodes sense taints PreferNoSchedule |
Guanya el de més puntuació; si empaten, se n'escull un a l'atzar entre els empatats. El scheduler escriu llavors el nodeName i el kubelet d'aquell node recull el pod.
Amb això ja s'entén la diferència essencial entre els dos tipus de regla que veurem: les required actuen en el filtratge (si no es compleixen, el pod no hi va), i les preferred actuen en la puntuació (si no es compleixen, el pod hi va igualment però a un node pitjor puntuat).
nodeName i nodeSelector: els mecanismes bàsics
nodeName i nodeSelector: els mecanismes bàsicsnodeName: la força bruta
Se salta el scheduler completament: el kubelet d'aquell node veu un pod amb el seu nom i l'arrenca sense més.
No el facis servir mai en producció. Si el node no existeix, està ple o està caigut, el pod es queda penjat sense cap esdeveniment útil; si el node desapareix, no hi ha recol·locació. El seu únic ús legítim és la depuració puntual: forçar un pod a un node concret per investigar un problema local.
nodeSelector: el filtre simple
El pod només es programa en nodes que tinguin totes aquestes etiquetes amb aquests valors exactes. És una conjunció d'igualtats, res més.
Les etiquetes es posen sobre els nodes com sobre qualsevol objecte:
kubectl label node rutas-norte-m02 disc=ssd
kubectl label node rutas-norte-m03 disc=hdd
kubectl get nodes --show-labels | head -2Kubernetes hi afegeix a més unes quantes etiquetes estàndard que convé conèixer:
| Etiqueta | Contingut |
|---|---|
kubernetes.io/hostname |
Nom del node |
kubernetes.io/os |
linux, windows |
kubernetes.io/arch |
amd64, arm64 |
topology.kubernetes.io/zone |
Zona de disponibilitat |
topology.kubernetes.io/region |
Regió |
node.kubernetes.io/instance-type |
Tipus d'instància del proveïdor |
Els límits de nodeSelector són tres, i motiven tot el que ve després:
- Només igualtat exacta. No es pot dir "SSD o NVMe", ni "qualsevol node que no sigui d'analítica".
- És obligatori. Si cap node no hi casa, el pod es queda
Pendingper sempre. No hi ha manera d'expressar una preferència. - No diu res sobre altres pods. No permet "no em posis amb els meus germans".
- Afinitat de node:
required davant de preferred
required davant de preferredL'afinitat de node resol els dos primers límits: sintaxi expressiva i la possibilitat de preferir en comptes d'exigir.
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disc
operator: In
values: ["ssd", "nvme"]
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["eu-west-1a"]
- weight: 20
preference:
matchExpressions:
- key: gamma
operator: In
values: ["alta"]Els noms llargs, desxifrats
Es llegeixen en dues meitats:
requiredDuringScheduling/preferredDuringScheduling: si la regla és obligatòria (filtratge) o desitjable (puntuació).IgnoredDuringExecution: què passa si l'etiqueta del node canvia mentre el pod ja està corrent. La resposta és: res. El pod continua on és.
Aquest IgnoredDuringExecution és més important del que sembla. Si programes postgres-reserves en un node amb disc=ssd i algú canvia aquesta etiqueta a disc=hdd, el pod no es mou. L'afinitat s'avalua en el moment de programar i mai més. Existia un RequiredDuringExecution planificat que desallotjaria pods en deixar-se de complir la regla, però no es va implementar mai. Per a desallotjament per condicions del node, el mecanisme és l'efecte NoExecute dels taints (apartat 5).
Estructura i operadors
nodeSelectorTermsés una llista amb semàntica OR: n'hi ha prou que es compleixi un terme.matchExpressionsdins d'un terme és una llista amb semàntica AND: s'han de complir totes.
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
# Terme A: node amb SSD I amb almenys 16 nuclis
- matchExpressions:
- key: disc
operator: In
values: ["ssd"]
- key: nuclis
operator: Gt
values: ["15"]
# ...O BÉ Terme B: qualsevol node NVMe
- matchExpressions:
- key: disc
operator: In
values: ["nvme"]Operadors disponibles:
| Operador | Significat | Exemple |
|---|---|---|
In |
El valor és a la llista | disc In [ssd, nvme] |
NotIn |
El valor no és a la llista | gamma NotIn [economica] |
Exists |
L'etiqueta existeix, sense importar el valor | disc Exists |
DoesNotExist |
L'etiqueta no existeix | node-role.kubernetes.io/analitica DoesNotExist |
Gt |
Més gran que (valor enter, a values amb un sol element) |
nuclis Gt 15 |
Lt |
Més petit que | carrega-mitjana Lt 50 |
NotIn i DoesNotExist donen l'antiafinitat de node: no hi ha un objecte separat per a això, s'expressa negant.
Detall sobre Gt i Lt: comparen com a enters, la llista values ha de tenir exactament un element, i el valor de l'etiqueta del node ha de parsejar com a enter. nuclis Gt "15" significa estrictament més gran que 15, és a dir, 16 o més.
Els pesos de preferred
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80 # d'1 a 100
preference:
matchExpressions: [...]Cada regla preferred que es compleixi suma el seu weight a la puntuació del node. Amb dues regles de pes 80 i 20, un node que compleixi totes dues puntua 100 en aquest complement; un que només compleixi la primera, 80. Els pesos són relatius entre si: el que importa és la proporció, no el valor absolut.
Combinació típica i molt recomanable: required per al que és un requisit real (arquitectura de CPU, presència d'un disc local) i preferred per al que és una optimització (zona concreta, gamma de màquina). Posar en required el que en realitat era una preferència és la primera causa de pods Pending innecessaris.
- Afinitat i antiafinitat entre pods:
topologyKey
topologyKeyL'afinitat de node mira etiquetes del node. L'afinitat entre pods mira quins altres pods hi ha ja en aquell node o en aquella zona. És el que permet dir "vull estar a prop de la memòria cau" o "no vull estar amb les meves germanes".
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: api-reserves
entorn: pro
topologyKey: kubernetes.io/hostnameLlegeix-ho així: "no em col·loquis en un node (kubernetes.io/hostname) on ja hi hagi un pod amb app=api-reserves i entorn=pro".
topologyKey: la peça central
topologyKey és una etiqueta del node que defineix l'àmbit en què s'aplica la regla. El scheduler agrupa els nodes pel valor d'aquesta etiqueta, i cada grup és un "domini topològic".
topologyKey |
Domini | Efecte d'una antiafinitat |
|---|---|---|
kubernetes.io/hostname |
Un node | Com a molt un pod del grup per node |
topology.kubernetes.io/zone |
Una zona de disponibilitat | Com a molt un per zona |
topology.kubernetes.io/region |
Una regió | Com a molt un per regió |
Etiqueta pròpia, p. ex. bastidor |
Un bastidor físic | Com a molt un per bastidor |
Triar bé el topologyKey és triar contra quina fallada et protegeixes:
hostnameprotegeix contra el reinici o la caiguda d'un node.zoneprotegeix contra la caiguda d'un centre de dades sencer.regionprotegeix contra una catàstrofe regional, a costa de latència entre rèpliques.
Compte amb required i hostname: si exigeixes com a molt una rèplica per node i tens 3 nodes, no podràs passar de 3 rèpliques. La quarta es quedarà Pending indefinidament. Aquest error apareix sovint combinat amb un autoescalat que intenta pujar a 10.
La solució habitual és fer servir preferred, que reparteix però no bloqueja:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: api-reserves
entorn: pro
topologyKey: kubernetes.io/hostnameAmb weight: 100, el scheduler penalitza molt els nodes que ja allotgen una rèplica, així que reparteix mentre pot; quan no queda alternativa, en col·loca dues juntes en comptes de deixar el pod Pending. Per a api-reserves, la disponibilitat de la qual importa més que la perfecció del repartiment, és l'elecció correcta.
Fixa't en el canvi de sintaxi entre les dues variants: a required la llista conté directament els termes; a preferred cada element és un objecte amb weight i podAffinityTerm.
podAffinity: atraure en comptes de repel·lir
El cas simètric: worker-notificacions consulta molt redis-cache i li convé estar al mateix node, on el trànsit no travessa la xarxa física.
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 50
podAffinityTerm:
labelSelector:
matchLabels:
app: redis-cache
entorn: pro
topologyKey: kubernetes.io/hostnameFes servir podAffinity amb moderació. Concentrar pods relacionats al mateix node millora la latència i empitjora la disponibilitat: aquell node es converteix en un punt únic de fallada per a dos components alhora. A Rutas Norte el deixem en preferred amb pes mitjà i no l'apliquem a res crític.
El cost de còmput
Aquest és un advertiment pràctic que la documentació oficial subratlla. Per avaluar afinitat entre pods, el scheduler ha d'examinar, per a cada node candidat, els pods de tots els nodes del seu domini topològic. La complexitat creix amb el producte de nodes per pods, no linealment.
En un clúster de 10 nodes és imperceptible. En un de diversos centenars, amb regles d'afinitat entre pods en moltes càrregues de treball, el temps de decisió per pod pot passar de mil·lisegons a segons, i en un desplegament massiu això es nota. La documentació desaconsella expressament aquestes regles en clústers de més de diversos centenars de nodes.
Mitigacions:
- Fes servir
topologyKey: kubernetes.io/hostname(dominis petits) abans quezone(dominis grans). - Acota l'àmbit amb
namespaceSelectorper no avaluar pods d'altres namespaces. - Per al cas concret de "repartir rèpliques de manera equilibrada",
topologySpreadConstraintsés més eficient; ho veiem a l'apartat 8.
- Taints i toleracions: el node rebutja
Afinitat i antiafinitat són mecanismes del pod: el pod expressa on vol anar. Els taints són el mecanisme invers, del node: el node declara que rebutja pods llevat que estiguin expressament autoritzats.
La diferència importa perquè resol un problema que l'afinitat no pot resoldre: reservar un grup de nodes. Amb afinitat, perquè només les càrregues d'anàlisi anessin als nodes d'anàlisi, caldria afegir una regla "no hi vagis" a tots els altres pods del clúster, inclosos els que desplegui algú demà. Amb un taint, el node es protegeix sol.
# Posar un taint
kubectl taint nodes rutas-norte-m04 dedicat=analitica:NoSchedule
# Treure'l (fixa't en el guionet final)
kubectl taint nodes rutas-norte-m04 dedicat=analitica:NoSchedule-
# Consultar
kubectl describe node rutas-norte-m04 | grep -A3 TaintsUn taint té la forma clau=valor:efecte.
Els tres efectes
| Efecte | En programar | Als pods que ja són al node |
|---|---|---|
NoSchedule |
Rebutja els pods que no el toleren | No els toca |
PreferNoSchedule |
Els evita si pot, però els accepta si no hi ha alternativa | No els toca |
NoExecute |
Rebutja els pods que no el toleren | Els desallotja |
NoExecute és l'únic dels tres que actua sobre pods en execució. És el mecanisme que sí que fa el que RequiredDuringExecution hauria fet a l'afinitat.
La sintaxi d'una toleració
spec:
tolerations:
# Forma 1: amb operator Equal (per defecte): clau, valor i efecte exactes
- key: dedicat
operator: Equal
value: analitica
effect: NoSchedule
# Forma 2: amb operator Exists: la clau existeix, el valor tant se val
- key: dedicat
operator: Exists
effect: NoSchedule
# Forma 3: tolerar TOT (fer servir amb molt de compte)
- operator: ExistsRegles de la sintaxi:
operator: Equal(el valor per defecte) exigeix que coincideixinkey,valueieffect.operator: Existsexigeix que coincideixinkeyieffect; no ha de portarvalue.- Ometre
effectsignifica "qualsevol efecte per a aquella clau". - Ometre
keyamboperator: Existssignifica "tots els taints". És el que fan servir els DaemonSets de CNI que han de córrer passi el que passi (06-02).
tolerationSeconds
Només té sentit amb NoExecute, i expressa quant de temps aguanta el pod al node abans de ser desallotjat:
"Si el node es torna inabastable, queda-t'hi 5 minuts per si torna; si no, desallotja'm." És exactament el que Kubernetes afegeix per defecte a tots els pods, com veurem ara.
L'advertiment fonamental
Una toleració no atrau el pod: només evita que el rebutgin.
Un pod amb la toleració de dedicat=analitica pot anar als nodes d'analítica, però també a qualsevol altre node del clúster, i probablement acabi en un altre. Per reservar nodes de veritat calen les dues peces:
spec:
# 1. El taint del node impedeix que hi entrin els altres
tolerations:
- key: dedicat
operator: Equal
value: analitica
effect: NoSchedule
# 2. L'afinitat fa que aquest pod sí que hi vagi
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: dedicat
operator: In
values: ["analitica"]Taint + toleració = exclusivitat. Taint + toleració + afinitat = dedicació.
- Taints automàtics de Kubernetes
Kubernetes posa taints pel seu compte quan un node té problemes. Conèixer-los explica molts comportaments que altrament semblen màgics.
| Taint | Efecte | Quan el posa el sistema |
|---|---|---|
node.kubernetes.io/not-ready |
NoExecute |
El node no està Ready |
node.kubernetes.io/unreachable |
NoExecute |
El controlador de nodes va perdre contacte amb el kubelet |
node.kubernetes.io/memory-pressure |
NoSchedule |
Al node li queda poca memòria |
node.kubernetes.io/disk-pressure |
NoSchedule |
Al node li queda poc disc |
node.kubernetes.io/pid-pressure |
NoSchedule |
Al node li queden pocs PID |
node.kubernetes.io/network-unavailable |
NoSchedule |
La xarxa del node no està configurada |
node.kubernetes.io/unschedulable |
NoSchedule |
Algú va executar kubectl cordon |
node.kubernetes.io/out-of-service |
NoExecute |
Un administrador el marca com a fora de servei |
I el més conegut, que no és automàtic sinó que el posa kubeadm en crear el clúster:
És el que va obligar el DaemonSet de logs de 06-02 a portar una toleració explícita.
El desallotjament per node caigut
Aquí hi ha el mecanisme real darrere de "si un node cau, els pods es recreen en un altre lloc":
- El kubelet deixa d'enviar batecs.
- Passats 40 segons, el controlador de nodes marca el node
NotReadyi li afegeix el taintnode.kubernetes.io/unreachable:NoExecute. - Els pods del node no es desallotgen immediatament, perquè Kubernetes els va afegir automàticament aquesta toleració en crear-los:
tolerations:
- key: node.kubernetes.io/not-ready
operator: Exists
effect: NoExecute
tolerationSeconds: 300
- key: node.kubernetes.io/unreachable
operator: Exists
effect: NoExecute
tolerationSeconds: 300- Als 300 segons (5 minuts) s'esgota la tolerància i els pods es marquen per esborrar. Els seus controladors creen substituts en altres nodes.
Això explica el retard d'uns cinc minuts entre que un node cau i que els seus pods reapareixen en un altre lloc. Es pot ajustar per pod:
# api-reserves és sensible a la latència de recuperació: 30 s en comptes de 300
tolerations:
- key: node.kubernetes.io/unreachable
operator: Exists
effect: NoExecute
tolerationSeconds: 30
- key: node.kubernetes.io/not-ready
operator: Exists
effect: NoExecute
tolerationSeconds: 30Abaixar-lo molt té el seu risc: un tall de xarxa transitori de 40 segons provocaria un desallotjament massiu i una recreació innecessària. Per a postgres-reserves convé el contrari: un valor alt, perquè recrear la base de dades en un altre node implica desmuntar i tornar a muntar un volum, i és millor esperar que el node torni.
Un avís: els pods de DaemonSet no reben aquestes toleracions automàtiques amb tolerationSeconds; en el seu lloc toleren aquests taints indefinidament, perquè els agents continuïn funcionant en un node amb problemes.
PriorityClass i preemption
PriorityClass i preemptionQuan el clúster és ple, què passa si arriba un pod important? Sense prioritats, es queda Pending com qualsevol altre. Amb prioritats, pot desallotjar pods menys importants per fer-se lloc.
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: rutasnorte-critica
value: 100000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "Components sense els quals Rutas Norte no pot vendre bitllets"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: rutasnorte-normal
value: 10000
globalDefault: true
description: "Prioritat per defecte de les càrregues de Rutas Norte"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: rutasnorte-lot
value: 100
globalDefault: false
preemptionPolicy: Never
description: "Treballs per lots que poden esperar"Ús en un pod:
Les PriorityClass són objectes de clúster, no de namespace. Kubernetes en porta dues de predefinides per a components del sistema, system-cluster-critical (2000000000) i system-node-critical (2000001000); aquesta última és la que fem servir al DaemonSet de logs de 06-02.
Què fa la prioritat
Actua en dos moments:
- A la cua del scheduler: els pods pendents s'ordenen per prioritat descendent. Un pod crític s'avalua abans que un de lot, encara que portés menys temps esperant.
- A la preemption: si un pod d'alta prioritat no cap en cap node, el scheduler busca un node on, desallotjant pods de menor prioritat, sí que hi cabria. Si el troba, marca aquests pods per esborrar (amb el seu
SIGTERMi el seu període de gràcia de 02-01) i programa el pod important al seu lloc.
preemptionPolicy: Never fa que un pod es beneficiï de la prioritat a la cua però no desallotgi mai ningú. És el correcte per a treballs per lots: que es programin aviat si hi ha lloc, però que no tirin res avall.
El perill d'abusar de les prioritats
És un mecanisme que es degrada sol si es fa servir malament:
- La inflació de prioritats. Cada equip posa la seva càrrega en "crítica". Al final tot és crític i la prioritat deixa de discriminar. Defineix poques classes (tres n'hi ha prou) i documenta què justifica cadascuna.
- La preemption en cascada. El pod A desallotja el B; el controlador de B el recrea en un altre node; allà desallotja el C… Un clúster al límit amb prioritats mal repartides pot passar minuts movent pods sense estabilitzar-se.
- Els desallotjaments no respecten
PodDisruptionBudgetde manera absoluta. El scheduler intenta respectar-los, però si no troba una altra opció, desallotja igualment. Els PDB són tema de 09-05. - Els pods amb estat pateixen molt. Desallotjar
postgres-reservesimplica tancar connexions, desmuntar el volum i tornar-lo a muntar en un altre node. Posa-li prioritat alta i no li posispreemptionPolicy: PreemptLowerPriorityesperant que es recol·loqui sol: el que vols és que no el toquin.
Regla pràctica: fes servir prioritats per protegir el que és important de ser desallotjat, no per aconseguir que el que és important desallotgi agressivament.
topologySpreadConstraints: menció i remissió
topologySpreadConstraints: menció i remissióExisteix un tercer mecanisme, més modern i més eficient que l'antiafinitat per al cas concret de repartir rèpliques de manera equilibrada:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: api-reserves
entorn: proEn una frase: "la diferència entre la zona amb més rèpliques d'api-reserves i la que en té menys no ha de passar d'1; si no es pot complir, programa'l igualment".
Avantatges davant de podAntiAffinity:
podAntiAffinity |
topologySpreadConstraints |
|
|---|---|---|
| Expressa | "cap de costat d'un altre" | "repartits amb desviació màxima N" |
| Granularitat | Binària | Numèrica, mitjançant maxSkew |
| Comportament si no hi cap | Pending (amb required) |
ScheduleAnyway o DoNotSchedule, a triar |
| Cost de còmput | Alt en clústers grans | Menor |
La seva aplicació completa a l'alta disponibilitat —com combinar-lo amb PodDisruptionBudgets per sobreviure a la pèrdua d'una zona— és contingut de la lliçó 09-05. Aquí n'hi ha prou amb saber que existeix i que, per al cas "reparteix les meves rèpliques", sol ser millor eina que l'antiafinitat.
- El pla de planificació de Rutas Norte
Ho ajuntem tot en un pla coherent per a rutas-norte-pro.
Etiquetatge i taints dels nodes
# Nodes amb SSD per a la base de dades
kubectl label node rutas-norte-m02 disc=ssd gamma=alta
kubectl label node rutas-norte-m03 disc=ssd gamma=alta
# Nodes estàndard per a la web i l'API
kubectl label node rutas-norte-m04 disc=hdd gamma=estandar
kubectl label node rutas-norte-m05 disc=hdd gamma=estandar
# Node dedicat a càrregues d'anàlisi, protegit amb taint
kubectl label node rutas-norte-m06 dedicat=analitica
kubectl taint node rutas-norte-m06 dedicat=analitica:NoSchedulegraph TB
subgraph SSD[Nodes disc=ssd]
M2[m02] --> PG[postgres-reserves-0]
M3[m03]
end
subgraph EST[Nodes gamma=estandar]
M4[m04] --> A1[api-reserves]
M4 --> T1[botiga-web]
M5[m05] --> A2[api-reserves]
M5 --> T2[botiga-web]
end
subgraph ANA[Node dedicat=analitica<br/>taint NoSchedule]
M6[m06] --> IO[informes-ocupacio]
end
DS[DaemonSet recollector-logs<br/>tolera tot] -.-> M2
DS -.-> M4
DS -.-> M6
postgres-reserves: fixat a nodes SSD
# k8s/entorns/pro/postgres-reserves-planificacio.yaml (fragment del StatefulSet)
spec:
template:
spec:
priorityClassName: rutasnorte-critica
affinity:
nodeAffinity:
# Requisit real: sense SSD les consultes d'ocupació no compleixen el seu SLA
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disc
operator: In
values: ["ssd", "nvme"]
# Preferència: millor a la zona on és la resta de la plataforma
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 60
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["eu-west-1a"]
# Mai dues rèpliques de PostgreSQL al mateix node
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: postgres-reserves
entorn: pro
topologyKey: kubernetes.io/hostname
# Davant d'un node inabastable, esperar més del normal abans de desallotjar:
# remuntar el volum en un altre node és car
tolerations:
- key: node.kubernetes.io/unreachable
operator: Exists
effect: NoExecute
tolerationSeconds: 600Aquí required a l'antiafinitat sí que és correcte: dues instàncies de PostgreSQL al mateix node no aporten res i sí que introdueixen contenció de disc. I com que el StatefulSet té una rèplica, no hi ha risc de bloqueig.
api-reserves i botiga-web: repartiment entre nodes
# k8s/entorns/pro/api-reserves-planificacio.yaml (fragment del Deployment)
spec:
replicas: 6
template:
spec:
priorityClassName: rutasnorte-critica
affinity:
nodeAffinity:
# Fora dels nodes d'analítica i dels de base de dades
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: dedicat
operator: DoesNotExist
- key: gamma
operator: In
values: ["estandar", "alta"]
podAntiAffinity:
# preferred, no required: amb 6 rèpliques i pocs nodes, required bloquejaria
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: api-reserves
entorn: pro
topologyKey: kubernetes.io/hostname
- weight: 50
podAffinityTerm:
labelSelector:
matchLabels:
app: api-reserves
entorn: pro
topologyKey: topology.kubernetes.io/zone
tolerations:
- key: node.kubernetes.io/unreachable
operator: Exists
effect: NoExecute
tolerationSeconds: 60Dues regles amb pesos diferents: repartir entre nodes importa el doble que repartir entre zones. El resultat en un desplegament típic:
kubectl get pods -n rutas-norte-pro -l app=api-reserves \
-o custom-columns=POD:.metadata.name,NODE:.spec.nodeName --sort-by=.spec.nodeNamePOD NODE
api-reserves-6c8f9d4b7-2m5kx rutas-norte-m02
api-reserves-6c8f9d4b7-jm2xq rutas-norte-m03
api-reserves-6c8f9d4b7-p8t4v rutas-norte-m04
api-reserves-6c8f9d4b7-q1w7z rutas-norte-m04
api-reserves-6c8f9d4b7-r9k3b rutas-norte-m05
api-reserves-6c8f9d4b7-x4n6c rutas-norte-m05Sis rèpliques en quatre nodes, tan repartides com sigui possible. Amb required la sisena s'hauria quedat Pending. botiga-web porta una configuració equivalent.
informes-ocupacio: al node d'anàlisi
El CronJob de 06-03 fa consultes agregades pesades. Que corri al node dedicat evita que competeixi amb la venda de bitllets:
# k8s/entorns/pro/informes-ocupacio-planificacio.yaml (fragment del jobTemplate)
spec:
priorityClassName: rutasnorte-lot # preemptionPolicy: Never
tolerations:
- key: dedicat
operator: Equal
value: analitica
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: dedicat
operator: In
values: ["analitica"]Les dues peces juntes, com vam explicar a l'apartat 5: la toleració li permet entrar on ningú més no pot, i l'afinitat l'obliga a anar-hi. La prioritat rutasnorte-lot amb preemptionPolicy: Never garanteix que un informe nocturn no desallotjarà mai res.
El DaemonSet de logs
Ja el vam escriure a 06-02, i ara s'entén del tot:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
- key: dedicat
operator: Equal
value: analitica
effect: NoSchedule # també al node d'anàlisiSense la segona toleració, el node m06 no tindria recol·lector i els logs dels informes nocturns no arribarien enlloc: exactament els logs que voldries consultar quan un informe falla a les tres de la matinada.
Resum del pla
| Component | Afinitat de node | Antiafinitat | Toleracions | Prioritat |
|---|---|---|---|---|
postgres-reserves |
required: disc in [ssd,nvme] |
required per hostname |
unreachable 600 s |
crítica |
api-reserves |
required: no analítica |
preferred per hostname i zone |
unreachable 60 s |
crítica |
botiga-web |
required: no analítica |
preferred per hostname |
per defecte | normal |
redis-cache |
cap | preferred per hostname |
per defecte | normal |
worker-notificacions |
required: no analítica |
preferred per hostname |
per defecte | normal |
informes-ocupacio |
required: analítica |
cap | dedicat=analitica |
lot (sense preemption) |
recollector-logs (DS) |
cap | no aplica | control-plane + analítica | system-node-critical |
- Diagnòstic del pod eternament
Pending
PendingUn pod Pending no ha trobat node. El scheduler et diu exactament per què, i aprendre a llegir aquest missatge és l'habilitat més rendible d'aquesta lliçó.
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 6m12s default-scheduler 0/6 nodes are available:
1 node(s) had untolerated taint {dedicat: analitica},
2 node(s) didn't match Pod's node affinity/selector,
3 Insufficient cpu.
preemption: 0/6 nodes are available:
1 Preemption is not helpful for scheduling,
5 No preemption victims found for incoming pod.Com es llegeix aquest missatge
La primera línia dona el recompte: de 6 nodes, 0 disponibles. Després, el desglossament per motiu, i la suma dels números ha de donar 6:
1 node(s) had untolerated taint {dedicat: analitica}→ el nodem06va rebutjar el pod perquè no porta la toleració. Si el pod erainformes-ocupacio, hi falta la toleració.2 node(s) didn't match Pod's node affinity/selector→ dos nodes no compleixen l'afinitatrequired.3 Insufficient cpu→ tres nodes no tenen prou CPU lliure per a lesrequests.
El bloc preemption: explica per què la prioritat tampoc no va ajudar: No preemption victims found significa que no hi ha pods de menor prioritat que, desallotjats, deixarien lloc.
Taula de motius i què fer
| Missatge | Causa | Solució |
|---|---|---|
Insufficient cpu / Insufficient memory |
No hi ha recursos lliures per a les requests |
Abaixar requests, afegir nodes, escalar el clúster (09-03) |
had untolerated taint {clau: valor} |
El node té un taint que el pod no tolera | Afegir la toleració o treure el taint |
didn't match Pod's node affinity/selector |
L'afinitat required o el nodeSelector no hi casen |
Revisar les etiquetes del node amb kubectl get nodes --show-labels |
didn't match pod anti-affinity rules |
Ja hi ha un pod del grup a cada domini | Canviar a preferred, ampliar topologyKey o afegir nodes |
node(s) had volume node affinity conflict |
El PV és en una altra zona que el node | Revisar volumeBindingMode de la StorageClass (05-04) |
node(s) were unschedulable |
Node amb cordon |
kubectl uncordon <node> |
node(s) had no available disk |
Pressió de disc | Alliberar espai al node |
exceeded quota (a FailedCreate, no Pending) |
ResourceQuota del namespace esgotada | Ajustar la quota (03-04) |
Rutina de diagnòstic
# 1. L'esdeveniment del scheduler: el 90 % de les vegades n'hi ha prou amb això
kubectl describe pod <pod> -n <ns> | grep -A15 Events
# 2. Quant demana el pod?
kubectl get pod <pod> -n <ns> \
-o jsonpath='{.spec.containers[*].resources.requests}{"\n"}'
# 3. Quant queda lliure a cada node? (mira "Allocated resources")
kubectl describe nodes | grep -A6 "Allocated resources"
# 4. Quines etiquetes tenen els nodes?
kubectl get nodes --show-labels
# 5. Quins taints tenen?
kubectl get nodes -o custom-columns=NODE:.metadata.name,TAINTS:.spec.taints
# 6. Esdeveniments recents del namespace, ordenats
kubectl get events -n <ns> --sort-by=.lastTimestamp | tail -20Consell de mètode: comença sempre per l'esdeveniment del scheduler. És l'únic lloc on el sistema et diu, amb recompte per motiu, què va descartar cada node. Inspeccionar nodes a ull abans de llegir-lo és perdre el temps.
Errors Comuns i Consells
Confondre toleració amb afinitat. És el malentès més estès del tema. Una toleració només evita el rebuig; no porta el pod enlloc. Per dedicar nodes calen les dues coses.
required on n'hi havia prou amb preferred. Cada regla required és un filtre absolut que pot deixar un pod Pending per sempre. Abans d'escriure-la, pregunta't: "si no es compleix, prefereixo que el pod no s'executi?". Si la resposta és no, va a preferred.
Antiafinitat required amb topologyKey: hostname i moltes rèpliques. Limita les rèpliques al nombre de nodes. Combinada amb un HPA que intenta pujar a 15 en un pic de pont, produeix pods Pending justament quan més falta fan.
Esperar que un canvi d'etiqueta mogui pods. IgnoredDuringExecution significa exactament el que diu. Per moure un pod cal esborrar-lo, o fer servir un taint NoExecute.
Fer servir nodeName en manifests de producció. Se salta el scheduler i amb ell tota la resiliència. Si el node cau, el pod no es recol·loca.
Posar operator: Exists amb value. L'API ho rebutja. Amb Exists no s'especifica valor.
Oblidar el guionet en treure un taint. kubectl taint node m06 dedicat=analitica:NoSchedule afegeix; amb el guionet final treu. Sense el guionet s'acaba amb taints duplicats.
Inflació de PriorityClass. Si tot és crític, res no ho és. Tres classes ben definides i documentades basten per a gairebé qualsevol plataforma.
Preemption sobre càrregues amb estat. Desallotjar postgres-reserves implica desmuntar i remuntar un volum en un altre node. Protegeix-lo amb prioritat alta, però no comptis que la preemption el recol·loqui netament.
Consell: kubectl get pods -o wide --sort-by=.spec.nodeName és l'ordre per verificar d'un cop d'ull que el repartiment és el que esperaves.
Consell: comprova una regla abans d'aplicar-la en producció. Aplica el manifest a rutas-norte-dev, escala a la xifra que faries servir en producció i comprova que cap rèplica no queda Pending. Una antiafinitat required mal calibrada només es manifesta quan el nombre de rèpliques supera el de nodes.
Consell: documenta per què existeix cada regla. Un podAntiAffinity sense comentari és indistingible d'un copiar i enganxar, i ningú no s'atrevirà a tocar-lo d'aquí a un any. Una línia de comentari amb el motiu estalvia molt.
Exercicis
Exercici 1: afinitat de node obligatòria i preferida
Al teu minikube (perfil rutas-norte, diversos nodes), etiqueta un node amb disc=ssd i un altre amb disc=hdd. Crea a rutas-norte-dev un Deployment bd-demo de 2 rèpliques de nginx:1.27.2-alpine que:
- Exigeixi (
required) córrer en nodes ambdisca[ssd, nvme]. - Prefereixi (
preferred, pes 70) nodes ambgamma=alta.
Comprova on es col·loquen. Després escala a 4 rèpliques i observa què passa.
Exercici 2: antiafinitat required i el seu límit
Crea un Deployment api-demo de 2 rèpliques amb antiafinitat required per kubernetes.io/hostname sobre la seva pròpia etiqueta app. Verifica que les dues rèpliques cauen en nodes diferents. Escala a un nombre més gran que el de nodes disponibles i diagnostica el pod Pending llegint l'esdeveniment del scheduler. Corregeix el problema passant a preferred.
Exercici 3: node dedicat amb taint, toleració i afinitat
Tria un node, etiqueta'l dedicat=analitica i posa-li el taint dedicat=analitica:NoSchedule. Després:
- Comprova que un pod normal no s'hi col·loca.
- Crea un Job
analitica-demoque sí que hi corri, amb toleració i afinitat. - Demostra que només la toleració no n'hi ha prou: crea un pod amb la toleració però sense afinitat i comprova on acaba.
- Neteja el taint i les etiquetes.
Solucions
Solució 1
kubectl get nodes
kubectl label node rutas-norte-m02 disc=ssd gamma=alta --overwrite
kubectl label node rutas-norte-m03 disc=hdd gamma=estandar --overwrite# /tmp/bd-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: bd-demo
namespace: rutas-norte-dev
labels:
app: bd-demo
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
replicas: 2
selector:
matchLabels:
app: bd-demo
entorn: dev
template:
metadata:
labels:
app: bd-demo
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
automountServiceAccountToken: false
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disc
operator: In
values: ["ssd", "nvme"]
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 70
preference:
matchExpressions:
- key: gamma
operator: In
values: ["alta"]
containers:
- name: nginx
image: nginx:1.27.2-alpine
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128MiNAME READY STATUS RESTARTS AGE NODE
bd-demo-7f9c4d8b6-h2m4x 1/1 Running 0 18s rutas-norte-m02
bd-demo-7f9c4d8b6-t5k7p 1/1 Running 0 18s rutas-norte-m02Les dues rèpliques van a m02, l'únic node amb disc=ssd. Que a més tingui gamma=alta li dona 70 punts extra, però era irrellevant: el filtratge ja havia deixat un sol candidat.
kubectl scale deployment bd-demo -n rutas-norte-dev --replicas=4
kubectl get pods -n rutas-norte-dev -l app=bd-demo -o wideNAME READY STATUS RESTARTS AGE NODE
bd-demo-7f9c4d8b6-h2m4x 1/1 Running 0 2m rutas-norte-m02
bd-demo-7f9c4d8b6-t5k7p 1/1 Running 0 2m rutas-norte-m02
bd-demo-7f9c4d8b6-v8n3q 1/1 Running 0 12s rutas-norte-m02
bd-demo-7f9c4d8b6-w1x6z 1/1 Running 0 12s rutas-norte-m02Les quatre al mateix node. L'afinitat de node diu on pot anar un pod, però no diu res sobre repartir. Per a això cal antiafinitat, que és l'exercici 2. Si m02 es reinicia, cau bd-demo sencer.
Solució 2
# /tmp/api-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-demo
namespace: rutas-norte-dev
labels:
app: api-demo
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
replicas: 2
selector:
matchLabels:
app: api-demo
entorn: dev
template:
metadata:
labels:
app: api-demo
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
automountServiceAccountToken: false
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: api-demo
entorn: dev
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx:1.27.2-alpine
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128MiNAME READY STATUS RESTARTS AGE NODE
api-demo-8b5d7c9f4-k3m2x 1/1 Running 0 15s rutas-norte-m02
api-demo-8b5d7c9f4-r7t9p 1/1 Running 0 15s rutas-norte-m03Una per node, tal com es demanava.
kubectl scale deployment api-demo -n rutas-norte-dev --replicas=4
kubectl get pods -n rutas-norte-dev -l app=api-demo -o wideNAME READY STATUS RESTARTS AGE NODE
api-demo-8b5d7c9f4-k3m2x 1/1 Running 0 2m rutas-norte-m02
api-demo-8b5d7c9f4-r7t9p 1/1 Running 0 2m rutas-norte-m03
api-demo-8b5d7c9f4-w4n8q 0/1 Pending 0 20s <none>
api-demo-8b5d7c9f4-z2v5c 0/1 Pending 0 20s <none>kubectl describe pod -n rutas-norte-dev \
$(kubectl get pod -n rutas-norte-dev -l app=api-demo \
--field-selector=status.phase=Pending -o jsonpath='{.items[0].metadata.name}') | tail -5Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 25s default-scheduler 0/3 nodes are available:
1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: },
2 node(s) didn't match pod anti-affinity rules.El missatge és explícit: dos nodes ja tenen una rèplica i la regla required prohibeix una segona; el tercer és el pla de control, amb el seu taint. Amb antiafinitat required per hostname, el màxim de rèpliques és el nombre de nodes elegibles.
Correcció:
kubectl patch deployment api-demo -n rutas-norte-dev --type=json -p='[
{"op": "remove", "path": "/spec/template/spec/affinity/podAntiAffinity/requiredDuringSchedulingIgnoredDuringExecution"},
{"op": "add", "path": "/spec/template/spec/affinity/podAntiAffinity/preferredDuringSchedulingIgnoredDuringExecution",
"value": [{"weight": 100, "podAffinityTerm": {
"labelSelector": {"matchLabels": {"app": "api-demo", "entorn": "dev"}},
"topologyKey": "kubernetes.io/hostname"}}]}
]'
kubectl rollout status deployment/api-demo -n rutas-norte-dev
kubectl get pods -n rutas-norte-dev -l app=api-demo -o wideNAME READY STATUS RESTARTS AGE NODE
api-demo-6d4f8a7c2-b1n5k 1/1 Running 0 40s rutas-norte-m02
api-demo-6d4f8a7c2-h9m3t 1/1 Running 0 38s rutas-norte-m03
api-demo-6d4f8a7c2-p6x2w 1/1 Running 0 35s rutas-norte-m02
api-demo-6d4f8a7c2-y8k4r 1/1 Running 0 33s rutas-norte-m03Quatre rèpliques repartides dues i dues: la preferència va repartir tan bé com va poder i, esgotades les alternatives, va permetre duplicar en comptes de bloquejar.
Solució 3
kubectl label node rutas-norte-m03 dedicat=analitica --overwrite
kubectl taint node rutas-norte-m03 dedicat=analitica:NoSchedule
kubectl get nodes -o custom-columns=NODE:.metadata.name,TAINTS:.spec.taintsNODE TAINTS
rutas-norte [map[effect:NoSchedule key:node-role.kubernetes.io/control-plane]]
rutas-norte-m02 <none>
rutas-norte-m03 [map[effect:NoSchedule key:dedicat value:analitica]]1. Un pod normal no hi va:
kubectl run normal-demo -n rutas-norte-dev --image=nginx:1.27.2-alpine --restart=Never
kubectl wait --for=condition=ready pod/normal-demo -n rutas-norte-dev --timeout=60s
kubectl get pod normal-demo -n rutas-norte-dev -o wideVa acabar a m02, l'únic sense taint que el rebutgi.
2. Job amb toleració i afinitat:
# /tmp/analitica-demo.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: analitica-demo
namespace: rutas-norte-dev
labels:
app: informes-ocupacio
entorn: dev
spec:
backoffLimit: 1
ttlSecondsAfterFinished: 3600
template:
metadata:
labels:
app: informes-ocupacio
entorn: dev
spec:
restartPolicy: Never
automountServiceAccountToken: false
tolerations:
- key: dedicat
operator: Equal
value: analitica
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: dedicat
operator: In
values: ["analitica"]
containers:
- name: calcul
image: busybox:1.36
command: ["sh", "-c", "echo 'calculant ocupació al node dedicat'; sleep 10"]
resources:
requests:
cpu: 20m
memory: 32Mi
limits:
cpu: 100m
memory: 64Mikubectl apply -f /tmp/analitica-demo.yaml
kubectl get pods -n rutas-norte-dev -l job-name=analitica-demo -o wide3. Només la toleració no n'hi ha prou:
# /tmp/nomes-toleracio.yaml
apiVersion: v1
kind: Pod
metadata:
name: nomes-toleracio
namespace: rutas-norte-dev
labels:
app: nomes-toleracio
entorn: dev
spec:
automountServiceAccountToken: false
tolerations:
- key: dedicat
operator: Equal
value: analitica
effect: NoSchedule
containers:
- name: nginx
image: nginx:1.27.2-alpine
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mikubectl apply -f /tmp/nomes-toleracio.yaml
kubectl wait --for=condition=ready pod/nomes-toleracio -n rutas-norte-dev --timeout=60s
kubectl get pod nomes-toleracio -n rutas-norte-dev -o widePodia haver anat a m03 i no hi va anar: la toleració permet però no atrau. El scheduler va triar m02 per la seva puntuació. Això confirma l'advertiment de l'apartat 5: per dedicar nodes calen taint + toleració + afinitat.
4. Neteja:
kubectl taint node rutas-norte-m03 dedicat=analitica:NoSchedule-
kubectl label node rutas-norte-m03 dedicat-
kubectl label node rutas-norte-m02 disc- gamma- --overwrite
kubectl label node rutas-norte-m03 disc- gamma- --overwrite
kubectl delete -f /tmp/analitica-demo.yaml -f /tmp/nomes-toleracio.yaml \
-f /tmp/api-demo.yaml -f /tmp/bd-demo.yaml
kubectl delete pod normal-demo -n rutas-norte-devConclusió
El kube-scheduler decideix en dues fases: filtra els nodes inviables i puntua els que queden. Aquesta estructura explica tota la lliçó: les regles required actuen en el filtratge i poden deixar un pod Pending; les preferred actuen en la puntuació i només influeixen en l'elecció.
Hem recorregut els mecanismes de menys a més expressiu: nodeName (força bruta, només per depurar), nodeSelector (igualtat simple), afinitat de node amb els seus nodeSelectorTerms en OR, els seus matchExpressions en AND i els seus operadors In, NotIn, Exists, DoesNotExist, Gt i Lt. L'IgnoredDuringExecution dels seus noms llargs significa que un canvi a les etiquetes del node no mourà mai un pod ja programat.
L'afinitat i antiafinitat entre pods miren qui més hi ha al domini que defineix topologyKey: node, zona o regió, segons de quina fallada et vulguis protegir. El seu cost de còmput és real en clústers grans, i per al cas concret de repartir rèpliques convé mirar cap a topologySpreadConstraints, l'ús del qual per a alta disponibilitat és contingut de 09-05.
Els taints inverteixen la relació: és el node qui rebutja, amb tres efectes (NoSchedule, PreferNoSchedule i NoExecute, l'únic que desallotja pods en marxa). Kubernetes els fa servir pel seu compte quan un node falla, i les toleracions automàtiques amb tolerationSeconds: 300 expliquen el retard de cinc minuts abans que els pods d'un node caigut reapareguin en un altre lloc. La regla que cal gravar: una toleració permet, no atrau; per dedicar nodes calen taint, toleració i afinitat.
Les PriorityClass ordenen la cua i habiliten la preemption, amb el risc de la inflació de prioritats i de les cascades de desallotjament. I l'esdeveniment FailedScheduling, amb el seu recompte de nodes descartats per cada motiu, és la primera i gairebé sempre l'única parada del diagnòstic d'un pod Pending.
Amb això, el pla de planificació de Rutas Norte queda complet: postgres-reserves als nodes amb SSD, api-reserves i botiga-web repartides entre nodes, un node d'anàlisi reservat per a informes-ocupacio i un DaemonSet de logs que arriba a tot arreu.
Fins aquí hem fet servir els objectes que Kubernetes porta de fàbrica: Pods, Deployments, StatefulSets, Services, PVC, Jobs. Però al mòdul 4 vam instal·lar cert-manager i vam començar a crear objectes de tipus Certificate i Issuer, i al mòdul 5 vam crear VolumeSnapshot. Cap d'aquests tipus no forma part de Kubernetes: algú els va afegir a l'API, i el clúster els tracta exactament igual que els natius —kubectl get, describe, explain, validació, RBAC—. Com es fa això, i com definiríem el nostre propi tipus RutaProgramada per a Rutas Norte, és el tema de la lliçó següent: les Definicions de Recursos Personalitzats.
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
