Tenim la imatge cineclick/churn-api:0.1.0 corrent en un contenidor. És un gran avenç, però un contenidor únic no és producció: si el procés mor de matinada, es queda mort; si el trànsit es multiplica durant una nit d'estrenes, ningú no afegeix capacitat; si la versió nova resulta estar trencada, ningú no la retira. Tot això — reiniciar, replicar, escalar, actualitzar sense tallar el servei — és la feina d'un orquestrador, i l'estàndard de la indústria és Kubernetes. En aquesta lliçó aprendràs els quatre conceptes de Kubernetes que un ML engineer necessita (Pod, Deployment, Service, HPA), escriurem els manifestos reals del servei de churn — amb probes apuntant al nostre /salut, configuració per ConfigMap i secrets per Secret —, practicarem el rollout i el rollback d'una imatge nova, i avaluarem l'alternativa serverless. No és un curs de Kubernetes (el portal en té un de dedicat si vols aprofundir); és exactament la porció que necessites per desplegar i operar el teu model.
Contingut
- Què aporta un orquestrador
- Kubernetes mínim per a ML engineers: Pod, Deployment, Service, HPA
- Els manifestos del servei churn-api
- Configuració i secrets: ConfigMap i Secret
- Rollout i rollback: dos nivells diferents de "tornar enrere"
- Autoescalat amb HPA
- L'alternativa serverless: escala a zero i cold starts
- Serving gestionat i especialitzat, en una pinzellada
- La decisió de CineClick
Què aporta un orquestrador
Un orquestrador converteix "tinc una imatge" en "tinc un servei operat". Les seves aportacions, en l'ordre en què les trobaries a faltar:
- Rèpliques: executa N còpies idèntiques del contenidor i reparteix el trànsit entre elles. Més capacitat i, sobretot, tolerància a fallades: una rèplica caiguda no tomba el servei.
- Self-healing: vigila cada contenidor (amb les probes que veurem) i reinicia automàticament el que mor o deixa de respondre. El "qui el reinicia a les 3 de la matinada?" té resposta: la màquina.
- Rollout i rollback: desplega una versió nova substituint rèpliques gradualment sense tallar el servei, i permet tornar a l'anterior amb una comanda.
- Service discovery i balanceig: dona al conjunt de rèpliques un nom DNS estable i una IP única; els consumidors no saben (ni han de saber) quantes rèpliques hi ha ni on són.
- Gestió declarativa: descrius l'estat desitjat en YAML ("vull 3 rèpliques d'aquesta imatge amb aquests recursos") i Kubernetes treballa contínuament perquè la realitat hi coincideixi. Això encaixa amb la filosofia del curs: igual que
dvc.yamldeclara el pipeline, els manifestos declaren el desplegament — tot a Git, tot auditable.
Kubernetes mínim per a ML engineers: Pod, Deployment, Service, HPA
Quatre conceptes basten per al nostre cas:
| Objecte | Què és | Per a churn-api |
|---|---|---|
| Pod | La unitat mínima d'execució: un (o pocs) contenidors amb xarxa i emmagatzematge compartits. Efímer: pot morir i ser reemplaçat en una altra màquina. | Un contenidor de cineclick/churn-api:0.1.0 |
| Deployment | Declara "vull N rèpliques d'aquest Pod, amb aquesta imatge i aquests recursos" i les manté. Gestiona rollouts i rollbacks. | 3 rèpliques del servei |
| Service | Nom DNS i IP estables que balancegen el trànsit entre els Pods vius del Deployment. | churn-api — on cridarà el frontend |
| HPA (Horizontal Pod Autoscaler) | Ajusta automàticament el nombre de rèpliques segons una mètrica (CPU, memòria...). | Entre 3 i 10 rèpliques segons CPU |
La regla mental: mai no crees Pods a mà — crees un Deployment que crea i cuida els Pods, i un Service que els dona porta d'entrada. El Pod és ramat, no mascota: no li posis afecte individual, perquè Kubernetes el matarà i el recrearà quan li convingui (i això és bo: per això el model es descarrega del registry en arrencar i no depèn de res local).
flowchart LR
C[Frontend CineClick] --> S[Service churn-api]
S --> P1[Pod rèplica 1]
S --> P2[Pod rèplica 2]
S --> P3[Pod rèplica 3]
D[Deployment<br/>replicas: 3<br/>imatge: 0.1.0] -.crea i vigila.-> P1 & P2 & P3
H[HPA<br/>CPU > 60% ⇒ més rèpliques] -.ajusta replicas.-> DEls manifestos del servei churn-api
Els manifestos viuen al repo, a deploy/k8s/. Primer el Deployment, el fitxer central:
# deploy/k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: churn-api
labels:
app: churn-api
spec:
replicas: 3 # 3 còpies: capacitat + tolerància a fallades
selector:
matchLabels:
app: churn-api
template: # la plantilla del Pod que es replicarà
metadata:
labels:
app: churn-api
spec:
containers:
- name: churn-api
image: cineclick/churn-api:0.1.0 # el tag semàntic de 04-03
ports:
- containerPort: 8000
resources:
requests: # el que el Pod té GARANTIT (serveix per planificar)
cpu: "250m" # 0.25 CPUs
memory: "512Mi"
limits: # sostre: per sobre, throttling (CPU) o kill (memòria)
cpu: "1"
memory: "1Gi"
env:
- name: MLFLOW_TRACKING_URI
valueFrom:
configMapKeyRef:
name: churn-api-config
key: mlflow_tracking_uri
- name: LLINDAR_ABANDONAMENT
valueFrom:
configMapKeyRef:
name: churn-api-config
key: llindar_abandonament
- name: API_KEY
valueFrom:
secretKeyRef:
name: churn-api-secrets
key: api_key
readinessProbe: # pot rebre trànsit? (si no: se'l retira del Service)
httpGet:
path: /salut
port: 8000
initialDelaySeconds: 15 # marge per descarregar el model del registry
periodSeconds: 10
livenessProbe: # és viu? (si no: Kubernetes el reinicia)
httpGet:
path: /salut
port: 8000
initialDelaySeconds: 30
periodSeconds: 20
failureThreshold: 3 # 3 fallades seguides abans de reiniciarPunts que mereixen explicació:
requestsilimits: elsrequestssón la reserva amb què Kubernetes decideix a quina màquina cap el Pod; elslimits, el sostre. Per a un servei de ML, dimensiona la memòria mirant quant ocupa el model carregat (el nostre RandomForest de 300 arbres ocupa uns quants centenars de MB en memòria; 512Mi de request amb límit 1Gi dona marge). Un límit de memòria massa just produeix el clàssic Pod que mor ambOOMKilledjust en carregar el model.- Les dues probes apunten a
/salut, l'endpoint que vam escriure a 04-02 — i ara el seu disseny pren sentit: com que/salutverifica que el model és a la memòria, la readinessProbe garanteix que un Pod acabat de crear no rep trànsit fins que ha acabat de descarregar el campió del registry. La livenessProbe detecta processos penjats i els reinicia. I aquí es tanca el cercle del "fallar ràpid" de 04-02: si el registry està caigut, el Pod nou mor a l'arrencada, Kubernetes el reintenta amb espera creixent (CrashLoopBackOff), i mentrestant les 3 rèpliques velles continuen servint — el risc que vam acceptar a 04-03, mitigat exactament com vam prometre. replicas: 3: mínim raonable en producció — aguanta la caiguda d'una rèplica i els reinicis de rollout sense quedar-se a zero.
El Service, molt més curt:
# deploy/k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
name: churn-api
spec:
selector:
app: churn-api # enruta a tots els Pods amb aquesta etiqueta
ports:
- port: 80 # port "públic" dins del clúster
targetPort: 8000 # port del contenidorDes de qualsevol Pod del clúster, el frontend crida http://churn-api/predir — nom estable, balanceig automàtic entre les rèpliques ready. (L'exposició fora del clúster — Ingress, load balancer del cloud — depèn de la plataforma de cada empresa i la deixem indicada.)
Aplicar i comprovar:
kubectl apply -f deploy/k8s/ kubectl get pods -l app=churn-api # 3 pods Running i READY 1/1 kubectl logs -l app=churn-api --tail=5 # "Model churn-cineclick v2 carregat." x3
Configuració i secrets: ConfigMap i Secret
El principi de 04-03 — mateixa imatge per a tots els entorns, la configuració entra per fora — es materialitza en dos objectes:
# deploy/k8s/configmap.yaml — configuració NO sensible, per entorn apiVersion: v1 kind: ConfigMap metadata: name: churn-api-config data: mlflow_tracking_uri: "http://mlflow.mlops.svc.cluster.local:5000" llindar_abandonament: "0.5" --- # deploy/k8s/secret.yaml — sensible: MAI a Git en clar apiVersion: v1 kind: Secret metadata: name: churn-api-secrets type: Opaque stringData: api_key: "CANVIAR-fora-de-git"
- El ConfigMap de producció apunta al MLflow del clúster; el de staging, a un altre. La imatge no canvia.
- El Secret guarda l'API key de 04-02. Compte: el YAML de l'exemple és il·lustratiu — en un repo real els Secrets no es commitegen en clar; es creen amb
kubectl create secret, o amb eines de la plataforma (Sealed Secrets, External Secrets, el gestor de secrets del cloud). La regla de 04-03 continua vigent un nivell més amunt: els secrets no viuen ni a la imatge ni a Git.
Rollout i rollback: dos nivells diferents de "tornar enrere"
Suposa que publiquem la versió 0.2.0 del servei (nou endpoint /predir-lot de l'exercici de 04-02). El desplegament és canviar la imatge del Deployment:
kubectl set image deployment/churn-api churn-api=cineclick/churn-api:0.2.0 kubectl rollout status deployment/churn-api # observa el reemplaçament gradual
Kubernetes executa un rolling update: crea Pods amb la imatge nova, espera que la seva readinessProbe passi (model descarregat, /salut OK), els envia trànsit i només llavors retira Pods vells — un a un, sense interrupció del servei. Si la 0.2.0 resulta defectuosa:
kubectl rollout undo deployment/churn-api # torna a la revisió anterior (0.1.0) kubectl rollout history deployment/churn-api # historial de revisions
I aquí convé aturar-se, perquè CineClick té ara dos nivells de rollback que no s'han de confondre:
| Rollback d'imatge | Rollback de model | |
|---|---|---|
| Què reverteix | Codi del servei, dependències, entorn | El model que se serveix |
| Eina | kubectl rollout undo |
Moure l'àlies @champion a MLflow (03-02) |
| Quan fer-lo servir | Bug a l'API, dependència trencada, imatge defectuosa | El model nou prediu pitjor al món real |
| Requereix redesplegar | Sí (rolling update invers) | No la imatge — però sí reiniciar Pods (kubectl rollout restart) perquè el lifespan recarregui |
| Qui decideix | Enginyeria | Enginyeria + negoci (flux amb revisió humana de 03-02) |
Aquesta separació és fruit directe de la decisió de 04-03 (model fora de la imatge): cada problema es reverteix amb l'eina del seu nivell, sense arrossegar l'altre. Si el model estigués enfornat a la imatge, tots dos rollbacks serien el mateix i sempre pagaries el preu complet. Nota final: el rolling update és l'estratègia estàndard de Kubernetes; les estratègies avançades de release — shadow, canary, A/B, que decideixen quant trànsit veu la versió nova abans de confiar-hi — són matèria de la lliçó 05-04.
Autoescalat amb HPA
Amb 3 rèpliques fixes, la nit d'estrenes amb pic de cancel·lacions satura el servei, i la matinada d'un dimarts malbarata diners. L'HPA ajusta les rèpliques a la demanda:
# deploy/k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: churn-api
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: churn-api
minReplicas: 3 # mai menys del mínim de producció
maxReplicas: 10 # sostre de despesa
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60 # si la mitjana supera el 60% del request, afegeix rèpliquesCom raona: l'objectiu és mantenir l'ús mitjà de CPU al 60% del request (aquí, el 60% de 250m). Si les rèpliques van al 90%, l'HPA calcula quantes caldrien per tornar al 60% i les crea; si van al 20%, en retira (sense baixar de 3). Dos matisos per a serveis de ML:
- L'HPA només funciona bé si els
requestsestan ben posats: són la vara de mesurar del percentatge. - La CPU és un bon proxy per a la nostra inferència (el
predictdel RandomForest és CPU pur), però recorda que cada rèplica nova triga a estar ready el que trigui a descarregar el model — l'HPA reacciona a pics en desenes de segons, no instantàniament. Per a pics previsibles (la campanya d'una estrena), escalar preventivament (kubectl scale --replicas=6) continua sent legítim.
L'alternativa serverless: escala a zero i cold starts
Kubernetes exigeix tenir (o pagar) un clúster i algú que l'entengui. L'alternativa serverless de contenidors (Cloud Run, Azure Container Apps, AWS App Runner / Lambda amb imatges) pren la teva mateixa imatge Docker i s'ocupa de tot: aprovisionar, escalar segons peticions, cobrar només per ús... incloent-hi escalar a zero quan no hi ha trànsit.
El preu d'escalar a zero és el cold start: la primera petició després d'un període de silenci ha d'esperar que s'arrenqui un contenidor — i en el nostre cas l'arrencada inclou descarregar el model del registry. Per a churn-api serien uns quants segons; per a un model de deep learning de gigabytes, desenes. Un cold start de 5 s contra un pressupost de 300 ms és un incompliment de 16x.
| Criteri | Serverless encaixa | Serverless NO encaixa |
|---|---|---|
| Trànsit | Esporàdic, amb valls llargues o impredictible | Sostingut 24/7 (a volum constant, surt més car per petició) |
| Latència | Tolerant a segons ocasionals (o pagues instàncies mínimes calentes) | Pressupost estricte de ms a TOTES les peticions |
| Mida del model | Petit (arrencada ràpida) | Gran (cold starts de desenes de segons) |
| Equip | Sense experiència/ganes d'operar Kubernetes | Ja opera un clúster amb més serveis |
| Exemple | API interna de scoring usada uns quants cops al dia; entorns de demo | El flux de cancel·lació de CineClick |
Apunt que reprendrem a 04-05: el patró batch de CineClick, que corre 10 minuts a la setmana, és un candidat ideal a còmput efímer/serverless — pagar per un job que corre, no per un servidor que espera.
Serving gestionat i especialitzat, en una pinzellada
Hi ha un tercer camí que has de conèixer encara que no el fem servir: plataformes de serving específiques de ML.
- KServe i Seldon Core: capes sobre Kubernetes que entenen de models — els dones una URI de model (suporten MLflow) i generen el servei, amb extres com autoescalat per peticions, canary natiu o explicadors. A canvi, una altra peça de plataforma per operar.
- Endpoints gestionats del cloud (SageMaker Endpoints, Vertex AI Prediction, Azure ML Endpoints): el proveïdor ho opera tot; tu hi puges el model. Màxima comoditat, a canvi d'acoblament al proveïdor, cost i menys control sobre el contenidor (el nostre requisit d'importar
construir_featuresdel paquet exigeix contenidor propi o imatges personalitzades).
Són opcions raonables en organitzacions ja casades amb un cloud o amb desenes de models per servir. Per a un primer servei, entendre'l fins al fons — com estem fent — val més que delegar-lo en una caixa negra.
La decisió de CineClick
Apliquem els criteris: el flux de cancel·lació té trànsit sostingut durant tot el dia (la gent cancel·la a qualsevol hora), pressupost estricte de 300 ms que descarta cold starts, i la plataforma de CineClick ja opera un clúster Kubernetes gestionat al seu cloud per a la resta de microserveis — hi ha equip, monitoratge i costum. Decisió, documentada al costat de la de 04-01:
El servei
churn-apies desplega al Kubernetes gestionat del cloud de CineClick: Deployment amb 3 rèpliques (imatgecineclick/churn-api:0.1.0), Servicechurn-api, probes contra/salut, configuració per ConfigMap (MLFLOW_TRACKING_URI,LLINDAR_ABANDONAMENT) i API key en Secret, HPA de 3 a 10 rèpliques al 60% de CPU. Serverless queda descartat per a aquest servei pels cold starts davant el pressupost de 300 ms, però anotat com a opció per al job batch setmanal.
Els manifestos queden a deploy/k8s/ dins del repo; avui els apliquem amb kubectl apply a mà, i aquesta frase — a mà — ja t'hauria d'incomodar. És el buit que el mòdul 5 ve a omplir.
Errors Comuns i Consells
- Error: desplegar sense readinessProbe (o amb una que no comprova el model). Kubernetes enviaria trànsit a Pods que encara estan descarregant el model del registry: errors 500 a cada rollout i a cada escalat. La nostra probe contra
/salut(que verifica el model en memòria) ho evita — el disseny de 04-02 treballant. - Error: límits de memòria per sota del que ocupa el model carregat. Símptoma inconfusible: Pods en
OOMKilled/CrashLoopBackOffjust després d'arrencar. Mesura la memòria del procés amb el model carregat i deixa marge. - Error: confondre els dos rollbacks. "El model nou va malament" no s'arregla amb
kubectl rollout undo(això reverteix la imatge): s'arregla movent@champional registry i reiniciant els Pods. I viceversa. - Error:
livenessmassa agressiva. UninitialDelaySecondscurt més un registry lent = Kubernetes matant Pods que estaven a punt d'estar llestos, en bucle. Dona marge a l'arrencada (nosaltres: 30 s) i deixa l'exigència fina a la readiness. - Error: tractar l'HPA com a màgia. Sense
requestsrealistes no escala bé, i no és instantani: cada rèplica nova paga l'arrencada completa (contenidor + descàrrega del model). Per a pics coneguts, escala preventivament. - Consell: els manifestos viuen al repo (
deploy/k8s/), versionats com el codi i el pipeline. A 05-02 serà el CD qui els apliqui; que ja siguin a Git és mitja batalla. - Consell: si vols aprofundir en Kubernetes (Ingress, namespaces, RBAC, operadors), el portal té un curs dedicat; aquí ens quedem deliberadament en la porció que un ML engineer opera cada dia.
Exercicis
Exercici 1
Màrqueting llança una campanya de TV el dissabte a les 21:00 i s'espera que les visites (i cancel·lacions) es tripliquin entre les 21:00 i les 23:00. Amb l'HPA configurat (3–10 rèpliques, 60% CPU), què faries el divendres? Raona per què l'HPA sol podria no bastar i dona la comanda concreta.
Exercici 2
Després de desplegar la imatge 0.2.0, els logs mostren que /predir-lot retorna errors 500 per un bug de serialització, tot i que /predir funciona. Paral·lelament, la Laura sospita que el model v2 s'està degradant i vol tornar a la v1. Indica les dues accions, amb les seves comandes/passos, i explica per què són independents.
Exercici 3
Un Pod nou porta 10 minuts en CrashLoopBackOff. El kubectl logs del Pod mostra: KeyError: 'MLFLOW_TRACKING_URI'. Diagnostica la causa més probable i els passos per confirmar-la i corregir-la. Per què les altres rèpliques continuen funcionant?
Solucions
Solució 1
L'HPA reacciona després que la CPU pugi, i cada rèplica nova triga a estar llesta (programar el Pod + arrencar el contenidor + descarregar el model + passar la readinessProbe): en un pic vertical a les 21:00, els primers minuts es servirien amb capacitat insuficient. Per a un pic previsible, escala preventivament: el divendres (o el dissabte a la tarda), kubectl scale deployment/churn-api --replicas=8. Matís important: amb l'HPA actiu, aquest escalat manual hi conviu — l'HPA no baixarà del seu càlcul, però sí que podria reduir si la CPU està baixa abans del pic; l'opció robusta és apujar temporalment minReplicas a 8 a l'HPA (editant hpa.yaml i aplicant-lo) i restaurar-lo el diumenge. L'essencial: autoescalat reactiu per a l'imprevisible, planificació per a l'anunciat.
Solució 2
- Bug de la imatge 0.2.0:
kubectl rollout undo deployment/churn-api— rolling update invers cap a la 0.1.0, sense tall de servei. Després, arreglar el bug, publicar la 0.2.1 i redesplegar. - Degradació del model v2: és el flux de 03-02, no de Kubernetes — amb l'evidència a la mà i revisió humana, moure l'àlies
@championde v2 a v1 al registry MLflow, i tot seguitkubectl rollout restart deployment/churn-apiperquè els Pods recarreguin el model al seu lifespan (verificable aGET /versio, que passarà a respondre"1").
Són independents perquè a 04-03 vam separar deliberadament els cicles de vida: la imatge governa el codi; el registry governa el model. Pots revertir qualsevol dels dos sense tocar l'altre — i en aquest escenari fas tots dos, cadascun amb la seva eina i el seu procés de decisió.
Solució 3
La causa més probable: el Pod arrenca sense la variable MLFLOW_TRACKING_URI — recorda que a 04-02 la llegim amb os.environ[...] precisament per fallar ràpid si falta. Com que la variable ve d'un ConfigMap, els sospitosos són: el ConfigMap churn-api-config no existeix en aquell namespace, la clau es diu diferent (mlflow_tracking_uri), o el Deployment referencia malament el nom. Confirmació: kubectl describe pod <pod> (mostrarà errors de referència al ConfigMap) i kubectl get configmap churn-api-config -o yaml (verificar existència i clau). Correcció: crear/corregir el ConfigMap i deixar que el Deployment recreï el Pod (o kubectl rollout restart). Les altres rèpliques continuen funcionant si van arrencar abans del problema (per exemple, algú va esborrar o reanomenar el ConfigMap després): les variables s'injecten en crear el Pod, així que els vius conserven el seu valor — un altre exemple de per què "funciona als Pods vells" no demostra que el desplegament estigui sa.
Conclusió
El servei de churn ja és un ciutadà de producció: 3 rèpliques (fins a 10 amb l'HPA) del contenidor cineclick/churn-api:0.1.0 darrere del Service churn-api, amb Kubernetes reiniciant el que mor, retenint el trànsit fins que cada Pod ha carregat el campió (readinessProbe contra /salut), la configuració entrant per ConfigMap i els secrets per Secret, i dues palanques de rollback ben separades: kubectl rollout undo per a la imatge, moure @champion per al model. Saps a més quan aquesta maquinària sobra — trànsit esporàdic i tolerància a cold starts: serverless — i que hi ha plataformes de serving especialitzades per a quan els models es comptin per desenes. Queda una pregunta incòmoda abans de tancar el mòdul: aquest servei, és ràpid? Quant triga de debò una predicció, on se'n va el temps, quant costa cada mil crides, i què es pot guanyar abans de pagar més infraestructura? La lliçó següent mesura primer — percentils, no mitjanes — i optimitza després, per ordre de rendibilitat.
Curs de MLOps
Mòdul 1: Fonaments de MLOps
- Què és MLOps i per què els models moren al notebook
- El cicle de vida d'un model de ML en producció
- Nivells de maduresa MLOps i rols de l'equip
- El projecte del curs: del notebook a producció
Mòdul 2: Del notebook al codi reproduïble
- Estructura d'un projecte de ML: del notebook al paquet
- Entorns reproduïbles i gestió de dependències
- Versionat de dades amb DVC
- Pipelines d'entrenament reproduïbles
Mòdul 3: Experiments i registre de models
- Tracking d'experiments amb MLflow
- Model registry: versionar i promocionar models
- Feature stores: quan i per a què
Mòdul 4: Servir models en producció
- Patrons de desplegament: batch, online i streaming
- Un servei de predicció amb FastAPI
- Empaquetatge amb Docker
- Escalat i desplegament: Kubernetes i serverless
- Optimització de la inferència: latència i cost
Mòdul 5: Automatització: CI/CD i orquestració
- CI per a ML: tests de codi, dades i models
- CD: automatitzar el desplegament del model
- Orquestració de pipelines de ML
- Estratègies de release: shadow, canary i A/B
