Docker Compose aixeca el sistema de TechCorp en un portàtil, però es queda curt tan bon punt hi ha més d'una màquina: si el servidor que executa servei-comandes cau, ningú no l'arrenca en un altre lloc; si el Black Friday exigeix deu rèpliques de servei-cataleg, algú ha de decidir on posar-les; si cal desplegar la versió 1.0.1 sense tallar el servei, cal coordinar a mà l'arrencada de la nova i l'aturada de la vella. Un orquestrador fa tot això de manera declarativa: li dius què vols ("dues rèpliques d'aquesta imatge, sanes, accessibles amb aquest nom") i ell s'ocupa del com, de manera contínua. Kubernetes és l'orquestrador de la llista curta de 04-01. En aquesta lliçó n'entenem l'arquitectura i els objectes, muntem un clúster local, i escrivim i apliquem els manifestos complets de servei-comandes amb els noms que 03-05 (Service, /health/*) i 04-03 (servei-comandes-config, comandes-db, comandes-rabbitmq) van deixar fixats. El pipeline que aplicarà aquests manifestos automàticament (05-03) i les estratègies per canviar de versió sense talls (05-04) venen després.
Contingut
- Per què cal un orquestrador
- Arquitectura de Kubernetes: pla de control i nodes
- Els objectes bàsics
- Entorn local amb kind i
kubectlimprescindible Namespace,ConfigMapiSecretdeservei-comandes- Un
Jobper a les migracions - El
Deploymentdeservei-comandes, línia a línia - El
Servicei el nom DNSservei-comandes:3002 Ingress: exposar el gateway aapi.techcorp.example- Escalat manual i estat del desplegament
- RabbitMQ i PostgreSQL: dins del clúster o gestionats
- Helm i Kustomize per no duplicar YAML entre sis serveis
- Per què cal un orquestrador
El que un orquestrador resol, comparat amb "contenidors en màquines":
| Necessitat | Sense orquestrador | Amb Kubernetes |
|---|---|---|
| Rèpliques | Scripts que executen docker run en N màquines |
replicas: 2 en un Deployment; Kubernetes en manté sempre dues |
| Reinicis | restart: always per màquina; si la màquina mor, res |
Si un pod o un node cau, es recrea en un altre node |
| Distribució | Algú decideix a quina màquina va cada servei | L'scheduler tria node segons la CPU/memòria demanades i les regles |
| Xarxa i descobriment | Ports i IP a mà, o Consul (03-05) | Service + DNS intern: http://servei-comandes:3002 |
| Configuració i secrets | Fitxers .env copiats a cada màquina |
ConfigMap/Secret injectats com a variables (04-03) |
| Desplegaments | Aturar la vella, arrencar la nova, creuar els dits | RollingUpdate amb readiness: sense talls (05-04) |
| Salut | HEALTHCHECK de Docker, sense conseqüències |
Probes que reinicien pods i els treuen del balanceig |
Tot es declara en YAML i es desa a git; el clúster reconcilia contínuament l'estat real amb el desitjat. Aquesta és la idea central: no executes ordres imperatives, descrius el resultat.
- Arquitectura de Kubernetes: pla de control i nodes
flowchart TB
subgraph CP["Pla de control"]
API[API server<br/>única porta: kubectl, controladors, kubelets]
ETCD[(etcd<br/>estat desitjat i real)]
SCH[Scheduler<br/>assigna pods a nodes]
CM[Controller manager<br/>Deployment, ReplicaSet, Job, Endpoints...]
API <--> ETCD
SCH --> API
CM --> API
end
subgraph N1["Node 1"]
K1[kubelet] --> R1[containerd]
R1 --> P1[pod servei-comandes-7d9f-abc]
R1 --> P2[pod servei-cataleg-5c1b-xyz]
KP1[kube-proxy]
end
subgraph N2["Node 2"]
K2[kubelet] --> R2[containerd]
R2 --> P3[pod servei-comandes-7d9f-def]
KP2[kube-proxy]
end
API --> K1
API --> K2
kubectl -->|kubectl apply -f| API
- API server: rep totes les peticions (de
kubectl, dels controladors, dels kubelets), les valida i les persisteix a etcd, la base de dades clau-valor del clúster. - Scheduler: observa pods sense node assignat i els en tria un segons els recursos sol·licitats, les afinitats i les restriccions.
- Controller manager: executa els bucles de control. El controlador de
DeploymentcreaReplicaSet; el deReplicaSetcrea o esborra pods fins a quadrarreplicas; el d'Endpointsmanté la llista de pods llestos de cadaService(el "registre" de 03-05). - kubelet (a cada node): agent que parla amb l'API server, arrenca els contenidors dels pods assignats al seu node a través del runtime (containerd, la mateixa tecnologia de contenidors de 05-01) i executa les probes.
- kube-proxy: programa les regles de xarxa perquè la ClusterIP d'un
Servicereparteixi entre els seus pods.
Un desenvolupador de TechCorp només parla amb l'API server (kubectl), i ni tan sols això en producció: ho farà el pipeline o Argo CD (05-03).
- Els objectes bàsics
| Objecte | Què és | Ús a TechCorp |
|---|---|---|
| Pod | Unitat mínima: un o més contenidors que comparteixen xarxa (mateixa IP) i emmagatzematge; efímer | Un contenidor servei-comandes per pod (més un sidecar si hi hagués mesh, 05-05) |
| ReplicaSet | Manté N pods idèntics vius | No s'escriu mai a mà: el crea el Deployment |
| Deployment | Descriu la plantilla de pod, les rèpliques i com actualitzar-les | Un per servei stateless: els sis serveis i el gateway |
| Service | Nom i IP estables davant d'un conjunt de pods (03-05). Tipus: ClusterIP (intern, per defecte), NodePort (port a cada node), LoadBalancer (balancejador del núvol) |
ClusterIP per a tots els serveis interns; el gateway s'exposa per Ingress |
| Ingress | Regla HTTP (host/ruta → Service) executada per un ingress controller (NGINX, Traefik) | api.techcorp.example → gateway:8080 |
| ConfigMap | Parells clau-valor no sensibles | servei-comandes-config (04-03) |
| Secret | Parells clau-valor sensibles (base64, no xifrat per defecte) | comandes-db, comandes-rabbitmq (04-03) |
| Namespace | Partició lògica del clúster per a noms, quotes i permisos | techcorp per a tots els serveis; plataforma per a ingress, observabilitat |
| StatefulSet | Pods amb identitat i disc estables | PostgreSQL/RabbitMQ en dev (apartat 11); cap servei de TechCorp |
| Job / CronJob | Tasca que acaba / tasca programada | Migracions (Job); una neteja nocturna de claus_idempotencia seria un CronJob |
- Entorn local amb kind i
kubectl imprescindible
kubectl imprescindiblekind (Kubernetes in Docker) crea un clúster complet dins de contenidors; minikube és l'alternativa equivalent. TechCorp fa servir kind perquè és el mateix que corre a CI. El fitxer de configuració publica els ports 80/443 del node al portàtil perquè l'Ingress funcioni:
# techcorp/plataforma/local/kind.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings: # 80/443 del node → portàtil, perquè l'Ingress (apartat 9) respongui a localhost
- { containerPort: 80, hostPort: 80 }
- { containerPort: 443, hostPort: 443 }
- role: worker
- role: workerkind create cluster --name techcorp --config plataforma/local/kind.yaml
kubectl cluster-info # comprova que kubectl apunta al clúster kind-techcorp
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
kind load docker-image ghcr.io/techcorp/servei-comandes:1.0.0 --name techcorp # imatge local (05-01) sense passar per ghcr.ioLes ordres de kubectl que es fan servir cada dia (kubectl config set-context --current --namespace=techcorp evita repetir -n techcorp; en el que segueix l'ometem):
kubectl apply -f fitxer.yaml # crear o actualitzar (declaratiu, idempotent); -k per a un directori Kustomize
kubectl get pods -n techcorp -w # llistar (i -w: seguir canvis); també get deploy/svc/cm/secret/ingress/job
kubectl describe pod servei-comandes-7d9f-abc -n techcorp # detall i, sobretot, la secció Events (per què no arrenca)
kubectl logs -f deploy/servei-comandes -n techcorp # logs (d'un pod del Deployment); --previous si s'ha reiniciat
kubectl port-forward svc/servei-comandes 3002:3002 -n techcorp # túnel local per provar amb curl sense Ingress
kubectl rollout status deploy/servei-comandes -n techcorp # espera que el desplegament acabi (o falli)
kubectl exec -it deploy/servei-comandes -n techcorp -- sh # shell en un pod; kubectl delete -f: esborrar el que s'ha declarat
Namespace, ConfigMap i Secret de servei-comandes
Namespace, ConfigMap i Secret de servei-comandesEls manifestos viuen a techcorp/plataforma/k8s/servei-comandes/base/ (l'apartat 12 n'explica l'estructura). Primer el namespace i la configuració no sensible, que és exactament la columna "producció" de la taula de 04-03:
# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: techcorp
---
# k8s/servei-comandes/base/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: servei-comandes-config # el nom acordat a 04-03
namespace: techcorp
data: # tot són cadenes: els números van entre cometes
NODE_ENV: production
PORT: "3002"
LOG_NIVELL: info
CATALEG_URL: http://servei-cataleg:3001 # noms DNS dels Service (03-05)
CLIENTS_URL: http://servei-clients:3004
TIMEOUT_HTTP_MS: "2000"
OUTBOX_INTERVAL_MS: "250"
CATALEG_REMOT: "true"Els secrets no van en YAML dins del repositori. Es creen amb kubectl (o els injecta un gestor extern, 07-04):
kubectl create secret generic comandes-db \
--from-literal=COMANDES_DB_URL='postgres://svc_comandes:[email protected]:5432/comandes'
kubectl create secret generic comandes-rabbitmq \
--from-literal=RABBITMQ_URL='amqp://comandes:Pr0d-Rq7t...@rabbitmq:5672'
kubectl get secret comandes-db -o yaml
# data:
# COMANDES_DB_URL: cG9zdGdyZXM6Ly9zdmNfY29tYW5kZXM6UHIwZC1YazN2Li4uQHBnLWNvbWFuZGVzLi4uAquest valor és base64, no xifrat: echo cG9z... | base64 -d retorna la contrasenya. Un Secret només és "secret" perquè l'accés s'hi restringeix amb permisos (RBAC) i perquè etcd es pot xifrar en repòs; totes dues coses són de 07-04. Per això els secrets no es versionen en text pla i per això el .dockerignore de 05-01 excloïa .env. Les claus del Secret es diuen com les variables d'entorn (COMANDES_DB_URL) per poder-les injectar amb envFrom.
- Un
Job per a les migracions
Job per a les migracionsscripts/migrar.js (04-04) s'ha d'executar abans que arrenqui la nova versió de Comandes, una vegada, i fallar sorollosament si no pot. És un Job:
# k8s/servei-comandes/base/job-migracions.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: servei-comandes-migracions-1-0-0 # el nom porta la versió: un Job és immutable, cada versió crea el seu
namespace: techcorp
spec:
backoffLimit: 3 # reintents si el pod falla (p. ex. PostgreSQL encara no accepta connexions)
ttlSecondsAfterFinished: 3600 # s'esborra sol una hora després d'acabar
template:
spec:
restartPolicy: Never # un Job no reinicia el contenidor: crea un altre pod si cal
securityContext:
runAsNonRoot: true
containers:
- name: migracions
image: ghcr.io/techcorp/servei-comandes:1.0.0 # la MATEIXA imatge del servei: migracions/ i scripts/ van a dins (05-01)
command: ["node", "scripts/migrar.js"]
envFrom:
- secretRef: { name: comandes-db } # només necessita COMANDES_DB_URL
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { memory: 256Mi }kubectl apply -f k8s/servei-comandes/base/job-migracions.yaml
kubectl wait --for=condition=complete job/servei-comandes-migracions-1-0-0 --timeout=120s
kubectl logs job/servei-comandes-migracions-1-0-0 # "aplicada 001-esquema-inicial.sql" ... o l'error SQLL'alternativa és un init container dins del pod del servei; funciona, però executa les migracions a cada rèplica en arrencar (amb dues rèpliques, dues vegades, i migrar.js ha de suportar la concurrència). El Job les executa una vegada per versió i es veu a kubectl get jobs. A 05-03 el pipeline el llança i espera abans d'aplicar el Deployment.
- El
Deployment de servei-comandes, línia a línia
Deployment de servei-comandes, línia a línia# k8s/servei-comandes/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: servei-comandes
namespace: techcorp
labels:
app: servei-comandes
spec:
replicas: 2 # dos pods sempre; l'scheduler els reparteix entre nodes
selector:
matchLabels:
app: servei-comandes # quins pods gestiona aquest Deployment (ha de coincidir amb template.metadata.labels)
# strategy: RollingUpdate és el valor per defecte; els seus paràmetres s'afinen a 05-04
template: # plantilla del pod
metadata:
labels:
app: servei-comandes
version: v1 # etiqueta extra que 05-04 i 05-05 faran servir per encaminar entre versions
spec:
terminationGracePeriodSeconds: 30 # després de SIGTERM, quant espera el kubelet abans de SIGKILL (l'aturada de 04-02 triga < 10 s)
securityContext:
runAsNonRoot: true # el kubelet rebutja el pod si la imatge intentés córrer com a root (USER node a 05-01)
runAsUser: 1000 # uid de l'usuari 'node' de la imatge base
containers:
- name: servei-comandes
image: ghcr.io/techcorp/servei-comandes:1.0.0 # etiqueta concreta, mai latest (05-01)
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 3002 # el PORT del ConfigMap; documenta i dona nom al port
envFrom: # totes les claus d'aquests objectes passen a ser variables d'entorn (04-03)
- configMapRef: { name: servei-comandes-config }
- secretRef: { name: comandes-db }
- secretRef: { name: comandes-rabbitmq }
resources:
requests: # el que l'scheduler reserva per col·locar el pod
cpu: 100m # 0,1 nuclis
memory: 128Mi
limits: # sostre: si la memòria se supera, el contenidor mor (OOMKilled)
cpu: 500m
memory: 256Mi
readinessProbe: # pot atendre? Si falla, el pod surt dels Endpoints del Service (03-05)
httpGet: { path: /health/ready, port: http }
initialDelaySeconds: 5 # el servei triga ~2 s a arrencar i connectar; 5 s de marge
periodSeconds: 5
failureThreshold: 3 # tres fallades seguides (15 s) → no llest; un èxit → llest una altra vegada
livenessProbe: # és viu? Si falla, el kubelet REINICIA el contenidor
httpGet: { path: /health/live, port: http }
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true # la imatge és només de lectura; Node no escriu a discEls punts que generen més dubtes:
selectorilabels: elDeployment(a través del seuReplicaSet) "posseeix" els pods l'etiqueta dels qualsapp: servei-comandescoincideix; elServicede l'apartat 8 fa servir el mateix selector. Canviarselectoren unDeploymentexistent no està permès: es tria bé des del començament.readinessProbedavant delivenessProbe(03-05): readiness mira dependències pròpies (PostgreSQL i RabbitMQ al/health/readyde 04-04) i el seu efecte és deixar de rebre trànsit; liveness només mira que el procés respon i el seu efecte és reiniciar. Posar la comprovació de la base de dades a liveness és l'error clàssic: si PostgreSQL cau, Kubernetes reinicia tots els pods de Comandes en bucle sense arreglar res.terminationGracePeriodSeconds: 30tanca el cercle de l'aturada ordenada: en esborrar un pod, Kubernetes el treu dels Endpoints i envia SIGTERM al PID 1 (elnodede 05-01); el servei posa/health/readya 503, acaba les peticions en curs i surt en menys de 10 s; si no ho fes, als 30 s arribaria SIGKILL. Amb dues rèpliques i aquest contracte, un desplegament no perd cap petició (05-04 ho mostra pas a pas).securityContext:runAsNonRootverifica el que la imatge ja fa (USER node);readOnlyRootFilesystemobliga que qualsevol escriptura vagi a unemptyDirexplícit. La resta d'enduriment (capabilities, seccomp, polítiques d'admissió) és de 07-04.
- El
Service i el nom DNS servei-comandes:3002
Service i el nom DNS servei-comandes:3002# k8s/servei-comandes/base/service.yaml
apiVersion: v1
kind: Service
metadata:
name: servei-comandes # → DNS servei-comandes.techcorp.svc.cluster.local, o simplement servei-comandes
namespace: techcorp
spec:
type: ClusterIP # només accessible dins del clúster (el gateway i altres serveis)
selector:
app: servei-comandes # els pods llestos amb aquesta etiqueta són els Endpoints
ports:
- name: http
port: 3002 # port del Service (el que fan servir els que criden: COMANDES_URL=http://servei-comandes:3002)
targetPort: http # port del contenidor (pel nom, definit al Deployment)Amb això, el COMANDES_URL=http://servei-comandes:3002 del gateway i el CATALEG_URL=http://servei-cataleg:3001 del ConfigMap de Comandes resolen exactament com a Compose (05-01) i com prometia 03-05, sense que cap servei sàpiga quantes rèpliques hi ha ni a quin node són.
Aplicar i comprovar tot l'anterior:
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/servei-comandes/base/ # configmap, job, deployment, service (els secrets ja existeixen)
kubectl rollout status deploy/servei-comandes # deployment "servei-comandes" successfully rolled out
kubectl get pods -l app=servei-comandes # servei-comandes-7d9f6c4b8-abcde 1/1 Running, ...-fghij 1/1 Running
kubectl get endpoints servei-comandes # 10.244.1.7:3002,10.244.2.4:3002 → els dos pods llestos
kubectl port-forward svc/servei-comandes 3002:3002 &
curl -s localhost:3002/health/ready # {"estat":"ok","dependencies":{"postgres":"ok","rabbitmq":"ok"}}Si un pod es queda en CrashLoopBackOff, kubectl logs --previous ensenyarà gairebé sempre el missatge de config.js (04-03) dient quina variable falta: la validació fail-fast està pensada per a aquest moment.
Ingress: exposar el gateway a api.techcorp.example
Ingress: exposar el gateway a api.techcorp.exampleEl gateway (03-04) es desplega igual que qualsevol servei (Deployment + Service gateway:8080, amb el seu ConfigMap d'URL). L'única cosa que surt del clúster és un Ingress que l'ingress controller (NGINX a kind i en producció; Traefik seria equivalent amb ingressClassName: traefik) converteix en regles de proxy:
# k8s/gateway/base/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gateway
namespace: techcorp
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 2m
spec:
ingressClassName: nginx
rules:
- host: api.techcorp.example # en local: afegir "127.0.0.1 api.techcorp.example" a /etc/hosts
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gateway
port: { number: 8080 }
# tls: [{ hosts: [api.techcorp.example], secretName: api-techcorp-tls }] # certificats: 07-02kubectl apply -f k8s/gateway/base/
curl -s http://api.techcorp.example/api/v1/productes?ids=p-501 | jq .dades[0].nom # "Auriculars BT X200"L'ingress controller balanceja en capa 7 cap al Service del gateway, i el gateway cap als serveis interns (03-05). L'únic punt d'entrada continua sent el 8080 del gateway; cap servei-* no té Ingress.
- Escalat manual i estat del desplegament
kubectl scale deploy/servei-cataleg --replicas=6 # pic de catàleg (×20 en campanyes, 01-05): més pods, mateix Service
kubectl get pods -l app=servei-cataleg -w # els nous passen per ContainerCreating → Running → Ready
kubectl scale deploy/servei-cataleg --replicas=2 # tornada a la normalitat
kubectl rollout history deploy/servei-comandes # revisions (cada canvi de template en crea una)kubectl scale és manual: algú decideix el nombre. L'escalat automàtic per CPU o per mètriques (HorizontalPodAutoscaler) és de 06-04, quan tinguem mètriques amb què decidir. Aquí l'important és que escalar és canviar un número i que el Service reparteix només entre les rèpliques llestes.
- RabbitMQ i PostgreSQL: dins del clúster o gestionats
| Criteri | Dins del clúster (StatefulSet, normalment via Helm o un operador) | Servei gestionat (RDS/Cloud SQL, Amazon MQ/CloudAMQP) |
|---|---|---|
| Operació (còpies, pedaços, failover, discos) | La fa l'equip de Plataforma | La fa el proveïdor |
| Cost | Només còmput/emmagatzematge del clúster | Més car per unitat, sense hores d'operació |
| Rendiment i control | Total; també tota la responsabilitat | Menys ajustaments fins; garanties de disponibilitat per contracte |
| Entorn local/CI | Imprescindible (no hi ha núvol a kind) | No aplica |
| Risc | Perdre dades per un error d'operació d'un equip de 4 persones | Acoblament al proveïdor |
Decisió de TechCorp: en dev i al clúster kind de CI, PostgreSQL i RabbitMQ dins del clúster amb Helm (dues ordres, dades d'un sol ús); a staging i producció, gestionats, amb l'URL als Secret (comandes-db, comandes-rabbitmq) i el nom rabbitmq resolt per un Service de tipus ExternalName si cal mantenir RABBITMQ_URL estable. L'equip de Plataforma té quatre persones i la seva prioritat és el gateway, el clúster i el pipeline, no fer de DBA.
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install rabbitmq bitnami/rabbitmq -n techcorp --set auth.username=comandes --set auth.password=dev-rabbit
helm install pg-comandes bitnami/postgresql -n techcorp --set auth.username=svc_comandes --set auth.password=dev-comandes --set auth.database=comandesHelm instal·la un chart (paquet de manifestos parametritzats) que crea l'StatefulSet, el Service (rabbitmq, pg-comandes-postgresql) i els volums; RABBITMQ_URL=amqp://comandes:dev-rabbit@rabbitmq:5672 funciona a kind sense tocar els manifestos dels serveis.
- Helm i Kustomize per no duplicar YAML entre sis serveis
Els quatre fitxers de Comandes es repetirien gairebé idèntics per a Catàleg, Inventari, Pagaments, Notificacions, Clients i el gateway, i a més per entorn (dev, staging, prod). Dues eines eviten copiar i enganxar:
| Kustomize | Helm | |
|---|---|---|
| Idea | YAML base + pedaços per entorn; sense plantilles | Plantilles Go amb values.yaml; paquets versionats (charts) |
Integrat a kubectl |
Sí (kubectl apply -k) |
No (binari helm) |
| Corba | Baixa: és YAML normal | Mitjana: sintaxi de plantilles |
| Encaix | Manifestos propis de TechCorp | Instal·lar programari de tercers (RabbitMQ, ingress-nginx, Prometheus a 06-01) |
TechCorp fa servir Kustomize per als seus serveis i Helm per a tercers. Estructura a techcorp/plataforma/k8s/:
k8s/ ├── namespace.yaml ├── servei-comandes/ │ ├── base/ │ │ ├── kustomization.yaml │ │ ├── configmap.yaml deployment.yaml service.yaml job-migracions.yaml │ └── overlays/ │ ├── dev/kustomization.yaml │ └── prod/kustomization.yaml ├── servei-cataleg/ ... (mateixa forma) └── gateway/ ...
# k8s/servei-comandes/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: techcorp
resources: [configmap.yaml, deployment.yaml, service.yaml, job-migracions.yaml]
commonLabels:
app.kubernetes.io/part-of: techcorp-shop
---
# k8s/servei-comandes/overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources: [../../base]
replicas: [{ name: servei-comandes, count: 1 }] # en dev, una rèplica
images: # aquí és on el pipeline de 05-03 canviarà l'etiqueta
- { name: ghcr.io/techcorp/servei-comandes, newTag: sha-9f3c2ab }
patches:
- patch: |- # sobreescriu només aquestes claus del ConfigMap base
apiVersion: v1
kind: ConfigMap
metadata: { name: servei-comandes-config }
data: { LOG_NIVELL: debug, OUTBOX_INTERVAL_MS: "500" }
---
# k8s/servei-comandes/overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources: [../../base]
replicas: [{ name: servei-comandes, count: 3 }]
images:
- { name: ghcr.io/techcorp/servei-comandes, newTag: 1.0.0 }kubectl kustomize k8s/servei-comandes/overlays/dev # mostra el YAML final sense aplicar-lo
kubectl apply -k k8s/servei-comandes/overlays/dev # aplica l'overlay de dev
kubectl apply -k k8s/servei-comandes/overlays/prod # ...o el de prod, amb la mateixa baseLa base és una per servei, i els overlays només diuen en què es diferencia cada entorn: rèpliques, etiqueta d'imatge, dues claus de configuració. Quan l'equip d'Inventari creï el seu servei, copiarà servei-comandes/ i canviarà noms, port (3006) i ConfigMap: és la part de "plantilla" de la regla del Luis; la part d'"automatitzar" és de 05-03.
Errors Comuns i Consells
- Comprovar la base de dades al
livenessProbe. PostgreSQL cau 30 s i Kubernetes reinicia tots els pods de Comandes en bucle. Dependències a readiness; a liveness, només el procés. - Sense
resources. Senserequestsl'scheduler apila pods en un node; senselimitsel primer que es dispara en memòria tomba els altres. Els valors de partida s'afinen mesurant (06-04). - Etiquetes del
selectorque no coincideixen ambtemplate.metadata.labels. L'applyfalla amb un missatge clar; alService, en canvi, un selector mal escrit simplement deixa elsEndpointsbuits i el servei "no respon".kubectl get endpointsés la primera comprovació. - Secrets en YAML dins del repositori "perquè són en base64". No és xifratge.
kubectl create secreto eines de 07-04 (Sealed Secrets, External Secrets). - Reaplicar un
Jobamb el mateix nom i canvis: Kubernetes el rebutja ("field is immutable"). Nom amb versió okubectl delete jobabans. - Consell:
kubectl describe podi la seva seccióEventsresponen el 90 % dels "no arrenca";kubectl get events --sort-by=.lastTimestampdona la vista del namespace.
Exercicis
Exercici 1. Escriu el Deployment i el Service de servei-cataleg (imatge ghcr.io/techcorp/servei-cataleg:1.4.2, port 3001, ConfigMap servei-cataleg-config amb PORT, MONGO_BD, LOG_NIVELL, NODE_ENV; Secret cataleg-mongo amb MONGO_URL) indicant només les línies que difereixen dels de Comandes. Quantes rèpliques posaries a l'overlay de prod i per què?
Exercici 2. Un pod de servei-comandes mostra READY 0/1 durant minuts però STATUS Running i sense reinicis. kubectl logs ensenya "escoltant al 3002" i cap traça d'error. Enumera, en ordre, les tres ordres que executaries per diagnosticar-ho i les dues causes més probables.
Exercici 3. La Marta pregunta per què el Job de migracions porta la versió al nom i què passa si el pipeline desplega la 1.0.1 sense haver canviat cap migració. Respon i proposa com evitar que el Job falli en aquest cas.
Solucions
Solució 1. Difereixen: metadata.name, labels/selector (app: servei-cataleg), image: ghcr.io/techcorp/servei-cataleg:1.4.2, containerPort: 3001, envFrom amb configMapRef: servei-cataleg-config i secretRef: cataleg-mongo, i al Service port: 3001 (el CATALEG_URL=http://servei-cataleg:3001 de tots). Les probes apunten als mateixos /health/live i /health/ready (contracte de 03-05); terminationGracePeriodSeconds, securityContext i resources poden ser iguals. No hi ha Job de migracions (MongoDB sense esquema; la llavor és només de dev). Rèpliques a prod: més que Comandes (per exemple 4), perquè Catàleg rep els pics ×20 de campanyes (01-05) i és només de lectura, barat de replicar; el nombre definitiu el donarà l'HPA de 06-04.
Solució 2. (1) kubectl describe pod <nom>: a Events apareixerà "Readiness probe failed: HTTP probe failed with statuscode: 503" (o timeout). (2) kubectl port-forward pod/<nom> 3002:3002 i curl localhost:3002/health/ready: el cos diu quina dependència està malament (postgres o rabbitmq). (3) kubectl get secret comandes-db -o jsonpath='{.data.COMANDES_DB_URL}' | base64 -d (i el mateix per a RabbitMQ) per veure on apunta. Causes més probables: l'URL del Secret apunta a un host incorrecte o amb credencials dolentes (el servei arrenca perquè config.js només valida el format, però /health/ready no pot fer SELECT 1), o la dependència no és accessible des del namespace (RabbitMQ encara no instal·lat, Service rabbitmq inexistent). És el comportament desitjat: no llest, sense trànsit, sense reinicis en bucle.
Solució 3. Un Job és immutable i el seu nom únic: si es reapliqués servei-comandes-migracions amb una altra image, l'API server el rebutjaria. Amb la versió al nom, cada desplegament crea un Job nou, en queda registre (kubectl get jobs) i ttlSecondsAfterFinished el neteja. Si la 1.0.1 no porta migracions noves, el Job arrenca, migrar.js consulta migracions_aplicades, veu que 001-004 ja hi són i acaba amb codi 0 sense fer res: és idempotent per disseny (04-04), així que no falla; simplement és un Job de dos segons. L'alternativa d'ometre el Job quan "no hi ha canvis" exigeix que algú ho decideixi, i la regla del Luis prefereix que sigui sempre el mateix pipeline.
Conclusió
Kubernetes executa les imatges de 05-01 de manera declarativa i contínua: un pla de control (API server, etcd, scheduler, controller manager) que reconcilia l'estat desitjat, i nodes amb kubelet, kube-proxy i containerd que el materialitzen. Per a servei-comandes hem escrit i aplicat al namespace techcorp el ConfigMap servei-comandes-config, els Secret comandes-db i comandes-rabbitmq creats amb kubectl create secret (base64, no xifrat), un Job versionat que executa scripts/migrar.js amb la mateixa imatge, un Deployment de dues rèpliques amb envFrom, resources, readinessProbe a /health/ready, livenessProbe a /health/live, terminationGracePeriodSeconds: 30 i runAsNonRoot, i el Service que dona el nom http://servei-comandes:3002; el gateway surt a l'exterior per un Ingress NGINX a api.techcorp.example; RabbitMQ i PostgreSQL van amb Helm en dev/kind i gestionats en producció; i Kustomize (base + overlays dev/prod, amb images: com el punt que canviarà el pipeline) evita duplicar YAML entre els sis serveis. Tot s'ha aplicat a mà amb kubectl apply, i això és precisament el que la lliçó següent elimina: un pipeline de CI/CD per servei que prova, verifica pactes, construeix la imatge i actualitza aquests manifestos sense que ningú escrigui cap ordre.
Curs de Microserveis
Mòdul 1: Introducció als Microserveis
- Conceptes Bàsics de Microserveis
- Avantatges i Desavantatges dels Microserveis
- Comparació amb l'Arquitectura Monolítica
- Quan Adoptar Microserveis: Criteris de Decisió
- El Cas Pràctic del Curs: la Botiga Online de TechCorp
Mòdul 2: Disseny de Microserveis
- Principis de Disseny de Microserveis
- Descomposició d'Aplicacions Monolítiques
- Definició de Bounded Contexts
- Gestió de Dades: una Base de Dades per Servei
- Consistència Distribuïda: Sagues, CQRS i Event Sourcing
Mòdul 3: Comunicació entre Microserveis
- APIs RESTful
- Missatgeria Asíncrona
- Protocols de Comunicació: gRPC, GraphQL
- API Gateway i Backend for Frontend
- Descobriment de Serveis i Balanceig de Càrrega
- Contractes i Versionat d'APIs
Mòdul 4: Implementació de Microserveis
- Elecció de Tecnologies i Eines
- Desenvolupament d'un Microservei Simple
- Gestió de Configuració
- Integració Pràctica: Consumir APIs i Publicar Esdeveniments
- Proves en Microserveis: Unitàries, d'Integració i de Contracte
Mòdul 5: Desplegament i Orquestració
- Contenidors i Docker
- Orquestració amb Kubernetes
- CI/CD per a Microserveis
- Estratègies de Desplegament: Rolling, Blue-Green i Canary
- Service Mesh: Istio i Linkerd
Mòdul 6: Monitoratge i Manteniment
- Monitoratge i Logging
- Traçabilitat Distribuïda amb OpenTelemetry
- Gestió d'Errors i Recuperació
- Escalabilitat i Rendiment
- SLOs, Alertes i Gestió d'Incidents
Mòdul 7: Seguretat en Microserveis
- Autenticació i Autorització
- Seguretat en la Comunicació
- Pràctiques de Seguretat
- Seguretat en Contenidors i Kubernetes
