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
- Contenidors davant de màquines virtuals.
- Anatomia de Docker: imatges, contenidors i registres.
- Construir una imatge amb un Dockerfile.
- Per què necessitem un orquestrador.
- Conceptes clau de Kubernetes: Pod, Deployment i Service.
- Un manifest de Kubernetes pas a pas.
- Errors comuns i consells.
- Exercicis i solucions.
- 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]
endExplicació 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.
- 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.-tl'etiqueta amb el nomlamevaappi la versió1.0; el.indica que el Dockerfile és al directori actual.docker run -d -p 8080:8080: arrenca un contenidor.-dl'executa en segon pla (detached);-p 8080:8080mapeja 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.
- 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'anomenembuildper 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 elpom.xmli 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.
- 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ò.
- 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.-> P3Explicació: 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.
- 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 clusterExplicació camp a camp:
kind: Deploymentireplicas: 3: demanem un Deployment que mantingui tres rèpliques.selector.matchLabels+template.metadata.labels: el Deployment vincula els Pods que tinguin l'etiquetaapp: 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.250msignifica 0,25 de CPU;256Misón 256 mebibytes de RAM. Això evita que un Pod ofegui els altres.readinessProbe: Kubernetes consulta/healthper saber quan el Pod està llest. Fins que respongui OK, el Service no li envia trànsit.- Al Service,
selector: app: lamevaappconnecta el Service amb aquests Pods;port: 80és on escolta el Service itargetPort: 8080és el port del contenidor;type: ClusterIPel fa accessible només internament (per exposar-lo a l'exterior s'usaLoadBalancero unIngress). - 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
USERsense 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 enlatesten producció perquè és ambigu i dificulta els rollbacks.
Exercicis
- Explica amb les teves paraules per què un contenidor arrenca en segons i una VM en minuts.
- Al manifest d'exemple, què passa si elimines manualment un dels tres Pods del Deployment? Per què?
- 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
- 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.
- 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.
- Canviaries
type: ClusterIPpertype: 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 aClusterIPi 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
- Què és l'Arquitectura d'Aplicacions?
- El Rol de l'Arquitecte de Programari
- Atributs de Qualitat i Requisits No Funcionals
- Decisions Arquitectòniques i Compromisos (Trade-offs)
- Documentació d'Arquitectura: Vistes i el Model C4
Mòdul 2: Principis i Tàctiques de Disseny
- Acoblament, Cohesió i Separació de Responsabilitats
- Principis SOLID Aplicats a l'Arquitectura
- DRY, KISS, YAGNI i Altres Principis de Disseny
- Tàctiques Arquitectòniques per als Atributs de Qualitat
- Gestió del Deute Tècnic
Mòdul 3: Estils i Patrons Arquitectònics
- Arquitectura Monolítica
- Arquitectura en Capes (N-Tier)
- Arquitectura Client-Servidor
- Arquitectura Hexagonal (Ports i Adaptadors)
- Arquitectura Neta i Ceba (Clean & Onion)
Mòdul 4: Arquitectures Distribuïdes i Microserveis
- Introducció als Sistemes Distribuïts
- Arquitectura de Microserveis
- Descomposició de Serveis i Bounded Contexts
- API Gateway, Service Discovery i Comunicació entre Serveis
- Patrons de Resiliència: Circuit Breaker, Retry i Bulkhead
- El Teorema CAP i la Consistència de Dades
Mòdul 5: Arquitectures Dirigides per Esdeveniments i Missatgeria
- Fonaments de l'Arquitectura Orientada a Esdeveniments
- Missatgeria Asíncrona: Cues i Brokers
- Patrons d'Esdeveniments: Event Sourcing i CQRS
- Gestió de Transaccions Distribuïdes: Patró Saga
- Streaming de Dades en Temps Real
Mòdul 6: Disseny Dirigit pel Domini (DDD)
- Conceptes Fonamentals del DDD
- Disseny Estratègic: Bounded Contexts i Llenguatge Ubic
- Disseny Tàctic: Entitats, Agregats i Repositoris
- Mapatge de Contextos (Context Mapping)
Mòdul 7: Dades i Persistència
- Estratègies de Persistència: SQL vs NoSQL
- Patrons d'Accés a Dades: Repository, Unit of Work i DAO
- Base de Dades per Servei i Gestió de Dades Distribuïdes
- Cau i Estratègies d'Invalidació
Mòdul 8: Arquitectura al Núvol i Desplegament
- Fonaments del Cloud Computing (IaaS, PaaS, SaaS)
- Contenidors i Orquestració amb Docker i Kubernetes
- Arquitectura Serverless
- Patrons de Disseny Cloud-Native
- Infraestructura com a Codi (IaC)
Mòdul 9: Qualitat, Seguretat i Observabilitat
- Escalabilitat: Horitzontal vs Vertical i Balanceig de Càrrega
- Alta Disponibilitat i Tolerància a Fallades
- Seguretat per Disseny i Autenticació/Autorització
- Observabilitat: Logging, Mètriques i Traçabilitat
- Rendiment i Proves de Càrrega
