La lliçó anterior va acabar amb un problema sense resoldre: api-reserves i botiga-web funcionen a rutas-norte-dev, però com a pods solts. Si el node cau, si el kubelet els desallotja per manca de memòria o si algú els esborra, desapareixen i ningú no els torna a la vida. Necessitem alguna cosa que vigili permanentment i actuï. Aquest alguna cosa és un controlador, i el primer que coneixerem a fons és el ReplicaSet: l'objecte l'única missió del qual és garantir que en tot moment existeixi un nombre concret de pods que encaixin amb un conjunt d'etiquetes. En aquesta lliçó veuràs què és exactament un controlador i com el de ReplicaSet materialitza el bucle de reconciliació que vas estudiar a l'arquitectura, escriuràs el teu primer manifest de ReplicaSet per a botiga-web, comprovaràs l'autoreparació esborrant pods a propòsit, entendràs com l'esborrat en cascada s'aguanta sobre les ownerReferences, descobriràs el mecanisme d'adopció de pods orfes i per què un selector massa ampli és una bomba de rellotgeria, i aprendràs a escalar amb kubectl scale. I acabaràs sabent per què, malgrat tot això, gairebé mai no escriuràs un ReplicaSet a mà.
Contingut
- Què és un controlador
- El controlador de ReplicaSet i el bucle de reconciliació
- Anatomia del manifest:
replicas,selectoritemplate - La regla d'or: el
templateha d'encaixar amb elselector - Autoreparació en directe amb
botiga-web ownerReferencesi esborrat en cascada- Adopció de pods orfes i el perill dels selectors amplis
- Escalat manual amb
kubectl scale - ReplicaSet enfront de ReplicationController
- Per què gairebé mai no es crea un ReplicaSet a mà
- Què és un controlador
A Arquitectura de Kubernetes vam definir el bucle de reconciliació com el cor del sistema. Un controlador és simplement un programa que executa aquest bucle per a un tipus d'objecte:
flowchart LR
A["Observa<br/>l'estat desitjat<br/>(spec)"] --> B["Observa<br/>l'estat real<br/>(món)"]
B --> C{"Coincideixen?"}
C -->|Sí| D["No fa res.<br/>Actualitza status"]
C -->|No| E["Actua per<br/>acostar el real<br/>al desitjat"]
E --> A
D --> A
Característiques que comparteixen tots els controladors de Kubernetes i que convé interioritzar:
- No acaben mai. Són bucles infinits, no scripts d'un sol tret.
- Són idempotents. Executar el bucle mil vegades amb l'estat ja correcte no canvia res.
- Només parlen amb el kube-apiserver. Mai no contacten directament amb els nodes ni amb els contenidors. Demanen canvis a l'API i el kubelet els materialitza.
- Actuen per nivell, no per esdeveniment. No reaccionen a "s'ha esborrat un pod"; comparen constantment quants n'hi ha contra quants n'hi hauria d'haver. Per això són robustos: si el controlador ha estat caigut deu minuts, en tornar corregeix la diferència acumulada sense necessitat de reproduir l'històric.
La majoria viu dins del kube-controller-manager: el de Deployment, el de ReplicaSet, el de Job, el de Namespace, el d'endpoints, el de ServiceAccount... Tots amb la mateixa anatomia i responsabilitats diferents.
- El controlador de ReplicaSet i el bucle de reconciliació
Concretem el bucle genèric per al cas que ens ocupa. El controlador de ReplicaSet executa, sense parar, per a cada ReplicaSet del clúster:
- Llegeix
spec.replicas: quants pods han d'existir (estat desitjat). - Llegeix
spec.selector: quines etiquetes identifiquen els seus pods. - Consulta a l'API quants pods d'aquell namespace encaixen amb el selector i no estan acabant (estat real).
- Compara:
- Si falten pods, en crea tants com en falten fent servir
spec.templatecom a motlle. - Si en sobren, tria víctimes i les esborra.
- Si coincideixen, no fa res i actualitza el
status.
- Si falten pods, en crea tants com en falten fent servir
flowchart TD
RS["ReplicaSet botiga-web<br/>spec.replicas = 3<br/>spec.selector: app=botiga-web"]
RS --> Q{"Pods amb app=botiga-web<br/>trobats: 2"}
Q -->|2 < 3| CREATE["Crear 1 pod<br/>a partir de spec.template"]
CREATE --> API["kube-apiserver<br/>persisteix el pod nou"]
API --> SCH["kube-scheduler<br/>li assigna node"]
SCH --> KUB["kubelet<br/>arrenca els contenidors"]
KUB --> Q
Un matís decisiu, i que explica tot el que ve després: el ReplicaSet no porta una llista dels pods que ha creat. No té memòria. Cada volta del bucle torna a preguntar "quins pods hi ha amb aquestes etiquetes?". La seva relació amb els pods és purament per etiqueta. Això té dues conseqüències enormes:
- Si n'esborres un pod seu, a la volta següent veu que en falten i en crea un de nou. Autoreparació.
- Si apareix per allà un pod amb aquestes mateixes etiquetes creat per un altre, el ReplicaSet el compta com a propi. Adopció. I si amb ell ja en sobren, n'esborrarà algun.
Quan tria qui esborrar en cas que en sobrin: el controlador prioritza eliminar pods Pending sobre Running, els que porten menys temps llestos, els que tenen més reinicis i els que són en nodes amb més rèpliques. És a dir, sacrifica sempre el menys valuós.
- Anatomia del manifest:
replicas, selector i template
replicas, selector i templateConvertirem botiga-web en una cosa que es cuidi sola. Crea el fitxer al repositori del projecte:
# k8s/base/botiga-web-replicaset.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: botiga-web
namespace: rutas-norte-dev
labels:
app: botiga-web
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
replicas: 3
selector:
matchLabels:
app: botiga-web
entorn: dev
template:
metadata:
labels:
app: botiga-web
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
ports:
- name: http
containerPort: 80
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"Lectura camp per camp, amb atenció al que canvia respecte del pod solt:
apiVersion: apps/v1. Aquí sí que hi ha grup. El Pod és al grup core (v1), però ReplicaSet, Deployment, StatefulSet i DaemonSet viuen al grupapps. Si escriusv1a seques, l'API rebutjarà el manifest ambno matches for kind "ReplicaSet" in version "v1".metadata(el de dalt): identifica el ReplicaSet, no els pods. Les seves etiquetes serveixen per trobar el mateix ReplicaSet ambkubectl get rs -l ....spec.replicas: 3: l'estat desitjat. És un número, no un rang. L'escalat automàtic per càrrega arriba al mòdul 9.spec.selector.matchLabels: quins pods són meus. Aquí exigim dues etiquetes: un pod ha de tenirapp=botiga-webientorn=devper comptar. El selector admet també expressions més riques (matchExpressions), que veurem a Etiquetes, Selectors i Anotacions. És un camp obligatori i immutable.spec.template: la plantilla del pod. És exactament un manifest de pod senseapiVersionnikind, amb el seu propimetadata(etiquetes dels pods fills) i el seuspec(contenidors). És el motlle que el controlador fa servir per fabricar rèpliques.
Fixa't en l'estructura de nina russa, que és el punt on més s'encalla tothom al principi:
ReplicaSet
├── metadata <- identifica el ReplicaSet
└── spec
├── replicas <- quants pods
├── selector <- quins pods són meus
└── template
├── metadata <- etiquetes de CADA POD creat
└── spec <- contenidors de CADA POD creatAbans d'aplicar-lo, retira el pod solt de botiga-web per no interferir en l'experiment (veuràs a l'apartat 7 exactament què passaria si no ho féssim):
kubectl delete pod botiga-web --ignore-not-found
kubectl apply -f k8s/base/botiga-web-replicaset.yaml
kubectl get rs,podspod "botiga-web" deleted
replicaset.apps/botiga-web created
NAME DESIRED CURRENT READY AGE
replicaset.apps/botiga-web 3 3 3 8s
NAME READY STATUS RESTARTS AGE
pod/api-reserves 1/1 Running 0 51m
pod/botiga-web-4kx7d 1/1 Running 0 8s
pod/botiga-web-9wq2m 1/1 Running 0 8s
pod/botiga-web-pv6cl 1/1 Running 0 8sDues observacions sobre aquella sortida:
- Els pods s'anomenen
botiga-web-<sufix aleatori>. El controlador genera el nom a partir del nom del ReplicaSet més cinc caràcters aleatoris, perquè no hi pot haver tres objectes amb el mateix nom en un namespace. - Les columnes del ReplicaSet:
DESIREDésspec.replicas;CURRENT, quants pods existeixen;READY, quants estan llestos per rebre trànsit. Quan les tres coincideixen, el bucle és en equilibri.
- La regla d'or: el
template ha d'encaixar amb el selector
template ha d'encaixar amb el selectorAquesta és la restricció que més manifests rebutjats provoca:
Les etiquetes de
spec.template.metadata.labelshan de satisferspec.selector. Si no, l'API rebutja l'objecte.
La raó és de pura lògica: si el ReplicaSet fabriqués pods que el seu propi selector no reconeix, comptaria zero pods propis, en crearia tres més, tampoc no els reconeixeria, en crearia tres més… un bucle infinit que ompliria el clúster. Kubernetes ho impedeix d'arrel validant el manifest.
Comprova-ho provocant l'error a propòsit:
sed 's/app: botiga-web$/app: botiga-web-mal/' k8s/base/botiga-web-replicaset.yaml \
| kubectl apply --dry-run=server -f -The ReplicaSet "botiga-web" is invalid: spec.template.metadata.labels:
Invalid value: map[string]string{"app":"botiga-web-mal", ...}:
`selector` does not match template `labels`Fixa't en l'asimetria, que sí que és vàlida i molt útil: el template pot tenir més etiquetes que el selector. Al nostre manifest el selector demana app i entorn, però els pods porten a més app.kubernetes.io/part-of. Això està permès; el que és prohibit és que en falti alguna de les que el selector exigeix.
Com a norma de projecte: mantén el selector tan petit i estable com sigui possible (una o dues etiquetes que identifiquin inequívocament el component i l'entorn) i afegeix la resta de metadades només al template.
- Autoreparació en directe amb
botiga-web
botiga-webHa arribat el moment de comprovar el que el mòdul 1 ens va deixar pendent. Obre dues terminals.
A la primera, posa't a observar:
A la segona, mata un pod a traïció (fes servir un dels noms reals del teu clúster):
A la primera terminal veuràs una cosa així:
NAME READY STATUS RESTARTS AGE
botiga-web-4kx7d 1/1 Running 0 4m
botiga-web-9wq2m 1/1 Running 0 4m
botiga-web-pv6cl 1/1 Running 0 4m
botiga-web-9wq2m 1/1 Terminating 0 4m
botiga-web-t8m4r 0/1 Pending 0 0s
botiga-web-t8m4r 0/1 ContainerCreating 0 0s
botiga-web-9wq2m 0/1 Terminating 0 4m
botiga-web-t8m4r 1/1 Running 0 2sMenys de dos segons. I una cosa molt reveladora: el pod substitut (botiga-web-t8m4r) apareix en Pending abans que l'esborrat de l'anterior hagi acabat. El controlador no espera que el vell desaparegui del tot; tan bon punt l'API marca el pod com a acabant, deixa de comptar-lo i actua.
Els esdeveniments del ReplicaSet ho expliquen per escrit:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 4m replicaset-controller Created pod: botiga-web-4kx7d
Normal SuccessfulCreate 4m replicaset-controller Created pod: botiga-web-9wq2m
Normal SuccessfulCreate 4m replicaset-controller Created pod: botiga-web-pv6cl
Normal SuccessfulCreate 9s replicaset-controller Created pod: botiga-web-t8m4rCompara-ho amb el que va passar al mòdul 1 en esborrar el pod solt: res. Silenci. Aquí, en canvi, hi ha un replicaset-controller signant cada creació. Aquesta és, en una línia, la diferència entre un contenidor i una plataforma. Per a Rutas Norte significa que la caiguda nocturna de quatre hores i mitja del març s'hauria resolt sola en dos segons, de matinada, sense que ningú se n'assabentés.
Un experiment encara més contundent: esborra'ls tots alhora.
pod "botiga-web-4kx7d" deleted
pod "botiga-web-pv6cl" deleted
pod "botiga-web-t8m4r" deleted
NAME READY STATUS RESTARTS AGE
botiga-web-2jf9x 1/1 Running 0 3s
botiga-web-hs4bd 1/1 Running 0 3s
botiga-web-zq7nm 1/1 Running 0 3sTres pods nous, amb noms nous i IPs noves. I aquí treu el cap el problema següent del curs: si les IPs canvien cada vegada, a quina adreça es connecta botiga-web per parlar amb api-reserves? Aquesta és la feina dels Serveis.
ownerReferences i esborrat en cascada
ownerReferences i esborrat en cascadaCom sap Kubernetes que aquests pods "pertanyen" al ReplicaSet, si la relació és per etiquetes? Perquè, en crear-los, el controlador estampa a cada pod una referència al seu propietari:
[
{
"apiVersion": "apps/v1",
"kind": "ReplicaSet",
"name": "botiga-web",
"uid": "6c1f8f0e-2b7a-4a91-9a3d-1d8f2c5e77b1",
"controller": true,
"blockOwnerDeletion": true
}
]Camp per camp:
| Camp | Significat |
|---|---|
kind / name / uid |
Qui és el propietari. L'uid importa: si esborres el ReplicaSet i en crees un altre amb el mateix nom, és un objecte diferent |
controller: true |
Aquest propietari és el controlador del pod. Només n'hi pot haver un |
blockOwnerDeletion: true |
El propietari no es considera esborrat fins que aquest fill desaparegui |
Sobre aquestes referències treballa el garbage collector, un altre controlador del kube-controller-manager: recorre els objectes, i quan en troba un el propietari del qual ja no existeix, l'esborra. Això és l'esborrat en cascada, i és la raó que kubectl delete rs botiga-web s'endugui també els tres pods.
Kubernetes ofereix tres polítiques de propagació:
| Política | Comportament | Com es demana |
|---|---|---|
Background (per defecte) |
Esborra el propietari immediatament; el recol·lector esborra els fills després | kubectl delete rs botiga-web |
Foreground |
Marca el propietari com a "en esborrat", esborra primer els fills i al final el propietari | --cascade=foreground |
Orphan |
Esborra només el propietari i deixa vius els fills, eliminant-los les ownerReferences |
--cascade=orphan |
La tercera és un recurs d'emergència molt útil, i de passada ens prepara el terreny per a l'apartat següent:
replicaset.apps "botiga-web" deleted
NAME READY STATUS RESTARTS AGE
pod/botiga-web-2jf9x 1/1 Running 0 6m
pod/botiga-web-hs4bd 1/1 Running 0 6m
pod/botiga-web-zq7nm 1/1 Running 0 6mEl ReplicaSet ja no existeix, però els tres pods continuen servint trànsit. Ara són orfes: ningú no els vigila. Si n'esborres un, no torna.
- Adopció de pods orfes i el perill dels selectors amplis
Tenim tres pods orfes amb les etiquetes app=botiga-web, entorn=dev. Torna a crear el ReplicaSet:
replicaset.apps/botiga-web created
NAME DESIRED CURRENT READY AGE
replicaset.apps/botiga-web 3 3 3 4s
NAME READY STATUS RESTARTS AGE
pod/botiga-web-2jf9x 1/1 Running 0 8m
pod/botiga-web-hs4bd 1/1 Running 0 8m
pod/botiga-web-zq7nm 1/1 Running 0 8mMira bé les edats: 8 minuts. El ReplicaSet acaba de néixer i no ha creat ni un sol pod. Ha adoptat els tres orfes perquè encaixaven amb el seu selector i no tenien propietari. En adoptar-los, els ha escrit les seves ownerReferences:
Les regles exactes de l'adopció:
- El pod ha de ser al mateix namespace.
- Les seves etiquetes han d'encaixar amb el selector.
- No ha de tenir ja unes
ownerReferencesambcontroller: true. Un pod amb propietari no es roba: el ReplicaSet l'ignora.
El perill: selectors massa amplis
Aquí hi ha el parany. Suposem que algú, amb pressa, defineix el ReplicaSet de botiga-web amb un selector mandrós:
# MALAMENT: selector massa ampli
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/part-of: rutas-norte # tots els components porten aquesta etiqueta!Aquest selector encaixa amb tots els pods de la plataforma: botiga-web, api-reserves, redis-cache, el worker... Conseqüències, en cadena:
- El ReplicaSet compta tots els pods de Rutas Norte que hi ha al namespace. Suposem-ne 5.
- El seu estat desitjat són 3. En sobren 2.
- Tria dues víctimes i les esborra. Pot perfectament esborrar
api-reservesi el worker. - Els controladors d'aquests components els recreen, el ReplicaSet torna a veure que en sobren i els esborra un altre cop. Guerra de controladors.
És un incident real i força habitual en clústers joves, i el símptoma —pods que s'esborren sols sense explicació— és desconcertant fins que s'entén el mecanisme. Simulem-lo amb seguretat per veure-ho amb els nostres propis ulls:
# Un pod solt que porta per casualitat les etiquetes del selector
kubectl run intrus --image=nginx:1.27-alpine \
--labels="app=botiga-web,entorn=dev,app.kubernetes.io/part-of=rutas-norte"
sleep 5
kubectl get pods -l app=botiga-webpod/intrus created
NAME READY STATUS RESTARTS AGE
intrus 1/1 Terminating 0 5s
botiga-web-2jf9x 1/1 Running 0 12m
botiga-web-hs4bd 1/1 Running 0 12m
botiga-web-zq7nm 1/1 Running 0 12mEl ReplicaSet ha adoptat intrus, n'ha comptat 4 on n'hi havia d'haver 3 i l'ha executat a l'acte (era el més jove, la víctima preferent). Si en lloc d'un nginx de prova hagués estat un pod important, hauria caigut igual.
Les regles del projecte que se'n deriven:
- El selector ha d'identificar el component de manera inequívoca:
app: <component>mésentorn: <entorn>, mai noméspart-of. - Cap component no comparteix selector amb un altre.
- No creïs mai pods solts amb les etiquetes d'un component governat. Per depurar, fes servir etiquetes diferents o pods efímers amb
--rm.
- Escalat manual amb
kubectl scale
kubectl scaleCanviar el nombre de rèpliques és canviar spec.replicas. Hi ha tres formes.
Imperativa, ràpida (per a incidències):
Condicional, molt útil en scripts per evitar trepitjar canvis d'altri:
Si en aquell moment no hi ha exactament 5 rèpliques, l'ordre falla en lloc d'aplicar el canvi.
Declarativa, la correcta segons les convencions del projecte: edita replicas: 5 al fitxer, fes commit i aplica.
| Forma | Avantatge | Problema |
|---|---|---|
kubectl scale |
Instantani, ideal en un incident | El clúster deixa de coincidir amb Git; el pròxim apply reverteix el canvi |
| Editar el manifest | Traçable, revisable, reproduïble | Més lent |
La regla que ja coneixes del mòdul 1 continua vigent: si un canvi no és a Git, no existeix. Escala amb kubectl scale per apagar un foc, però porta el canvi al fitxer immediatament després.
Torna a 3 abans de continuar:
I observa què passa en reduir:
NAME READY STATUS RESTARTS AGE
botiga-web-2jf9x 1/1 Running 0 16m
botiga-web-hs4bd 1/1 Running 0 16m
botiga-web-k9dpq 1/1 Terminating 0 40s
botiga-web-w2sxv 1/1 Terminating 0 40s
botiga-web-zq7nm 1/1 Running 0 16mEls que se'n van són els més joves, exactament com havíem anticipat: el controlador sacrifica el menys consolidat.
- ReplicaSet enfront de ReplicationController
Veuràs documentació antiga i respostes de fòrums parlant de ReplicationController. És l'antecessor del ReplicaSet, de l'època de Kubernetes 1.0, i continua existint per compatibilitat, però no l'has de fer servir.
| Aspecte | ReplicationController | ReplicaSet |
|---|---|---|
| Grup d'API | v1 (core) |
apps/v1 |
| Selector | Només igualtat simple (app: botiga-web) |
Igualtat i conjunts (matchExpressions: in, notin, exists) |
| Camp del selector | spec.selector com a mapa pla |
spec.selector.matchLabels / matchExpressions |
| El fa servir el Deployment | No | Sí |
| Estat | Obsolet de fet | Vigent |
La diferència funcional rellevant és el selector de conjunts. Gràcies a matchExpressions, un Deployment pot llançar consultes com "pods d'api-reserves el pod-template-hash dels quals sigui un d'aquests dos", que és just el que necessita per gestionar dues versions simultànies durant una actualització progressiva. Amb el selector pla del ReplicationController allò no era expressable, i per això els Deployments es van construir sobre ReplicaSets.
Resum operatiu: si veus kind: ReplicationController, és codi heretat. Migra a Deployment.
- Per què gairebé mai no es crea un ReplicaSet a mà
I aquí arriba la sorpresa de la lliçó: després de tot això, a la teva vida professional escriuràs molt pocs ReplicaSets. El que escriuràs són Deployments.
La raó és que al ReplicaSet li falta l'essencial per operar un servei: no sap canviar de versió. És magnífic mantenint N còpies d'una plantilla, però si modifiques la imatge del seu template:
kubectl set image rs/botiga-web nginx=nginx:1.27.1-alpine
kubectl get pods -l app=botiga-web -o custom-columns=NOM:.metadata.name,IMATGE:.spec.containers[0].imagereplicaset.apps/botiga-web image updated
NOM IMATGE
botiga-web-2jf9x nginx:1.27-alpine
botiga-web-hs4bd nginx:1.27-alpine
botiga-web-zq7nm nginx:1.27-alpineEls pods continuen amb la imatge vella. El ReplicaSet només fa servir el template quan crea un pod; els existents no es toquen perquè l'estat desitjat ("tres pods amb aquestes etiquetes") continua satisfet. Per desplegar la versió nova hauries d'esborrar-los a mà, un a un, esperant entremig. Sense control de ritme, sense verificació, sense marxa enrere.
Això és precisament el que aporta el Deployment: gestiona ReplicaSets, no pods. Quan canvies la imatge, crea un ReplicaSet nou amb la versió nova i va traspassant rèpliques d'un a l'altre de forma controlada.
| Necessitat | ReplicaSet | Deployment |
|---|---|---|
| Mantenir N rèpliques vives | Sí | Sí (a través d'un ReplicaSet) |
| Escalar | Sí | Sí |
| Actualitzar la imatge sense tall | No | Sí |
| Tornar a la versió anterior | No | Sí (rollout undo) |
| Historial de revisions | No | Sí |
| Pausar un desplegament a mitges | No | Sí |
Per això la regla del projecte és:
A Rutas Norte no es creen ReplicaSets a mà. Es creen Deployments, i ells gestionen els seus ReplicaSets.
Aleshores, per a què serveix aquesta lliçó? Per entendre el nivell intermedi. Quan a la lliçó següent vegis dos ReplicaSets del mateix Deployment convivint durant una actualització, o quan en producció hagis d'esbrinar per què hi ha pods de dues versions alhora, l'explicació serà en el que acabes d'aprendre. Els ReplicaSets no desapareixen: es tornen invisibles perquè els gestiona un altre.
Deixa el clúster llest per a la lliçó següent:
replicaset.apps "botiga-web" deleted
pod "api-reserves" deleted
No resources found in rutas-norte-dev namespace.Errors Comuns i Consells
- Fer servir
apiVersion: v1per a un ReplicaSet. És aapps/v1. L'error ésno matches for kind "ReplicaSet" in version "v1". - Etiquetes del
templateque no encaixen amb elselector. L'API rebutja l'objecte ambselector does not match template labels. Revisa que estàs mirantspec.template.metadata.labelsi no elmetadata.labelsde dalt: són llocs diferents. - Selectors massa amplis. És l'error més destructiu d'aquesta lliçó: un ReplicaSet pot adoptar i esborrar pods d'un altre component. Selector = component + entorn, sempre.
- Crear pods solts amb les etiquetes d'un component governat. Seran adoptats i, molt probablement, executats en segons.
- Esperar que canviar el
templateactualitzi els pods existents. No ho fa. Només afecta els pods que es creïn a partir d'aquell moment. Aquesta és la mancança que resol el Deployment. - Intentar canviar el
selectord'un ReplicaSet existent. És immutable. Cal esborrar i recrear (o, a la pràctica, deixar que el Deployment gestioni el canvi creant un ReplicaSet nou). - Confondre
CURRENTambREADY.CURRENTcompta pods que existeixen;READY, pods que poden atendre trànsit. Un3/3aCURRENTamb0aREADYés un desplegament trencat. - Consell:
kubectl get rs -o wideafegeix les columnesCONTAINERS,IMAGESiSELECTOR. És la manera més ràpida de veure d'un cop d'ull quina versió serveix cada ReplicaSet. - Consell: quan alguna cosa es comporti de forma inexplicable, mira sempre
kubectl get pod <nom> -o jsonpath='{.metadata.ownerReferences}'. Saber qui és el propietari d'un pod resol la meitat dels misteris.
Exercicis
Exercici 1: ReplicaSet d'api-reserves i autoreparació
Escriu k8s/base/api-reserves-replicaset.yaml amb un ReplicaSet de 2 rèpliques d'api-reserves a rutas-norte-dev, reaprofitant el contenidor de la lliçó Pods (imatge node:20-alpine amb el servidor mínim) i respectant les tres etiquetes del projecte. El selector ha de fer servir app i entorn.
- Aplica'l i comprova que hi ha 2 pods.
- Esborra'n un i mesura quant triga a aparèixer el substitut.
- Esbrina, amb una sola ordre, qui és el propietari del pod nou.
- Mostra els esdeveniments del ReplicaSet que documenten la creació.
Exercici 2: Orfes i adopció
Partint del ReplicaSet de l'exercici anterior:
- Esborra'l amb la política que deixa vius els pods i comprova que hi continuen sent.
- Verifica que els pods ja no tenen propietari.
- Torna a aplicar el mateix manifest i demostra, mirant l'edat dels pods, que no s'han creat pods nous.
- Explica en tres línies per què això és un avantatge operatiu real i què hauria passat si el ReplicaSet nou hagués demanat 1 rèplica en lloc de 2.
Exercici 3: Investigar un selector perillós
Un company ha desplegat a rutas-norte-dev aquest manifest i des d'aleshores "els pods s'esborren sols":
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: monitor-plataforma
namespace: rutas-norte-dev
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/part-of: rutas-norte
template:
metadata:
labels:
app.kubernetes.io/part-of: rutas-norte
spec:
containers:
- name: monitor
image: busybox:1.36
command: ["sh", "-c", "while true; do sleep 30; done"]- Explica exactament què està passant i per què.
- Prediu què passaria si estiguessin funcionant els 2 pods d'
api-reservesde l'exercici 1 quan s'aplica. - Proposa el manifest corregit.
- Indica quina ordre faries servir, en un clúster real, per identificar el culpable d'un esborrat inesperat.
Solucions
Solució 1
# k8s/base/api-reserves-replicaset.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: api-reserves
namespace: rutas-norte-dev
labels:
app: api-reserves
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
replicas: 2
selector:
matchLabels:
app: api-reserves
entorn: dev
template:
metadata:
labels:
app: api-reserves
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
containers:
- name: api
image: node:20-alpine
command: ["node", "-e"]
args:
- |
const http = require('http');
http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({servei: 'api-reserves', pod: process.env.HOSTNAME}));
}).listen(3000, () => console.log('api-reserves escoltant al 3000'));
ports:
- name: http
containerPort: 3000
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"kubectl apply -f k8s/base/api-reserves-replicaset.yaml
kubectl get rs api-reserves
kubectl get pods -l app=api-reservesreplicaset.apps/api-reserves created
NAME DESIRED CURRENT READY AGE
api-reserves 2 2 2 12s
NAME READY STATUS RESTARTS AGE
api-reserves-c8n4t 1/1 Running 0 12s
api-reserves-xq7dz 1/1 Running 0 12spod "api-reserves-c8n4t" deleted
NAME READY STATUS RESTARTS AGE
api-reserves-m3kp9 1/1 Running 0 2s
api-reserves-xq7dz 1/1 Running 0 1mEl substitut apareix en 1-2 segons.
kubectl get pod api-reserves-m3kp9 \
-o jsonpath='{.metadata.ownerReferences[0].kind}/{.metadata.ownerReferences[0].name}{"\n"}'
kubectl describe rs api-reserves | tail -5ReplicaSet/api-reserves
Events:
Type Reason Age From Message
Normal SuccessfulCreate 1m replicaset-controller Created pod: api-reserves-c8n4t
Normal SuccessfulCreate 1m replicaset-controller Created pod: api-reserves-xq7dz
Normal SuccessfulCreate 8s replicaset-controller Created pod: api-reserves-m3kp9Solució 2
# 1. Esborrat deixant orfes
kubectl delete rs api-reserves --cascade=orphan
kubectl get rs,pods -l app=api-reservesreplicaset.apps "api-reserves" deleted
NAME READY STATUS RESTARTS AGE
pod/api-reserves-m3kp9 1/1 Running 0 4m
pod/api-reserves-xq7dz 1/1 Running 0 5m# 2. Sense propietari
kubectl get pod api-reserves-m3kp9 -o jsonpath='{.metadata.ownerReferences}{"\n"}'La sortida buida confirma que ja no tenen ownerReferences.
# 3. Readopcio
kubectl apply -f k8s/base/api-reserves-replicaset.yaml
kubectl get rs,pods -l app=api-reservesreplicaset.apps/api-reserves created
NAME DESIRED CURRENT READY AGE
replicaset.apps/api-reserves 2 2 2 3s
NAME READY STATUS RESTARTS AGE
pod/api-reserves-m3kp9 1/1 Running 0 5m
pod/api-reserves-xq7dz 1/1 Running 0 6mEls pods tenen 5 i 6 minuts, mentre que el ReplicaSet en té 3 segons: no s'ha creat res de nou, s'han adoptat els existents.
- L'avantatge operatiu és que permet substituir el controlador sense tocar el servei: si necessites recrear el ReplicaSet (per exemple, perquè cal canviar-ne el selector, que és immutable), l'esborres amb
--cascade=orphan, apliques el nou i els pods continuen atenent peticions sense un sol segon de tall. Si el ReplicaSet nou hagués demanat 1 rèplica, hauria adoptat els dos orfes, n'hauria comptat 2 on n'hi havia d'haver 1 i n'hauria esborrat un immediatament, triant el més jove.
Solució 3
-
El selector
app.kubernetes.io/part-of: rutas-norteencaixa amb tots els pods de la plataforma, perquè aquesta etiqueta és comuna als sis components per convenció del projecte. El ReplicaSetmonitor-plataformaadopta tot el que troba al namespace, ho compara amb el seureplicas: 1i esborra tot el que sobra. Com que els altres controladors recreen els seus pods, s'entra en un cicle de creació i esborrat continu: la "guerra de controladors". -
Amb els 2 pods d'
api-reservesfuncionant, en aplicar el manifest el monitor els adoptaria juntament amb el seu propi pod (3 en total), veuria que en sobren 2 i n'esborraria dos, molt probablement els d'api-reservessi són els més joves. El ReplicaSet d'api-reservesels recrearia, el monitor els tornaria a esborrar, i l'API s'ompliria d'esdevenimentsSuccessfulDeleteiSuccessfulCreate. Amb tota probabilitat,api-reservesdeixaria de donar servei de forma intermitent. -
Manifest corregit: selector propi i exclusiu del component.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: monitor-plataforma
namespace: rutas-norte-dev
labels:
app: monitor-plataforma
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
replicas: 1
selector:
matchLabels:
app: monitor-plataforma # identifica NOMES aquest component
entorn: dev
template:
metadata:
labels:
app: monitor-plataforma
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
containers:
- name: monitor
image: busybox:1.36
command: ["sh", "-c", "while true; do sleep 30; done"]- Per identificar el culpable d'un esborrat inesperat:
# Quins controladors han esborrat pods recentment al namespace
kubectl get events -n rutas-norte-dev --sort-by=.lastTimestamp \
--field-selector reason=SuccessfulDelete
# Qui es el propietari actual de cada pod: revela adopcions indegudes
kubectl get pods -n rutas-norte-dev \
-o custom-columns=POD:.metadata.name,PROPIETARI:.metadata.ownerReferences[0].name
# Quins selectors hi ha declarats i si se solapen
kubectl get rs -n rutas-norte-dev -o wideConclusió
Ja tens el primer controlador del curs completament desmuntat. Saps que un controlador és un bucle infinit, idempotent i per nivell que només parla amb el kube-apiserver, i has vist com el controlador de ReplicaSet el concreta: llegeix spec.replicas, compta els pods que encaixen amb spec.selector i crea o esborra fins a quadrar, fent servir spec.template com a motlle. Coneixes l'estructura de nina russa del manifest i la regla innegociable que les etiquetes del template han de satisfer el selector.
Has comprovat l'autoreparació en primera persona: vas esborrar un pod de botiga-web i va tornar en dos segons; els vas esborrar tots i van tornar els tres. Aquest és, literalment, el problema que va costar a Rutas Norte quatre hores i mitja de vendes una nit de març, resolt. Entens que la relació propietari-fill es materialitza en les ownerReferences, que el garbage collector la fa servir per a l'esborrat en cascada, i que les tres polítiques de propagació —Background, Foreground i Orphan— et donen control fi, inclosa la maniobra de substituir un controlador sense tallar el servei. Has vist la cara amable de l'adopció d'orfes i la seva cara perillosa: un selector massa ampli converteix un ReplicaSet en un destructor de pods aliens. I saps escalar amb kubectl scale, sense oblidar que el canvi ha d'acabar sempre a Git.
Però també n'has descobert el límit: canviar la imatge del template no actualitza els pods existents. Un ReplicaSet manté, no desplega. No sap canviar de versió, ni tornar enrere, ni portar un historial. Tot això ho aporta el nivell superior, que és exactament la lliçó següent: Deployments. Allà convertirem per fi el pod solt de botiga-web en un Deployment de 3 rèpliques, crearem el d'api-reserves, i veurem aparèixer sota el capó, gestionat automàticament, el mateix ReplicaSet que acabem d'aprendre a llegir.
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
