Un contenidor és una unitat de programari que empaqueta una aplicació juntament amb totes les seves dependències (llibreries, runtime, fitxers de configuració) de manera que s'executa igual en qualsevol entorn: el portàtil del desenvolupador, un servidor de proves o el núvol en producció. Resol el clàssic "a la meva màquina funciona". Docker és l'eina més popular per construir i executar contenidors, i Kubernetes és l'orquestrador estàndard de la indústria per gestionar milers de contenidors en producció: els desplega, els escala, els reinicia si fallen i els connecta entre si. Dominar tots dos és imprescindible en arquitectura cloud-native, perquè gairebé tota aplicació moderna es lliura en contenidors. Aquesta lliçó et porta des del concepte fins a un desplegament real a Kubernetes.

Contingut

  1. Contenidors davant de màquines virtuals.
  2. Anatomia de Docker: imatges, contenidors i registres.
  3. Construir una imatge amb un Dockerfile.
  4. Per què necessitem un orquestrador.
  5. Conceptes clau de Kubernetes: Pod, Deployment i Service.
  6. Un manifest de Kubernetes pas a pas.
  7. Errors comuns i consells.
  8. Exercicis i solucions.

  1. Contenidors davant de màquines virtuals

Tots dos aïllen aplicacions, però de forma molt diferent. Una màquina virtual (VM) virtualitza el maquinari complet i inclou un sistema operatiu convidat sencer. Un contenidor virtualitza només el sistema operatiu: comparteix el kernel de l'amfitrió i empaqueta únicament l'aplicació i les seves dependències.

Aspecte Màquina Virtual Contenidor
Aïllament Maquinari complet (hipervisor) Procés (namespaces del kernel)
Sistema operatiu Un de complet per VM Comparteix el kernel de l'amfitrió
Mida Gigabytes Megabytes
Temps d'arrencada Minuts Segons o menys
Densitat (quants per host) Pocs Molts
Portabilitat Mitjana Molt alta
graph TB
    subgraph "Maquines Virtuals"
        HW1[Maquinari] --> HYP[Hipervisor]
        HYP --> VM1[SO Convidat + App A]
        HYP --> VM2[SO Convidat + App B]
    end
    subgraph "Contenidors"
        HW2[Maquinari] --> OS[SO Amfitrio]
        OS --> DOCK[Docker Engine]
        DOCK --> C1[App A]
        DOCK --> C2[App B]
    end

Explicació del diagrama: en les VM cada aplicació arrossega el seu propi sistema operatiu complet sobre un hipervisor, cosa que consumeix molts recursos. En els contenidors, el motor (Docker) comparteix un únic sistema operatiu amfitrió, així que els contenidors són lleugers i arrenquen en segons. No són enemics: sovint s'executen contenidors dins de VM.

  1. Anatomia de Docker

Tres conceptes que no has de confondre:

  • Imatge (image): plantilla immutable de només lectura que conté la teva aplicació i dependències. És com una "foto" o motlle.
  • Contenidor (container): una instància en execució d'una imatge. D'una imatge pots arrencar molts contenidors.
  • Registre (registry): magatzem d'imatges (Docker Hub, GitHub Container Registry, AWS ECR). Puges (push) i baixes (pull) imatges des d'allà.
# Construir una imatge a partir del Dockerfile del directori actual
docker build -t lamevaapp:1.0 .

# Executar un contenidor a partir de la imatge, publicant el port 8080
docker run -d -p 8080:8080 --name lamevaapp lamevaapp:1.0

# Veure els contenidors en execucio
docker ps

# Pujar la imatge a un registre
docker push elmeuregistre.io/lamevaapp:1.0

Explicació de les comandes:

  • docker build -t lamevaapp:1.0 .: construeix la imatge. -t l'etiqueta amb el nom lamevaapp i la versió 1.0; el . indica que el Dockerfile és al directori actual.
  • docker run -d -p 8080:8080: arrenca un contenidor. -d l'executa en segon pla (detached); -p 8080:8080 mapeja el port de l'amfitrió al del contenidor per poder accedir-hi des de fora.
  • docker ps: llista els contenidors actius, el seu estat i ports.
  • docker push: publica la imatge en un registre perquè d'altres (o un clúster) la baixin.

  1. Construir una imatge amb un Dockerfile

Un Dockerfile és un fitxer de text amb instruccions per construir la imatge, pas a pas. Cada instrucció crea una "capa" que es pot posar a la memòria cau. Vegem un exemple realista amb multi-stage build (compilació en diverses etapes) per a una aplicació Java:

# --- Etapa 1: compilacio ---
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

# --- Etapa 2: imatge final, lleugera ---
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=build /app/target/lamevaapp.jar app.jar
EXPOSE 8080
USER 1000
ENTRYPOINT ["java", "-jar", "app.jar"]

