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

  1. Per què automatitzar
  2. Infraestructura com a codi: configuració davant d'aprovisionament
  3. Ansible: inventari, playbooks, rols i idempotència
  4. Immutabilitat: imatges, registre i etiquetes per commit
  5. Kubernetes: per què un orquestrador i com està fet
  6. Objectes essencials
  7. Manifestos de Quilòmetre Zero: k8s/
  8. Estratègies de desplegament: rolling, blue/green, canary
  9. Namespaces, RBAC, service mesh i operadors
  10. GitOps i CI/CD
  11. docker-compose davant de Kubernetes
  12. Errors comuns i consells
  13. Exercicis i solucions
  14. Conclusió

  1. 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-2 es va crear copiant inventari-1 i 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 DELETE de 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.

  1. 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.

  1. 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/km0ops

El 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:
    - kafka

I 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=required

La 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.

  1. 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".

  1. 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.

  1. 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

  1. Manifestos de Quilòmetre Zero: 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 curs

Cada 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 entrada

Kong (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à

  1. 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-8b2d4e1

Per 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.

  1. 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.

  1. 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.

  1. 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: sense creates: que formata un broker a cada execució. Fes servir mòduls declaratius; per a command/shell, sempre creates:/removes: o changed_when:; prova amb --check --diff.
  • Reiniciar tots els nodes amb estat alhora. serial: 1 i 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 un rollout undo pot "tornar" a la mateixa imatge. Versió + hash de commit, sempre.
  • Deployments sense resources. Sense requests l'scheduler col·loca a cegues i l'HPA per CPU no funciona; sense limits un 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.
  • maxUnavailable alt "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 apply a 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 Secret de 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

Mòdul 2: Comunicació en Sistemes Distribuïts

Mòdul 3: Consistència i Replicació

Mòdul 4: Emmagatzematge Distribuït

Mòdul 5: Computació Distribuïda

Mòdul 6: Seguretat en Sistemes Distribuïts

Mòdul 7: Monitoratge i Manteniment

Mòdul 8: Casos d'Estudi i Aplicacions

© Copyright 2026. Tots els drets reservats