Swarm et va donar un clúster amb les idees justes i una corba d'aprenentatge amable. Kubernetes et dona l'estàndard de la indústria a canvi d'un vocabulari nou. La bona notícia és que els conceptes ja els tens: estat desitjat, reconciliació, rèpliques, xarxa entre nodes i secrets muntats com a fitxers. Aquesta lliçó posa noms nous a idees conegudes i hi afegeix les que falten, sense desplegar encara Aurora Libros sencera.

Contingut

  1. Per què existeix Kubernetes i què hi afegeix
  2. El model declaratiu i els controladors
  3. Arquitectura del clúster
  4. El runtime després de dockershim: CRI i OCI
  5. Els objectes fonamentals d'un cop d'ull
  6. Pod: la unitat de desplegament
  7. ReplicaSet i Deployment
  8. Service, els seus tipus i Ingress
  9. ConfigMap i Secret
  10. Namespace
  11. PersistentVolume, PersistentVolumeClaim i StorageClass
  12. Job, CronJob, StatefulSet i DaemonSet
  13. Anatomia d'un manifest: etiquetes i selectors
  14. kubectl essencial, traduït des de Docker
  15. Un clúster local per practicar
  16. Glossari Docker → Kubernetes

  1. Per què existeix Kubernetes i què hi afegeix

Kubernetes va néixer a Google a partir de l'experiència de Borg i es va donar a la CNCF el 2015. El seu objectiu no era millorar Swarm, sinó resoldre l'orquestració a escala de milers de nodes i d'equips, i per això el seu disseny és més ambiciós i també més complex.

Capacitat Swarm Kubernetes
Unitat mínima Un contenidor per tasca Pod: diversos contenidors acoblats
Extensibilitat Fixa CRD i operadors: objectes propis
Autoescalat No HPA, VPA i autoescalat de nodes (06-06)
Emmagatzematge Volums locals o plugins CSI, PVC i aprovisionament dinàmic
Control d'accés Rols: manager/worker RBAC per usuari, recurs i verb
Encaminament HTTP Nginx que hi poses tu Ingress i Gateway API natius
Càrregues amb estat Sense suport específic StatefulSet amb identitat estable
Corba d'aprenentatge Dies Setmanes
Ecosistema Reduït Enorme: Helm, Argo, Prometheus, malles

L'última fila és la que decideix a la majoria de les organitzacions. La primera és la que més canvia la teva manera de pensar, i és la que veurem a fons.

  1. El model declaratiu i els controladors

El cor conceptual és idèntic al de Swarm: descrius l'estat desitjat i el sistema el manté. La diferència és que Kubernetes ho generalitza: tot és un objecte en una base de dades (etcd), i per cada tipus d'objecte hi ha un controlador executant el mateix bucle.

observar l'estat real → comparar-lo amb el desitjat → actuar per acostar-los → repetir

Aquest patró s'anomena reconciliation loop, i explica comportaments que al principi desconcerten:

  • Esborres un Pod creat per un Deployment i reapareix: el seu controlador veu que falten rèpliques.
  • Edites un Pod a mà i el teu canvi desapareix al desplegament següent: la font de veritat és la plantilla del Deployment.
  • Apliques un manifest dues vegades i no passa res: no descrius accions, descrius destins. L'operació és idempotent.

  1. Arquitectura del clúster

flowchart TB
    U["kubectl / CI"] -->|API REST| API
    subgraph CP["Pla de control"]
        API[kube-apiserver] <--> ETCD[(etcd)]
        SCH[kube-scheduler] --> API
        CM[kube-controller-manager] --> API
    end
    API --> K1[kubelet · node-1]
    API --> K2[kubelet · node-2]
    K1 --> R1["containerd (CRI) → runc"]
    K2 --> R2["containerd (CRI) → runc"]
    style API fill:#e8f0fe,stroke:#3367d6
