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

  1. Què aporta un orquestrador
  2. Kubernetes mínim per a ML engineers: Pod, Deployment, Service, HPA
  3. Els manifestos del servei churn-api
  4. Configuració i secrets: ConfigMap i Secret
  5. Rollout i rollback: dos nivells diferents de "tornar enrere"
  6. Autoescalat amb HPA
  7. L'alternativa serverless: escala a zero i cold starts
  8. Serving gestionat i especialitzat, en una pinzellada
  9. 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.yaml declara 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.-> D

Els 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 reiniciar

Punts que mereixen explicació:

  • requests i limits: els requests són la reserva amb què Kubernetes decideix a quina màquina cap el Pod; els limits, 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 amb OOMKilled just 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 /salut verifica 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 contenidor

Des 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èpliques

Com 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 requests estan ben posats: són la vara de mesurar del percentatge.
  • La CPU és un bon proxy per a la nostra inferència (el predict del 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_features del 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-api es desplega al Kubernetes gestionat del cloud de CineClick: Deployment amb 3 rèpliques (imatge cineclick/churn-api:0.1.0), Service churn-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/CrashLoopBackOff just 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 @champion al registry i reiniciant els Pods. I viceversa.
  • Error: liveness massa agressiva. Un initialDelaySeconds curt 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 requests realistes 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 @champion de v2 a v1 al registry MLflow, i tot seguit kubectl rollout restart deployment/churn-api perquè els Pods recarreguin el model al seu lifespan (verificable a GET /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.

© Copyright 2026. Tots els drets reservats