Explicació instrucció a instrucció:

  • FROM maven:... AS build: parteix d'una imatge base amb Maven i Java per compilar. L'anomenem build per referir-nos-hi després.
  • WORKDIR /app: estableix el directori de treball dins de la imatge.
  • COPY pom.xml . + RUN mvn dependency:go-offline: copiem primer només el pom.xml i baixem les dependències. Així, si el codi canvia però les dependències no, Docker reutilitza aquesta capa de la memòria cau i la construcció és més ràpida.
  • COPY src ./src + RUN mvn package: copia el codi font i genera el .jar.
  • Segon FROM eclipse-temurin:17-jre-alpine: comença una imatge final molt més petita (només el runtime de Java, sobre Alpine Linux). Aquesta tècnica deixa fora Maven i el codi font, reduint la mida i la superfície d'atac.
  • COPY --from=build ...: copia només l'artefacte compilat des de l'etapa anterior.
  • EXPOSE 8080: documenta el port que fa servir l'app.
  • USER 1000: executa el procés amb un usuari sense privilegis (no root), bona pràctica de seguretat.
  • ENTRYPOINT [...]: comanda que s'executa en arrencar el contenidor.

  1. Per què necessitem un orquestrador

Executar un o dos contenidors amb docker run és fàcil. Però què passa amb centenars de contenidors en desenes de servidors? Necessites resoldre: en quin servidor col·loco cada contenidor? què faig si un contenidor cau? com escalo de 3 a 30 rèpliques en un pic de trànsit? com actualitzo sense temps d'inactivitat? com es troben entre si? Fer-ho a mà és inviable. Un orquestrador com Kubernetes automatitza tot això.

  1. Conceptes clau de Kubernetes

Kubernetes (abreujat K8s) organitza els contenidors amb diverses abstraccions. Les tres fonamentals:

Objecte Què és Analogia
Pod Unitat mínima desplegable: un o diversos contenidors que comparteixen xarxa i emmagatzematge Una "càpsula" amb la teva app
Deployment Gestiona rèpliques de Pods i actualitzacions controlades El "gestor" que manté N còpies vives
Service Punt d'accés de xarxa estable cap a un conjunt de Pods La "recepció" amb adreça fixa
  • Pod: el més petit que desplega Kubernetes. Normalment un contenidor per Pod. Els Pods són efímers: poden morir i recrear-se amb una altra IP.
  • Deployment: declares "vull 3 rèpliques d'aquesta imatge" i Kubernetes les manté. Si un Pod mor, en crea un altre. Permet rolling updates (actualitzacions sense caiguda).
  • Service: com que els Pods canvien d'IP, el Service dona una adreça estable i reparteix el trànsit entre els Pods sans (balanceig de càrrega intern).
graph LR
    USER[Usuari] --> SVC[Service: IP estable]
    SVC --> P1[Pod 1]
    SVC --> P2[Pod 2]
    SVC --> P3[Pod 3]
    DEP[Deployment] -.gestiona.-> P1
    DEP -.gestiona.-> P2
    DEP -.gestiona.-> P3

Explicació: el Deployment crea i vigila els tres Pods. El Service rep el trànsit de l'usuari i el reparteix entre els Pods disponibles. Si el Pod 2 cau, el Deployment en crea un de nou i el Service deixa d'enviar-li trànsit fins que estigui llest.

  1. Un manifest de Kubernetes pas a pas

A Kubernetes descrius l'estat desitjat en fitxers YAML declaratius. Aquí un Deployment i un Service per a la nostra aplicació:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: lamevaapp-deployment
spec:
  replicas: 3                  # nombre de Pods desitjats
  selector:
    matchLabels:
      app: lamevaapp           # selecciona els Pods amb aquesta etiqueta
  template:                    # plantilla de cada Pod
    metadata:
      labels:
        app: lamevaapp
    spec:
      containers:
        - name: lamevaapp
          image: elmeuregistre.io/lamevaapp:1.0
          ports:
            - containerPort: 8080
          resources:           # limits de recursos
            requests:
              cpu: "250m"
              memory: "256Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
          readinessProbe:      # quan esta llest per rebre trafic
            httpGet:
              path: /health
              port: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: lamevaapp-service
spec:
  selector:
    app: lamevaapp             # enruta cap als Pods amb aquesta etiqueta
  ports:
    - port: 80                 # port del Service
      targetPort: 8080         # port del contenidor
  type: ClusterIP              # accessible nomes dins del cluster