Component On Responsabilitat
kube-apiserver Pla de control Única porta d'entrada; valida, autentica i escriu a etcd
etcd Pla de control Base de dades clau-valor amb tot l'estat. La seva còpia de seguretat és la del clúster
kube-scheduler Pla de control Decideix en quin node va cada Pod nou segons recursos, afinitats i taints
kube-controller-manager Pla de control Executa els controladors (Deployment, ReplicaSet, Node...)
kubelet Cada node Parla amb el runtime perquè els Pods assignats existeixin i en reporta l'estat
kube-proxy Cada node Programa les regles de xarxa que fan funcionar els Services
Runtime (CRI) Cada node Executa els contenidors: containerd, CRI-O

Fixa't en la propietat que fa robust el disseny: ningú no parla amb ningú llevat que sigui amb el kube-apiserver. El scheduler no truca al kubelet; escriu a l'API que un Pod va a cert node, i el kubelet d'aquell node, que està observant l'API, actua. Tot passa pel mateix punt d'autenticació i auditoria.

  1. El runtime després de dockershim: CRI i OCI

El 2022, Kubernetes 1.24 va eliminar dockershim, l'adaptador que li permetia fer servir Docker Engine com a runtime. Allò va generar titulars del tipus "Kubernetes elimina Docker" que van confondre tothom.

El que va passar en realitat és molt menys dramàtic:

  • Kubernetes parla amb els runtimes per una interfície anomenada CRI (Container Runtime Interface).
  • Docker Engine no implementa CRI, així que calia un adaptador (dockershim) mantingut pel mateix projecte.
  • Aquell adaptador es va retirar, i els nodes fan servir containerd o CRI-O directament, que sí que parlen CRI. I containerd és, precisament, el mateix component que Docker Engine fa servir per dins des de 01-03.

Les teves imatges continuen valent exactament igual. El que construeixes amb docker build és una imatge OCI, un format estàndard, no un format "de Docker". La ghcr.io/auroralibros/aurora-api:2.0.0 que va produir el teu pipeline s'executa a containerd sense cap modificació. L'única cosa que va canviar és quin programa l'arrenca al node.

  1. Els objectes fonamentals d'un cop d'ull

Objecte Per a què El crees a mà?
Pod Un o diversos contenidors que comparteixen xarxa i emmagatzematge Gairebé mai
ReplicaSet Manté N Pods idèntics No: el crea el Deployment
Deployment Gestiona ReplicaSets i les actualitzacions progressives
Service IP i nom DNS estables davant d'un conjunt de Pods
Ingress Encaminament HTTP/HTTPS d'entrada per host i ruta
ConfigMap Configuració no sensible
Secret Configuració sensible (codificada en base64)
Namespace Espai de noms lògic dins del clúster
PersistentVolumeClaim Petició d'emmagatzematge persistent
StorageClass Com s'aprovisiona aquest emmagatzematge La dona el clúster
Job / CronJob Tasca que acaba / tasca periòdica
StatefulSet Pods amb identitat i disc estables
DaemonSet Un Pod per node

  1. Pod: la unitat de desplegament

Un Pod és un grup de contenidors que comparteixen namespace de xarxa (mateixa IP, es parlen per localhost), namespace IPC i, opcionalment, volums. És la unitat que el scheduler col·loca i que s'escala: a Kubernetes no es repliquen contenidors, es repliquen Pods.

apiVersion: v1
kind: Pod
metadata:
  name: aurora-cache
  labels: { app: aurora-cache }
spec:
  containers:
    - name: redis
      image: redis:7-alpine
      args: ["--maxmemory", "200mb", "--maxmemory-policy", "allkeys-lru"]
      ports: [{ containerPort: 6379 }]
      resources:
        requests: { memory: 64Mi, cpu: 50m }
        limits:   { memory: 256Mi, cpu: 500m }

Gairebé mai no es crea un Pod a mà, per una raó contundent: un Pod no es recupera. Si el node mor, el Pod mor amb ell i ningú no el recrea; és l'equivalent exacte d'un docker run sense --restart. El que es crea és un Deployment, que a través del seu ReplicaSet garanteix que sempre hi hagi tants Pods com vas demanar.

La majoria dels Pods tenen un sol contenidor. El cas multicontenidor és el patró sidecar: un contenidor auxiliar que acompanya el principal —un recol·lector de logs, un proxy de malla de serveis, un sincronitzador de fitxers— i es beneficia de compartir xarxa i volums.

  1. ReplicaSet i Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: aurora-cache
