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

  1. El model de recursos: requests i limits
  2. Unitats: milicores, Mi davant de M
  3. Qui fa servir les requests: el planificador
  4. Qui fa servir els limits: el kubelet i els cgroups
  5. CPU comprimible davant de memòria incomprimible
  6. Diagnosticar l'estrangulament de CPU
  7. Diagnosticar un OOMKilled
  8. ephemeral-storage i el desallotjament per disc
  9. Sobrecompromís del clúster
  10. ResourceQuota: què limita i com es llegeix
  11. L'efecte que obliga a declarar recursos
  12. Les quotes dels tres entorns de Rutas Norte
  13. Com triar valors raonables

  1. El model de recursos: requests i limits

Cada 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: 512Mi

La 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 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:

  1. requests és el que "costa" al clúster. Un pod amb requests: 4Gi bloqueja 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.
  2. limits no reserva res. Un pod amb limits: 8Gi no 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

  1. Unitats: milicores, Mi davant de M

CPU

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:

            limits:
              memory: 512m           # <- MINUSCULA: 512 MILIbytes = 0,512 bytes

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.

kubectl describe pod api-reserves-x -n rutas-norte-dev | grep -A4 Limits
    Limits:
      memory:  512m
    Requests:
      memory:  512m

Si veus això, ja saps quin és el problema.

  1. Qui fa servir les requests: el planificador

El 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:

lliure = assignable - suma de les REQUESTS de tots els pods ja assignats a aquell node

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).

kubectl describe node rutas-norte | grep -A8 "Capacity:"
Capacity:
  cpu:                4
  ephemeral-storage:  61202244Ki
  memory:             8039484Ki
  pods:               110
Allocatable:
  cpu:                4
  ephemeral-storage:  56403448552
  memory:             7937084Ki
  pods:               110

En 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.

kubectl describe node rutas-norte | grep -A12 "Allocated resources"
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 -5
NAME                       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.

  1. Qui fa servir els limits: el kubelet i els cgroups

Els 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'
536870912
100000 100000
10

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. Amb limits: cpu: 500m seria 50000 100000.
  • cpu.weight = 10: deriva de la request de 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 de request rep 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.

  1. 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 limits de memòria s'han de calibrar amb cura, perquè quedar-se curt mata el procés. Els limits de 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:

  • requests de CPU i memòria: sempre, a tots els contenidors. Sense negociació.
  • limits de memòria: sempre. Un pod sense límit de memòria pot tombar el node sencer i provocar desallotjaments en cadena.
  • limits de CPU: sí a dev i pre (per detectar aviat el consum excessiu) i generosos a pro, entre 2 i 4 vegades la request, per permetre absorbir els pics de ponts i vacances.

  1. 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:

kubectl exec -n rutas-norte-pro deploy/api-reserves -- cat /sys/fs/cgroup/cpu.stat
usage_usec 184920331
user_usec 152014882
system_usec 32905449
nr_periods 918442
nr_throttled 214883
throttled_usec 41220984

La 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:

throttling = nr_throttled / nr_periods = 214883 / 918442 = 23,4 %

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 198445

La 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-reservesrequests: 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.

  1. Diagnosticar un OOMKilled

El cas oposat és escandalós i fàcil d'identificar:

kubectl get pods -n rutas-norte-pro -l app=api-reserves
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)   41m

El detall és a describe:

kubectl describe pod api-reserves-7f4b8c9d6-8mvtr -n rutas-norte-pro
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:  256Mi

Les 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:

kubectl logs api-reserves-7f4b8c9d6-8mvtr -n rutas-norte-pro --previous | tail -6
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 / 512MB

Aquí 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.

  1. ephemeral-storage i el desallotjament per disc

Hi 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: 2Gi

Què 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:

kubectl describe node rutas-norte | grep -A6 Conditions
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 status

DiskPressure: 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.

  1. Sobrecompromís del clúster

Tornem a aquella línia d'abans:

  Resource  Requests      Limits
  cpu       2350m (58%)   5200m (130%)

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.

  1. 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 PVC

Les 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 Succeeded o Failed s'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:

kubectl describe resourcequota quota-dev -n rutas-norte-dev
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       0

Dues 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 -3
deployment.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=2

Fixa'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 Conditions
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
api-reserves   8/12    12           8           3h

Conditions:
  Type             Status  Reason
  ----             ------  ------
  Available        True    MinimumReplicasAvailable
  ReplicaProgressing False ProgressDeadlineExceeded

Davant 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.

  1. 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.cpu o limits.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:

kubectl run prova-sense-recursos --image=nginx:1.27.1-alpine -n rutas-norte-dev
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-recursos

