Portem des del mòdul 2 escrivint blocs resources a tots els manifests de Rutas Norte —cpu: 250m, memory: 256Mi— sense haver explicat mai què signifiquen exactament ni d'on surten aquests números. La lliçó anterior fins i tot els va exposar al contenidor amb resourceFieldRef perquè api-reserves es dimensionés a si mateix. Ha arribat el moment de prendre-se'ls seriosament, perquè són la peça que decideix en quin node cap cada pod, quanta CPU rep quan hi ha competència i quin procés mor quan falta memòria. I arrosseguem a més l'últim deute del mòdul 2: cap namespace no té quota, així que avui mateix un desplegament equivocat a rutas-norte-dev pot menjar-se la capacitat del clúster i deixar sense lloc els pods de producció. En aquesta lliçó entendràs el model de recursos complet i posaràs una ResourceQuota a cadascun dels tres entorns.
Contingut
- El model de recursos:
requestsilimits - Unitats: milicores,
Midavant deM - Qui fa servir les
requests: el planificador - Qui fa servir els
limits: el kubelet i els cgroups - CPU comprimible davant de memòria incomprimible
- Diagnosticar l'estrangulament de CPU
- Diagnosticar un
OOMKilled ephemeral-storagei el desallotjament per disc- Sobrecompromís del clúster
- ResourceQuota: què limita i com es llegeix
- L'efecte que obliga a declarar recursos
- Les quotes dels tres entorns de Rutas Norte
- Com triar valors raonables
- El model de recursos:
requests i limits
requests i limitsCada contenidor d'un pod pot declarar dos números per cada tipus de recurs:
- name: api
image: registry.rutasnorte.example/api-reserves:2.5.0
resources:
requests: # el que el contenidor NECESSITA GARANTIT
cpu: 250m
memory: 256Mi
limits: # el SOSTRE que no pot superar
cpu: "1"
memory: 512MiLa diferència conceptual, que és la base de tot:
requests |
limits |
|
|---|---|---|
| Significat | "Reserva'm això" | "No em deixis passar d'aquí" |
| Qui ho fa servir | El planificador, en triar node | El kubelet, en temps d'execució |
| Quan actua | Una vegada, en crear el pod | Contínuament, mentre el pod viu |
| Si el node no en té | El pod queda en Pending |
— |
| Si se supera | No es pot superar: està garantit | CPU: s'estrangula. Memòria: OOMKilled |
| Afecta la factura del clúster | Sí: és capacitat compromesa | Indirectament |
| Si s'omet | S'assumeix 0 (perillós) | S'assumeix il·limitat (perillós) |
Una analogia útil, i que encaixa amb Rutas Norte: pensa en un autobús de la flota. La request és el seient reservat: està pagat i ningú més no el pot ocupar, tant si el fas servir com si no. El limit és l'equipatge màxim permès: en pots portar menys, però si intentes passar del màxim t'ho impedeixen a la porta.
I les dues conseqüències que cal fixar des del principi:
requestsés el que "costa" al clúster. Un pod ambrequests: 4Gibloqueja 4 GiB de capacitat del node encara que el seu procés en faci servir 100 MiB. El planificador considera aquell node amb 4 GiB menys disponibles per a tota la resta.limitsno reserva res. Un pod amblimits: 8Gino reserva 8 GiB; simplement no en podrà passar si intenta fer-los servir.
Pots declarar només requests, només limits, tots dos o cap:
| Declaració | Efecte |
|---|---|
Tots dos, amb requests < limits |
L'habitual: garantia mínima amb capacitat de ràfega |
| Tots dos, iguals | Màxima previsibilitat i màxima prioritat (ho veuràs a 03-05) |
Només limits |
Kubernetes copia el limit a la request. Compte: reserves més del que et penses |
Només requests |
Sense sostre: el contenidor pot consumir tot el node |
| Cap | El pitjor cas: sense garantia i sense sostre |
- Unitats: milicores,
Mi davant de M
Mi davant de MCPU
La unitat de CPU és el core (més exactament, un fil d'execució: un vCPU al núvol, un hiperfil en metall). Es pot expressar de dues formes:
| Escriptura | Significat |
|---|---|
1 o 1000m |
Un core complet |
500m |
Mig core |
250m |
Un quart de core |
100m |
Una desena part de core |
0.5 |
Equivalent a 500m, però es desaconsella |
1m |
El mínim admès |
La m és de mili: 1000m = 1 core. La forma amb m és la preferida perquè evita els errors de coma flotant i perquè 0.1 pot patir problemes de representació. Escriu sempre milicores.
La CPU és fraccionable i elàstica: si demanes 250m no et donen un quart de processador físic, sinó un quart del temps de CPU disponible en una finestra de temps. I si el node està ociós, en pots fer servir més (fins al teu limit).
Memòria
La memòria es mesura en bytes i admet dues famílies de sufixos que no són equivalents:
| Sufix | Base | Valor | Exemple |
|---|---|---|---|
Ki |
2 | 1.024 | 512Ki = 524.288 bytes |
Mi |
2 | 1.048.576 | 256Mi = 268.435.456 bytes |
Gi |
2 | 1.073.741.824 | 1Gi = 1.073.741.824 bytes |
Ti |
2 | 2⁴⁰ | |
k |
10 | 1.000 | 512k = 512.000 bytes |
M |
10 | 1.000.000 | 256M = 256.000.000 bytes |
G |
10 | 1.000.000.000 | 1G = 1.000.000.000 bytes |
256M és un 6,9 % menys de memòria que 256Mi. I 1G és 74 MiB menys que 1Gi. En un límit de memòria, aquest 6,9 % és exactament la diferència entre un contenidor que va just i un que mor amb OOMKilled sota càrrega.
Regla per a Rutas Norte i per a qualsevol projecte seriós: fes servir sempre Mi i Gi, mai M ni G. És el que fan totes les eines de Kubernetes i el que espera qualsevol que llegeixi el teu manifest.
I un error tipogràfic que costa car:
Kubernetes ho accepta sense protestar: m és un sufix vàlid (mili). El contenidor rep un límit de mig byte i mor a l'instant. El símptoma és un CrashLoopBackOff desconcertant. m només té sentit a la CPU.
Si veus això, ja saps quin és el problema.
- Qui fa servir les
requests: el planificador
requests: el planificadorEl kube-scheduler que vas estudiar al mòdul 1 té una feina: triar un node per a cada pod nou. I la decisió es basa exclusivament en les requests, no en l'ús real.
flowchart TB
A["Pod nou:<br/>requests cpu=250m mem=256Mi"] --> B["FILTRATGE<br/>Quins nodes tenen capacitat ASSIGNABLE lliure?"]
B --> C{"node-1<br/>lliure: 200m CPU"}
B --> D{"node-2<br/>lliure: 1500m CPU"}
B --> E{"node-3<br/>lliure: 800m CPU"}
C -->|"NO hi cap"| F["Descartat"]
D -->|"hi cap"| G["PUNTUACIO"]
E -->|"hi cap"| G
G --> H["Triat: el de millor puntuacio"]
H --> I["El pod es LLIGA al node<br/>spec.nodeName"]
El càlcul que fa el planificador per a cada node és:
Dos matisos crítics:
Matís 1: assignable no és la capacitat total del node. Kubernetes en reserva una part per al sistema operatiu i per als seus propis components (kubeReserved, systemReserved, evictionHard).
Capacity:
cpu: 4
ephemeral-storage: 61202244Ki
memory: 8039484Ki
pods: 110
Allocatable:
cpu: 4
ephemeral-storage: 56403448552
memory: 7937084Ki
pods: 110En aquest minikube la diferència és petita, però en un node gestionat del núvol pot ser el 10-15 % de la memòria. Planifica sempre sobre Allocatable, no sobre Capacity.
Matís 2: se sumen les requests, no l'ús real. Aquesta és la font de la confusió més comuna. Un node pot estar al 8 % d'ús real de CPU i tot i així rebutjar un pod nou perquè la suma de requests del que ja hi ha assignat no deixa forat.
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 2350m (58%) 5200m (130%)
memory 2816Mi (36%) 4608Mi (59%)
ephemeral-storage 0 (0%) 0 (0%)Llegeix-ho amb atenció: els Requests de CPU sumen el 58 % de l'assignable, però els Limits sumen el 130 %. Això és el sobrecompromís de l'apartat 9, i aquell avís entre parèntesis ho adverteix explícitament.
Quan no hi ha forat en cap node, el pod es queda en Pending:
kubectl get pods -n rutas-norte-dev
kubectl describe pod api-reserves-6d8f7c-abc -n rutas-norte-dev | tail -5NAME READY STATUS RESTARTS AGE
api-reserves-6d8f7c-abc 0/1 Pending 0 2m14s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 2m default-scheduler 0/3 nodes are available:
3 Insufficient memory. preemption: 0/3 nodes are available: 3 No preemption victims found.Insufficient memory amb el pod en Pending significa sempre el mateix: les requests no hi caben. Les solucions són baixar les requests (si estaven inflades), afegir un node, o esperar que l'autoescalador de clúster ho faci per tu.
Un avís sobre les requests inflades: és temptador demanar de més "per si de cas". Però cada MiB de request que no fas servir és capacitat del clúster que ningú no pot fer servir i que estàs pagant. En clústers reals és normal trobar un 30-40 % de capacitat compromesa i sense fer servir només per requests mal calibrades.
- Qui fa servir els
limits: el kubelet i els cgroups
limits: el kubelet i els cgroupsEls limits no els veu el planificador. Els aplica el kubelet al node, i no pel seu compte: ho demana al kernel de Linux a través dels cgroups (control groups), el mecanisme que permet limitar els recursos d'un conjunt de processos.
Ho podem veure des de dins del contenidor. Amb limits: cpu: "1" i memory: 512Mi en cgroups v2:
kubectl exec -n rutas-norte-dev deploy/api-reserves -- sh -c \
'cat /sys/fs/cgroup/memory.max; cat /sys/fs/cgroup/cpu.max; cat /sys/fs/cgroup/cpu.weight'Desxifrem les tres línies, perquè expliquen el comportament sencer:
memory.max = 536870912: són exactament 512 MiB. És un topall dur. Si el procés intenta reservar el byte 536870913, el kernel dispara l'OOM killer.cpu.max = 100000 100000: són quota i període en microsegons. Significa "100.000 µs de CPU cada 100.000 µs", és a dir, un core complet. Amblimits: cpu: 500mseria50000 100000.cpu.weight = 10: deriva de larequestde CPU (250m). És el pes relatiu amb què el planificador del kernel reparteix el temps de CPU quan hi ha competència. Un contenidor amb el doble derequestrep el doble de temps.
Aquesta última línia és important i poc coneguda: la request de CPU no és només per al planificador de Kubernetes; també determina la prioritat relativa dins del node. Dos pods al mateix node competint per CPU es reparteixen el temps en proporció a les seves requests.
- CPU comprimible davant de memòria incomprimible
Aquesta és la distinció més important de la lliçó i la que cal entendre de debò.
| CPU | Memòria | |
|---|---|---|
| Tipus de recurs | Comprimible | Incomprimible |
| Es pot treure en calent | Sí: n'hi ha prou de donar-li menys temps | No: el que està reservat, està reservat |
En superar el limit |
Estrangulament (throttling) | OOMKilled: el procés mor |
| Conseqüència | El procés va més lent | El contenidor es reinicia i es perd la feina en curs |
| Es veu a | Mètriques de container_cpu_cfs_throttled_seconds |
kubectl describe pod, RESTARTS |
| Gravetat | Latència alta, temps d'espera | Pèrdua de peticions, tall de servei |
CPU: estrangulament. Si el teu contenidor té limits: cpu: 500m i intenta fer-ne servir més, el kernel simplement no li dona més temps de CPU en aquell període de 100 ms. El procés no mor: es queda esperant. L'efecte visible és latència. Per a api-reserves això significa que una petició que trigava 80 ms comença a trigar 400 ms, i si el TEMPS_ESPERA_MS de botiga-web està a 2000, amb prou estrangulament es comencen a veure errors de temps d'espera al navegador del client.
Memòria: mort. No hi ha manera d'"estrangular" la memòria. Si un procés ha reservat 512 MiB i en demana més, el kernel no li pot treure res: o li dona memòria o mata algú. Amb limits: memory: 512Mi, mata el procés que s'ha passat, dins del cgroup del contenidor. El contenidor mor amb codi de sortida 137 i el kubelet el reinicia segons el restartPolicy.
Conseqüència pràctica per al disseny:
Els
limitsde memòria s'han de calibrar amb cura, perquè quedar-se curt mata el procés. Elslimitsde CPU són molt menys perillosos, però també més discutibles.
I aquí hi ha un debat real a la comunitat que convé conèixer: posar o no limits de CPU?
| A favor de posar límit de CPU | En contra de posar límit de CPU |
|---|---|
| Evita que un procés desbocat degradi els seus veïns | Estrangula encara que el node estigui ociós: es malbarata capacitat |
| Fa el rendiment predictible entre entorns | Pot provocar estrangulament en ràfegues curtes d'arrencada |
| Permet detectar abans que l'aplicació necessita més | Amb fils, l'estrangulament afecta tot el procés, no només el fil culpable |
Necessari per a la classe Guaranteed (03-05) |
Molts equips grans els ometen deliberadament |
Postura de Rutas Norte, que és la recomanable per a un equip que comença:
requestsde CPU i memòria: sempre, a tots els contenidors. Sense negociació.limitsde memòria: sempre. Un pod sense límit de memòria pot tombar el node sencer i provocar desallotjaments en cadena.limitsde CPU: sí adevipre(per detectar aviat el consum excessiu) i generosos apro, entre 2 i 4 vegades larequest, per permetre absorbir els pics de ponts i vacances.
- Diagnosticar l'estrangulament de CPU
L'estrangulament és traïdor perquè no apareix en cap estat del pod. El pod està Running, READY 1/1, sense reinicis, i tot i així respon malament.
El senyal és al mateix cgroup:
usage_usec 184920331
user_usec 152014882
system_usec 32905449
nr_periods 918442
nr_throttled 214883
throttled_usec 41220984La lectura:
nr_periods: quants períodes de 100 ms han transcorregut.nr_throttled: en quants d'ells el contenidor va esgotar la seva quota i va ser estrangulat.throttled_usec: microsegons totals que ha passat esperant.
L'indicador clau és el quocient:
Gairebé una quarta part dels períodes han estat estrangulats. Interpretació:
| Quocient | Diagnòstic |
|---|---|
| < 1 % | Normal. Ràfegues puntuals |
| 1-5 % | Vigilar. Acceptable en processos per lots |
| 5-25 % | Problema real de latència. Pujar el limit |
| > 25 % | Greu. El límit està clarament mal posat |
Una ordre per revisar tots els pods d'un component:
for P in $(kubectl get pods -n rutas-norte-pro -l app=api-reserves -o name); do
echo "--- $P"
kubectl exec -n rutas-norte-pro "$P" -- sh -c \
'awk "/nr_periods|nr_throttled/ {print \$1, \$2}" /sys/fs/cgroup/cpu.stat'
done--- pod/api-reserves-7f4b8c9d6-2xkqp
nr_periods 918442
nr_throttled 214883
--- pod/api-reserves-7f4b8c9d6-8mvtr
nr_periods 917210
nr_throttled 198445La manera correcta de vigilar això de manera contínua és amb mètriques: la sèrie container_cpu_cfs_throttled_periods_total de cAdvisor, que veuràs a Monitoratge amb Prometheus. L'ordre de dalt serveix per a un diagnòstic puntual.
La correcció: pujar el limit de CPU. Si api-reserves té requests: 250m i limits: 500m amb un 23 % d'estrangulament, pujar el límit a "1" sol resoldre-ho sense cost real, perquè el limit no reserva capacitat.
- Diagnosticar un
OOMKilled
OOMKilledEl cas oposat és escandalós i fàcil d'identificar:
NAME READY STATUS RESTARTS AGE
api-reserves-7f4b8c9d6-2xkqp 1/1 Running 4 (2m ago) 41m
api-reserves-7f4b8c9d6-8mvtr 0/1 OOMKilled 3 (18s ago) 41mEl detall és a describe:
Containers:
api:
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Started: Wed, 05 Aug 2026 23:12:04 +0200
Finished: Wed, 05 Aug 2026 23:14:41 +0200
Restart Count: 3
Limits:
cpu: 1
memory: 512Mi
Requests:
cpu: 250m
memory: 256MiLes tres pistes inequívoques: Reason: OOMKilled, Exit Code: 137 (que és 128 + 9, el senyal SIGKILL) i un Restart Count que creix.
Compte amb el Last State: descriu el contenidor anterior. Si el pod està Running ara mateix però té RESTARTS 4, el Last State et diu per què va morir la vegada passada. És la primera cosa que cal mirar davant d'un pod amb reinicis.
I un advertiment sobre kubectl logs: en reiniciar-se el contenidor, els logs comencen de zero. Per veure els del contenidor mort, que són els que contenen la pista:
2026-08-05T23:14:38.221Z INFO [pod=api-reserves-7f4b8c9d6-8mvtr] consulta_disponibilitat
ruta=Bilbao-Santander dates=2026-08-14..2026-08-17 resultats=8412
2026-08-05T23:14:40.887Z WARN [pod=api-reserves-7f4b8c9d6-8mvtr] heap 478MB / 512MBAquí hi ha la causa: una consulta de disponibilitat d'un pont va retornar 8.412 resultats i l'aplicació els va carregar tots a memòria.
Les cinc causes típiques d'un OOMKilled i el seu tractament:
| Causa | Senyal | Solució |
|---|---|---|
| Límit massa baix | Mor sempre sota càrrega normal | Pujar limits.memory |
| Fuita de memòria | Mor periòdicament, cada vegada més ràpid | Arreglar el codi; mentrestant, pujar el límit endarrereix el problema, no el resol |
| Pic puntual (consulta gran) | Mor només amb certes peticions | Paginar la consulta, limitar resultats |
| Entorn d'execució que no veu el límit | La JVM o Node reserven segons la RAM del node | -XX:MaxRAMPercentage o --max-old-space-size amb resourceFieldRef (03-03) |
| Contenidor equivocat | El que mor és el sidecar, no l'app | Mirar el nom del contenidor a describe |
La quarta mereix una nota, perquè és la més freqüent i la més invisible: un procés Java sense MaxRAMPercentage en un node de 32 GiB amb un limit de 512 MiB dimensiona el seu munt segons els 32 GiB del node i mor tan bon punt comença a treballar. La lliçó anterior ja et va donar l'eina per arreglar-ho.
ephemeral-storage i el desallotjament per disc
ephemeral-storage i el desallotjament per discHi ha un tercer recurs que gairebé ningú no declara i que provoca incidents desconcertants: l'emmagatzematge efímer, és a dir, el disc del node que fa servir el contenidor per a:
- La capa d'escriptura del seu sistema de fitxers (tot el que escrigui fora d'un volum).
- Els volums
emptyDir. - Els logs del contenidor (el que escriu a stdout/stderr, que el kubelet desa al disc del node).
Es declara igual que els altres:
resources:
requests:
cpu: 250m
memory: 256Mi
ephemeral-storage: 1Gi
limits:
cpu: "1"
memory: 512Mi
ephemeral-storage: 2GiQuè passa en superar el límit: el kubelet desallotja el pod, amb Reason: Evicted:
kubectl get pods -n rutas-norte-pro | grep Evicted
kubectl describe pod worker-notificacions-6f7d9-abcde -n rutas-norte-pro | grep -A3 "Status:"worker-notificacions-6f7d9-abcde 0/1 Evicted 0 34m
Status: Failed
Reason: Evicted
Message: Pod ephemeral local storage usage exceeds the total limit of containers 2Gi.Un matís rellevant: a diferència de l'OOMKilled, el desallotjament per disc no és immediat. El kubelet comprova l'ús cada 10 segons aproximadament, així que un procés pot omplir el disc abans que el desallotgin.
I el problema més gros: si el disc del node s'omple, el kubelet entra en pressió i desallotja pods encara que tinguin els seus límits en ordre. És una fallada que afecta tot el node. Els senyals:
Conditions:
Type Status Reason Message
---- ------ ------ -------
MemoryPressure False KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure True KubeletHasDiskPressure kubelet has disk pressure
PIDPressure False KubeletHasSufficientPID kubelet has sufficient PID available
Ready True KubeletReady kubelet is posting ready statusDiskPressure: True explica desallotjaments aparentment aleatoris. L'ordre en què el kubelet tria les víctimes depèn de la classe de QoS, que és el tema de la lliçó següent.
A Rutas Norte, el candidat natural a omplir el disc és worker-notificacions: si genera un log per cada correu enviat i en un pont se n'envien desenes de milers, sense rotació de logs el disc del node s'omple. La regla pràctica: declara ephemeral-storage en qualsevol component que escrigui logs voluminosos o faci servir emptyDir, i vigila el DiskPressure dels nodes.
- Sobrecompromís del clúster
Tornem a aquella línia d'abans:
La suma dels límits de CPU és el 130 % de l'assignable del node. Està el clúster mal configurat? No: això és normal i fins i tot desitjable.
flowchart TB
subgraph N["Node: 4 cores assignables"]
R["REQUESTS compromeses: 2350m<br/>(garantides, el planificador no les sobrepassa)"]
L["LIMITS sumats: 5200m<br/>(el planificador NO els mira)"]
end
R --> S["Segur: el planificador mai no<br/>compromet mes requests que capacitat"]
L --> P["Sobrecompromis: nomes es un problema<br/>si TOTS piquen alhora"]
La lògica del sobrecompromís: els pods no consumeixen el seu màxim simultàniament. worker-notificacions treballa a ràfegues, api-reserves té pics en hores concretes, botiga-web serveix estàtics amb poc consum. Permetre que la suma de límits superi la capacitat aprofita aquests forats.
On és el perill, segons el recurs:
| Recurs | Si tots demanen alhora |
|---|---|
| CPU | Tots s'estrangulen proporcionalment a la seva request. Latència alta, sense caigudes. Recuperable |
| Memòria | El node es queda sense memòria. El kubelet desallotja pods. Si va molt ràpid, l'OOM killer del kernel mata processos. Caigudes reals |
La conclusió operativa és asimètrica i cal gravar-la:
Sobrecomprometre CPU és acceptable i habitual. Sobrecomprometre memòria de manera agressiva és perillós.
Per a Rutas Norte, les relacions recomanades:
| Recurs | Relació limit / request |
Motiu |
|---|---|---|
| CPU | Entre 2 i 4 | Absorbir pics de ponts sense malbaratar capacitat |
| Memòria | Entre 1 i 1,5 | Com més ajustat, menys risc de desallotjaments en cadena |
Memòria de postgres-reserves |
Exactament 1 (iguals) | Una base de dades no ha de ser mai candidata a desallotjament |
Aquest últim cas, amb requests == limits, té un nom i conseqüències molt concretes sobre qui mor primer: és la classe Guaranteed, i és el tema de la lliçó següent.
I un advertiment sobre memòria que sorprèn molta gent: Kubernetes desactiva el swap per defecte (encara que des de 1.30 hi ha suport beta configurable). Sense swap, el kernel no té amortidor: quan la memòria s'acaba, s'acaba. És un motiu més per no sobrecomprometre-la.
- ResourceQuota: què limita i com es llegeix
Tot l'anterior és per contenidor. La ResourceQuota actua a un altre nivell: posa un sostre al conjunt d'un namespace. És l'objecte que salda l'últim deute del mòdul 2.
apiVersion: v1
kind: ResourceQuota
metadata:
name: quota-dev
namespace: rutas-norte-dev
spec:
hard:
# --- Computacio ---
requests.cpu: "2" # suma de les requests de CPU de tots els pods
requests.memory: 4Gi
limits.cpu: "4" # suma dels limits
limits.memory: 8Gi
requests.ephemeral-storage: 10Gi
# --- Nombre d'objectes ---
pods: "20"
services: "10"
configmaps: "20"
secrets: "20"
persistentvolumeclaims: "5"
services.loadbalancers: "0" # prohibit crear LoadBalancers a dev
services.nodeports: "0"
count/deployments.apps: "10"
count/jobs.batch: "10"
count/cronjobs.batch: "5"
# --- Emmagatzematge ---
requests.storage: 20Gi # suma del demanat per tots els PVCLes tres famílies de límits:
| Família | Exemples | Què controla |
|---|---|---|
| Computació | requests.cpu, limits.memory |
Que un entorn no consumeixi tota la capacitat |
| Nombre d'objectes | pods, services, secrets |
Que ningú no saturi etcd ni el pla de control |
| Emmagatzematge | requests.storage, persistentvolumeclaims |
El cost dels discos (mòdul 5) |
Punts que defineixen el seu comportament:
- És un objecte amb namespace i només afecta el seu namespace. No existeixen quotes globals de clúster.
- S'aplica en el moment de la creació. Un pod que superaria la quota es rebutja; els que ja existeixen no es toquen.
- Hi pot haver diverses ResourceQuota en un namespace. S'apliquen totes i cal complir-les totes.
- Els pods acabats no compten. Els que estan en
SucceededoFaileds'exclouen del còmput. services.loadbalancers: "0"és una eina de control de cost molt útil: cada LoadBalancer al núvol costa diners.
La lectura d'una quota:
Name: quota-dev
Namespace: rutas-norte-dev
Resource Used Hard
-------- ---- ----
configmaps 6 20
count/cronjobs.batch 0 5
count/deployments.apps 5 10
limits.cpu 2300m 4
limits.memory 3200Mi 8Gi
persistentvolumeclaims 1 5
pods 9 20
requests.cpu 1150m 2
requests.memory 1600Mi 4Gi
secrets 4 20
services 4 10
services.loadbalancers 0 0Dues columnes: Used (el consumit ara) i Hard (el sostre). Aquest namespace està al 57 % de les requests de CPU i al 40 % de memòria. És la vista que respon a "quant marge em queda a desenvolupament?".
Quan algú intenta passar-se:
kubectl scale deploy/api-reserves --replicas=12 -n rutas-norte-dev
kubectl get events -n rutas-norte-dev --sort-by=.lastTimestamp | tail -3deployment.apps/api-reserves scaled
LAST SEEN TYPE REASON OBJECT MESSAGE
3s Warning FailedCreate replicaset/api-reserves-6d8f7c9b Error creating: pods
"api-reserves-6d8f7c9b-" is forbidden: exceeded quota: quota-dev, requested: requests.cpu=250m,
used: requests.cpu=1900m, limited: requests.cpu=2Fixa't en un detall molt important per al diagnòstic: l'ordre kubectl scale ha tingut èxit. El Deployment ara diu 12 rèpliques. El que falla és la creació dels pods per part del ReplicaSet, i aquesta fallada no apareix a kubectl get deploy més que com un READY 8/12 que dura per sempre.
kubectl get deploy api-reserves -n rutas-norte-dev
kubectl describe deploy api-reserves -n rutas-norte-dev | grep -A4 ConditionsNAME READY UP-TO-DATE AVAILABLE AGE
api-reserves 8/12 12 8 3h
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
ReplicaProgressing False ProgressDeadlineExceededDavant d'un Deployment encallat en READY x/y, mira sempre els esdeveniments del ReplicaSet i la quota del namespace. És una de les causes més freqüents i de les menys evidents, i connecta directament amb el diagnòstic de desplegaments encallats que vas veure al mòdul 2.
Àmbits (scopes)
Una quota es pot aplicar només a un subconjunt de pods:
apiVersion: v1
kind: ResourceQuota
metadata:
name: quota-alta-prioritat
namespace: rutas-norte-pro
spec:
hard:
requests.cpu: "8"
requests.memory: 16Gi
scopeSelector:
matchExpressions:
- operator: In
scopeName: PriorityClass
values: ["alta"]Àmbits disponibles: Terminating i NotTerminating (segons tinguin activeDeadlineSeconds), BestEffort i NotBestEffort (segons la classe de QoS de la lliçó següent), i PriorityClass. Serveixen per donar més marge al que és crític que al que és experimental dins del mateix namespace.
- L'efecte que obliga a declarar recursos
Aquí hi ha el comportament més útil i alhora més desconcertant de les ResourceQuota:
Si un namespace té una quota que limita
requests.cpuolimits.memory, aleshores TOT pod creat en ell ha de declarar aquell recurs. Si no el declara, es rebutja.
La lògica és evident tan bon punt s'hi pensa: per comptabilitzar la suma de requests.cpu del namespace, Kubernetes necessita que tots els pods declarin requests.cpu. Un pod sense declarar-la faria el còmput impossible.
Demostració. Amb la quota de dev activa, intentem crear un pod sense recursos:
Error from server (Forbidden): pods "prova-sense-recursos" is forbidden: failed quota: quota-dev:
must specify limits.cpu for: prova-sense-recursos; limits.memory for: prova-sense-recursos;
requests.cpu for: prova-sense-recursos; requests.memory for: prova-sense-recursosAquest és, de bon tros, l'efecte secundari més valuós de les quotes: converteix la disciplina de declarar recursos en una cosa obligatòria a nivell de plataforma, no en una recomanació que la gent oblida.
Però té un cost immediat: trenca tot el que és còmode. Un pod efímer per depurar, un kubectl run ràpid, un Job puntual... tots fallen. I aquí és on apareix la peça que falta, que és el tema de la lliçó següent: un LimitRange al namespace injecta valors per defecte als contenidors que no declaren res, amb la qual cosa el pod passa la validació de la quota sense que ningú escrigui resources a mà.
flowchart LR
A["Pod sense resources"] --> B{"Hi ha LimitRange<br/>al namespace?"}
B -->|"Si"| C["S'injecten<br/>default i defaultRequest"]
B -->|"No"| D["El pod continua<br/>sense resources"]
C --> E{"Hi ha ResourceQuota<br/>de computacio?"}
D --> E
E -->|"Si, i el pod declara"| F["Es comptabilitza. ACCEPTAT"]
E -->|"Si, i no declara"| G["REBUTJAT<br/>must specify requests.cpu"]
E -->|"No"| F
ResourceQuota i LimitRange són inseparables. Posar una quota sense un LimitRange és una decisió defensable (obliga tothom a pensar els seus recursos), però converteix el dia a dia en una molèstia constant. La combinació de totes dues és la configuració estàndard de qualsevol namespace seriós.
- Les quotes dels tres entorns de Rutas Norte
Ara sí: apliquem les quotes i el deute queda saldat. El criteri és que dev no pugui fer mal i pro tingui marge per als pics de ponts i vacances.
rutas-norte-dev
apiVersion: v1
kind: ResourceQuota
metadata:
name: quota-dev
namespace: rutas-norte-dev
labels:
app.kubernetes.io/part-of: rutas-norte
entorn: dev
spec:
hard:
requests.cpu: "2" # 2 cores compromesos com a maxim
requests.memory: 4Gi
limits.cpu: "4"
limits.memory: 8Gi
pods: "25"
services: "10"
services.loadbalancers: "0" # res de balancejadors de pagament a dev
services.nodeports: "2"
persistentvolumeclaims: "4"
requests.storage: 10Gi
configmaps: "30"
secrets: "30"
count/deployments.apps: "15"
count/cronjobs.batch: "5"rutas-norte-pre
apiVersion: v1
kind: ResourceQuota
metadata:
name: quota-pre
namespace: rutas-norte-pre
labels:
app.kubernetes.io/part-of: rutas-norte
entorn: pre
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 12Gi
pods: "30"
services: "15"
services.loadbalancers: "1" # un, per provar l'Ingress real
persistentvolumeclaims: "6"
requests.storage: 50Gi
configmaps: "40"
secrets: "40"
count/deployments.apps: "20"rutas-norte-pro
apiVersion: v1
kind: ResourceQuota
metadata:
name: quota-pro
namespace: rutas-norte-pro
labels:
app.kubernetes.io/part-of: rutas-norte
entorn: pro
spec:
hard:
requests.cpu: "24" # marge per a l'autoescalat del modul 9
requests.memory: 48Gi
limits.cpu: "48"
limits.memory: 64Gi
pods: "150"
services: "25"
services.loadbalancers: "3"
persistentvolumeclaims: "12"
requests.storage: 500Gi
configmaps: "60"
secrets: "60"
count/deployments.apps: "30"Comparativa i justificació:
| Recurs | dev |
pre |
pro |
Per què |
|---|---|---|---|---|
requests.cpu |
2 | 4 | 24 | pro serveix trànsit real i ha de créixer en ponts |
requests.memory |
4Gi | 8Gi | 48Gi | Ídem, més la memòria cau i la base de dades |
pods |
25 | 30 | 150 | L'HPA necessita sostre alt a pro |
services.loadbalancers |
0 | 1 | 3 | Cadascun costa diners al mes |
requests.storage |
10Gi | 50Gi | 500Gi | Les dades de reserves només creixen a producció |
Relació limits/requests CPU |
2× | 2× | 2× | Sobrecompromís moderat i uniforme |
Relació limits/requests memòria |
2× | 1,5× | 1,33× | Més conservador com més crític |
Aplicació i verificació:
kubectl apply -f k8s/entorns/dev/resourcequota.yaml
kubectl apply -f k8s/entorns/pre/resourcequota.yaml
kubectl apply -f k8s/entorns/pro/resourcequota.yaml
kubectl get resourcequota -Aresourcequota/quota-dev created
resourcequota/quota-pre created
resourcequota/quota-pro created
NAMESPACE NAME AGE REQUEST LIMIT
rutas-norte-dev quota-dev 8s pods: 9/25, requests.cpu: 1150m/2, ... limits.cpu: 2300m/4, ...
rutas-norte-pre quota-pre 7s pods: 4/30, requests.cpu: 500m/4, ... limits.cpu: 1000m/8, ...
rutas-norte-pro quota-pro 6s pods: 14/150, requests.cpu: 4200m/24, ... limits.cpu: 9600m/48, ...El deute està saldat. Ara, si algú escala api-reserves a 50 rèpliques a desenvolupament per error, o si un bucle de reinici crea pods sense parar, el dany queda contingut dins de rutas-norte-dev: els pods es quedaran sense crear i producció no se n'assabenta. Recorda del mòdul 2 que el namespace no aïlla la xarxa ni el DNS; la quota és una de les poques coses que el namespace sí que aïlla de debò, i per això és tan valuosa.
Una nota final sobre la suma total. Si sumes les requests.cpu de les tres quotes surten 30 cores, i el teu clúster en pot tenir menys. Això és intencionat: la quota és un sostre per entorn, no una reserva. Ningú no garanteix que puguis arribar al sostre dels tres alhora; el que garanteix és que cap d'ells sol no pugui passar del seu sostre.
- Com triar valors raonables
Els números anteriors no surten de la intuïció. Aquest és el mètode.
Pas 1: mesurar, no endevinar. Necessites metrics-server, que vam activar com a addon al mòdul 1:
NAME CPU(cores) MEMORY(bytes)
postgres-reserves-5d8f6b9c4-k2mnp 180m 1842Mi
api-reserves-7f4b8c9d6-2xkqp 310m 387Mi
api-reserves-7f4b8c9d6-8mvtr 285m 371Mi
redis-cache-6c9d8f7b5-vn4qx 22m 148Mi
worker-notificacions-6f7d9c8b4-lm3pt 95m 212Mi
botiga-web-5b7c9f4d8-h7pnk 8m 14MiEl detall d'aquesta ordre i les seves limitacions (és una foto instantània, no un històric) és el tema de Servidor de Mètriques. Per calibrar bé cal un històric amb percentils, que és el que dona Prometheus.
Pas 2: aplicar les fórmules.
| Recurs | Fórmula | Raonament |
|---|---|---|
requests.memory |
Percentil 95 de l'ús × 1,2 | La memòria no s'estrangula: queda't curt i mors |
limits.memory |
requests.memory × 1,3 a 1,5 |
Marge per a pics, sense sobrecomprometre |
requests.cpu |
Percentil 50 (mediana) de l'ús | La CPU sí que es comprimeix: la mediana basta per a la garantia |
limits.cpu |
requests.cpu × 2 a 4 |
Absorbir ràfegues sense estrangular |
Fixa't en l'asimetria: memòria pel percentil 95, CPU per la mediana. És l'aplicació directa de l'apartat 5.
Pas 3: calcular per a api-reserves en producció. Amb un històric de dues setmanes, incloent-hi un pont:
resources:
requests:
cpu: 300m # ~ mediana
memory: 534Mi # 445Mi x 1,2 -> arrodonim a 512Mi
limits:
cpu: "1" # ~ 3x la request, cobreix el pic de 940m
memory: 768Mi # 512Mi x 1,5Pas 4: iterar. Els valors inicials estan malament per definició. Revisa'ls:
- Després de cada canvi significatiu de l'aplicació.
- Després de cada temporada alta (a Rutas Norte, després de cada pont i de l'estiu).
- Quan aparegui estrangulament per sobre del 5 % o qualsevol
OOMKilled. - Trimestralment, per recuperar capacitat compromesa i sense fer servir.
Quatre regles que estalvien disgustos:
- Comença generós i baixa. Un límit alt de més costa capacitat; un de baix de més provoca caigudes a producció. A la primera iteració, prefereix el malbaratament.
- Multiplica per rèpliques abans de mirar la quota.
requests.cpu: 300m× 8 rèpliques = 2400m del pressupost depro. - Els components amb estat són diferents.
postgres-reservesha de tenirrequests == limitsde memòria: és la classeGuaranteedde la lliçó següent, i una base de dades no ha de ser mai la primera candidata a morir. - Documenta d'on surten els números. Un comentari al YAML amb la data del mesurament i el percentil fet servir converteix el manifest en una cosa revisable:
resources:
# Calibrat 2026-07-28 sobre 14 dies (inclou pont de juliol)
# CPU: mediana 280m, p95 620m | Memoria: p95 445Mi, pic 502Mi
requests:
cpu: 300m
memory: 512Mi
limits:
cpu: "1"
memory: 768MiErrors Comuns i Consells
| Error | Símptoma | Solució |
|---|---|---|
memory: 512m en minúscula |
CrashLoopBackOff immediat |
m és mili. Fes servir 512Mi |
Confondre M amb Mi |
OOMKilled inexplicable amb el 6,9 % de memòria de menys |
Fes servir sempre Mi/Gi |
Pod en Pending amb Insufficient memory |
No hi ha node amb forat per a les requests |
Baixar requests, afegir node o autoescalar |
| Mirar l'ús real esperant que el planificador el faci servir | "Si el node està al 10 %, per què no hi cap?" | El planificador només mira requests |
Només limits sense requests |
Es reserva més del previst | Kubernetes copia el limit a la request |
Cap limit de memòria |
Un pod pot tombar el node | Declara sempre limits.memory |
| Límit de CPU molt ajustat | Latència alta sense reinicis ni errors visibles | Mirar cpu.stat: nr_throttled/nr_periods |
| JVM o Node sense conèixer el seu límit | OOMKilled amb un límit aparentment suficient |
MaxRAMPercentage o resourceFieldRef (03-03) |
No mirar --previous als logs |
"No hi ha res als logs" després d'un reinici | kubectl logs --previous |
| Quota sense LimitRange | kubectl run falla sempre |
Afegeix un LimitRange (03-05) |
Deployment encallat en READY 8/12 |
La quota rebutja els pods nous, sense error visible al Deployment | kubectl get events i describe quota |
| Quota posada sense avisar l'equip | Desplegaments que fallen sense explicació | Comunica-ho i deixa marge inicial |
No declarar ephemeral-storage |
Pods Evicted i DiskPressure al node |
Declarar-lo en el que escrigui logs o faci servir emptyDir |
Consells:
- Tota plantilla de manifest ha de portar
resources. Posa-ho a la teva plantilla base perquè no calgui recordar-ho. - Revisa la quota abans d'escalar.
kubectl describe quota -n <ns>abans d'unkubectl scale. - Vigila les
requestssense fer servir. És la despesa invisible més gran d'un clúster: capacitat compromesa que ningú no aprofita. - Afegeix una alerta de quota al 80 %. Que te n'assabentis abans que un desplegament falli, no durant.
ephemeral-storageno és opcional a producció. Un node ambDiskPressuredesallotja pods sans.
Exercicis
Exercici 1: Provocar i diagnosticar les dues fallades
- Crea a
rutas-norte-devun poddevorador-memoriaamblimits.memory: 128Mique executi un procés que intenti reservar 300 MiB. Fes servir la imatgepolinux/stressobusyboxamb un fitxer a/dev/shm. - Observa'n l'estat, identifica el
Reasoni l'Exit Code, i explica què significa137. - Crea un pod
devorador-cpuamblimits.cpu: 100mque executi un bucle infinit. - Comprova amb
kubectl topquanta CPU consumeix realment i llegeix/sys/fs/cgroup/cpu.statper calcular el percentatge d'estrangulament. - Explica en una taula per què un va morir i l'altre no, malgrat que tots dos van superar el seu límit.
Exercici 2: Posar quota als tres entorns
- Aplica les tres ResourceQuota de l'apartat 12.
- Mostra el consum actual de cada namespace amb
kubectl describe. - Intenta escalar
api-reservesarutas-norte-deva un nombre de rèpliques que superi la quota. Quina ordre falla i quina té èxit? Localitza el missatge d'error exacte. - Intenta crear un pod amb
kubectl runsense declarar recursos arutas-norte-dev. Copia l'error i explica'l. - Calcula quantes rèpliques d'
api-reserves(ambrequests: cpu 250m, memory 256Mi) hi caben com a màxim arutas-norte-devsegons cadascun dels quatre límits de computació, i indica quin és el que mana.
Exercici 3: Calibrar worker-notificacions
Disposes d'aquests mesuraments de dues setmanes a producció, incloent-hi un pont:
CPU: mediana 95m, p95 340m, pic 780m
Memoria: mediana 212Mi, p95 268Mi, pic 295Mi
Disc: logs 400 MiB/dia, emptyDir d'adjunts fins a 600 MiB- Calcula
requestsilimitsde CPU i memòria aplicant les fórmules de l'apartat 13, i justifica per què la memòria fa servir el p95 i la CPU la mediana. - Decideix un valor d'
ephemeral-storagei raona'l. - Escriu el bloc
resourcescomplet, amb el comentari de traçabilitat. - Amb 3 rèpliques a
rutas-norte-pro, calcula quin percentatge de la quota de producció consumeix aquest component. - Si el pic de CPU és de 780m i poses
limits.cpu: 700m, què li passarà al component durant el pont? És acceptable per a un treballador de correus? Compara-ho amb la resposta si fosapi-reserves.
Solucions
Solució 1
apiVersion: v1
kind: Pod
metadata:
name: devorador-memoria
namespace: rutas-norte-dev
labels:
app: laboratori
entorn: dev
spec:
restartPolicy: Never
containers:
- name: estres
image: polinux/stress:1.0.4
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "300M", "--vm-hang", "1"]
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mikubectl apply -f devorador-memoria.yaml
sleep 15
kubectl get pod devorador-memoria -n rutas-norte-dev
kubectl describe pod devorador-memoria -n rutas-norte-dev | grep -A6 "Last State"pod/devorador-memoria created
NAME READY STATUS RESTARTS AGE
devorador-memoria 0/1 OOMKilled 0 15s
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Started: Wed, 05 Aug 2026 23:41:02 +0200
Finished: Wed, 05 Aug 2026 23:41:04 +0200El 137 és 128 + 9. Per convenció d'Unix, un procés acabat per un senyal retorna 128 + número de senyal, i el senyal 9 és SIGKILL. És la signatura inequívoca d'una mort violenta: el kernel no va donar al procés cap oportunitat de netejar. (El seu parent, el 143 = 128 + 15, és SIGTERM: terminació ordenada, la del mòdul 2.)
El devorador de CPU:
apiVersion: v1
kind: Pod
metadata:
name: devorador-cpu
namespace: rutas-norte-dev
labels:
app: laboratori
entorn: dev
spec:
containers:
- name: bucle
image: busybox:1.36
command: ["sh", "-c", "while true; do :; done"]
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
cpu: 100m
memory: 64Mikubectl apply -f devorador-cpu.yaml
sleep 60
kubectl top pod devorador-cpu -n rutas-norte-dev
kubectl exec devorador-cpu -n rutas-norte-dev -- cat /sys/fs/cgroup/cpu.statpod/devorador-cpu created
NAME CPU(cores) MEMORY(bytes)
devorador-cpu 100m 1Mi
nr_periods 604
nr_throttled 601
throttled_usec 53420118El bucle infinit voldria consumir un core sencer (1000m), però kubectl top mostra exactament 100m: el límit es compleix al milicore. I l'estrangulament és de 601/604 = 99,5 %: pràcticament tots els períodes han estat retallats. Tot i així, el pod està Running i sense reinicis.
devorador-memoria |
devorador-cpu |
|
|---|---|---|
| Va superar el seu límit | Sí (300 MiB > 128 MiB) | Sí (voldria 1000m > 100m) |
| Recurs | Incomprimible | Comprimible |
| Què va poder fer el kernel | Res: la memòria demanada no es pot "donar més a poc a poc" | Donar-li menys temps de CPU |
| Resultat | OOMKilled, codi 137 |
Running, estrangulat al 99,5 % |
| Impacte en el negoci | Pèrdua de la feina en curs | Lentitud |
Solució 2
kubectl apply -f k8s/entorns/dev/resourcequota.yaml \
-f k8s/entorns/pre/resourcequota.yaml \
-f k8s/entorns/pro/resourcequota.yaml
kubectl describe quota -n rutas-norte-devName: quota-dev
Namespace: rutas-norte-dev
Resource Used Hard
-------- ---- ----
count/deployments.apps 5 15
limits.cpu 2300m 4
limits.memory 3200Mi 8Gi
pods 9 25
requests.cpu 1150m 2
requests.memory 1600Mi 4Gi
services 4 10
services.loadbalancers 0 0kubectl scale deploy/api-reserves --replicas=12 -n rutas-norte-dev
kubectl get deploy api-reserves -n rutas-norte-dev
kubectl get events -n rutas-norte-dev --field-selector reason=FailedCreate | tail -2deployment.apps/api-reserves scaled
NAME READY UP-TO-DATE AVAILABLE AGE
api-reserves 3/12 12 3 3h
LAST SEEN TYPE REASON OBJECT MESSAGE
5s Warning FailedCreate replicaset/api-reserves-6d8f7c9b Error creating: pods
"api-reserves-6d8f7c9b-" is forbidden: exceeded quota: quota-dev,
requested: requests.cpu=250m, used: requests.cpu=1900m, limited: requests.cpu=2kubectl scale té èxit i la creació dels pods falla. El Deployment queda amb READY 3/12 indefinidament. Aquesta asimetria és la clau del diagnòstic: l'error mai no és a l'objecte que has tocat, sinó als esdeveniments del ReplicaSet.
Error from server (Forbidden): pods "prova" is forbidden: failed quota: quota-dev:
must specify limits.cpu for: prova; limits.memory for: prova;
requests.cpu for: prova; requests.memory for: provaLa quota controla requests.cpu, requests.memory, limits.cpu i limits.memory. Per poder comptabilitzar aquestes quatre sumes necessita que tots els pods les declarin, així que rebutja el que no ho fa. La solució elegant, sense obligar a escriure-les a mà, és el LimitRange de la lliçó següent.
El càlcul de rèpliques màximes, amb requests: cpu 250m, memory 256Mi i limits: cpu 500m, memory 512Mi:
| Límit de la quota | Sostre | Consum per rèplica | Rèpliques màximes |
|---|---|---|---|
requests.cpu |
2000m | 250m | 8 |
requests.memory |
4096Mi | 256Mi | 16 |
limits.cpu |
4000m | 500m | 8 |
limits.memory |
8192Mi | 512Mi | 16 |
pods |
25 | 1 | 25 |
Mana requests.cpu (i limits.cpu, empatats): 8 rèpliques. El límit efectiu és sempre el més restrictiu, i aquí és la CPU. I compte: aquestes 8 rèpliques serien el namespace sencer, així que cal descomptar el que ja consumeixen botiga-web, postgres-reserves, redis-cache i worker-notificacions.
Solució 3
- Aplicant les fórmules:
requests.memory = p95 x 1,2 = 268Mi x 1,2 = 321,6Mi -> arrodonim a 320Mi
limits.memory = 320Mi x 1,5 = 480Mi -> arrodonim a 512Mi (cobreix el pic de 295Mi amb folgança)
requests.cpu = mediana = 95m -> arrodonim a 100m
limits.cpu = 100m x 4 = 400m -> pugem a 800m per cobrir el pic de 780mL'asimetria entre memòria i CPU és exactament la de l'apartat 5. La memòria fa servir el p95 perquè quedar-se curt no significa "anar a poc a poc", significa OOMKilled: es perd el correu de confirmació que s'estava enviant i el client no rep el seu bitllet. La CPU fa servir la mediana perquè quedar-se curt només significa que el treballador triga més a buidar la cua; el limit generós permet que en un pont acceleri i es recuperi.
ephemeral-storage: 400 MiB/dia de logs més fins a 600 MiB d'emptyDird'adjunts. Assumint rotació diària de logs i un marge de seguretat:
requests.ephemeral-storage = 400Mi (logs) + 600Mi (emptyDir) = 1Gi
limits.ephemeral-storage = 1Gi x 2 = 2GiEl factor 2 cobreix el cas que la rotació de logs falli un dia o que un pont generi el doble d'adjunts. Sense aquest límit, una fallada de rotació ompliria el disc del node i provocaria DiskPressure, desallotjant també els pods sans que comparteixen node, inclòs postgres-reserves.
- El bloc complet:
- name: worker
image: registry.rutasnorte.example/worker-notificacions:1.8.0
resources:
# Calibrat 2026-08-01 sobre 14 dies (inclou pont del 15 d'agost)
# CPU: mediana 95m, p95 340m, pic 780m -> request=mediana, limit>pic
# Memoria: mediana 212Mi, p95 268Mi, pic 295Mi -> request=p95 x1,2
# Disc: 400Mi/dia de logs + 600Mi d'emptyDir d'adjunts
requests:
cpu: 100m
memory: 320Mi
ephemeral-storage: 1Gi
limits:
cpu: 800m
memory: 512Mi
ephemeral-storage: 2Gi- Amb 3 rèpliques a
rutas-norte-pro:
| Recurs | Consum (3 rèpliques) | Quota de pro |
Percentatge |
|---|---|---|---|
requests.cpu |
300m | 24000m | 1,25 % |
requests.memory |
960Mi | 49152Mi | 1,95 % |
limits.cpu |
2400m | 48000m | 5,0 % |
limits.memory |
1536Mi | 65536Mi | 2,3 % |
pods |
3 | 150 | 2,0 % |
Consum molt modest: hi ha marge de sobres perquè api-reserves escali als pics.
- Amb
limits.cpu: 700mi un pic de 780m, el treballador s'estrangula durant el pont. No mor: processa més a poc a poc. L'efecte concret és que la cua de notificacions creix i els correus de confirmació es retarden, potser de 30 segons a diversos minuts.
És acceptable? Per a worker-notificacions, sí, amb matisos. És un procés asíncron: el client ja té la seva reserva confirmada a la pantalla quan el correu s'envia. Un retard de minuts és molest però no trenca el negoci, i la cua es buida sola quan passa el pic. Caldria vigilar que el retard no creixi sense límit: si la taxa d'arribada supera la de procés, la cua no es recupera mai i allà sí que hi ha incident.
Per a api-reserves la resposta seria que no. És síncron: el client està esperant davant de la pantalla. Un estrangulament del 20 % converteix una resposta de 80 ms en una de 400 ms, botiga-web comença a esgotar el seu TEMPS_ESPERA_MS de 2000, i el client veu errors just en el moment de màxima venda de l'any. En un component de cara a l'usuari, el límit de CPU ha de cobrir el pic amb folgança.
Aquesta diferència —entre el que només va més a poc a poc i el que el client pateix— és la que cal tenir al cap en calibrar cada component.
Conclusió
Ja no escrius resources a ull. Entens el model complet: les requests són el que el planificador reserva en triar node i el que determina la prioritat relativa de CPU dins del node; els limits són el sostre que el kubelet imposa a través de cgroups en temps d'execució. Saps que el planificador només mira requests, mai l'ús real, cosa que explica per què un node al 10 % d'ús pot rebutjar un pod. I domines les unitats, incloses les dues trampes que costen incidents reals: la diferència del 6,9 % entre M i Mi, i el 512m en minúscula que fixa un límit de mig byte.
Tens clara la distinció fonamental: la CPU és comprimible i superar-la només produeix estrangulament —diagnosticable amb el quocient nr_throttled/nr_periods de cpu.stat, invisible a l'estat del pod—, mentre que la memòria és incomprimible i superar-la produeix OOMKilled amb codi 137, amb les cinc causes típiques i el reflex de mirar kubectl logs --previous. Coneixes l'ephemeral-storage i el desallotjament per disc que pot tombar pods sans de tot un node, i entens per què sobrecomprometre CPU és normal i sobrecomprometre memòria és perillós.
I has saldat l'últim deute del mòdul 2: els tres entorns de Rutas Norte tenen ResourceQuota. rutas-norte-dev no pot passar de 2 cores i 4 GiB compromesos, ni crear un sol LoadBalancer; rutas-norte-pro té 24 cores i 48 GiB de marge per als pics de ponts i vacances. Saps llegir kubectl describe quota, reconèixer l'error de quota superada, i diagnosticar el cas traïdor en què kubectl scale té èxit i el Deployment es queda per sempre en READY 8/12. I tens un mètode per triar valors: mesurar amb kubectl top, aplicar el p95 a la memòria i la mediana a la CPU, documentar la data i el percentil al mateix manifest, i iterar després de cada temporada alta.
Queda un cap solt que ha aparegut dues vegades en aquesta lliçó. Posar quota de computació obliga tots els pods a declarar recursos, i això trenca qualsevol kubectl run ràpid i qualsevol manifest d'un tercer. La peça que falta és el LimitRange, que injecta valors per defecte als contenidors que no declaren res. I hi ha un altre cap encara més interessant: sabem que quan falta memòria en un node algú mor, però no qui. La resposta no és aleatòria: depèn d'una classificació que Kubernetes assigna a cada pod segons com hagi declarat els seus recursos. La lliçó següent, LimitRanges i Classes de Qualitat de Servei (QoS), cobreix les dues coses: els camps default, min, max i maxLimitRequestRatio, la seva interacció exacta amb la ResourceQuota, i les classes Guaranteed, Burstable i BestEffort amb el seu efecte sobre l'oom_score_adj del kernel i l'ordre de desallotjament del kubelet. En acabar sabràs per què postgres-reserves ha de ser Guaranteed i podràs provocar un desallotjament al teu minikube per veure amb els teus propis ulls quin pod cau primer.
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