spec:
  replicas: 1
  selector:
    matchLabels: { app: aurora-cache }     # quins Pods em pertanyen
  template:                                # la plantilla del Pod (sense apiVersion ni kind)
    metadata:
      labels: { app: aurora-cache }        # HA de casar amb el selector
    spec:
      containers:
        - name: redis
          image: redis:7-alpine
          ports: [{ containerPort: 6379 }]

La cadena de responsabilitat té tres baules, i entendre-la evita molta confusió:

Deployment  →  ReplicaSet  →  Pods
(versions)     (quants)       (processos)

El ReplicaSet només sap comptar: manté N Pods que casin amb el seu selector. El Deployment gestiona ReplicaSets: en canviar la imatge en crea un de nou, l'omple mentre buida l'antic, i conserva el vell amb zero rèpliques per poder desfer. Aquell ReplicaSet buit que veus a kubectl get rs no és brossa: és el teu historial de rollback (06-07).

Un avís: el selector d'un Deployment és immutable. Si el canvies, cal esborrar i recrear l'objecte. Convé triar bé les etiquetes la primera vegada.

  1. Service, els seus tipus i Ingress

Els Pods són bestiar, no mascotes: neixen i moren amb IPs diferents. Un Service aporta una IP virtual i un nom DNS estables davant d'un conjunt canviant de Pods, seleccionats —de nou— per etiquetes.

apiVersion: v1
kind: Service
metadata:
  name: aurora-cache
spec:
  type: ClusterIP
  selector: { app: aurora-cache }    # qualsevol Pod amb aquesta etiqueta entra al balanceig
  ports:
    - port: 6379          # el port del Service
      targetPort: 6379    # el port del contenidor

Els Pods hi arriben per DNS amb aurora-cache dins del namespace, o aurora-cache.aurora.svc.cluster.local des de fora: és l'equivalent del DNS per nom de servei de Compose i Swarm.

Tipus Què exposa Abast Ús a Aurora Libros
ClusterIP (per defecte) IP virtual interna Només dins del clúster aurora-db, aurora-cache, aurora-api
NodePort Un port (30000-32767) a tots els nodes Extern, tosc Proves locals
LoadBalancer Un balancejador del proveïdor de núvol Extern, un per servei Només aurora-web
ExternalName Un CNAME a un domini extern Migrar a una BD gestionada
Headless (clusterIP: None) Sense IP virtual: DNS a cada Pod Intern aurora-db en StatefulSet

Un LoadBalancer per servei es torna car de seguida: cadascun és un balancejador facturat del proveïdor. La solució estàndard és un Ingress: un únic punt d'entrada que encamina per host i per ruta cap a diversos Services.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: aurora
  annotations: { nginx.ingress.kubernetes.io/proxy-body-size: "8m" }
spec:
  ingressClassName: nginx            # quin controlador l'atén
  rules:
    - host: llibres.aurora.example
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend: { service: { name: aurora-api, port: { number: 3000 } } }
          - path: /
            pathType: Prefix
            backend: { service: { name: aurora-web, port: { number: 80 } } }

Detall que causa desconcert la primera vegada: l'objecte Ingress no fa res per si sol. És una declaració d'intencions que necessita un ingress controller instal·lat al clúster (ingress-nginx, Traefik, HAProxy...) que la llegeixi i configuri el proxy real. Sense controlador, l'objecte existeix, no dona error i el trànsit no arriba enlloc.

  1. ConfigMap i Secret

apiVersion: v1
kind: ConfigMap
metadata: { name: aurora-config }
data:
  DB_HOST: aurora-db
  DB_NAME: aurora_llibres
  CACHE_TTL: "60"
  TERMINI_ATURADA_MS: "15000"
---
apiVersion: v1
kind: Secret
metadata: { name: aurora-secrets }
type: Opaque
stringData:                          # stringData accepta text pla; Kubernetes el codifica
  DB_PASSWORD: clau-ficticia-aurora