Aquest é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.

  1. 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 Sobrecompromís moderat i uniforme
Relació limits/requests memòria 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 -A
resourcequota/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.

  1. 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:

kubectl top pods -n rutas-norte-pro --sort-by=memory
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           14Mi

El 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:

CPU:     mediana 280m, p95 620m, pic 940m
Memoria: mediana 370Mi, p95 445Mi, pic 502Mi
          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,5

Pas 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:

  1. 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.
  2. Multiplica per rèpliques abans de mirar la quota. requests.cpu: 300m × 8 rèpliques = 2400m del pressupost de pro.
  3. Els components amb estat són diferents. postgres-reserves ha de tenir requests == limits de memòria: és la classe Guaranteed de la lliçó següent, i una base de dades no ha de ser mai la primera candidata a morir.
  4. 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: 768Mi

Errors 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:

  1. Tota plantilla de manifest ha de portar resources. Posa-ho a la teva plantilla base perquè no calgui recordar-ho.
  2. Revisa la quota abans d'escalar. kubectl describe quota -n <ns> abans d'un kubectl scale.
  3. Vigila les requests sense fer servir. És la despesa invisible més gran d'un clúster: capacitat compromesa que ningú no aprofita.
  4. Afegeix una alerta de quota al 80 %. Que te n'assabentis abans que un desplegament falli, no durant.
  5. ephemeral-storage no és opcional a producció. Un node amb DiskPressure desallotja pods sans.

Exercicis

Exercici 1: Provocar i diagnosticar les dues fallades

  1. Crea a rutas-norte-dev un pod devorador-memoria amb limits.memory: 128Mi que executi un procés que intenti reservar 300 MiB. Fes servir la imatge polinux/stress o busybox amb un fitxer a /dev/shm.
  2. Observa'n l'estat, identifica el Reason i l'Exit Code, i explica què significa 137.
  3. Crea un pod devorador-cpu amb limits.cpu: 100m que executi un bucle infinit.
  4. Comprova amb kubectl top quanta CPU consumeix realment i llegeix /sys/fs/cgroup/cpu.stat per calcular el percentatge d'estrangulament.
  5. 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

  1. Aplica les tres ResourceQuota de l'apartat 12.
  2. Mostra el consum actual de cada namespace amb kubectl describe.
  3. Intenta escalar api-reserves a rutas-norte-dev a un nombre de rèpliques que superi la quota. Quina ordre falla i quina té èxit? Localitza el missatge d'error exacte.
  4. Intenta crear un pod amb kubectl run sense declarar recursos a rutas-norte-dev. Copia l'error i explica'l.
  5. Calcula quantes rèpliques d'api-reserves (amb requests: cpu 250m, memory 256Mi) hi caben com a màxim a rutas-norte-dev segons 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
  1. Calcula requests i limits de 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.
  2. Decideix un valor d'ephemeral-storage i raona'l.
  3. Escriu el bloc resources complet, amb el comentari de traçabilitat.
  4. Amb 3 rèpliques a rutas-norte-pro, calcula quin percentatge de la quota de producció consumeix aquest component.
  5. 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 fos api-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: 128Mi
kubectl 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 +0200

El 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: 64Mi
kubectl 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.stat
pod/devorador-cpu created

NAME            CPU(cores)   MEMORY(bytes)
devorador-cpu   100m         1Mi

nr_periods 604
nr_throttled 601
throttled_usec 53420118

El 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-dev
Name:                     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       0
kubectl 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 -2
deployment.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=2

kubectl 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.

kubectl run prova --image=nginx:1.27.1-alpine -n rutas-norte-dev
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: prova

La 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

  1. 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 780m

L'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.

  1. ephemeral-storage: 400 MiB/dia de logs més fins a 600 MiB d'emptyDir d'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 = 2Gi

El 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.

  1. 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
  1. 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.

  1. Amb limits.cpu: 700m i 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

Mòdul 2: Components Principals de Kubernetes

Mòdul 3: Gestió de Configuració i Secrets

Mòdul 4: Xarxes a Kubernetes

Mòdul 5: Emmagatzematge a Kubernetes

Mòdul 6: Conceptes Avançats de Kubernetes

Mòdul 7: Monitoratge i Registre

Mòdul 8: Seguretat a Kubernetes

Mòdul 9: Escalat i Rendiment

Mòdul 10: Ecosistema i Eines de Kubernetes

Mòdul 11: Estudis de Cas i Aplicacions del Món Real

Mòdul 12: Preparació per a la Certificació de Kubernetes

© Copyright 2026. Tots els drets reservats