Explicació camp a camp:

  • kind: Deployment i replicas: 3: demanem un Deployment que mantingui tres rèpliques.
  • selector.matchLabels + template.metadata.labels: el Deployment vincula els Pods que tinguin l'etiqueta app: lamevaapp. Les etiquetes són l'adhesiu de Kubernetes.
  • image: la imatge del registre que hem vist abans.
  • resources.requests/limits: requests és el que el Pod reserva com a mínim; limits és el màxim que pot consumir. 250m significa 0,25 de CPU; 256Mi són 256 mebibytes de RAM. Això evita que un Pod ofegui els altres.
  • readinessProbe: Kubernetes consulta /health per saber quan el Pod està llest. Fins que respongui OK, el Service no li envia trànsit.
  • Al Service, selector: app: lamevaapp connecta el Service amb aquests Pods; port: 80 és on escolta el Service i targetPort: 8080 és el port del contenidor; type: ClusterIP el fa accessible només internament (per exposar-lo a l'exterior s'usa LoadBalancer o un Ingress).
  • El --- separa dos documents YAML al mateix fitxer.
# Aplicar el manifest al cluster
kubectl apply -f lamevaapp.yaml

# Veure l'estat dels Pods
kubectl get pods

# Escalar a 5 repliques en calent
kubectl scale deployment lamevaapp-deployment --replicas=5

Explicació: kubectl apply envia l'estat desitjat al clúster, que s'encarrega d'assolir-lo. kubectl get pods mostra els Pods i el seu estat. kubectl scale canvia el nombre de rèpliques sense editar el fitxer.

Errors Comuns i Consells

  • Construir imatges enormes. Fes servir multi-stage builds i imatges base lleugeres (alpine, distroless). Una imatge d'1 GB és lenta de desplegar i un risc de seguretat.
  • Executar com a root. Defineix un USER sense privilegis al Dockerfile. Un contenidor compromès com a root és més perillós.
  • No definir requests i limits. Sense límits, un Pod pot consumir tota la memòria del node i tombar els seus veïns.
  • Oblidar les probes. Sense readinessProbe/livenessProbe, Kubernetes envia trànsit a Pods que encara no estan llestos o no reinicia els que s'han penjat.
  • Tractar els Pods com a mascotes. Els Pods són bestiar, no mascotes: són efímers i reemplaçables. No guardis estat dins d'ells; fes servir volums o serveis externs.
  • Consell: versiona sempre les imatges amb etiquetes explícites (lamevaapp:1.0), mai confiïs en latest en producció perquè és ambigu i dificulta els rollbacks.

Exercicis

  1. Explica amb les teves paraules per què un contenidor arrenca en segons i una VM en minuts.
  2. Al manifest d'exemple, què passa si elimines manualment un dels tres Pods del Deployment? Per què?
  3. Vols exposar la teva aplicació al trànsit extern d'Internet. El Service de l'exemple és de tipus ClusterIP. Què canviaries i quines alternatives tens?

Solucions

  1. El contenidor comparteix el kernel del sistema operatiu amfitrió i només arrenca el procés de l'aplicació amb les seves dependències, mentre que la VM ha d'arrencar un sistema operatiu convidat complet (kernel, serveis, etc.) sobre l'hipervisor. Arrencar menys coses implica menys temps.
  2. El Deployment detecta que només hi ha 2 Pods quan l'estat desitjat són 3 i crea automàticament un Pod nou per tornar a 3 rèpliques. Kubernetes reconcilia contínuament l'estat real amb el declarat.
  3. Canviaries type: ClusterIP per type: LoadBalancer (que aprovisiona un balancejador de càrrega del proveïdor cloud amb IP pública) o, de forma més habitual i flexible, mantindries el Service com a ClusterIP i col·locaries al davant un Ingress amb regles d'enrutament per host/ruta i terminació TLS.

Conclusió

Has après la diferència entre contenidors i VM, com Docker empaqueta aplicacions mitjançant imatges i Dockerfiles, per què cal un orquestrador i els tres pilars de Kubernetes (Pod, Deployment, Service) plasmats en un manifest YAML real. Els contenidors et donen portabilitat i Kubernetes et dona escala i resiliència. A la lliçó següent explorarem un model encara més abstracte en què ni tan sols gestiones contenidors ni servidors: l'arquitectura serverless.

Curs d'Arquitectura d'Aplicacions

Mòdul 1: Fonaments de l'Arquitectura d'Aplicacions

Mòdul 2: Principis i Tàctiques de Disseny

Mòdul 3: Estils i Patrons Arquitectònics

Mòdul 4: Arquitectures Distribuïdes i Microserveis

Mòdul 5: Arquitectures Dirigides per Esdeveniments i Missatgeria

Mòdul 6: Disseny Dirigit pel Domini (DDD)

Mòdul 7: Dades i Persistència

Mòdul 8: Arquitectura al Núvol i Desplegament

Mòdul 9: Qualitat, Seguretat i Observabilitat

Mòdul 10: Evolució, Governança i Casos Pràctics

© Copyright 2026. Tots els drets reservats