Tots dos es consumeixen igual, i hi ha dues maneres amb implicacions diferents:

      envFrom:                                   # totes les claus com a variables
        - configMapRef: { name: aurora-config }
      env:                                       # una clau concreta
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef: { name: aurora-secrets, key: DB_PASSWORD }
      volumeMounts:                              # o com a fitxers: el patró _FILE de 06-01
        - { name: secrets, mountPath: /run/secrets, readOnly: true }

Advertència important sobre Secret. Un Secret de Kubernetes està en base64, que no és xifratge: qualsevol amb permís de lectura sobre l'objecte veu el valor amb un base64 -d. A més, etcd no xifra en repòs llevat que s'activi explícitament. Per a producció cal activar el xifratge en repòs, restringir l'accés amb RBAC i valorar un gestor extern (Vault, Sealed Secrets, el gestor de secrets del proveïdor de núvol amb el driver CSI). És una decisió que has d'acordar amb el responsable de seguretat de la teva organització, no un ajust per defecte acceptable.

I una diferència pràctica respecte de Swarm: muntat com a volum, un Secret o ConfigMap actualitzat es propaga al fitxer del Pod sense reiniciar-lo (amb un retard de fins a un minut); injectat com a variable d'entorn, no s'actualitza mai fins que el Pod es recrea.

  1. Namespace

apiVersion: v1
kind: Namespace
metadata:
  name: aurora
  labels: { entorn: produccio }

Un Namespace és una partició lògica del clúster. Dona tres coses: noms que no col·lideixen (pot haver-hi un aurora-api a aurora i un altre a aurora-staging), un àmbit per a RBAC i quotes, i un límit de xarxa si fas servir NetworkPolicy. No és un límit de seguretat fort per si mateix: no aïlla el kernel, només l'API.

Amb kubectl config set-context --current --namespace=aurora t'estalvies escriure -n aurora a cada comanda.

  1. PersistentVolume, PersistentVolumeClaim i StorageClass

Kubernetes separa qui demana emmagatzematge de qui el proporciona, que és justament el que faltava a Swarm.

Objecte Qui l'escriu Què diu
StorageClass La plataforma Com es crea el disc (tipus, política d'esborrat)
PersistentVolumeClaim (PVC) Tu "Vull 10 GiB amb accés ReadWriteOnce"
PersistentVolume (PV) L'aprovisionador El disc real, ja creat
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: aurora-dades }
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: standard
  resources: { requests: { storage: 10Gi } }
Mode d'accés Significat Qui el suporta
ReadWriteOnce (RWO) Escriptura des d'un node Discos de bloc: EBS, PD, iSCSI
ReadOnlyMany (ROX) Lectura des de diversos nodes NFS, emmagatzematge d'objectes
ReadWriteMany (RWX) Escriptura des de diversos nodes NFS, CephFS, EFS

Gairebé tots els discos de bloc dels proveïdors de núvol són RWO, i d'aquí surt un límit molt real: un PVC d'aquest tipus no es pot muntar alhora en Pods de nodes diferents. És la raó tècnica per la qual aurora-db no s'escala replicant Pods, i la tractarem a 06-06.

  1. Job, CronJob, StatefulSet i DaemonSet

Objecte Garantia A Aurora Libros
Job Executa fins a completar N vegades amb èxit Migració de l'esquema abans d'un desplegament
CronJob Crea Jobs segons una expressió cron Còpia nocturna amb pg_dump
StatefulSet Nom i identitat estables (-0, -1), PVC propi per Pod, ordre d'arrencada i aturada aurora-db
DaemonSet Un Pod a cada node (i als que s'hi afegeixin) Promtail, node-exporter

El DaemonSet és literalment el mode: global de Swarm. El StatefulSet és el que Swarm no tenia: cada rèplica rep un nom estable i el seu propi volum, sobreviu al reinici amb la mateixa identitat i s'arrenca i s'atura en ordre. És el que necessita qualsevol cosa amb estat, i el faràs servir a 06-05.

  1. Anatomia d'un manifest: etiquetes i selectors

Tots els objectes comparteixen quatre camps de primer nivell:

Camp Què és Exemple
apiVersion Grup i versió de l'API apps/v1, v1, networking.k8s.io/v1
kind Tipus d'objecte Deployment, Service
metadata Nom, namespace, etiquetes, anotacions name: aurora-api
spec L'estat desitjat replicas: 3
status L'estat real (l'escriu el sistema, no tu) readyReplicas: 3

