La lliçó anterior va acabar amb un recompte: sis serveis, tres magatzems, Kafka, Redis, MinIO, Kong, Keycloak, Vault, Prometheus, Loki, Tempo, Patroni, etcd. Desenes de contenidors, centenars de paràmetres, i un docker-compose.yml que ja no cap en una pantalla. Mentre tot això es desplegui com el monòlit de 01-06 (els dimarts i els dijous, amb un ssh, un git pull i una llista de passos en un document que algú segueix a mà), la plataforma té dos problemes que cap patró de resiliència no arregla: no és reproduïble (ningú no sap exactament què hi ha a inventari-2 ni per què difereix d'inventari-1) i depèn que una persona no s'equivoqui un dijous a les 18:00. Aquesta lliçó tracta d'eliminar la persona del camí crític: primer descrivint la infraestructura com a codi (Ansible per configurar màquines; imatges immutables per als serveis), i després delegant en un orquestrador, Kubernetes, les decisions que avui pren algú a mà: on corre cada rèplica, quantes en calen, què fer quan una mor, i com portar una versió nova a producció sense que l'Anna ho noti. Les proves que donen confiança per automatitzar són la lliçó 07-06; els serveis gestionats al núvol, 08-03.
Contingut
- Per què automatitzar
- Infraestructura com a codi: configuració davant d'aprovisionament
- Ansible: inventari, playbooks, rols i idempotència
- Immutabilitat: imatges, registre i etiquetes per commit
- Kubernetes: per què un orquestrador i com està fet
- Objectes essencials
- Manifestos de Quilòmetre Zero:
k8s/ - Estratègies de desplegament: rolling, blue/green, canary
- Namespaces, RBAC, service mesh i operadors
- GitOps i CI/CD
- docker-compose davant de Kubernetes
- Errors comuns i consells
- Exercicis i solucions
- Conclusió
- Per què automatitzar
Tres raons, i les tres ja s'han vist al curs sense anomenar-les:
- Reproduïbilitat. A 07-03 es va dir que la redundància només protegeix contra fallades independents, i que tres rèpliques "iguals" configurades a mà mai no ho són del tot. Si
inventari-2es va crear copiantinventari-1i editant "el que calia", hi ha diferències que ningú no recorda, i una d'elles serà la causa d'un incident. Automatitzar és que la descripció sigui l'única font: es pot destruir un node i recrear-lo idèntic. - Escala. Amb 3 rèpliques es pot fer a mà; amb 30, durant la Setmana de la Verema, no. I "a mà" inclou decidir quantes en calen: l'autoescalat és automatització d'una decisió, no només d'una tasca.
- Errors humans. El
DELETEde 07-03, el desplegament del dissabte de 07-01 amb el 8 % d'errors, el certificat que va caducar un diumenge: els tres tenen en comú que algú va fer alguna cosa a mà o va deixar de fer-la. L'automatització no elimina l'error (un script equivocat s'equivoca 30 vegades en 30 nodes), però el fa revisable abans (codi en un pull request), provable (07-06) i reversible (rollout undo).
El monòlit de 01-06 es desplegava amb una finestra de manteniment; l'objectiu aquí és que un desplegament de comandes sigui un esdeveniment tan rutinari que passi diverses vegades al dia sense que ningú se n'adoni, inclosa l'Anna.
- Infraestructura com a codi: configuració davant d'aprovisionament
Infraestructura com a codi (IaC) és tractar servidors, xarxes, configuracions i desplegaments com a fitxers en un repositori: versionats, revisats i aplicats per una eina. Dues famílies que convé no confondre:
| Gestió de configuració | Aprovisionament | |
|---|---|---|
| Pregunta | "Donada aquesta màquina, què ha de tenir instal·lat i configurat?" | "Quines màquines, xarxes, discs i balancejadors han d'existir?" |
| Eines | Ansible, Puppet, Chef, Salt | Terraform, OpenTofu, Pulumi, CloudFormation |
| Model | Procedimental-idempotent: tasques que porten la màquina a l'estat desitjat | Declaratiu: descriu l'estat final i calcula la diferència |
| A Quilòmetre Zero | Instal·lar i configurar els nodes de Kafka, Cassandra, Patroni; preparar els nodes del clúster de Kubernetes | Crear les màquines virtuals, la xarxa i el balancejador al proveïdor (08-03) |
Ansible és la que toca aquesta lliçó, perquè és la que configura el que ja existeix (les màquines físiques o virtuals de Quilòmetre Zero) i perquè el seu concepte central, la idempotència de tasques, és el mateix que governa els reintents de 02-05 i 07-04. Terraform es veu a 08-03, quan la infraestructura passi a ser una API de núvol.
- Ansible: inventari, playbooks, rols i idempotència
Ansible no necessita agent: es connecta per SSH a cada màquina de l'inventari, executa mòduls (petits programes Python que saben instal·lar paquets, escriure fitxers, gestionar serveis) i retorna si ha canviat res. Un playbook és un YAML que diu quines tasques aplicar a quin grup de màquines; un rol és un playbook empaquetat i reutilitzable (tasques, plantilles, variables per defecte, handlers).
# km0/ansible/inventari.ini
[kafka]
kafka-1 ansible_host=10.10.1.11 kafka_id=1 zona=bcn
kafka-2 ansible_host=10.10.2.11 kafka_id=2 zona=vlc
kafka-3 ansible_host=10.10.3.11 kafka_id=3 zona=gir
[cassandra]
cass-1 ansible_host=10.10.1.21 zona=bcn
cass-2 ansible_host=10.10.2.21 zona=vlc
cass-3 ansible_host=10.10.3.21 zona=gir
[patroni]
inv-bcn ansible_host=10.10.1.31
inv-vlc ansible_host=10.10.2.31
inv-gir ansible_host=10.10.3.31
[k8s_control]
k8s-cp-1 ansible_host=10.10.1.41
[k8s_workers]
k8s-w-[1:6] ansible_host=10.10.1.5[1:6]
[all:vars]
ansible_user=km0ops
ansible_ssh_private_key_file=~/.ssh/km0opsEl playbook de Kafka delega en un rol i passa les variables del clúster:
# km0/ansible/kafka.yml
- name: Configurar els brokers de Kafka de Quilòmetre Zero
hosts: kafka
become: true # sudo per instal·lar i tocar /etc
serial: 1 # UN broker cada vegada: mai dos brokers reiniciant-se alhora (ISR, 07-03)
vars:
kafka_version: "3.8.0"
kafka_cluster_id: "km0-kafka-prod"
kafka_controller_quorum: "1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093"
kafka_default_replication_factor: 3
kafka_min_insync_replicas: 2
kafka_log_retention_hours: 168
roles:
- kafkaI el rol, amb tasques curosament idempotents:
# km0/ansible/roles/kafka/tasks/main.yml
- name: Usuari de sistema per a Kafka
ansible.builtin.user:
name: kafka
system: true
shell: /usr/sbin/nologin
# Mòdul 'user': si l'usuari existeix amb aquests atributs, no fa res (changed=false)
- name: Java 17
ansible.builtin.apt:
name: openjdk-17-jre-headless
state: present
update_cache: true
cache_valid_time: 3600
- name: Descarregar i desempaquetar Kafka {{ kafka_version }}
ansible.builtin.unarchive:
src: "https://archive.apache.org/dist/kafka/{{ kafka_version }}/kafka_2.13-{{ kafka_version }}.tgz"
dest: /opt
remote_src: true
creates: "/opt/kafka_2.13-{{ kafka_version }}" # si ja existeix, no descarrega: idempotent
owner: kafka
group: kafka
- name: Enllaç /opt/kafka a la versió activa
ansible.builtin.file:
src: "/opt/kafka_2.13-{{ kafka_version }}"
dest: /opt/kafka
state: link
- name: Directori de dades al disc dedicat
ansible.builtin.file:
path: /var/lib/kafka
state: directory
owner: kafka
group: kafka
mode: "0750"
- name: Configuració del broker (KRaft)
ansible.builtin.template:
src: server.properties.j2
dest: /opt/kafka/config/kraft/server.properties
owner: kafka
group: kafka
mode: "0640"
notify: reiniciar kafka # només si el fitxer HA CANVIAT es dispara el handler
- name: Certificat i clau del broker des de Vault (06-04)
community.hashi_vault.vault_pki_generate_certificate:
engine_mount_point: pki_int
role_name: kafka-broker
common_name: "{{ inventory_hostname }}.km0.internal"
ttl: 720h
register: cert
changed_when: false # generar un cert no és "canviar la màquina"; el fitxer de sota sí
no_log: true # mai imprimir la clau privada a la sortida
- name: Escriure el keystore del broker
ansible.builtin.copy:
content: "{{ cert.data.data.certificate }}\n{{ cert.data.data.private_key }}"
dest: /etc/kafka/broker.pem
owner: kafka
mode: "0600"
no_log: true
notify: reiniciar kafka
- name: Formatar l'emmagatzematge KRaft (només la primera vegada)
ansible.builtin.command:
cmd: /opt/kafka/bin/kafka-storage.sh format -t {{ kafka_cluster_id }} -c /opt/kafka/config/kraft/server.properties
creates: /var/lib/kafka/meta.properties # si existeix, ja està formatat: no es repeteix MAI
become_user: kafka
- name: Unitat systemd
ansible.builtin.template:
src: kafka.service.j2
dest: /etc/systemd/system/kafka.service
notify: reiniciar kafka
- name: Kafka habilitat i arrencat
ansible.builtin.systemd:
name: kafka
state: started
enabled: true
daemon_reload: true
- name: Esperar que el broker accepti connexions abans de passar al següent
ansible.builtin.wait_for:
port: 9092
host: "{{ ansible_host }}"
timeout: 120# km0/ansible/roles/kafka/handlers/main.yml
- name: reiniciar kafka
ansible.builtin.systemd:
name: kafka
state: restarted{# km0/ansible/roles/kafka/templates/server.properties.j2 #}
process.roles=broker,controller
node.id={{ kafka_id }}
controller.quorum.voters={{ kafka_controller_quorum }}
listeners=SSL://:9092,CONTROLLER://:9093
broker.rack={{ zona }}
log.dirs=/var/lib/kafka
default.replication.factor={{ kafka_default_replication_factor }}
min.insync.replicas={{ kafka_min_insync_replicas }}
unclean.leader.election.enable=false
log.retention.hours={{ kafka_log_retention_hours }}
ssl.keystore.type=PEM
ssl.keystore.location=/etc/kafka/broker.pem
ssl.truststore.location=/etc/kafka/ca.pem
ssl.client.auth=requiredLa idempotència és a cada detall: creates: evita repetir descàrregues i, sobretot, evita tornar a formatar un broker amb dades (un kafka-storage.sh format sobre un broker en producció el destrueix); template només notifica el handler si el contingut ha canviat, així que executar el playbook deu vegades seguides reinicia Kafka zero vegades; serial: 1 amb wait_for al final fa que un desplegament de configuració recorri els brokers d'un en un respectant les ISR de 07-03. Executar ansible-playbook -i inventari.ini kafka.yml --check --diff mostra què canviaria sense canviar res: és la revisió prèvia que l'ssh dels dimarts mai no va tenir. La mateixa estructura (rol cassandra amb nodetool drain abans de reiniciar, rol patroni amb patronictl switchover si el node és líder) s'aplica a la resta de màquines amb estat.
- Immutabilitat: imatges, registre i etiquetes per commit
Ansible configura màquines que canvien. Per als serveis de km0/ se segueix el camí contrari: la infraestructura immutable. Un servei no s'"actualitza": es construeix una imatge de contenidor nova amb tot a dins (intèrpret, dependències fixades, codi, configuració per defecte), es puja a un registre i es desplega substituint els contenidors vells per nous. Mai no es fa ssh a un contenidor per arreglar res; si alguna cosa està malament, es construeix una altra imatge.
# km0/serveis/comandes/Dockerfile
FROM python:3.12-slim AS base
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app
RUN useradd --system --uid 10001 km0
FROM base AS deps
COPY serveis/comandes/requirements.lock . # versions exactes (06-05: dependències fixades)
RUN pip install --no-cache-dir -r requirements.lock
FROM deps AS final
COPY serveis/comu /app/serveis/comu
COPY serveis/comandes /app/serveis/comandes
COPY contractes /app/contractes
USER km0 # mai root dins del contenidor
EXPOSE 8000
ENTRYPOINT ["uvicorn", "serveis.comandes.app:app", "--host", "0.0.0.0", "--port", "8000"]L'etiqueta de la imatge és la clau de la reproduïbilitat: registre.km0.internal/km0/comandes:1.14.2-3f9a1c7 (versió semàntica + hash curt del commit). Mai latest: latest és un punter mòbil, i "quina versió hi ha a producció" ha de tenir una resposta exacta. La mateixa imatge recorre proves, staging i producció; el que canvia entre entorns és la configuració injectada (variables d'entorn, ConfigMap, Secret), no la imatge. Aquest és el principi "build once, deploy many".
- Kubernetes: per què un orquestrador i com està fet
Amb imatges immutables, queda decidir on corre cada contenidor, quants n'hi ha, què passa quan un mor o un node cau, com es troben entre si i com se substitueixen per la versió nova. A docker-compose tot això ho decideix una persona, en un sol node. Un orquestrador ho decideix contínuament, en un clúster:
| Necessitat | El que feia la persona | El que fa Kubernetes |
|---|---|---|
| Planificació | "comandes-3 al node 2, que hi té lloc" |
L'scheduler col·loca cada Pod segons recursos, afinitats i zones |
| Autoescalat | "Verema: pujo a 6 rèpliques el divendres" | L'HPA ajusta rèpliques segons CPU o una mètrica de Prometheus (lag de Kafka) |
| Autoreparació | Alerta ObjectiuCaigut, ssh, docker restart |
Reinicia per liveness; recrea Pods en un altre node si el node cau |
| Desplegament progressiu | Finestra de manteniment | Rolling update amb readiness; canary; rollout undo |
| Descobriment i balanceig | Editar prometheus.yml i la config de Kong amb cada IP |
Service amb DNS estable i balanceig entre Pods preparats |
| Configuració i secrets | Fitxers copiats a mà | ConfigMap, Secret, integració amb Vault |
flowchart TB
subgraph cp["Pla de control"]
API[kube-apiserver<br/>única porta d'entrada]
ETCD[(etcd<br/>estat desitjat i actual)]
SCH[kube-scheduler<br/>tria node per a cada Pod]
CM[controller-manager<br/>bucles: Deployment, ReplicaSet, HPA...]
API <--> ETCD
SCH --> API
CM --> API
end
subgraph w1["Node treballador 1 (zona bcn)"]
K1[kubelet] --> P1[Pod comandes-7d9f-abc<br/>+ sidecar Envoy]
K1 --> P2[Pod cataleg-5c1e-xyz]
KP1[kube-proxy]
end
subgraph w2["Node treballador 2 (zona vlc)"]
K2[kubelet] --> P3[Pod comandes-7d9f-def]
K2 --> P4[Pod inventari-0<br/>StatefulSet]
KP2[kube-proxy]
end
API --> K1
API --> K2
OPS[kubectl apply -f k8s/<br/>Argo CD] --> API
PROM[Prometheus<br/>service discovery] --> API
La idea que ho explica tot és el bucle de reconciliació: l'usuari declara a l'API l'estat desitjat ("3 rèpliques de comandes:1.14.2"), etcd el desa (el mateix etcd de 03-03: Kubernetes és, en el fons, un conjunt de controladors sobre un magatzem de consens), i cada controlador compara contínuament l'estat desitjat amb el real i actua per acostar-los: si hi ha 2 Pods i se'n desitgen 3, en crea un; si el node 1 desapareix, els seus Pods es recreen en un altre. No hi ha "ordre de desplegar"; hi ha "canviar l'estat desitjat" i esperar que els controladors convergeixin. És la mateixa filosofia de la idempotència d'Ansible, però executant-se sense parar.
- Objectes essencials
| Objecte | Què és | A Quilòmetre Zero |
|---|---|---|
| Pod | La unitat mínima: un o més contenidors que comparteixen xarxa i emmagatzematge; efímer, amb IP pròpia | Un contenidor de comandes + el sidecar Envoy del mesh |
| ReplicaSet | Manté N Pods idèntics vius | El crea el Deployment; no s'escriu a mà |
| Deployment | Gestiona ReplicaSets per a serveis sense estat: rèpliques, plantilla del Pod, estratègia d'actualització, historial per a undo |
comandes, cataleg, pagaments, repartiment, analitica, Kong |
| StatefulSet | Pods amb identitat estable (inventari-0, -1, -2), disc propi persistent i arrencada ordenada |
Cassandra, Kafka, PostgreSQL (tot i que en producció es prefereixen operadors, secció 9) |
| Service | Nom DNS i IP estables que balancegen cap als Pods preparats d'un selector | comandes.km0.svc.cluster.local:8000 |
| Ingress | Regla d'entrada HTTP/HTTPS des de fora del clúster cap a Services | Exposa Kong; Kong fa la resta (06-05) |
| ConfigMap | Configuració no secreta (fitxers, variables) | prometheus.yml, KM0_TRACES_RATIO |
| Secret | Dades sensibles codificades en base64 (i xifrades a etcd si es configura) | Credencials de Vault (la resta les dona Vault dinàmicament, 06-04) |
| Job / CronJob | Un Pod que corre fins a acabar; el CronJob el programa | reconciliacio_nocturna.py (07-03), base_backup.sh, prova de restauració |
| HPA | Horizontal Pod Autoscaler: ajusta les rèpliques d'un Deployment segons mètriques | comandes per CPU; analitica per lag de Kafka |
| PersistentVolumeClaim | Petició de disc que sobreviu al Pod | Les dades de cada node de Cassandra |
| Namespace | Partició lògica del clúster: noms, quotes, permisos | km0-prod, km0-staging, observabilitat |
- Manifestos de Quilòmetre Zero:
k8s/
k8s/k8s/comandes-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: comandes
namespace: km0-prod
labels: {app: comandes, equip: comerc}
spec:
replicas: 3
revisionHistoryLimit: 10 # quants ReplicaSets antics conservar per a `rollout undo`
selector:
matchLabels: {app: comandes}
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # com a màxim 1 Pod extra durant l'actualització (4 en total)
maxUnavailable: 0 # mai menys de 3 preparats: la capacitat no baixa
template:
metadata:
labels: {app: comandes, version: "1.14.2"}
annotations:
prometheus.io/scrape: "true" # Prometheus (07-01) descobreix el Pod per aquesta anotació
prometheus.io/port: "8000"
prometheus.io/path: /metrics
spec:
serviceAccountName: comandes # identitat del Pod: per a RBAC i per autenticar-se a Vault
securityContext:
runAsNonRoot: true
runAsUser: 10001
topologySpreadConstraints: # repartir les rèpliques entre zones (07-03: dominis de fallada)
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector: {matchLabels: {app: comandes}}
containers:
- name: comandes
image: registre.km0.internal/km0/comandes:1.14.2-3f9a1c7
ports: [{containerPort: 8000, name: http}]
env:
- name: KM0_SERVEI
value: comandes
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: otel-collector.observabilitat.svc:4317
envFrom:
- configMapRef: {name: comandes-config} # KM0_LOG_NIVELL, KM0_TRACES_RATIO...
- secretRef: {name: comandes-vault} # VAULT_ROLE_ID / VAULT_SECRET_ID (AppRole, 06-04)
resources:
requests: {cpu: "250m", memory: "256Mi"} # el que l'scheduler reserva: base per a l'HPA
limits: {cpu: "1", memory: "512Mi"} # sostre: per sobre, throttling de CPU / OOMKilled
startupProbe: # 07-03: mentre arrenca, no aplicar liveness
httpGet: {path: /salut/viu, port: http}
failureThreshold: 30
periodSeconds: 2 # fins a 60 s per carregar certificats de Vault
livenessProbe:
httpGet: {path: /salut/viu, port: http}
periodSeconds: 10
failureThreshold: 3 # 30 s sense respondre: reinici
readinessProbe:
httpGet: {path: /salut/preparat, port: http}
periodSeconds: 5
failureThreshold: 2 # 10 s amb PostgreSQL/Kafka caiguts: fora del Service
successThreshold: 1
lifecycle:
preStop:
exec: {command: ["sleep", "5"]} # donar temps que el Service deixi d'enviar trànsit
terminationGracePeriodSeconds: 30 # SIGTERM, i 30 s per acabar les peticions en cursCada bloc respon a alguna cosa vista abans: les sondes són les de 07-03 amb els seus paràmetres; requests alimenta l'scheduler i l'HPA; limits és un bulkhead de recursos (07-04) per Pod; maxUnavailable: 0 garanteix que un desplegament no redueix la capacitat; topologySpreadConstraints reparteix per zones; preStop + terminationGracePeriodSeconds són l'apagada ordenada (una petició de 400 ms no es talla a mitges). I envFrom: secretRef només dona al Pod el mínim per presentar-se davant de Vault, que és qui lliura credencials dinàmiques i certificats.
k8s/comandes-service.yaml
apiVersion: v1
kind: Service
metadata:
name: comandes
namespace: km0-prod
spec:
selector: {app: comandes} # qualsevol Pod amb aquesta etiqueta i readiness OK rep trànsit
ports:
- name: http
port: 8000
targetPort: http
type: ClusterIP # només abastable dins del clúster: Kong és l'única entradaKong (dins del clúster) encamina /api/v1/comandes a http://comandes.km0-prod.svc:8000. Quan un Pod falla la readiness, desapareix dels endpoints del Service en segons; quan es desplega una versió nova, els Pods nous entren només quan estan preparats. Ningú no edita cap IP.
k8s/comandes-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: comandes
namespace: km0-prod
spec:
scaleTargetRef: {apiVersion: apps/v1, kind: Deployment, name: comandes}
minReplicas: 3
maxReplicas: 12
metrics:
- type: Resource
resource:
name: cpu
target: {type: Utilization, averageUtilization: 60} # 60 % dels `requests` de CPU
- type: External # mètrica de Prometheus via prometheus-adapter
external:
metric:
name: kafka_consumergroup_lag_sum
selector: {matchLabels: {consumergroup: comandes-saga, topic: comandes.esdeveniments}}
target: {type: AverageValue, averageValue: "2000"} # 2 000 missatges de lag per rèplica
behavior:
scaleUp:
stabilizationWindowSeconds: 30
policies: [{type: Pods, value: 3, periodSeconds: 60}] # com a màxim +3 Pods per minut
scaleDown:
stabilizationWindowSeconds: 300 # esperar 5 min abans de reduir: evitar oscil·lar
policies: [{type: Percent, value: 25, periodSeconds: 60}]L'HPA pren el màxim del que demanin les mètriques: si la CPU diu 5 rèpliques i el lag en diu 8, escala a 8. La mètrica externa ve del kafka_exporter de 07-01 a través de prometheus-adapter, que la publica a l'API de mètriques de Kubernetes. Les finestres d'estabilització són l'equivalent del for: de les alertes: escalar ràpid, reduir a poc a poc.
k8s/inventari-statefulset.yaml (reduït)
apiVersion: apps/v1
kind: StatefulSet
metadata: {name: inventari-db, namespace: km0-prod}
spec:
serviceName: inventari-db-headless # DNS per Pod: inventari-db-0.inventari-db-headless
replicas: 3
selector: {matchLabels: {app: inventari-db}}
template:
metadata: {labels: {app: inventari-db}}
spec:
containers:
- name: postgres
image: ghcr.io/zalando/spilo-16:3.3-p1 # PostgreSQL + Patroni (07-03)
env:
- name: SCOPE
value: km0-inventari
- name: KUBERNETES_USE_CONFIGMAPS # Patroni fa servir l'API de Kubernetes com a magatzem de consens
value: "true"
volumeMounts: [{name: dades, mountPath: /home/postgres/pgdata}]
volumeClaimTemplates: # cada Pod rep el SEU disc; sobreviu a reinicis i reprogramacions
- metadata: {name: dades}
spec:
accessModes: [ReadWriteOnce]
storageClassName: ssd-replicat
resources: {requests: {storage: 200Gi}}Un StatefulSet dona el que una base de dades necessita i un Deployment no: noms estables (inventari-db-0 és sempre el mateix, amb el mateix disc), arrencada i aturada ordenades, i un volum per rèplica. En producció, tanmateix, la pràctica habitual és delegar en un operador (secció 9).
k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: km0-vora
namespace: km0-prod
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod # certificat públic ACME renovat automàticament
spec:
ingressClassName: nginx
tls:
- hosts: [api.km0.example]
secretName: api-km0-tls
rules:
- host: api.km0.example
http:
paths:
- path: /
pathType: Prefix
backend:
service: {name: kong-proxy, port: {number: 443}}L'Ingress només porta el trànsit fins a Kong; tot el de 06-05 (JWT, rate limiting, OpenAPI, X-Request-Id) continua passant a Kong. Amb cert-manager, el certificat que caducava un diumenge es renova sol i l'alerta CertificatCaducaAviat de 07-01 vigila que sigui així.
Operar amb kubectl
# Provar en local: un clúster d'un node en Docker (kind) o una VM (minikube)
kind create cluster --name km0 --config k8s/local/kind.yaml
kubectl config use-context kind-km0
# Aplicar l'estat desitjat (idempotent: repetir-ho no canvia res si ja és així)
kubectl apply -f k8s/ -n km0-prod
kubectl get pods -n km0-prod -w # -w: observar com canvien els Pods en temps real
# NAME READY STATUS RESTARTS AGE
# comandes-7d9f6c4b8-abcde 2/2 Running 0 40s
# comandes-7d9f6c4b8-fghij 2/2 Running 0 40s
# comandes-7d9f6c4b8-klmno 1/2 Running 0 12s <- sidecar preparat, app encara a la startupProbe
# Desplegar una versió nova = canviar la imatge al manifest i aplicar (o, per provar, en línia)
kubectl set image deployment/comandes comandes=registre.km0.internal/km0/comandes:1.15.0-8b2d4e1 -n km0-prod
kubectl rollout status deployment/comandes -n km0-prod
# Waiting for deployment "comandes" rollout to finish: 1 out of 3 new replicas have been updated...
# deployment "comandes" successfully rolled out
# El dissabte de 07-01 (8 % d'errors després de desplegar): tornar enrere en segons
kubectl rollout undo deployment/comandes -n km0-prod
kubectl rollout history deployment/comandes -n km0-prod
kubectl describe pod comandes-7d9f6c4b8-klmno -n km0-prod # esdeveniments: per què no arrenca, sondes fallides
kubectl logs -f deployment/comandes -c comandes -n km0-prod # (en producció, Loki; això és per a local)
kubectl scale deployment/comandes --replicas=6 -n km0-prod # manual; l'HPA ho sobreescriurà
- Estratègies de desplegament: rolling, blue/green, canary
| Estratègia | Com | Capacitat extra | Risc d'exposició | Rollback | Quan |
|---|---|---|---|---|---|
| Rolling update | Substituir Pods d'un en un (o de maxSurge en maxSurge), esperant readiness |
maxSurge |
Tot el trànsit veu la versió nova progressivament; si el bug és subtil, arriba al 100 % | rollout undo (segons, però ja ha afectat) |
Per defecte per a canvis petits amb bones proves |
| Blue/green | Dos entorns complets; el Service apunta a un; es canvia el selector de cop | 100 % (dues còpies) | Zero fins al canvi; 100 % després | Canviar el selector de nou (instantani) | Canvis grans que han de ser atòmics (esquema + codi) |
| Canary | Versió nova amb una fracció del trànsit (1 %, 10 %, 50 %); s'observen els SLI (07-01); s'avança o s'avorta | Petita | Només la fracció canary | Posar el pes a 0 | Canvis amb risc desconegut; l'habitual en serveis crítics |
Un canary amb Kubernetes pur es fa amb dos Deployments sota el mateix Service; el repartiment és proporcional al nombre de Pods (1 canary de 10 = 10 %):
# k8s/comandes-canary.yaml: Deployment paral·lel amb la versió nova
apiVersion: apps/v1
kind: Deployment
metadata: {name: comandes-canary, namespace: km0-prod}
spec:
replicas: 1 # 1 de 4 Pods amb etiqueta app=comandes => ~25 % del trànsit
selector: {matchLabels: {app: comandes, track: canary}}
template:
metadata:
labels: {app: comandes, track: canary, version: "1.15.0"} # app=comandes: el Service l'inclou
spec:
# ... idèntic al Deployment estable llevat de la imatge
containers:
- name: comandes
image: registre.km0.internal/km0/comandes:1.15.0-8b2d4e1Per a pesos fins (1 %) independents del nombre de Pods, es fan servir les capacitats del mesh o de l'ingress (Istio VirtualService amb weight: 1/99; Kong amb upstreams ponderats) i eines com Argo Rollouts o Flagger que automatitzen l'anàlisi: consulten a Prometheus la taxa d'error i el p99 de la versió canary (etiqueta version a les mètriques de 07-01) i avancen o reverteixen sols segons l'SLO. El dissabte de 07-01, amb un canary al 10 % analitzat automàticament, el 8 % d'errors hauria afectat menys de l'1 % de les comandes durant dos minuts.
- Namespaces, RBAC, service mesh i operadors
Els Namespaces separen entorns i equips al mateix clúster: km0-prod, km0-staging, observabilitat, mesh. Cadascun amb ResourceQuota (CPU i memòria totals) i LimitRange (valors per defecte per a Pods sense resources), i amb RBAC de Kubernetes (diferent del RBAC de l'aplicació de 06-01, mateix model): qui pot fer què sobre quins objectes.
# k8s/rbac-operadors.yaml: en Jordi i la Marta poden veure-ho tot i reiniciar Deployments a prod, no esborrar Secrets
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: {name: operador, namespace: km0-prod}
rules:
- apiGroups: ["", "apps"]
resources: [pods, pods/log, deployments, replicasets, services, configmaps]
verbs: [get, list, watch]
- apiGroups: ["apps"]
resources: [deployments]
verbs: [patch] # rollout restart / undo
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: {name: operadors, namespace: km0-prod}
subjects:
- {kind: Group, name: km0-operadors, apiGroup: rbac.authorization.k8s.io} # grup de l'OIDC de Keycloak (06-03)
roleRef: {kind: Role, name: operador, apiGroup: rbac.authorization.k8s.io}Els Pods també tenen identitat (ServiceAccount), i amb ella s'autentiquen a Vault (06-04) i davant de l'API si ho necessiten; el principi és el mateix de mínim privilegi.
Service mesh. A 07-04 es va veure Envoy com a sidecar amb timeouts, reintents i outlier detection; a 06-04, mTLS entre serveis amb certificats de Vault gestionats per cada servei. Un service mesh (Istio, Linkerd) instal·la aquest sidecar automàticament a cada Pod del namespace i el configura des d'objectes de Kubernetes: mTLS obligatori i amb rotació automàtica de certificats (PeerAuthentication: STRICT), polítiques d'autorització servei a servei, timeouts i reintents per ruta (VirtualService, DestinationRule amb outlierDetection), i telemetria RED uniforme per a Prometheus sense instrumentar res. És la manera d'obtenir 06-04 i 07-04 sense codi per a serveis que no són Python (o per no dependre que cada equip ho faci bé), a canvi d'1-2 ms de latència, memòria per sidecar i una altra peça per operar. La decisió arquitectònica de quan val la pena es reprèn a 08-01.
Operadors. Un operador és un controlador que coneix un sistema concret: sap fer un failover de PostgreSQL, un nodetool drain abans de reiniciar un node de Cassandra, o reassignar particions de Kafka en afegir un broker. S'instal·la al clúster i s'hi parla amb objectes propis (kind: Kafka, kind: PostgresCluster). Per a Quilòmetre Zero: Strimzi per a Kafka, K8ssandra per a Cassandra, CloudNativePG o l'operador de Zalando (Patroni) per a PostgreSQL. Amb ells, el StatefulSet de la secció 7 se substitueix per una declaració de deu línies i l'operador s'ocupa del que 07-03 va fer a mà (i dels backups a MinIO). Aquí n'hi ha prou de saber que existeixen i que són la manera recomanada de fer córrer sistemes amb estat a Kubernetes.
- GitOps i CI/CD
Amb manifestos a k8s/, l'última peça és qui els aplica i quan. GitOps és la resposta: el repositori Git és l'única font de veritat de l'estat desitjat; ningú no executa kubectl apply a mà a producció; un agent al clúster (Argo CD o Flux) observa el repositori i reconcilia contínuament el clúster amb el que hi ha a la branca main. Desplegar és fer merge d'un pull request que canvia l'etiqueta d'imatge; revertir és revertir el commit; auditar qui ha desplegat què és git log. I un canvi manual al clúster ("drift") es detecta i es desfà.
flowchart LR
DEV[Desenvolupador<br/>push a branca] --> CI
subgraph CI["CI (GitHub Actions / GitLab CI)"]
T[pytest + contractes<br/>07-06] --> B[docker build<br/>comandes:1.15.0-8b2d4e1]
B --> S[escaneig d'imatge<br/>i signatura cosign]
S --> PUSH[push al registre]
end
PUSH --> PR[PR automàtic al repo de desplegament:<br/>k8s/comandes-deployment.yaml<br/>image: ...:1.15.0-8b2d4e1]
PR --> REV[Revisió i merge]
REV --> ARGO[Argo CD detecta el canvi<br/>i sincronitza km0-prod]
ARGO --> ROLL[Argo Rollouts: canary 10 %<br/>anàlisi amb Prometheus]
ROLL -- SLO OK --> FULL[100 %]
ROLL -- error rate > SLO --> ABORT[rollback automàtic<br/>+ alerta a #km0-operacions]
# .github/workflows/comandes.yml (fragment)
name: comandes
on:
push:
paths: ["serveis/comandes/**", "serveis/comu/**", "contractes/**"]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install -r serveis/comandes/requirements.lock -r requirements-test.txt
- run: pytest tests/unit tests/integracio -q # Testcontainers aixeca PostgreSQL i Kafka (07-06)
- run: python tests/contracte/verificar.py comandes # contractes consumidor-productor (07-06)
build:
needs: test
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.meta.outputs.tag }}
steps:
- uses: actions/checkout@v4
- id: meta
run: echo "tag=$(cat serveis/comandes/VERSION)-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- run: docker build -f serveis/comandes/Dockerfile -t registre.km0.internal/km0/comandes:${{ steps.meta.outputs.tag }} .
- run: trivy image --exit-code 1 --severity CRITICAL registre.km0.internal/km0/comandes:${{ steps.meta.outputs.tag }}
- run: docker push registre.km0.internal/km0/comandes:${{ steps.meta.outputs.tag }}
- run: cosign sign --key env://COSIGN_KEY registre.km0.internal/km0/comandes:${{ steps.meta.outputs.tag }}
promoure:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: {repository: km0/desplegament, token: "${{ secrets.DESPLEGAMENT_TOKEN }}"}
- run: |
sed -i "s|km0/comandes:.*|km0/comandes:${{ needs.build.outputs.tag }}|" k8s/comandes-deployment.yaml
git commit -am "comandes ${{ needs.build.outputs.tag }}" && git push origin HEAD:refs/heads/comandes-${{ needs.build.outputs.tag }}
gh pr create --fill --base main # el merge l'aprova una persona (o s'automatitza a staging)Dos repositoris: el del codi (que produeix imatges) i el del desplegament (k8s/, que Argo CD observa). Així, "quina versió hi ha a producció" és una línia en un YAML amb historial de Git, i el pipeline pot promoure a staging automàticament i a prod amb una aprovació. La signatura d'imatges (cosign) i una política d'admissió que només permet imatges signades tanquen el cercle amb 06-05: res no corre a producció que no hagi passat pel pipeline.
- docker-compose davant de Kubernetes
| Aspecte | docker-compose | Kubernetes |
|---|---|---|
| Abast | Un node | Un clúster de N nodes |
| Estat desitjat | S'aplica un cop (up) |
Reconciliat contínuament |
| Autoreparació | restart: always (reinici local) |
Reinici, reprogramació en un altre node, substitució |
| Escalat | --scale manual, un node |
HPA per mètriques, entre nodes |
| Desplegament | Aturar i arrencar (tall) | Rolling, canary, blue/green sense tall |
| Descobriment | DNS de compose (per nom de servei) | Service + DNS + balanceig per readiness |
| Configuració/secrets | .env, fitxers, secrets: |
ConfigMap, Secret, RBAC, Vault |
| Corba i cost operatiu | Minuts; gairebé nul | Setmanes; una plataforma per operar (o pagar-la gestionada, 08-03) |
| Quan | Desenvolupament local; demos; el laboratori d'aquest curs; producció petita en un sol node amb tolerància a caigudes | Producció amb més d'un node, escalat, desplegaments freqüents, diversos equips |
La regla pràctica: compose per desenvolupar (el docker-compose.yml de km0/ continua sent la manera d'aixecar la plataforma al portàtil), Kubernetes per a producció a partir del moment en què es necessita més d'un node o desplegar sense tall. Saltar a Kubernetes amb un equip de dues persones i un servei és pagar la complexitat sense cobrar-ne el benefici; quedar-se a compose amb sis serveis, tres zones i desplegaments diaris és el dels dimarts i dijous amb un altre nom.
Errors Comuns i Consells
- Tasques d'Ansible que no són idempotents. Un
command:sensecreates:que formata un broker a cada execució. Fes servir mòduls declaratius; per acommand/shell, semprecreates:/removes:ochanged_when:; prova amb--check --diff. - Reiniciar tots els nodes amb estat alhora.
serial: 1i una espera de salut entre nodes; per a Kafka i Cassandra, és la diferència entre manteniment i incident. image: comandes:latest. Ningú no sap què corre a producció i unrollout undopot "tornar" a la mateixa imatge. Versió + hash de commit, sempre.- Deployments sense
resources. Senserequestsl'scheduler col·loca a cegues i l'HPA per CPU no funciona; senselimitsun Pod amb una fuita de memòria s'emporta el node. Tots dos, mesurats amb les mètriques de 07-01. - Liveness que comprova dependències. Ja a 07-03: reinicis en cascada. Liveness mínima, readiness amb dependències, startup generosa.
maxUnavailablealt "per desplegar ràpid". Redueix la capacitat durant el desplegament, just quan la versió nova pot ser més lenta.maxUnavailable: 0,maxSurge: 1.kubectl applya mà a producció. Es perd l'historial i apareix drift. GitOps: tot passa per un PR; Argo CD/Flux apliquen.- Reduir rèpliques tan ràpid com es pugen. L'HPA oscil·la i cada baixada talla capacitat en un pic que torna. Finestra d'estabilització llarga a
scaleDown. - Secrets en ConfigMaps o al repositori. Un
Secretde Kubernetes és només base64; el que és sensible de debò ho lliura Vault en temps d'execució (06-04); a Git, mai. - Kubernetes perquè sí. Un clúster per a un servei amb dues persones és una càrrega sense benefici. Compose fins que les raons (nodes, escalat, desplegaments sense tall) siguin reals.
Exercicis
Exercici 1. En Jordi executa ansible-playbook -i inventari.ini kafka.yml per segona vegada sense haver canviat res, i veu changed=2 a kafka-2: les tasques "Certificat i clau del broker des de Vault" i "Escriure el keystore del broker" apareixen com a canviades, i Kafka s'ha reiniciat a kafka-2. (a) Què està passant i per què no va passar a kafka-1 ni a kafka-3? Pista: mira changed_when i el que fa copy quan el contingut difereix. (b) És un problema d'idempotència del rol o del disseny de certificats de 06-04? Proposa un canvi al rol que eviti reiniciar el broker a cada execució sense deixar de renovar el certificat quan toqui. (c) Què hauria passat sense serial: 1 i amb els tres brokers en la mateixa situació?
Exercici 2. Durant la Setmana de la Verema, comandes puja a 12 rèpliques (el màxim de l'HPA) i continua amb p99 de 700 ms. La Marta mira: la CPU dels Pods és al 35 %, el lag de comandes-saga és baix, km0_bulkhead_en_us{dependencia="inventari"} és a 24 a tots els Pods i km0_circuit_estat a 0. (a) Per què l'HPA no ajuda i què indica el bulkhead ple amb CPU baixa? (b) Què caldria escalar, i quin objecte de Kubernetes governa aquest component? Hi serveix un HPA? (c) Dissenya una mètrica d'HPA per a comandes que reflecteixi millor la saturació real que la CPU, fent servir alguna cosa de 07-01.
Exercici 3. Es desplegarà comandes 1.15.0, que canvia el format de l'esdeveniment comanda.confirmada a comandes.esdeveniments afegint-hi un camp obligatori que analitica 2.3 encara no entén. (a) Per què un rolling update de comandes és perillós aquí encara que les sondes estiguin bé, i quina estratègia de la secció 8 ho mitiga només en part? (b) Descriu un pla de desplegament en passos (amb les ordres o els PR de GitOps que corresponguin) que no trenqui analitica en cap moment, recolzant-te en la compatibilitat d'esquemes de 02-05. (c) Quina comprovació automàtica del pipeline de CI hauria bloquejat el PR abans d'arribar a aquesta situació? (Es desenvolupa a 07-06; n'hi ha prou d'anomenar-la i dir a quin job aniria.)
Solucions
Exercici 1.
(a) La tasca de Vault genera un certificat nou a cada execució (amb changed_when: false no es marca com a canvi, però el contingut registrat a cert és diferent cada vegada); la tasca copy compara el contingut nou amb el fitxer existent, veu que difereix (un altre certificat, una altra clau) i l'escriu: changed=true i notify: reiniciar kafka. A kafka-1 i kafka-3 no va passar perquè... sí que va passar, o hauria passat: si no es va veure és perquè el playbook amb serial: 1 es va interrompre o perquè en aquests nodes l'execució anterior va fallar abans d'escriure el keystore; en un rol així, tots els brokers es reiniciarien a cada execució. (b) És un defecte d'idempotència del rol, no del disseny de certificats: els certificats de curta durada de 06-04 són correctes, però el rol n'ha de generar un només quan l'actual és a punt de caducar: afegir una tasca prèvia que llegeixi la data de caducitat del certificat existent (community.crypto.x509_certificate_info) i executar la generació i la còpia amb when: cert_actual.expired or (cert_actual.not_after | to_datetime - now()) < 7 days; o, millor, treure la renovació del playbook i delegar-la a l'agent de Vault a cada node (Vault Agent amb plantilles, que renova i recarrega sense reiniciar el broker mitjançant kafka-configs dinàmic). (c) Els tres brokers reiniciant-se alhora deixen sense ISR totes les particions: amb min.insync.replicas=2 i acks=all, comandes.esdeveniments rebutja escriptures (NotEnoughReplicas) durant el reinici, l'outbox de comandes s'acumula i KafkaLagRepartiment i les alertes de 07-01 salten; sense unclean.leader.election.enable=false fins i tot hi podria haver pèrdua de missatges. serial: 1 amb wait_for converteix això en tres reinicis successius sense pèrdua de disponibilitat.
Exercici 2.
(a) L'HPA escala comandes per CPU, i comandes no està limitat per CPU: està esperant. km0_bulkhead_en_us a 24 (el màxim) amb CPU al 35 % i circuit tancat vol dir que totes les crides a inventari tenen èxit però triguen: els 24 permisos estan ocupats per peticions que esperen inventari, i les següents es rebutgen (BulkheadPle, fallback a "pendent de confirmar") o esperen un fil. Afegir rèpliques de comandes multiplica els bulkheads (12 × 24 = 288 crides concurrents) i empitjora la càrrega sobre inventari. (b) Cal escalar inventari (o la seva base de dades): inventari és un Deployment (sense estat, la base és a part) i sí que admet HPA, amb una mètrica de saturació pròpia (peticions en vol o p99 de ReservarEstoc); si el coll d'ampolla és km0_inventari (contenció de bloqueigs sobre vi-crianca, com a la traça de 07-02), afegir rèpliques d'inventari no ajuda: el StatefulSet/operador de PostgreSQL no s'escala horitzontalment per a escriptures, i la solució és de disseny (04-01: particionar l'estoc per mercat, o reserves per lots). (c) Una mètrica de Prometheus via prometheus-adapter: km0_peticions_en_vol{servei="comandes"} de mitjana per Pod amb objectiu, p. ex., 20 (amb 32 fils, 20 en vol de mitjana indica cua incipient); o directament la latència: histogram_quantile(0.99, ...) de POST /comandes amb objectiu 0,4 s. La primera és millor per a l'HPA perquè respon de manera gairebé lineal al nombre de rèpliques; la segona serveix més com a alerta. En tots dos casos, amb scaleDown lent.
Exercici 3.
(a) El rolling update substitueix Pods de comandes sense tall, però tan bon punt el primer Pod nou publica una comanda.confirmada amb el format nou, analitica 2.3 falla en deserialitzar-la: les sondes de comandes són perfectes; el dany és en un altre servei, a través de Kafka, i es manifesta com a missatges a la DLQ d'analitica (02-05) i lag. Un canary redueix la fracció d'esdeveniments amb format nou, però un sol missatge ja trenca el consumidor (o l'envia a la DLQ): mitiga el volum, no el problema. (b) Compatibilitat cap endavant i cap enrere: 1) PR a analitica (2.4) que tolera el camp nou (l'ignora si no l'entén, el fa servir si hi és) i desplegar-lo primer (kubectl rollout status deployment/analitica), amb l'esquema registrat com a compatible; 2) desplegar comandes 1.15.0 amb canary al 10 % (PR al repo de desplegament amb comandes-canary, o Argo Rollouts), observar la DLQ i el lag d'analitica i la taxa d'error de comandes; 3) promoure al 100 %; 4) només quan cap productor no emet el format vell (i els missatges vells han sortit de la retenció del tòpic, 7 dies), un PR a analitica que fa obligatori el camp. A més, fer el camp opcional a l'esquema en lloc d'obligatori evita el pas 4. En cap moment no hi ha un consumidor que no entengui el que hi ha al tòpic. (c) Una prova de contracte entre el productor comandes i el consumidor analitica sobre l'esquema de comanda.confirmada (verificació de compatibilitat de l'esquema contra els registrats, i del contracte consumidor-productor): aniria al job test del pipeline de comandes (python tests/contracte/verificar.py comandes), i hauria fallat en detectar un camp obligatori nou que un consumidor registrat no accepta. Es desenvolupa a 07-06.
Conclusió
Automatitzar és treure la persona del camí crític i deixar-hi al seu lloc una descripció que es pot revisar, provar i aplicar tantes vegades com calgui. Per a les màquines amb estat, Ansible: un inventari, playbooks i rols amb tasques idempotents (creates:, template amb handlers, --check --diff), i que recorren els brokers de Kafka d'un en un respectant les ISR. Per als serveis, imatges immutables etiquetades per versió i commit, construïdes un cop i desplegades a tots els entorns amb configuració injectada. I a sobre, Kubernetes: un magatzem de consens amb controladors que reconcilien sense parar l'estat desitjat amb el real; Deployments amb sondes, resources, repartiment per zones i apagada ordenada; Services que balancegen només cap a Pods preparats; HPA per CPU i per lag de Kafka; StatefulSets o operadors per a Cassandra, Kafka i PostgreSQL; Ingress cap a Kong; rolling, blue/green i canary amb anàlisi automàtica contra els SLO; namespaces i RBAC; el service mesh que dona mTLS i polítiques de resiliència sense codi; i GitOps amb un pipeline en què desplegar és fer merge i revertir és revertir un commit. Compose queda per al portàtil.
Però tot aquest engranatge descansa sobre una suposició que encara no s'ha examinat: que la versió 1.15.0 que el pipeline construeix, signa i desplega funciona. El pipeline executa pytest i verifica contractes, i el canary observa els SLO, però quina prova demostra que la saga compensa bé quan inventari cau a mig camí? Com se sap que el circuit breaker de 07-04 s'obre a temps, o que Patroni commuta en menys de 30 segons, abans que passi a producció un dissabte? Provar un sistema distribuït és més difícil que provar un programa: el no-determinisme, les fallades parcials i el temps fan que les proves unitàries no siguin suficients. L'última lliçó del mòdul tracta les proves en sistemes distribuïts (integració amb dependències reals, contractes, càrrega simulant la Setmana de la Verema) i l'enginyeria del caos: provocar les fallades de 07-03 i 07-04 a propòsit, amb hipòtesi i radi d'explosió controlat, per comprovar que la plataforma respon com es va dissenyar.
Curs d'Arquitectures Distribuïdes
Mòdul 1: Introducció als Sistemes Distribuïts
- Conceptes Bàsics de Sistemes Distribuïts
- Models de Sistemes Distribuïts
- Avantatges i Desafiaments dels Sistemes Distribuïts
- Les Fal·làcies de la Computació Distribuïda
- Temps, Rellotges i Ordenació d'Esdeveniments
- Del Monòlit a la Plataforma Distribuïda: el Cas Quilòmetre Zero
Mòdul 2: Comunicació en Sistemes Distribuïts
- Protocols de Comunicació
- RPC i RMI
- gRPC i Serialització de Dades
- Missatgeria i Cues de Missatges
- Patrons de Comunicació Asíncrona
Mòdul 3: Consistència i Replicació
- Models de Consistència
- El Teorema CAP i PACELC
- Algorismes de Consens
- Replicació de Dades
- Transaccions Distribuïdes i Sagues
Mòdul 4: Emmagatzematge Distribuït
- Particionament de Dades i Hashing Consistent
- Sistemes de Fitxers Distribuïts
- Emmagatzematge d'Objectes
- Bases de Dades Distribuïdes
- Memòries Cau Distribuïdes
Mòdul 5: Computació Distribuïda
- Models de Computació Distribuïda
- MapReduce i Hadoop
- Spark i Computació en Memòria
- Processament de Fluxos de Dades
- Planificació de Treballs i Pipelines de Dades
Mòdul 6: Seguretat en Sistemes Distribuïts
- Autenticació i Autorització
- Xifratge i Protecció de Dades
- Gestió d'Identitats
- Seguretat entre Serveis: mTLS i Gestió de Secrets
- Passarel·les d'API, Limitació de Taxa i Auditoria
Mòdul 7: Monitoratge i Manteniment
- Monitoratge de Sistemes Distribuïts
- Logs Centralitzats i Traçabilitat Distribuïda
- Gestió de Fallades i Recuperació
- Patrons de Resiliència: Timeouts, Reintents i Circuit Breaker
- Automatització i Orquestració
- Proves en Sistemes Distribuïts i Enginyeria del Caos
