Tanquem el mòdul 8 amb una plataforma segura i auditable, però amb una confessió incòmoda: els components de Rutas Norte continuen tenint un nombre de rèpliques fix, escrit a mà. api-reserves té replicas: 4 i botiga-web té replicas: 3 perquè algú, fa mesos, va mesurar un dimarts qualsevol i li va semblar suficient. Aquest número no sap res del pont de maig. L'any passat la plataforma va caure als onze minuts d'obrir la venda, i la resposta de l'equip va ser un kubectl scale a corre-cuita des d'un portàtil, a l'andana d'una estació, amb el tren sortint.
Aquesta lliçó elimina aquesta cursa. L'HorizontalPodAutoscaler (HPA) és l'objecte de Kubernetes que ajusta automàticament el nombre de rèpliques d'un Deployment en funció de mètriques observades. No és màgia: és un controlador més, amb el seu bucle de reconciliació —el mateix que vam estudiar al mòdul 1—, una fórmula aritmètica molt concreta i un grapat de paràmetres que cal entendre bé perquè el resultat sigui estabilitat i no un pèndol de pods arrencant i morint.
Veurem quines càrregues es poden escalar horitzontalment i quines no, l'API autoscaling/v2 completa, la fórmula exacta amb què es calculen les rèpliques desitjades, el bloc behavior que governa la velocitat de pujada i baixada, i els dos manifests definitius d'api-reserves i botiga-web. Acabarem amb un conflicte que mossega gairebé tots els equips la primera vegada: l'HPA i el camp replicas del YAML versionat barallant-se pel mateix número.
Contingut
- Escalar cap enfora davant d'escalar cap amunt
- Quines càrregues admeten escalat horitzontal i quines no
- Anatomia de l'HorizontalPodAutoscaler a
autoscaling/v2 - Els quatre tipus de mètrica:
Resource,Pods,ObjectiExternal - La fórmula exacta del controlador, pas a pas
- La banda de tolerància i el ball de rèpliques
- Requisits ineludibles:
requestsi metrics-server - Diagnòstic del temut
<unknown> - El bloc
behavior: polítiques, períodes i finestra d'estabilització - Diverses mètriques alhora: guanya la que demana més
- Els HPA de Rutas Norte, manifests complets
- Simulació d'una punta de càrrega i observació en viu
- El conflicte entre l'HPA i el camp
replicas - Què no escalar amb HPA i el coll d'ampolla real
- Errors comuns i consells
- Exercicis
- Conclusió
- Escalar cap enfora davant d'escalar cap amunt
Hi ha exactament dues maneres de donar més capacitat a un servei, i convé tenir els noms clars perquè la resta del mòdul s'hi recolza.
Escalar cap amunt (scale up, escalat vertical) és donar més recursos a cada instància: pujar el requests/limits de CPU de 200m a 800m, la memòria de 256Mi a 1Gi. El nombre de pods no canvia; cada pod és més gran. A Kubernetes això implica recrear el pod, perquè els recursos d'un contenidor formen part de la seva especificació immutable (amb el matís del redimensionament en calent que veurem a 09-02).
Escalar cap enfora (scale out, escalat horitzontal) és posar més instàncies de la mateixa mida: passar de 4 pods d'api-reserves a 12 pods idèntics. Cada pod continua sent igual de gran; n'hi ha més. A Kubernetes això és canviar un nombre enter: el replicas del Deployment.
| Aspecte | Escalat vertical (cap amunt) | Escalat horitzontal (cap enfora) |
|---|---|---|
| Què canvia | Mida de cada pod (requests/limits) |
Nombre de pods |
| Sostre | El node més gran disponible | Pràcticament il·limitat (amb nodes) |
| Requereix reiniciar el pod | Sí (excepte redimensionament en calent) | No, els pods existents no es toquen |
| Requisit de l'aplicació | Que sàpiga aprofitar més CPU/RAM | Que no tingui estat local ni afinitat de sessió |
| Tolerància a errors | No millora: continua havent-hi un punt únic | Millora: la càrrega es reparteix entre més instàncies |
| Cost de granularitat | Salta a esglaons grans | Fi: un pod cada vegada |
| Objecte de Kubernetes | VPA (09-02) o edició manual | HPA (aquesta lliçó), KEDA (09-04) |
| Velocitat de reacció | Lenta (recreació del pod) | Ràpida (segons, si la imatge està en memòria cau) |
La regla pràctica: l'escalat vertical resol el problema d'una instància mal dimensionada; l'escalat horitzontal resol el problema del volum de trànsit. No són alternatives, són eixos diferents. Rutas Norte necessita tots dos: encertar la mida de cada pod (09-02) i multiplicar el nombre de pods quan arriba el pont de maig (aquesta lliçó).
Un detall important que molts passen per alt: l'escalat horitzontal també millora la disponibilitat, mentre que el vertical no. Dotze pods d'api-reserves repartits entre nodes sobreviuen a la caiguda d'un node; un únic pod gegantí, no. Aquesta idea la reprendrem a fons a 09-05.
- Quines càrregues admeten escalat horitzontal i quines no
L'escalat horitzontal només funciona si qualsevol rèplica pot atendre qualsevol petició. Això s'anomena ser sense estat (stateless), i és una propietat de l'aplicació, no de Kubernetes. Kubernetes et deixarà escalar qualsevol cosa; que el resultat sigui correcte depèn de com estigui escrita l'aplicació.
Repassem els components de Rutas Norte:
| Component | Escalable horitzontalment? | Motiu |
|---|---|---|
botiga-web (nginx) |
Sí, sense reserves | Serveix actius estàtics i fa de proxy. No guarda res entre peticions. |
api-reserves (Node.js) |
Sí | API REST sense estat: la sessió va en un token signat, l'estat a PostgreSQL i redis-cache. |
worker-notificacions |
Sí, però la CPU no és el senyal correcte | Consumeix d'una cua; afegir consumidors accelera el buidatge. La mètrica útil és la longitud de la cua (09-04). |
redis-cache |
No amb HPA | És una memòria cau amb estat particionat. Afegir rèpliques no reparteix la càrrega sense sharding explícit. |
postgres-reserves |
Rotundament no | Base de dades primària. Afegir pods no crea més bases de dades: crea rèpliques que es barallen pel mateix volum o queden òrfenes. |
informes-ocupacio (CronJob) |
No aplica | El seu paral·lelisme es controla amb parallelism al Job (06-03), no amb HPA. |
El cas de postgres-reserves mereix un paràgraf. És un StatefulSet amb un volum persistent per rèplica. Si hi posessis un HPA i l'HPA decidís passar d'1 a 5 rèpliques, Kubernetes crearia cinc pods postgres-reserves-0 a postgres-reserves-4, cadascun amb el seu propi PVC buit, cadascun arrencant una base de dades independent i buida. El Service repartiria les peticions entre les cinc. El resultat seria una corrupció silenciosa de dades: unes reserves anirien a una base de dades i altres a una altra. Escalar una base de dades relacional és un problema d'arquitectura de dades (rèpliques de lectura, particionat, un operador que ho sàpiga fer — 06-07), no un problema de comptar pods.
Regla que convé memoritzar: si en afegir una rèplica el sistema no respon més peticions correctes per segon, l'HPA no és la teva eina.
- Anatomia de l'HorizontalPodAutoscaler a
autoscaling/v2
autoscaling/v2L'HPA és un objecte de l'API com qualsevol altre. La seva versió estable i actual és autoscaling/v2; les versions v2beta1 i v2beta2 estan eliminades des de fa diverses versions i autoscaling/v1 només permet CPU (el servidor la continua servint per compatibilitat, però no la facis servir: perds behavior i totes les mètriques que no siguin CPU).
Aquest és l'esquelet complet, amb comentaris sobre cada camp:
apiVersion: autoscaling/v2 # Versio estable. MAI autoscaling/v1 en material nou.
kind: HorizontalPodAutoscaler
metadata:
name: api-reserves
namespace: rutas-norte-pro
spec:
# A QUIN objecte li canvia el nombre de repliques.
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment # Tambe val StatefulSet o qualsevol recurs amb subrecurs /scale
name: api-reserves # Ha d'existir al MATEIX namespace que l'HPA
minReplicas: 4 # Terra. L'HPA mai baixara d'aqui.
maxReplicas: 30 # Sostre. L'HPA mai pujara d'aqui, passi el que passi.
# QUE mira per decidir. Es una LLISTA: n'hi pot haver diverses.
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 65 # Objectiu: 65 % del requests de CPU, de mitjana entre pods
# COM de rapid puja i baixa. Opcional pero gairebe sempre necessari.
behavior:
scaleUp: {}
scaleDown: {}Tres punts de partida que convé fixar abans de continuar:
scaleTargetRefapunta a un objecte que exposa el subrecurs/scale. Deployments, ReplicaSets i StatefulSets l'exposen. Un DaemonSet no, perquè el seu nombre de pods el determina el nombre de nodes (06-02), i no tindria sentit.minReplicasimaxReplicassón barreres dures. ElmaxReplicasés la teva xarxa de seguretat econòmica i operativa: si un error de l'aplicació dispara la CPU al 100 % sense que hi hagi trànsit real, l'HPA intentarà escalar sense fi; el sostre ho impedeix. Posa'l sempre, i posa'l pensant en què passaria si s'assolís.minReplicaspot ser 0 des de Kubernetes 1.30 si la feature gateHPAScaleToZeroestà activa, però això requereix una mètrica externa o d'objecte i no està habilitat per defecte a la majoria de clústers. L'escalat a zero de debò el farem amb KEDA a 09-04.
El bucle del controlador
L'HPA el gestiona l'horizontal-pod-autoscaler que viu dins del kube-controller-manager. El seu cicle per defecte és de 15 segons (--horizontal-pod-autoscaler-sync-period). A cada cicle:
flowchart TD
A[Cada 15 s] --> B[Llegeix l'HPA i l'objecte desti]
B --> C[Consulta les metriques<br/>metrics.k8s.io o APIs d'agregacio]
C --> D{Metriques disponibles?}
D -- No --> E[TARGETS = unknown<br/>no escala, emet esdeveniment]
D -- Si --> F[Aplica la formula<br/>repliques desitjades]
F --> G{Dins de la<br/>banda de tolerancia?}
G -- Si --> H[No fa res]
G -- No --> I[Aplica behavior:<br/>estabilitzacio i politiques]
I --> J[Acota entre min i maxReplicas]
J --> K[PATCH al subrecurs /scale]
K --> L[El Deployment ajusta el seu ReplicaSet]
Fixa't en el final del flux: l'HPA no crea pods. Modifica el camp replicas del Deployment mitjançant el subrecurs /scale, i a partir d'aquí el controlador de Deployments i el de ReplicaSets fan la seva feina habitual (mòdul 2). És reconciliació encadenada, exactament el patró que vam veure a 01-02.
- Els quatre tipus de mètrica:
Resource, Pods, Object i External
Resource, Pods, Object i ExternalEl bloc metrics accepta quatre tipus. Dos els faràs servir avui; els altres dos es defineixen aquí i s'exploten a 09-04.
| Tipus | D'on surten les dades | Què mesura | Àmbit | Es fa servir a |
|---|---|---|---|---|
Resource |
metrics-server (metrics.k8s.io) |
CPU o memòria dels pods del destí | Per pod, promitjat | 09-01 |
ContainerResource |
metrics-server | CPU/memòria d'un contenidor concret del pod | Per contenidor | 09-01 |
Pods |
API de mètriques personalitzades (custom.metrics.k8s.io) |
Qualsevol mètrica emesa pels pods del destí | Per pod, promitjat | 09-04 |
Object |
API de mètriques personalitzades | Una mètrica associada a un altre objecte de Kubernetes (un Ingress, un Service) | Valor únic | 09-04 |
External |
API de mètriques externes (external.metrics.k8s.io) |
Alguna cosa de fora del clúster: longitud d'una cua, missatges en un topic | Valor únic o dividit entre pods | 09-04 |
Resource amb Utilization
És la forma més comuna. Expressa l'objectiu com a percentatge del requests del contenidor:
Això significa: «mantén el consum mitjà de CPU dels pods d'api-reserves a l'entorn del 65 % del que cadascun té reservat a requests.cpu». Si requests.cpu és 500m, l'objectiu són 325m per pod.
És fonamental entendre que Utilization es calcula sobre requests, no sobre limits ni sobre la CPU del node. Un pod pot mostrar una utilització del 180 % si consumeix 900m amb un requests de 500m; això és perfectament possible perquè el requests és una reserva mínima, no un topall (mòdul 3).
Resource amb AverageValue
Expressa l'objectiu en unitats absolutes, ignorant el requests:
«Mantén el consum mitjà de memòria per pod en 700Mi.»
Quan cal fer servir cadascun?
Utilization |
AverageValue |
|
|---|---|---|
| S'expressa en | Percentatge del requests |
Unitats (m de CPU, Mi de memòria) |
Requereix requests declarat |
Sí, obligatori | No |
Es trenca si canvies el requests |
Sí: l'objectiu real es mou sol | No |
| Llegibilitat per a l'equip | Alta («al 65 % del seu») | Mitjana |
| Recomanat per a | CPU, el cas general | Memòria, o quan el requests canvia sovint (VPA) |
Un avís sobre la memòria com a mètrica d'HPA: gairebé mai és bona idea. Molts entorns d'execució (la JVM, el recol·lector d'escombraries de Node.js, PostgreSQL) no retornen la memòria al sistema encara que ja no la necessitin. El consum puja i es queda amunt. Un HPA per memòria escalaria cap enfora i mai tornaria a baixar, perquè la memòria dels pods vells mai baixa de l'objectiu. Fes servir CPU, o millor encara, una mètrica de negoci (09-04).
ContainerResource
Variant de Resource que mira un contenidor concret en lloc de sumar tots els del pod. És molt útil a Rutas Norte, on diversos pods porten sidecars:
metrics:
- type: ContainerResource
containerResource:
name: cpu
container: api # Nomes el contenidor "api", no el sidecar de metriques
target:
type: Utilization
averageUtilization: 65Sense això, un sidecar que consumeix 50m constants distorsiona el percentatge del pod sencer, i en pods petits la distorsió és gran. Si el teu destí té sidecars, prefereix ContainerResource.
Pods, Object i External (definició)
Els definim ara perquè en reconeguis la sintaxi, però el seu ús real arriba a 09-04.
# Pods: una metrica que emeten els mateixos pods del desti, promitjada entre ells.
- type: Pods
pods:
metric:
name: peticions_per_segon
target:
type: AverageValue
averageValue: "80" # 80 peticions per segon i pod
# Object: una metrica associada a UN ALTRE objecte del cluster.
- type: Object
object:
describedObject:
apiVersion: networking.k8s.io/v1
kind: Ingress
name: rutas-norte-public
metric:
name: peticions_per_segon
target:
type: Value
value: "2000" # Valor TOTAL de l'objecte, no per pod
# External: alguna cosa de fora del cluster.
- type: External
external:
metric:
name: longitud_cua_correus
selector:
matchLabels:
cua: notificacions-confirmacio
target:
type: AverageValue
averageValue: "500" # 500 missatges pendents per podDiferència clau entre Value i AverageValue a Object i External: amb Value l'HPA compara el valor brut amb l'objectiu; amb AverageValue divideix el valor entre el nombre de rèpliques abans de comparar. Per a una cua sempre vols AverageValue («cada worker es fa càrrec de 500 missatges»), perquè és el que fa que el nombre de rèpliques creixi amb la cua.
Els tipus Pods, Object i External no funcionen de fàbrica: necessiten un adaptador de mètriques registrat a la capa d'agregació de l'API. Sense ell, l'HPA reportarà <unknown> eternament. Això és exactament el que resol KEDA a 09-04.
- La fórmula exacta del controlador, pas a pas
Aquí hi ha el cor de la lliçó. Tot el comportament de l'HPA surt d'una única expressió:
On sostre() és l'arrodoniment cap amunt a l'enter següent. Apliquem-la a api-reserves amb números reals.
Situació de partida
api-reserves està desplegat amb aquests recursos (els vam fixar al mòdul 3):
I el seu HPA té averageUtilization: 65, minReplicas: 4, maxReplicas: 30.
Objectiu absolut per pod: 65 % de 500m = 325m.
Cas 1: dimarts a la tarda, tot tranquil
Hi ha 4 rèpliques. kubectl top pods mostra:
NAME CPU(cores) MEMORY(bytes)
api-reserves-7c9d4f8b6d-2mk8p 140m 310Mi
api-reserves-7c9d4f8b6d-5xqzn 155m 298Mi
api-reserves-7c9d4f8b6d-9jw4t 132m 305Mi
api-reserves-7c9d4f8b6d-hb7rc 149m 301MiMitjana: (140 + 155 + 132 + 149) / 4 = 144m.
La fórmula demana 2 rèpliques. Però minReplicas és 4, així que es queda en 4. Correcte: no volem baixar de 4 en producció per disponibilitat.
Cas 2: obertura de la venda del pont de maig
Continua havent-hi 4 rèpliques, però el trànsit es dispara:
NAME CPU(cores) MEMORY(bytes)
api-reserves-7c9d4f8b6d-2mk8p 910m 680Mi
api-reserves-7c9d4f8b6d-5xqzn 940m 702Mi
api-reserves-7c9d4f8b6d-9jw4t 895m 671Mi
api-reserves-7c9d4f8b6d-hb7rc 925m 694MiMitjana: (910 + 940 + 895 + 925) / 4 = 917,5m. Compte: estan fregant el seu limit de 1000m, és a dir, estan patint throttling (mòdul 3).
L'HPA vol 12 rèpliques. Està per sota de maxReplicas: 30, així que s'aplica (subjecte al behavior, que veurem a l'apartat 9).
Observa l'elegància de la fórmula: multiplica el nombre actual pel factor d'excés. Si vas al triple de l'objectiu amb 4 pods, demana 12 pods. És una regla de tres que assumeix implícitament que la càrrega es reparteix per igual entre les rèpliques, cosa que és certa si el Service fa un balanceig raonable i les peticions són homogènies.
Cas 3: passa la punta
Ara hi ha 12 rèpliques i la mitjana baixa a 120m:
Demana 5 rèpliques. Baixarà de 12 a 5, però no de cop: la finestra d'estabilització i les polítiques de scaleDown controlen el ritme (apartat 9).
Detalls fins de la fórmula que gairebé ningú coneix
Aquests matisos expliquen comportaments que d'altra manera semblen erràtics:
-
Pods sense mètriques. Si un pod encara no té mètriques (acaba d'arrencar), l'HPA l'exclou de la mitjana però el compta de manera conservadora: en escalar cap amunt assumeix consum 0 per a aquest pod (per no sobreestimar), i en escalar cap avall assumeix consum igual a l'objectiu (per no infraestimar). El resultat és que l'HPA és prudent en tots dos sentits mentre hi ha pods arrencant.
-
Pods no llestos. Els pods que no han passat la seva sonda
readinessProbes'exclouen del càlcul en escalar cap amunt. Això evita el clàssic bucle infernal: pods nous que encara no reben trànsit, amb CPU alta per l'arrencada, farien creure a l'HPA que cal escalar encara més. -
--horizontal-pod-autoscaler-initial-readiness-delay(30 s per defecte): durant els primers 30 segons de vida d'un pod, les seves mètriques s'ignoren completament. La justificació és clara: l'arrencada de Node.js consumeix molta CPU compilant i carregant mòduls, i no volem que aquest pic contamini la decisió. -
--horizontal-pod-autoscaler-cpu-initialization-period(5 min per defecte): un marge addicional per a mètriques de CPU de pods acabats d'estar llestos.
La conseqüència pràctica dels punts 3 i 4 és que l'HPA té una latència d'arrencada d'almenys mig minut per decisió, i per això el sobreaprovisionament de 09-03 i el preescalfament per cron de 09-04 tenen sentit.
- La banda de tolerància i el ball de rèpliques
Si l'HPA apliqués la fórmula literalment cada 15 segons, el nombre de rèpliques oscil·laria sense parar. Amb 4 pods a 320m i un objectiu de 325m la fórmula dona sostre(4 × 0,984) = 4; amb 330m dona sostre(4 × 1,015) = 5. Una fluctuació de 10m provocaria un pod amunt i avall indefinidament. A això se'n diu ball o thrashing, i és car: cada arrencada consumeix CPU, escalfa memòries cau des de zero i embruta les mètriques.
Kubernetes ho evita amb una banda de tolerància: si el quocient valorActual / valorObjectiu està entre 0,9 i 1,1 (és a dir, dins d'un ±10 %), l'HPA no fa res.
ratio = valorActual / valorObjectiu
|ratio - 1| <= 0,10 → no s'escala
|ratio - 1| > 0,10 → s'aplica la formulaAmb el nostre objectiu de 325m, la zona morta és:
| Consum mitjà | ratio | Actua l'HPA? |
|---|---|---|
| 280m | 0,86 | Sí, escala cap avall |
| 300m | 0,92 | No, dins de la banda |
| 325m | 1,00 | No |
| 355m | 1,09 | No, dins de la banda |
| 380m | 1,17 | Sí, escala cap amunt |
Aquest llindar és global del clúster (--horizontal-pod-autoscaler-tolerance), no configurable per HPA... fins fa poc. Des de Kubernetes 1.33 existeix la feature gate HPAConfigurableTolerance, que permet fixar la tolerància per mètrica dins del behavior:
behavior:
scaleUp:
tolerance: 0.05 # Mes sensible en pujar: reacciona amb un 5 % d'exces
scaleDown:
tolerance: 0.15 # Mes mandros en baixarCom a feature gate opcional, no la donis per feta: comprova la versió i la configuració del teu clúster abans de fer-la servir en producció. Al clúster de minikube del curs treballarem amb la tolerància per defecte del 10 %.
- Requisits ineludibles:
requests i metrics-server
requests i metrics-serverDos requisits, i cap no és negociable.
Requisit 1: metrics-server instal·lat
L'HPA amb mètriques de tipus Resource llegeix de l'API metrics.k8s.io, que serveix metrics-server. El vam instal·lar a 07-02 amb l'addon de minikube. Verifica-ho:
La columna AVAILABLE ha de dir True. Si diu False (MissingEndpoints) o similar, l'HPA no funcionarà.
Si kubectl top funciona, l'HPA tindrà dades. Si no, no hi ha HPA que valgui. És la primera comprovació sempre.
A minikube, si encara no el tens:
minikube -p rutas-norte addons enable metrics-server
# Triga entre 30 i 60 segons a comencar a servir dades.Requisit 2: requests declarats al contenidor
Aquesta és la trampa més freqüent. L'objectiu Utilization es calcula com a percentatge del requests. Si un contenidor no declara requests.cpu, l'HPA no té denominador, i la seva mètrica queda com <unknown> per sempre. No hi ha cap missatge d'error espectacular; simplement no escala.
# Fragment del Deployment d'api-reserves. SENSE AIXO NO HI HA HPA.
spec:
template:
spec:
containers:
- name: api
image: registry.rutasnorte.example/api-reserves:1.14.2
resources:
requests:
cpu: 500m # <-- L'HPA NECESSITA aquest valor
memory: 512Mi
limits:
cpu: "1"
memory: 1GiI compte amb els sidecars: si el pod té un contenidor sense requests.cpu, la mètrica del pod sencer queda invàlida per a l'HPA quan fas servir type: Resource. A Rutas Norte, api-reserves porta un sidecar exportador de mètriques; si aquest sidecar no declara requests, l'HPA de tot el Deployment es queda cec. Dues solucions:
- Declarar
requeststambé al sidecar (recomanat, i a més necessari per a la QoS del mòdul 3). - Fer servir
type: ContainerResourceapuntant només al contenidorapi.
Apliquem les dues a Rutas Norte: cinturó i tirants.
- Diagnòstic del temut
<unknown>
<unknown>Tard o d'hora veuràs això:
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
api-reserves Deployment/api-reserves <unknown>/65% 4 30 4 3m12s<unknown> significa: «l'HPA no ha pogut obtenir el valor actual d'aquesta mètrica». No escala, i punt. El diagnòstic es fa sempre en el mateix ordre:
Fixa't en el bloc Conditions i en els esdeveniments del final. Les causes, ordenades per freqüència:
Símptoma a describe |
Causa | Solució |
|---|---|---|
FailedGetResourceMetric ... unable to get metrics for resource cpu: no metrics returned from resource metrics API |
metrics-server no hi és o no respon | Instal·lar/reparar metrics-server (07-02) |
missing request for cpu on container X |
Un contenidor del pod no declara requests.cpu |
Afegir requests a tots els contenidors, sidecars inclosos |
ScalingActive=False, reason: FailedGetScale |
El scaleTargetRef apunta a un objecte que no existeix o està mal escrit |
Corregir name/kind/apiVersion |
did not receive metrics for any ready pods |
Tots els pods porten menys de 30 s llestos, o cap no passa la readiness | Esperar; revisar sondes (07-01) |
<unknown> només en mètriques Pods/External |
No hi ha adaptador de mètriques personalitzades registrat | Instal·lar l'adaptador o KEDA (09-04) |
Exemple de sortida real amb l'error de requests mancant:
Conditions:
Type Status Reason Message
---- ------ ------ -------
AbleToScale True SucceededGetScale the HPA controller was able to get the target's current scale
ScalingActive False FailedGetResourceMetric the HPA was unable to compute the replica count:
failed to get cpu utilization: missing request for cpu
in container exportador-metriques of Pod api-reserves-7c9d4f8b6d-2mk8p
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedGetResourceMetric 12s (x8 over 2m) horizontal-pod-autoscaler missing request for cpu
Warning FailedComputeMetricsReplicas 12s (x8 over 2m) horizontal-pod-autoscaler invalid metrics (1 invalid out of 1)El missatge assenyala amb nom i cognoms el contenidor culpable: exportador-metriques. Afegir-li requests.cpu resol el problema en el següent cicle de 15 segons.
Un truc de diagnòstic ràpid: consulta directament l'API de mètriques per veure què està veient l'HPA.
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/namespaces/rutas-norte-pro/pods" \
| python3 -m json.tool | head -40Si aquesta crida retorna dades i l'HPA continua en <unknown>, el problema és de requests, no de metrics-server.
- El bloc
behavior: polítiques, períodes i finestra d'estabilització
behavior: polítiques, períodes i finestra d'estabilitzacióFins ara hem vist quantes rèpliques vol l'HPA. El bloc behavior decideix a quina velocitat arriba a aquest número. És la diferència entre un autoescalat que funciona i un que fa por.
Estructura
behavior:
scaleUp:
stabilizationWindowSeconds: 0
selectPolicy: Max
policies:
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 4
periodSeconds: 30
scaleDown:
stabilizationWindowSeconds: 600
selectPolicy: Min
policies:
- type: Percent
value: 20
periodSeconds: 120Camp a camp:
policies — cada política limita quant pot canviar el nombre de rèpliques en una finestra de periodSeconds:
type: Percentambvalue: 100iperiodSeconds: 30significa «en qualsevol finestra de 30 segons pots com a molt duplicar el nombre de rèpliques» (augmentar un 100 % sobre la base).type: Podsambvalue: 4iperiodSeconds: 30significa «en qualsevol finestra de 30 segons pots afegir com a molt 4 pods».
selectPolicy — quan hi ha diverses polítiques, decideix quina mana:
| Valor | Efecte | Quan fer-lo servir |
|---|---|---|
Max (defecte a scaleUp) |
Permet el canvi més agressiu de totes les polítiques | Pujar ràpid |
Min (defecte a scaleDown) |
Permet el canvi més conservador | Baixar a poc a poc |
Disabled |
Desactiva l'escalat en aquesta direcció | Congelar baixades durant una campanya |
Exemple de l'efecte de selectPolicy: Max a scaleUp amb les nostres polítiques i 4 rèpliques actuals:
- Política de percentatge: 100 % de 4 = permet afegir 4 → 8 rèpliques.
- Política de pods: permet afegir 4 → 8 rèpliques.
Maxpren el més gran: 8 rèpliques.
Amb 20 rèpliques actuals:
- Percentatge: 100 % de 20 = permet afegir 20 → 40 rèpliques.
- Pods: permet afegir 4 → 24 rèpliques.
Maxpren 40 (limitat després permaxReplicas: 30→ 30).
I amb selectPolicy: Min hauria triat 24. Això il·lustra per què Max en pujar té sentit: quan ja tens moltes rèpliques, el percentatge et dona marge per créixer ràpid.
stabilizationWindowSeconds — és el paràmetre més important i el pitjor entès. Funciona així: l'HPA guarda un històric de les recomanacions dels últims N segons i fa servir el valor més conservador d'aquesta finestra.
- A
scaleUp, «més conservador» significa el mínim de les recomanacions recents. Amb una finestra de 60 s, si en l'últim minut l'HPA va recomanar 12, 8 i 14 rèpliques, escalarà a 8. Efecte: ignora pics aïllats. - A
scaleDown, «més conservador» significa el màxim de les recomanacions recents. Amb una finestra de 600 s, si en els últims 10 minuts va recomanar 12, 5 i 6, mantindrà 12. Efecte: no baixa fins que la calma se sosté deu minuts sencers.
Valors per defecte si no defineixes behavior:
scaleUp |
scaleDown |
|
|---|---|---|
stabilizationWindowSeconds |
0 | 300 (5 minuts) |
policies |
100 % cada 15 s i 4 pods cada 15 s | 100 % cada 15 s |
selectPolicy |
Max |
Min |
És a dir, per defecte l'HPA pot duplicar-se cada 15 segons i pot anar-se'n a minReplicas de cop després de 5 minuts de calma. El primer sol estar bé; el segon és massa brusc per a Rutas Norte.
La configuració raonada de Rutas Norte: pujar ràpid, baixar a poc a poc
La nostra política és deliberadament asimètrica, i convé entendre per què:
Pujar ràpid. El cost d'escalar de més durant uns minuts és uns cèntims de còmput. El cost d'escalar de menys durant uns minuts és que la venda del pont de maig cau i Rutas Norte perd milers d'euros en bitllets no venuts, més la reputació. Els costos són radicalment asimètrics, i per tant la resposta ho ha de ser. stabilizationWindowSeconds: 0 a scaleUp: quan la càrrega puja, s'actua.
Baixar a poc a poc. El trànsit d'un web de bitllets és irregular per naturalesa: una punta a les 10:00, una vall a les 10:03, una altra punta a les 10:05. Si baixéssim ràpid, estaríem destruint pods que cal tornar a crear trenta segons després, amb el cost d'arrencada en fred (connexions a PostgreSQL, memòria cau de rutes buida) i una latència pitjor per a l'usuari. stabilizationWindowSeconds: 600 a scaleDown: no baixem fins que la calma dura deu minuts. I ni tan sols llavors baixem de cop: un 20 % cada dos minuts.
La formulació mental: l'autoescalat cap amunt és un mecanisme de disponibilitat; l'autoescalat cap avall és un mecanisme de cost. La disponibilitat és urgent; l'estalvi pot esperar deu minuts.
Congelar l'escalat cap avall durant una campanya
Un ús avançat i molt pràctic de selectPolicy: Disabled. Durant els tres dies del pont de maig, l'equip de Rutas Norte pot decidir que no vol cap reducció de rèpliques, ni tan sols lenta:
S'aplica el divendres al matí i es reverteix el dilluns. És un interruptor d'emergència legítim i molt més segur que esborrar l'HPA.
- Diverses mètriques alhora: guanya la que demana més
El camp metrics és una llista. Quan n'hi ha diverses, l'HPA calcula la fórmula per a cadascuna per separat i després aplica una regla molt simple:
Es queda amb el nombre de rèpliques MÉS ALT de tots els càlculs.
És un OR de necessitat: n'hi ha prou que una sola mètrica demani més rèpliques perquè s'escali. Mai es promitgen ni es combinen.
Exemple amb dues mètriques a api-reserves (5 rèpliques actuals):
| Mètrica | Objectiu | Valor actual | Rèpliques que demana |
|---|---|---|---|
CPU (Utilization) |
65 % | 42 % | sostre(5 × 0,646) = 4 |
Peticions per segon (Pods) |
80 rps/pod | 190 rps/pod | sostre(5 × 2,375) = 12 |
| Decisió final | 12 |
La CPU diu que sobren pods; les peticions per segon diuen que en falten molts. Guanya 12. Aquesta asimetria és intencionada i correcta: l'HPA prefereix equivocar-se per excés de capacitat que per defecte.
Conseqüència pràctica que cal tenir present: si una de les mètriques es queda en <unknown>, l'HPA no pot garantir que no calgui escalar per ella. El seu comportament en aquest cas és conservador: si la mètrica vàlida demana pujar, puja; si demana baixar, no baixa, perquè no sap si la mètrica trencada justificaria mantenir les rèpliques. Això explica el desconcert habitual de «el meu HPA té una mètrica en <unknown> i s'ha quedat clavat amunt».
- Els HPA de Rutas Norte, manifests complets
Escriurem els dos manifests definitius. Van a k8s/entorns/pro/, perquè els valors de minReplicas difereixen per entorn.
HPA d'api-reserves
# k8s/entorns/pro/hpa-api-reserves.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-reserves
namespace: rutas-norte-pro
labels:
app: api-reserves
app.kubernetes.io/part-of: rutas-norte
entorn: pro
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-reserves
minReplicas: 4 # Terra de disponibilitat: 4 repliques repartides entre zones (09-05)
maxReplicas: 30 # Sostre: 30 x 500m = 15 nuclis de requests. Cap al pla de capacitat.
metrics:
# Metrica principal: CPU del contenidor "api", excloent el sidecar de metriques.
- type: ContainerResource
containerResource:
name: cpu
container: api
target:
type: Utilization
averageUtilization: 65
# 65 % de 500m = 325m per pod. Deixa un 35 % de marge per absorbir
# el temps que triguen a arrencar les repliques noves.
behavior:
scaleUp:
# Reaccio immediata: zero espera. La disponibilitat mana.
stabilizationWindowSeconds: 0
selectPolicy: Max
policies:
# Duplicar el nombre de repliques cada 30 s...
- type: Percent
value: 100
periodSeconds: 30
# ...o afegir 6 pods cada 30 s, el que permeti mes.
# Amb poques repliques mana la politica de pods; amb moltes, la de percentatge.
- type: Pods
value: 6
periodSeconds: 30
scaleDown:
# Deu minuts de calma sostinguda abans de comencar a baixar.
stabilizationWindowSeconds: 600
selectPolicy: Min
policies:
# Com a molt, treure el 20 % de les repliques cada 2 minuts.
- type: Percent
value: 20
periodSeconds: 120
# I mai mes de 3 pods cada 2 minuts. Amb selectPolicy: Min mana la mes suau.
- type: Pods
value: 3
periodSeconds: 120Un càlcul que justifica el maxReplicas: 30: cada pod reserva 500m de CPU i 512Mi de memòria. Trenta pods són 15 nuclis i 15 GiB només d'api-reserves. Sumant botiga-web, worker-notificacions i les bases de dades, això defineix la mida del clúster que necessitarem a 09-03. El maxReplicas no és un número que es posa a l'atzar: és un compromís de capacitat.
Un altre càlcul, sobre la velocitat de pujada. Partint de 4 rèpliques amb Max entre «duplicar» i «+6 pods»:
| Moment | Rèpliques | Política que mana |
|---|---|---|
| t = 0 s | 4 | — |
| t = 30 s | 10 | +6 pods (duplicar donaria 8) |
| t = 60 s | 20 | duplicar (+6 donaria 16) |
| t = 90 s | 30 | duplicar donaria 40, maxReplicas talla en 30 |
De 4 a 30 rèpliques en 90 segons. Sumant uns 20-30 segons d'arrencada de cada pod de Node.js, la plataforma passa de la seva capacitat de dimarts a la seva capacitat màxima en uns dos minuts. Això és el que cal comparar contra la velocitat a què arriba el trànsit.
HPA de botiga-web
botiga-web és nginx servint actius estàtics. El seu perfil és diferent: consumeix poquíssima CPU per petició, arrenca en un segon i el seu límit real és l'amplada de banda i el nombre de connexions, no el còmput.
# k8s/entorns/pro/hpa-botiga-web.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: botiga-web
namespace: rutas-norte-pro
labels:
app: botiga-web
app.kubernetes.io/part-of: rutas-norte
entorn: pro
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: botiga-web
minReplicas: 3
maxReplicas: 15
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
# Llindar MES BAIX que el de l'API: nginx arrenca en ~1 s i consumeix poc,
# aixi que ens podem permetre escalar abans sense cost apreciable.
# A mes es la porta d'entrada: si se satura, no entra ningu.
averageUtilization: 50
behavior:
scaleUp:
stabilizationWindowSeconds: 0
selectPolicy: Max
policies:
- type: Percent
value: 100
periodSeconds: 15 # Mes agressiu encara: nginx arrenca en un segon
- type: Pods
value: 4
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300 # 5 min: menys conservador que l'API,
# perque nginx no te arrencada en fred cara
selectPolicy: Min
policies:
- type: Percent
value: 25
periodSeconds: 60Comparació raonada de tots dos HPA:
| Paràmetre | api-reserves |
botiga-web |
Motiu de la diferència |
|---|---|---|---|
| Objectiu de CPU | 65 % | 50 % | nginx és barat d'escalar; l'API no |
minReplicas |
4 | 3 | Disponibilitat mínima acordada per component |
maxReplicas |
30 | 15 | L'API és el component car en CPU |
periodSeconds en pujar |
30 s | 15 s | nginx arrenca en 1 s; Node.js en 20-30 s |
| Estabilització en baixar | 600 s | 300 s | L'API té arrencada en fred costosa (pool de connexions, memòria cau) |
| Tipus de mètrica | ContainerResource |
Resource |
L'API té sidecar de mètriques; nginx no |
Aplicació
kubectl apply -f k8s/entorns/pro/hpa-api-reserves.yaml
kubectl apply -f k8s/entorns/pro/hpa-botiga-web.yaml
kubectl get hpa -n rutas-norte-proNAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
api-reserves Deployment/api-reserves 28%/65% 4 30 4 22s
botiga-web Deployment/botiga-web 11%/50% 3 15 3 19sLa columna TARGETS mostra valorActual/objectiu. Si veus números en lloc de <unknown>, tot està ben connectat.
- Simulació d'una punta de càrrega i observació en viu
Provocarem una punta a l'entorn de desenvolupament i veurem l'HPA treballar. Farem servir el perfil de minikube del curs.
Preparar el terreny
minikube -p rutas-norte addons enable metrics-server
kubectl -n rutas-norte-dev apply -f k8s/entorns/dev/hpa-api-reserves.yamlPer a la demostració, un HPA de desenvolupament amb llindars baixos perquè reaccioni ràpid:
# k8s/entorns/dev/hpa-api-reserves.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-reserves
namespace: rutas-norte-dev
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-reserves
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 40 # Llindar baix: escala aviat, es veu millor la demo
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Pods
value: 3
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 120 # Curt per a la demo; en pro seria 600Terminal 1: observar
Terminal 2: generar càrrega
Un pod efímer que martelleja l'endpoint de cerca de rutes en bucle:
kubectl -n rutas-norte-dev run generador-carrega \
--image=busybox:1.36 \
--restart=Never \
--rm -it \
-- /bin/sh -c \
'while true; do wget -q -O- http://api-reserves/rutes?origen=BIL\&desti=SDR > /dev/null; done'Explicació de l'ordre:
--rm -itcrea el pod, s'hi connecta i l'esborra en sortir ambCtrl+C.http://api-reservesfa servir el DNS intern del clúster (04-03): resol al Service del mateix namespace.- El bucle infinit de
wgetgenera peticions tan ràpid com pot.
Per a una punta més seriosa, diverses rèpliques del generador:
kubectl -n rutas-norte-dev create deployment generador-carrega \
--image=busybox:1.36 --replicas=6 \
-- /bin/sh -c 'while true; do wget -q -O- http://api-reserves/rutes > /dev/null; done'El que es veu al terminal 1
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
api-reserves Deployment/api-reserves 3%/40% 1 10 1 5m
api-reserves Deployment/api-reserves 3%/40% 1 10 1 5m15s
api-reserves Deployment/api-reserves 187%/40% 1 10 1 5m30s
api-reserves Deployment/api-reserves 187%/40% 1 10 4 5m30s
api-reserves Deployment/api-reserves 142%/40% 1 10 4 5m45s
api-reserves Deployment/api-reserves 142%/40% 1 10 7 5m45s
api-reserves Deployment/api-reserves 94%/40% 1 10 7 6m
api-reserves Deployment/api-reserves 94%/40% 1 10 10 6m
api-reserves Deployment/api-reserves 61%/40% 1 10 10 6m30s
api-reserves Deployment/api-reserves 38%/40% 1 10 10 7mLlegeix la seqüència amb la fórmula a la mà:
3%/40%amb 1 rèplica:sostre(1 × 0,075) = 1. Res a fer.187%/40%amb 1 rèplica:sostre(1 × 4,675) = 5. Però la políticaPods: 3 cada 15slimita a 4. Aquí tens elbehavioren acció.142%/40%amb 4 rèpliques:sostre(4 × 3,55) = 15. La política limita a 4+3 = 7.94%/40%amb 7 rèpliques:sostre(7 × 2,35) = 17. Limitat a 10 permaxReplicas.38%/40%amb 10 rèpliques: ratio 0,95, dins de la banda de tolerància. Estable. Objectiu assolit.
Ara atura el generador (Ctrl+C o esborrant el Deployment) i observa la baixada:
api-reserves Deployment/api-reserves 2%/40% 1 10 10 9m
api-reserves Deployment/api-reserves 2%/40% 1 10 10 10m
api-reserves Deployment/api-reserves 2%/40% 1 10 1 11mFixa-t'hi: la CPU cau immediatament al 2 %, però les rèpliques es queden en 10 durant dos minuts sencers. Aquesta és la stabilizationWindowSeconds: 120. Complerta la finestra, baixa a 1 de cop (en aquesta demo no vam definir polítiques de scaleDown, així que s'aplica la de per defecte: 100 % cada 15 s).
Els esdeveniments de l'escalat
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulRescale 5m30s horizontal-pod-autoscaler New size: 4; reason: cpu resource utilization (percentage of request) above target
Normal SuccessfulRescale 5m15s horizontal-pod-autoscaler New size: 7; reason: cpu resource utilization (percentage of request) above target
Normal SuccessfulRescale 5m horizontal-pod-autoscaler New size: 10; reason: cpu resource utilization (percentage of request) above target
Normal SuccessfulRescale 30s horizontal-pod-autoscaler New size: 1; reason: All metrics below targetAquests esdeveniments són or pur per a l'anàlisi posterior d'un incident: et diuen exactament quan va escalar, a quant i per què. Recorda que els esdeveniments caduquen (una hora per defecte); per a l'historial llarg cal el registre centralitzat del mòdul 7.
- El conflicte entre l'HPA i el camp
replicas
replicasAquest és l'error que mossega tots els equips exactament una vegada, i convé que no sigui al pont de maig.
El problema
El Deployment d'api-reserves que anem escrivint des del mòdul 2 diu:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-reserves
spec:
replicas: 4 # <-- Aquest camp
...I ara un HPA també governa aquest mateix camp. Tenim dues autoritats sobre el mateix número.
Mentre ningú apliqui el YAML, no passa res: l'HPA modifica el camp al servidor i l'objecte viu feliç amb 22 rèpliques. El problema apareix quan algú torna a aplicar el manifest.
Escenari real:
10:00 L'HPA escala api-reserves a 22 repliques. La venda va be.
10:14 Un company corregeix una etiqueta al Deployment i executa:
kubectl apply -f k8s/base/deployment-api-reserves.yaml
10:14 El manifest diu replicas: 4. L'API ho accepta.
22 pods -> 4 pods, INSTANTANIAMENT.
10:14 La plataforma cau.
10:14 L'HPA (seguent cicle, fins a 15 s despres) detecta CPU al 400 %
i torna a escalar. Pero ja hi ha 15-90 segons de caiguda.Quinze segons de caiguda total al pic de venda de l'any. Tot per un camp que no hi hauria de ser.
I és pitjor amb GitOps: si Argo CD (10-05) té el repositori com a font de veritat i detecta que el clúster té 22 rèpliques quan el Git diu 4, marcarà el recurs com a OutOfSync i, amb sincronització automàtica, ho «corregirà» a 4. Cada vegada. Tindràs un estira-i-arronsa permanent entre l'HPA i l'operador de GitOps, amb la plataforma oscil·lant entre 4 i 22 rèpliques indefinidament.
La solució: treure replicas del manifest
# k8s/base/deployment-api-reserves.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-reserves
namespace: rutas-norte-pro
labels:
app: api-reserves
app.kubernetes.io/part-of: rutas-norte
spec:
# SENSE camp "replicas": el governa l'HorizontalPodAutoscaler api-reserves.
# Veure k8s/entorns/pro/hpa-api-reserves.yaml
# Si el tornes a posar aqui, cada "kubectl apply" tirara la plataforma a terra
# durant el pic de transit. No ho facis.
selector:
matchLabels:
app: api-reserves
template:
metadata:
labels:
app: api-reserves
app.kubernetes.io/part-of: rutas-norte
spec:
containers:
- name: api
image: registry.rutasnorte.example/api-reserves:1.14.2
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "1"
memory: 1GiEn ometre replicas, el valor per defecte de l'API és 1, però només en la creació inicial. En aplicacions posteriors, el camp simplement no es toca: kubectl apply compara amb l'anotació kubectl.kubernetes.io/last-applied-configuration, veu que replicas no hi era abans ni hi és ara, i el deixa com està. L'HPA continua manant.
Una conseqüència a tenir en compte: la primera vegada que creïs el Deployment sense replicas, arrencarà amb 1 pod fins que l'HPA el porti a minReplicas al següent cicle (menys de 15 segons). És acceptable en un desplegament nou; si et molesta, crea l'HPA primer.
Aquest comentari explícit al YAML no és decoratiu: és documentació al lloc on algú prendrà la decisió equivocada. Escriu-lo.
Alternatives i els seus matisos
| Enfocament | Com | Quan fer-lo servir |
|---|---|---|
Ometre replicas |
Esborrar-lo del YAML | Recomanat. Simple, funciona amb apply i amb GitOps |
| Server-Side Apply amb propietat compartida | kubectl apply --server-side; l'HPA és propietari del camp |
Bo, però requereix que tot el flux faci servir SSA |
ignoreDifferences a Argo CD |
Configurar Argo CD perquè ignori /spec/replicas |
Necessari si per alguna raó no pots treure el camp |
| Kustomize sense patch de rèpliques | No fer servir replicas: als overlays |
Complementa la primera opció (10-04) |
Compte amb una trampa de Server-Side Apply: si apliques amb SSA i el camp replicas sí que és al teu manifest, en prendràs la propietat i l'HPA rebrà un conflicte. L'API t'ho dirà amb un error explícit de conflicte de propietaris, cosa que és millor que l'error silenciós de kubectl apply clàssic, però continua sent un error.
Verificar qui mana
kubectl get deployment api-reserves -n rutas-norte-pro \
-o jsonpath='{.metadata.managedFields[*].manager}{"\n"}'Si veus kube-controller-manager entre els gestors, l'HPA està escrivint el camp. És la confirmació que la cadena funciona.
- Què no escalar amb HPA i el coll d'ampolla real
Acabem amb la lliçó més valuosa de totes, i la que més diners estalvia.
L'error d'escalar la capa equivocada
Imagina aquesta seqüència al pont de maig:
- Arriba l'allau de trànsit.
- L'HPA d'
api-reservesescala de 4 a 30 rèpliques en dos minuts. Perfecte. - Les 30 rèpliques obren, cadascuna, el seu pool de 20 connexions a
postgres-reserves. Total: 600 connexions. postgres-reservestémax_connections = 200.- Les 400 connexions sobrants són rebutjades. Els pods d'
api-reservesretornen error 500. - La CPU d'
api-reservespuja (els reintents i la gestió d'errors consumeixen), així que l'HPA vol escalar més.maxReplicasho talla en 30. - La plataforma està caiguda amb 30 rèpliques on abans queia amb 4. Ha costat més diners i no ha servit de res.
Aquest patró té nom: desplaçar el coll d'ampolla sense resoldre'l. L'HPA no ho detecta perquè l'HPA només sap de CPU: no sap que la base de dades està saturada.
flowchart LR
A[Transit x10] --> B[botiga-web<br/>HPA: 3 a 15]
B --> C[api-reserves<br/>HPA: 4 a 30]
C --> D[postgres-reserves<br/>1 replica<br/>max_connections=200]
D -.->|COLL D'AMPOLLA| E[Errors 500]
C --> F[redis-cache<br/>1 replica]
style D fill:#f88,stroke:#900,stroke-width:3px
style E fill:#f88,stroke:#900
La capa que no escala determina la capacitat de tot el sistema. És la llei de l'anella més feble aplicada a l'arquitectura.
Què cal fer en canvi
Amb postgres-reserves com a límit, les palanques reals són:
| Palanca | Què fa | On es tracta |
|---|---|---|
| Limitar el pool per rèplica | Amb 30 rèpliques × 6 connexions = 180 < 200 | 09-06 |
| Posar un pool compartit (PgBouncer) | Multiplexa milers de connexions d'aplicació sobre poques de base de dades | 09-06 |
Cachejar a redis-cache |
La consulta de disponibilitat no arriba a PostgreSQL | 09-06 |
| Rèpliques de lectura | Les cerques van a la rèplica; només les reserves al primari | 06-07 (operador) |
| Escalar verticalment PostgreSQL | Més CPU/RAM i disc més ràpid | 09-02 i 09-06 |
Cap d'elles no és un HPA. La lliçó: abans de posar un HPA, identifica el recurs escàs. Si no és la CPU dels pods que escalaràs, l'HPA no t'ajudarà.
Llista de comprovació abans de posar un HPA
Pregunta't:
- L'aplicació és realment sense estat? Pot qualsevol rèplica atendre qualsevol petició sense memòria local ni afinitat de sessió?
- La CPU és el recurs escàs? O ho és la base de dades, el disc, la xarxa, un servei extern o un bloqueig global?
- La càrrega es reparteix per igual? Si un endpoint concentra el 90 % del cost i un sol client el crida, afegir rèpliques no ajuda.
- Quant triga un pod nou a ser útil? Si triga tres minuts, l'HPA arribarà tard a les puntes ràpides. Caldrà treballar l'arrencada (09-06) o preescalfar (09-04).
- Hi ha lloc al clúster per a les rèpliques noves? Si no, l'HPA només generarà pods
Pending(09-03). - El servei de sota aguanta N vegades més càrrega? El pool de connexions, la quota de l'API externa, el límit de taxa.
Només si les sis respostes són satisfactòries, l'HPA farà el que esperes.
Errors Comuns i Consells
Error 1: deixar el camp replicas al manifest. Ja ho hem desenvolupat a l'apartat 13, però mereix repetir-se perquè és l'error número u i les seves conseqüències són immediates i visibles. Treu-lo i deixa-hi un comentari al seu lloc.
Error 2: escalar per memòria. Els entorns d'execució amb recol·lector d'escombraries no retornen memòria al sistema. L'HPA puja i no torna a baixar mai. Fes servir CPU, o una mètrica de negoci (09-04). Si de debò necessites memòria, fes servir AverageValue en lloc d'Utilization i tingues expectatives realistes.
Error 3: posar l'objectiu d'utilització massa alt. Un averageUtilization: 90 sembla eficient, però deixa només un 10 % de marge. Quan l'HPA detecta la pujada, els pods nous triguen 30 segons a estar llestos, i durant aquests 30 segons els pods existents estan al 130 % i patint throttling. L'objectiu ha de deixar espai per al temps d'arrencada. Entre 50 % i 70 % és el rang sa; com més lent arrenqui la teva aplicació, més baix l'objectiu.
Error 4: maxReplicas sense pla de capacitat. Un maxReplicas: 200 en un clúster de tres nodes no et dona 200 rèpliques: et dona un munt de pods Pending i una falsa sensació de seguretat. Calcula maxReplicas × requests i compara-ho amb la capacitat real (09-03).
Error 5: oblidar els requests als sidecars. Un sol contenidor sense requests.cpu deixa cec l'HPA de tot el Deployment. A Rutas Norte, l'exportador de mètriques d'api-reserves és el sospitós habitual.
Error 6: no comprovar que els pods nous són útils. Un pod que arrenca i passa la readiness però té el pool de connexions buit i la memòria cau freda atén pitjor que un de calent. Durant els primers segons, escalar pot empitjorar la latència mitjana. Ajusta les sondes (07-01) perquè un pod només es declari llest quan de debò ho estigui.
Consell 1: posa primer l'HPA en mode observació. Crea'l amb minReplicas igual a maxReplicas (per exemple, tots dos a 4). L'HPA no podrà escalar, però calcularà i publicarà les mètriques a kubectl get hpa. Deixa'l una setmana, mira què hauria fet, i només llavors obre el rang. És gratis i evita ensurts.
Consell 2: alerta quan l'HPA estigui enganxat al sostre. Si api-reserves porta mitja hora en 30 rèpliques, el maxReplicas està limitant la capacitat i ningú se n'assabenta. Una alerta a Alertmanager (07-04):
kube_horizontalpodautoscaler_status_current_replicas
>= kube_horizontalpodautoscaler_spec_max_replicasConsell 3: registra un panell amb rèpliques i mètrica juntes. A Grafana, superposar «rèpliques actuals» i «CPU mitjana» al mateix gràfic fa evident si l'HPA reacciona bé o va per darrere. És el diagnòstic visual més útil que existeix per a un HPA.
Consell 4: prova l'HPA abans de necessitar-lo. Un assaig de càrrega contra rutas-norte-pre una setmana abans del pont de maig costa una tarda i evita la caiguda de l'any. Ho farem formalment amb k6 a 09-06.
Consell 5: els esdeveniments de l'HPA caduquen. Una hora per defecte. Si vols l'històric de l'incident, necessites EFK (07-05) o les mètriques de kube-state-metrics a Prometheus.
Exercicis
Exercici 1: aplicar la fórmula
botiga-web està desplegat amb requests.cpu: 200m i un HPA amb averageUtilization: 50, minReplicas: 3, maxReplicas: 15. En aquest moment hi ha 6 rèpliques i kubectl top dona:
botiga-web-6f7d9c4b58-2wq4m 210m
botiga-web-6f7d9c4b58-4kjnx 198m
botiga-web-6f7d9c4b58-7hgpz 225m
botiga-web-6f7d9c4b58-9mzbc 187m
botiga-web-6f7d9c4b58-kx2vt 204m
botiga-web-6f7d9c4b58-tn8fr 216mCalcula: (a) l'objectiu absolut per pod; (b) el consum mitjà; (c) el ratio i si està dins de la banda de tolerància; (d) les rèpliques desitjades; (e) què farà l'HPA amb aquest behavior:
behavior:
scaleUp:
stabilizationWindowSeconds: 0
selectPolicy: Max
policies:
- type: Percent
value: 100
periodSeconds: 15
- type: Pods
value: 4
periodSeconds: 15Exercici 2: diagnosticar un HPA mut
Un company ha desplegat worker-notificacions amb aquest HPA i es queixa que mai escala:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: worker-notificacions
namespace: rutas-norte-pro
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: worker-notificacions
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70El Deployment és:
spec:
template:
spec:
containers:
- name: worker
image: registry.rutasnorte.example/worker-notificacions:2.3.0
resources:
limits:
cpu: 500m
memory: 512Mi
- name: exportador-metriques
image: registry.rutasnorte.example/exportador:1.2.0kubectl get hpa mostra <unknown>/70%. Identifica tots els problemes, escriu les ordres de diagnòstic que executaries i les correccions. A més, explica per què fins i tot arreglant tot això aquest HPA continuarà sent una mala idea per a aquest component concret.
Exercici 3: dissenyar un behavior per a un escenari nou
Rutas Norte llança panell-conductors, una aplicació web interna que fan servir els 40 conductors de la flota. Perfil de càrrega:
- De dilluns a divendres, de 5:30 a 7:00, tots els conductors entren per consultar la seva ruta del dia: la càrrega passa de zero al seu màxim en quinze minuts.
- La resta del dia hi ha ús esporàdic, molt baix.
- A la nit no hi ha ningú.
- L'aplicació és Node.js i triga uns 25 segons a estar llesta.
- És interna: una caiguda de dos minuts és molesta però no costa diners.
- L'equip té pressió de costos: el clúster és compartit i hi ha ResourceQuota.
Escriu l'HPA complet (autoscaling/v2) raonant cada decisió: minReplicas, maxReplicas, mètrica, objectiu, i tot el behavior. Compara almenys tres decisions amb les de l'HPA d'api-reserves i explica per què difereixen.
Solucions
Solució 1
(a) Objectiu absolut per pod:
(b) Consum mitjà:
(c) Ratio i banda de tolerància:
(d) Rèpliques desitjades:
13 rèpliques, per sota de maxReplicas: 15.
(e) Què fa el behavior:
Rèpliques actuals: 6.
- Política
Percent 100 % / 15 s: permet afegir el 100 % de 6 = 6 → fins a 12 rèpliques. - Política
Pods 4 / 15 s: permet afegir 4 → fins a 10 rèpliques. selectPolicy: Maxpren el sostre més alt: 12.
L'HPA en vol 13 però només pot arribar a 12 en aquest pas. Escalarà a 12 rèpliques ara.
Al següent cicle (15 s després), amb 12 rèpliques i suposant que la càrrega total no canvia, el consum mitjà baixarà a uns 103m per pod. El nou ratio seria 1,03, dins de la banda de tolerància, així que es quedarà en 12 i no arribarà mai a 13. Aquest és un comportament habitual i correcte: la tolerància absorbeix l'últim salt.
Observació addicional: els pods estan al 103 % del seu requests (206m contra 200m). No estan patint throttling perquè el seu limit és més gran, però estan consumint més del reservat, cosa que significa que depenen de la CPU sobrant del node. És un senyal que el requests de botiga-web està infradimensionat i que el VPA de 09-02 hi tindria alguna cosa a dir.
Solució 2
Hi ha quatre problemes, un darrere l'altre.
Problema 1: el contenidor worker no declara requests.cpu.
Només té limits. Sense requests, l'HPA no té denominador per al percentatge.
Matís important que molts desconeixen: quan declares limits sense requests, Kubernetes omple automàticament requests amb el valor de limits. Així que aquest contenidor sí que acaba tenint requests.cpu: 500m. Com a efecte secundari, la QoS del pod seria Guaranteed si tots els contenidors estiguessin així (mòdul 3). Però...
Problema 2: el contenidor exportador-metriques no declara cap recurs.
Aquest sí que és fatal. Sense limits ni requests, no hi ha emplenament automàtic, i l'HPA no pot calcular la utilització del pod. Aquest és el que produeix el <unknown>. A més fa que la QoS del pod sigui Burstable en lloc de Guaranteed.
Problema 3: no se sap si metrics-server està disponible.
Cal verificar-ho abans que res.
Problema 4 (de disseny, i el més important): la CPU és el senyal equivocat per a aquest component.
Ordres de diagnòstic, en ordre:
# 1. Funciona metrics-server?
kubectl top pods -n rutas-norte-pro -l app=worker-notificacions
# 2. Que diu l'HPA exactament?
kubectl describe hpa worker-notificacions -n rutas-norte-pro
# 3. Quins recursos declara cada contenidor?
kubectl get deployment worker-notificacions -n rutas-norte-pro \
-o jsonpath='{range .spec.template.spec.containers[*]}{.name}{": "}{.resources}{"\n"}{end}'La sortida del pas 3 delataria el problema:
Correcció del Deployment:
spec:
template:
spec:
containers:
- name: worker
image: registry.rutasnorte.example/worker-notificacions:2.3.0
resources:
requests:
cpu: 300m # Explicit, no depenem de l'emplenament automatic
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
- name: exportador-metriques
image: registry.rutasnorte.example/exportador:1.2.0
resources:
requests:
cpu: 20m # <-- LA CORRECCIO CLAU
memory: 32Mi
limits:
cpu: 50m
memory: 64MiI, defensivament, canviar l'HPA a ContainerResource perquè el sidecar no distorsioni el percentatge:
metrics:
- type: ContainerResource
containerResource:
name: cpu
container: worker
target:
type: Utilization
averageUtilization: 70Per què continua sent mala idea encara que funcioni:
worker-notificacions consumeix missatges d'una cua de correus. La seva feina per missatge és sobretot espera d'entrada/sortida: parlar amb el servidor SMTP, esperar la resposta de xarxa. Això consumeix molt poca CPU.
Al pont de maig hi pot haver quaranta mil correus esperant a la cua i el worker estar al 20 % de CPU, perquè està bloquejat esperant l'SMTP la major part del temps. L'HPA per CPU veuria 20 % contra un objectiu de 70 % i conclouria que sobren rèpliques: escalaria cap avall justament quan més falten consumidors.
La mètrica correcta és la longitud de la cua: «vull un worker per cada 500 missatges pendents». Això no ho pot llegir l'HPA de fàbrica; cal una mètrica externa, i aquesta és exactament la raó de ser de KEDA a 09-04.
Solució 3
# k8s/entorns/pro/hpa-panell-conductors.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: panell-conductors
namespace: rutas-norte-pro
labels:
app: panell-conductors
app.kubernetes.io/part-of: rutas-norte
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: panell-conductors
# minReplicas: 1. No es un servei que generi ingressos i a la nit no hi ha
# ningu. Una replica basta per a l'us esporadic de la resta del dia i perque
# la primera peticio de les 5:30 no trobi el servei apagat.
# No hi posem 2 perque hi ha pressio de costos i una caiguda de 2 min es assumible.
minReplicas: 1
# maxReplicas: 6. Son 40 conductors concurrents com a maxim. Amb 6 repliques
# toquen a menys de 7 usuaris per pod: en sobra. Un sostre baix protegeix la
# ResourceQuota del namespace compartit.
maxReplicas: 6
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
# 50 %, no 65-70 %. L'aplicacio triga 25 s a estar llesta, aixi que
# necessitem marge per cobrir aquesta arrencada. A mes la pujada de les
# 5:30 es abrupta: millor anar per davant.
averageUtilization: 50
behavior:
scaleUp:
# Zero espera. La finestra critica dura 90 minuts al dia i tota la carrega
# arriba en els primers 15. Qualsevol retard es menja la finestra sencera.
stabilizationWindowSeconds: 0
selectPolicy: Max
policies:
# periodSeconds 30, no 15: l'aplicacio triga 25 s a estar llesta.
# Amb 15 s escalariem un altre cop ABANS que els pods anteriors
# comencessin a absorbir carrega, i sobreescalariem.
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 2
periodSeconds: 30
# Amb maxReplicas 6, "+2 pods" cobreix be el rang 1->3->6.
scaleDown:
# 300 s (5 min), no 600. El pic dura 90 minuts i despres no torna fins
# l'endema: no hi ha puntes intermitents que justifiquin esperar
# deu minuts, i la pressio de costos demana alliberar recursos aviat.
stabilizationWindowSeconds: 300
selectPolicy: Min
policies:
# Baixada suau: un pod cada 3 minuts. De 6 a 1 en 15 minuts.
# Suficient per no deixar tirat un resseguidor de les 7:05.
- type: Pods
value: 1
periodSeconds: 180Comparació raonada amb l'HPA d'api-reserves:
| Decisió | api-reserves |
panell-conductors |
Per què difereixen |
|---|---|---|---|
minReplicas |
4 | 1 | L'API genera ingressos i necessita disponibilitat multizona; el panell és intern, amb pressió de costos i una caiguda tolerable |
maxReplicas |
30 | 6 | El trànsit de l'API és públic i impredictible (x10 al pont); el panell té un sostre conegut de 40 usuaris |
| Objectiu de CPU | 65 % | 50 % | Totes dues són Node.js, però el panell pateix una pujada més abrupta (de zero al màxim en 15 min) i necessita més marge per cobrir els 25 s d'arrencada |
periodSeconds en pujar |
30 s | 30 s | Igual: totes dues són Node.js amb arrencada de 20-30 s. Coincidència justificada per la mateixa causa tècnica |
| Estabilització en baixar | 600 s | 300 s | El trànsit de l'API és intermitent durant hores; el del panell és un únic bloc diari que no torna |
| Política de baixada | 20 % / 120 s | 1 pod / 180 s | Amb només 6 rèpliques, un percentatge del 20 % donaria fraccions inútils; en números petits es raona millor en pods absoluts |
Consideració addicional que mereix nota: aquest perfil de càrrega —zero a la nit, allau a hora fixa— és el candidat perfecte per a un disparador cron que preescalfi el servei a les 5:15, en lloc d'esperar que la CPU pugi i reaccionar tard. Això ja no ho fa l'HPA de fàbrica: és KEDA, i ho veurem a 09-04. Un HPA reactiu sempre va per darrere d'una càrrega que puja en esglaó; només un senyal anticipat resol aquest cas de debò.
Conclusió
L'HorizontalPodAutoscaler converteix el nombre de rèpliques d'una dada fixa escrita a mà en una conseqüència observada del trànsit real. És la diferència entre dimensionar per a un dimarts qualsevol i sobreviure al pont de maig sense que ningú hagi d'obrir el portàtil a l'andana d'una estació.
L'essencial que t'endús d'aquesta lliçó:
- Escalar cap enfora i escalar cap amunt són eixos diferents. L'horitzontal resol el volum de trànsit i a més millora la disponibilitat; el vertical resol el dimensionament de cada instància. Rutas Norte necessita tots dos.
- Només s'escala horitzontalment el que és realment sense estat.
api-reservesibotiga-websí;postgres-reservesmai, i l'intent produiria corrupció de dades, no capacitat. - La fórmula és una regla de tres:
sostre(repliquesActuals × valorActual / valorObjectiu), amb una banda de tolerància del ±10 % que evita el ball. - Sense
requestsa tots els contenidors i sense metrics-server no hi ha HPA. El<unknown>de la columnaTARGETSgairebé sempre apunta a un sidecar oblidat. - El
behaviorés on es decideix si l'autoescalat funciona. L'asimetria de Rutas Norte —pujar en zero segons, baixar després de deu minuts de calma— reflecteix una asimetria real de costos: perdre vendes és molt més car que sobrar capacitat una estona. - Amb diverses mètriques, guanya la que demana més rèpliques. Un OR de necessitat, deliberadament conservador.
- El camp
replicasha de desaparèixer del manifest versionat. Si no, unkubectl applyinnocent tirarà la plataforma al pic de venda, i amb GitOps serà una guerra permanent (10-05). - Abans de posar un HPA, troba el recurs escàs. Escalar la capa web quan el límit és la base de dades multiplica el cost sense afegir un sol bitllet venut.
Rutas Norte ja té api-reserves i botiga-web responent al trànsit pel seu compte. Però continuem arrossegant un problema del qual hem parlat diverses vegades sense resoldre'l del tot: l'HPA es recolza en el requests de CPU com a referència de tot el seu càlcul, i aquest requests continua sent un número que vam posar a ull. A 07-02 el vam recalibrar comparant-lo amb el consum real, però ho vam fer a mà, una vegada, mirant kubectl top durant una tarda. Si el requests està malament, el percentatge de l'HPA menteix, i amb ell totes les seves decisions.
A la propera lliçó, Autoescalat Vertical de Pods, veurem el component que resol exactament aquest problema: el VPA observa el consum real durant dies, calcula percentils i et diu amb dades quins requests i quins limits haurien de tenir els teus contenidors. Descobrirem també per què el VPA i l'HPA no poden governar la mateixa mètrica sense barallar-se, i quin és el flux de treball que fan servir els equips seriosos: el VPA com a assessor, no com a pilot automàtic.
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