I ara el més important de tota la lliçó. A Kubernetes els objectes no es referencien per nom, sinó per etiquetes. Un Service no diu "envia trànsit als Pods del Deployment aurora-api"; diu "envia trànsit a tot Pod amb l'etiqueta app: aurora-api".

flowchart LR
    S["Service<br/>selector: app=aurora-api"] -.->|selecciona| P1["Pod app=aurora-api"]
    S -.-> P2["Pod app=aurora-api"]
    S -.-x P3["Pod app=aurora-web"]
    D["Deployment<br/>selector: app=aurora-api"] -->|crea| P1
    D --> P2

Aquest acoblament feble és potent i fràgil alhora. Potent perquè permet coses com dirigir trànsit a Pods de dos Deployments diferents alhora, que és la base del blue-green i del canari de 06-07. Fràgil perquè una errada d'escriptura en una etiqueta no dona cap error: el Service simplement no troba Pods, es queda sense endpoints i el trànsit retorna 503. És la fallada silenciosa més comuna, i es diagnostica sempre igual: kubectl get endpoints <servei>.

Les etiquetes recomanades per la comunitat, que convé adoptar des del principi:

  labels:
    app.kubernetes.io/name: aurora-api
    app.kubernetes.io/component: backend
    app.kubernetes.io/part-of: aurora-libros
    app.kubernetes.io/version: "2.0.0"

  1. kubectl essencial, traduït des de Docker

Tasca Docker / Compose kubectl
Crear o actualitzar docker compose up -d kubectl apply -f manifestos/
Llistar docker ps kubectl get pods
Veure-ho tot docker compose ps kubectl get all -n aurora
Detall i esdeveniments docker inspect kubectl describe pod <p>
Logs docker logs -f kubectl logs -f <p>
Logs de l'intent anterior kubectl logs <p> --previous
Shell a dins docker exec -it c sh kubectl exec -it <p> -- sh
Accedir a un port -p 8080:3000 kubectl port-forward svc/aurora-api 8080:3000
Eliminar docker compose down kubectl delete -f manifestos/
Escalar docker service scale kubectl scale deploy/aurora-api --replicas=3
Ús de recursos docker stats kubectl top pods
Consultar l'esquema docker run --help kubectl explain deployment.spec.strategy
kubectl get pods -o wide                       # afegeix node i IP
kubectl get deploy aurora-api -o yaml          # l'objecte sencer, amb el seu status
kubectl get pods -o jsonpath='{.items[*].spec.containers[*].image}'
kubectl get pods -w                            # observar canvis en directe
kubectl describe pod aurora-api-7d9f -n aurora # la secció Events és el 80 % del diagnòstic

Dues comandes mereixen destacar-se. kubectl explain és la documentació oficial sense sortir del terminal i serveix per a qualsevol camp de qualsevol objecte, inclosos els que instal·lis després. I kubectl describe acaba amb una llista d'esdeveniments que gairebé sempre conté la causa exacta del problema: per què el scheduler no troba node, per què falla la descàrrega de la imatge o per què una sonda no passa.

  1. Un clúster local per practicar

Eina Com funciona Arrencada Multinode Avantatge
kind Nodes com a contenidors Docker ~30 s Sí, trivial Ideal per a CI; carrega imatges locals
minikube VM o contenidor ~60 s Limitat Addons integrats (Ingress, dashboard)
k3d k3s (Kubernetes lleuger) en contenidors ~20 s El més lleuger; molt ràpid
Docker Desktop Un clúster d'un node integrat Un clic No Zero instal·lació addicional
# kind-aurora.yaml — tres nodes i el port 80 mapejat per a l'Ingress
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    kubeadmConfigPatches:
      - |
        kind: InitConfiguration
        nodeRegistration:
          kubeletExtraArgs: { node-labels: "ingress-ready=true" }
    extraPortMappings:
      - { containerPort: 80, hostPort: 8080, protocol: TCP }
  - role: worker
  - role: worker
kind create cluster --name aurora --config kind-aurora.yaml
kubectl get nodes
# NAME                   STATUS  ROLES          AGE  VERSION
# aurora-control-plane   Ready   control-plane  48s  v1.31.0
# aurora-worker          Ready   <none>         31s  v1.31.0
# aurora-worker2         Ready   <none>         31s  v1.31.0

# Primer contacte, amb el servei més simple d'Aurora Libros
kubectl create namespace aurora
kubectl run aurora-cache --image=redis:7-alpine -n aurora --port=6379 --labels=app=aurora-cache
kubectl exec -it aurora-cache -n aurora -- redis-cli ping     # PONG

  1. Glossari Docker → Kubernetes

El que ja saps A Kubernetes Matís
Contenidor Pod El Pod pot tenir diversos contenidors
compose.yaml Manifestos YAML Un per objecte, o diversos separats per ---
docker compose up -d kubectl apply -f . Declaratiu en tots dos casos
docker compose down kubectl delete -f .
Xarxa de Compose / DNS per nom Service El DNS el dona el Service, no la xarxa
Port publicat Service + Ingress Separació entre exposar i encaminar
Volum amb nom PVC + StorageClass Es demana, no es crea
deploy.replicas de Swarm spec.replicas del Deployment Concepte idèntic
mode: global DaemonSet Idèntic
Secret de Swarm Secret Base64, no xifrat per defecte
docker service ps kubectl get pods + describe Els esdeveniments són a describe
healthcheck livenessProbe + readinessProbe + startupProbe Les tres de 06-01
Projecte de Compose Namespace Espai de noms

Errors Habituals i Consells

  • Crear Pods solts. Ningú no els recrea si el node cau. Llevat que sigui per a una prova de trenta segons, fes servir sempre un Deployment.
  • Selector i etiquetes que no casen. L'error més freqüent i el més silenciós: el Deployment crea Pods que el seu Service no troba. Verifica-ho amb kubectl get endpoints.
  • Creure que un Secret està xifrat. És base64. Activa-li el xifratge en repòs i protegeix-lo amb RBAC.
  • Esperar que un Ingress funcioni sense controlador. L'objecte es crea sense queixar-se i no encamina res.
  • Oblidar el namespace. kubectl get pods mira el namespace per defecte i sembla que no hi hagi res. Fixa el context o fes servir -n.
  • Definir limits sense requests. Kubernetes copia el límit a la petició i reserves molt més del que necessites.
  • Editar Pods amb kubectl edit. El canvi dura fins al desplegament següent. Modifica el Deployment.
  • Consell: kubectl describe abans que kubectl logs. Si el Pod no arrenca, no hi ha logs; la causa és als esdeveniments.
  • Consell: kubectl apply --dry-run=server -f . valida els manifestos contra l'API real sense crear res. Fes-ho servir al pipeline.

Exercicis

Exercici 1. Munta un clúster kind de tres nodes, desplega aurora-cache amb un Deployment i el seu Service ClusterIP, i comprova des d'un altre Pod que el nom aurora-cache resol i respon.

Exercici 2. Demostra el bucle de reconciliació i la diferència entre Pod solt i Deployment: crea un Pod a mà i un altre gestionat per un Deployment, esborra'ls i observa què passa amb cadascun.

Exercici 3. Provoca expressament la fallada silenciosa de les etiquetes: canvia el selector d'un Service perquè no casi amb cap Pod i diagnostica-ho sense mirar el manifest.

Solucions

Solució 1.

kind create cluster --name aurora --config kind-aurora.yaml
kubectl create namespace aurora
kubectl config set-context --current --namespace=aurora
kubectl apply -f k8s/cache.yaml     # el Deployment i el Service de les seccions 7 i 8
kubectl get deploy,rs,pods,svc
# deployment.apps/aurora-cache             1/1  1  1                   12s
# replicaset.apps/aurora-cache-6b8d4c9f7   1    1  1                   12s
# pod/aurora-cache-6b8d4c9f7-x2klp         1/1  Running  0             12s
# service/aurora-cache  ClusterIP  10.96.184.22  <none>  6379/TCP      12s

En una sola sortida hi ha la cadena sencera de la secció 7: un Deployment que ha creat un ReplicaSet el nom del qual porta el sufix 6b8d4c9f7 —el hash de la plantilla del Pod—, i aquest ReplicaSet ha creat un Pod que hereta el hash i hi afegeix un sufix aleatori. Aquest hash és la peça clau del desplegament progressiu: canviar la imatge produeix un altre hash i, per tant, un ReplicaSet nou.

kubectl run client --rm -it --image=redis:7-alpine --restart=Never -- sh -c '
  nslookup aurora-cache | tail -2
  redis-cli -h aurora-cache ping
  redis-cli -h aurora-cache.aurora.svc.cluster.local set llibre "Rayuela"'
# Name:    aurora-cache.aurora.svc.cluster.local
# Address: 10.96.184.22
# PONG
# OK

El nom curt i el complet resolen a la mateixa IP, que és la IP virtual del Service, no la del Pod. Aquesta distinció és la que fa que els Pods puguin morir i renéixer amb altres adreces sense que ningú se n'hagi d'assabentar: el client parla sempre amb el Service. És el mateix servei que et donava el DNS intern de Compose i Swarm, amb una diferència important: aquí el proporciona un objecte explícit que pots inspeccionar i modificar.

Solució 2.

kubectl run solt --image=redis:7-alpine --labels=app=prova
kubectl create deployment gestionat --image=redis:7-alpine --replicas=2
kubectl get pods -o custom-columns=NOM:.metadata.name,PROPIETARI:.metadata.ownerReferences[0].kind
# NOM                          PROPIETARI
# solt                         <none>
# gestionat-5f7c8d9b4-hq2vn    ReplicaSet
# gestionat-5f7c8d9b4-t8plw    ReplicaSet

kubectl delete pod solt gestionat-5f7c8d9b4-hq2vn && sleep 5 && kubectl get pods
# NAME                        READY  STATUS   RESTARTS  AGE
# gestionat-5f7c8d9b4-t8plw   1/1    Running  0         2m
# gestionat-5f7c8d9b4-vk9xz   1/1    Running  0         5s

Vas esborrar dos Pods i només en va tornar un, amb nom diferent i cinc segons d'edat. La columna PROPIETARI explica exactament per què: el Pod solt no té ownerReferences, així que en esborrar-lo ningú no va trobar res a faltar; els altres pertanyen a un ReplicaSet el controlador del qual observa l'API, va veure que tenia un Pod on n'hi havia d'haver dos i en va crear un de nou.

Aquesta és la raó pràctica de la regla de la secció 6. Un Pod solt és equivalent a un docker run sense política de reinici: sobreviu mentre res no el molesti, i desapareix amb el seu node. El detall del nom diferent també importa, perquè revela que el Pod no es "reinicia": se'n crea un altre, amb una altra IP, i per això cap configuració no pot dependre de la identitat d'un Pod concret. Quan aquesta identitat sí que cal —com a aurora-db— existeix el StatefulSet.

Solució 3.

kubectl patch svc aurora-cache -p '{"spec":{"selector":{"app":"aurora-cach"}}}'   # errada
kubectl run client --rm -it --image=redis:7-alpine --restart=Never -- \
  redis-cli -h aurora-cache -t 3 ping
# Could not connect to Redis at aurora-cache:6379: Connection refused

El diagnòstic, sense obrir cap fitxer:

kubectl get endpoints aurora-cache
kubectl get svc aurora-cache -o jsonpath='{.spec.selector}'; echo
kubectl get pods --show-labels
NAME           ENDPOINTS   AGE
aurora-cache   <none>      14m
{"app":"aurora-cach"}
aurora-cache-6b8d4c9f7-x2klp   1/1  Running  app=aurora-cache,pod-template-hash=6b8d4c9f7

Els ENDPOINTS a <none> són el símptoma decisiu, i la seqüència de tres comandes és la que cal memoritzar: el Service existeix, té la seva IP virtual, el DNS resol perfectament... i al darrere no hi ha cap Pod. Comparant el selector (app=aurora-cach) amb les etiquetes reals (app=aurora-cache) apareix la lletra que falta.

kubectl patch svc aurora-cache -p '{"spec":{"selector":{"app":"aurora-cache"}}}'
kubectl get endpoints aurora-cache
# aurora-cache   10.244.1.7:6379   15m

El que té de pedagògic l'exercici és el que no va passar: ni el kubectl patch ni el kubectl get svc ni el describe del Deployment van donar el més mínim avís. Kubernetes no valida que un selector trobi res, perquè un Service que encara no té Pods és una situació legítima —els tindrà quan es despleguin—. El preu d'aquest acoblament feble, que a 06-07 et permetrà commutar trànsit entre dues versions canviant una etiqueta, és que una errada d'escriptura es manifesta com un 503 en producció i no com un error en aplicar el manifest.

D'aquí surt la regla d'operació: després de cada kubectl apply que toqui un Service, comprova'n els endpoints. És una comanda i evita la mena d'incident més frustrant d'aquesta plataforma.

Conclusió

Ja parles Kubernetes. Saps que el seu cor és el mateix bucle de reconciliació de Swarm, generalitzat a tots els objectes d'etcd, i que això explica que un Pod esborrat reaparegui i que un apply repetit no faci res. Coneixes l'arquitectura —kube-apiserver com a única porta, etcd com a estat, el planificador, els controladors, i a cada node el kubelet i el kube-proxy— i la propietat que la fa robusta: ningú no parla amb ningú llevat que sigui amb l'API. I tens resolt el malentès de dockershim: Kubernetes executa containerd per CRI, però les teves imatges valen igual perquè són OCI.

Domines el catàleg d'objectes amb el seu YAML mínim: el Pod com a unitat i per què no es crea a mà, la cadena Deployment → ReplicaSet → Pods on el ReplicaSet buit és el teu historial de rollback, el Service amb les seves cinc variants i l'Ingress que no encamina res sense un controlador instal·lat, ConfigMap i Secret —amb l'advertència que base64 no és xifratge—, el Namespace, la tríada PVC/PV/StorageClass amb els modes d'accés que explicaran a 06-06 per què una base de dades no es replica sense més, i els quatre controladors restants, entre ells el DaemonSet que és el mode: global de Swarm i el StatefulSet que Swarm no tenia.

Sobretot, has interioritzat la peça central: etiquetes i selectors. Tot es connecta per etiquetes, mai per nom, i ho has comprovat trencant un selector expressament per veure la fallada més silenciosa de la plataforma —un Service perfectament sa amb ENDPOINTS <none>— i diagnosticant-la amb tres comandes. Tradueixes cada operació de Docker al seu kubectl equivalent, tens un clúster kind de tres nodes amb aurora-cache corrent a dins, i un glossari que converteix el que ja sabies en el que acabes d'aprendre.

A la lliçó següent, Desplegant Contenidors Docker a Kubernetes, arriba l'aplicació sencera. Escriuràs els manifestos reals dels quatre serveis: el StatefulSet d'aurora-db amb els seus volumeClaimTemplates i el seu Service headless, el Deployment d'aurora-api amb les tres sondes de 06-01, el seu securityContext endurit i la seva configuració injectada, l'Ingress d'aurora-web, i els organitzaràs amb Kustomize en bases i overlays per entorn, amb la taula d'estats d'un Pod —Pending, ImagePullBackOff, CrashLoopBackOff, OOMKilled— i la seva comanda de diagnòstic per quan alguna cosa no arrenqui.

Docker: De Principiant a Avançat

Mòdul 1: Introducció a Docker

Mòdul 2: Treballant amb Imatges Docker

Mòdul 3: Contenidors Docker

Mòdul 4: Docker Compose

Mòdul 5: Conceptes Avançats de Docker

Mòdul 6: Docker en Producció

Mòdul 7: Ecosistema i Eines de Docker

© Copyright 2026. Tots els drets reservats