A 08-02 el sistema de TechCorp va quedar complet com a codi: sis serveis, un gateway, una llibreria i les seves proves. Aquesta lliçó el porta a un clúster i l'opera. La primera meitat respon a "com aixeco tot això des de zero?": el repositori techcorp/plataforma complet, el compose.yaml local amb els sis serveis i tota la infraestructura, l'ordre d'arrencada en un clúster nou, l'script que ho aplica, els Job de migració i la prova de fum amb un token de Keycloak. La segona meitat respon a "i què faig cada dia?": un desplegament normal de servei-comandes de punta a punta, la campanya de Black Friday, un incident en una DLQ pas a pas, la rotació d'un secret, l'actualització de Node i una evolució de contracte aplicada a tot el sistema, més una taula d'operacions freqüents i el cost mensual aproximat.

No reexpliquem cap YAML de 05-02 ni cap codi: quan aparegui un Deployment, un ServiceMonitor o un ExternalSecret, remetrem a la lliçó on es va escriure i mostrarem només el que canvia en passar d'un servei a sis.

Contingut

  1. El repositori techcorp/plataforma complet
  2. compose.yaml: el sistema sencer en un portàtil
  3. Ordre d'arrencada en un clúster nou
  4. scripts/desplegar-tot.sh i els Job de migració
  5. Verificació i prova de fum
  6. Operació: un desplegament normal de servei-comandes
  7. Operació: la campanya de Black Friday
  8. Operació: un incident a pagaments.estoc.dlq, pas a pas
  9. Operació: rotar un secret, actualitzar Node i evolucionar un contracte
  10. Runbook ràpid d'operacions freqüents
  11. Cost mensual aproximat i com reduir-lo

  1. El repositori techcorp/plataforma complet

És el repositori de l'equip de Plataforma i la font de veritat de producció (Argo CD el llegeix, 05-03 §6). Tot el que hi hem anat deixant al llarg del curs, ordenat:

techcorp/plataforma/
├── local/
│   ├── compose.yaml                     # 05-01, ampliat a l'apartat 2
│   ├── kind.yaml                        # 05-02 §4
│   └── keycloak/realm-techcorp.json     # el realm de 07-01 §4, importat en arrencar
├── k8s/
│   ├── namespace.yaml                   # techcorp (labels PSA restricted, 07-04 §3)
│   ├── servei-cataleg/  servei-comandes/  servei-inventari/  servei-pagaments/
│   ├── servei-notificacions/  servei-clients/  gateway/  bff-mobil/  consumidor-analitica/
│   │   ├── base/         # kustomization, configmap, deployment, service, job-migracions, serviceaccount, servicemonitor, pdb,
│   │   │                 # externalsecret(s), hpa | scaledobject (on escau), networkpolicy (07-04)
│   │   └── overlays/dev|staging|prod/kustomization.yaml         # rèpliques, newTag, pedaços de ConfigMap
│   ├── xarxa/                           # 00-deny-all, 01-permetre-dns, gateway, servei-*.yaml (07-04 §6)
│   ├── infra/                           # values de Helm i manifestos de tercers (05-02 §11-12)
│   │   ├── rabbitmq/values-{dev,prod}.yaml, definitions.json (vhost techcorp, usuaris per servei, cues, 07-02 §8)
│   │   ├── postgres/values-*.yaml, init/01-esquemes-svc.sql     # un usuari svc_* per servei (02-04)
│   │   ├── mongo/  redis/  keycloak/ (realm import)  ingress-nginx/  cert-manager/ (ClusterIssuer letsencrypt-prod, 07-02)
│   │   ├── external-secrets/ (ClusterSecretStore techcorp-vault, 07-04)  keda/
│   │   └── observabilitat/  kube-prometheus-stack, loki, promtail, otel-collector, jaeger (06-01, 06-02)
│   ├── slos/prometheusrule-slos-techcorp.yaml, alertmanager-config.yaml     # 06-05
│   └── argocd/  app-of-apps.yaml + una Application per servei i entorn     # 05-03 §6
├── observabilitat/dashboards/red-per-servei.json, saga-de-comandes.json, cues.json      # 06-01 §10
├── runbooks/  comandes/, cataleg/, comu/dlq.md, comu/circuit-obert.md, plataforma/…     # 06-05 §8
├── proves/e2e/*.e2e.test.js, proves/carrega/cataleg.js (k6)                 # 04-05, 06-04
├── scripts/desplegar-tot.sh, fum.sh, token-keycloak.sh                       # apartats 4-5
├── .github/workflows/servei-node-ci.yml (@v1, @v2)                           # 05-03 §11
├── SEGURETAT.md                                                              # 07-04 §10
└── README.md (mapa d'aquest arbre i "com aixecar-ho en local en 10 minuts")

Dues decisions que sostenen l'arbre: una base per servei, un overlay per entorn (05-02 §12: la base d'Inventari és la de Comandes amb noms, port 3006 i el seu ScaledObject en lloc d'HPA), i els tercers per Helm amb values versionats aquí, mai instal·lats a mà. Quan l'equip de Pagaments necessita un Secret nou, obre un PR contra aquest repositori; ningú no executa kubectl create secret a producció.

  1. compose.yaml: el sistema sencer en un portàtil

El compose.yaml de 05-01 §8 tenia PostgreSQL, MongoDB, RabbitMQ, la llavor, les migracions de Comandes, Catàleg, Comandes, l'stub de Clients i el gateway. Les addicions per al sistema complet, resumides (els serveis nous segueixen exactament el patró de servei-comandes a 05-01: image + build, variables de la taula de config.js de 08-02, depends_on amb condicions, stop_grace_period: 15s):

# techcorp/plataforma/local/compose.yaml — NOMÉS el que s'afegeix respecte de 05-01 §8
services:
  postgres:                                # ara crea les cinc bases i els usuaris svc_* en iniciar-se
    volumes: [pg-dades:/var/lib/postgresql/data, ../k8s/infra/postgres/init:/docker-entrypoint-initdb.d:ro]   # 01-esquemes-svc.sql
  redis: { image: redis:7-alpine, healthcheck: { test: ["CMD", "redis-cli", "ping"] } }
  keycloak:
    image: quay.io/keycloak/keycloak:25.0
    command: ["start-dev", "--import-realm"]           # realm techcorp de 07-01: clients botiga-web, bff-mobil, servei-*; usuària ana.ruiz
    volumes: [./keycloak:/opt/keycloak/data/import:ro]
    ports: ["8180:8080"]
    environment: { KEYCLOAK_ADMIN: admin, KEYCLOAK_ADMIN_PASSWORD: admin }        # només local
  jaeger: { image: jaegertracing/all-in-one:1.60, ports: ["16686:16686"] }        # UI; rep OTLP al 4317 (06-02)
  prometheus: { image: prom/prometheus:v2.53.0, volumes: [./prometheus.yml:/etc/prometheus/prometheus.yml:ro], ports: ["9090:9090"] }
  grafana: { image: grafana/grafana:11.1.0, ports: ["3000:3000"], volumes: [../observabilitat/dashboards:/var/lib/grafana/dashboards:ro, ./grafana-provisioning:/etc/grafana/provisioning:ro] }
  loki: { image: grafana/loki:3.1.0, ports: ["3100:3100"] }
  # --- migracions d'un sol ús, una per servei amb BD (mateixa imatge, command diferent; 05-01 §8) ---
  inventari-migracions: { image: ghcr.io/techcorp/servei-inventari:local, build: { context: ../../servei-inventari, secrets: [npmrc] }, command: ["node","scripts/migrar.js"], environment: { INVENTARI_DB_URL: postgres://svc_inventari:dev-inventari@postgres:5432/inventari }, depends_on: { postgres: { condition: service_healthy } }, restart: "no" }
  pagaments-migracions:     { image: ghcr.io/techcorp/servei-pagaments:local, build: { context: ../../servei-pagaments, secrets: [npmrc] }, command: ["node","scripts/migrar.js"], environment: { PAGAMENTS_DB_URL: postgres://svc_pagaments:dev-pagaments@postgres:5432/pagaments }, depends_on: { postgres: { condition: service_healthy } }, restart: "no" }
  notificacions-migracions: { image: ghcr.io/techcorp/servei-notificacions:local, build: { context: ../../servei-notificacions, secrets: [npmrc] }, command: ["node","scripts/migrar.js"], environment: { NOTIFICACIONS_DB_URL: postgres://svc_notificacions:dev-notif@postgres:5432/notificacions }, depends_on: { postgres: { condition: service_healthy } }, restart: "no" }
  clients-migracions:       { image: ghcr.io/techcorp/servei-clients:local, build: { context: ../../servei-clients, secrets: [npmrc] }, command: ["node","scripts/migrar.js"], environment: { CLIENTS_DB_URL: postgres://svc_clients:dev-clients@postgres:5432/clients }, depends_on: { postgres: { condition: service_healthy } }, restart: "no" }
  # --- serveis nous (patró de servei-comandes a 05-01; variables de 08-02) ---
  servei-inventari:        # PORT 3006, INVENTARI_DB_URL, RABBITMQ_URL, RESERVA_TTL_S 900, COMANDES_URL, OTEL_*; depends_on postgres, rabbitmq, inventari-migracions
  servei-pagaments:        # PORT 3003, PAGAMENTS_DB_URL, RABBITMQ_URL, PASSARELLA_URL http://passarella-falsa:4000, PASSARELLA_API_KEY dev, PAGAMENT_NOU_PROVEIDOR "false"
  servei-notificacions:    # PORT 3005, NOTIFICACIONS_DB_URL, RABBITMQ_URL, CORREU_PROVEIDOR consola
  servei-clients:          # SUBSTITUEIX l'stub de 04-04: build ../../servei-clients; CLIENTS_DB_URL, RABBITMQ_URL, KEYCLOAK_URL http://keycloak:8080, KEYCLOAK_REALM techcorp, KEYCLOAK_ADMIN_CLIENT_SECRET dev
  passarella-falsa:        # servei-pagaments/proves/dobles/passarellaFalsa.js: 200 tret de tok_rebutjar (402) i tok_503 (503); Idempotency-Key respectada
  gateway:                 # + KEYCLOAK_ISSUER http://keycloak:8080/realms/techcorp, KEYCLOAK_AUDIENCE techcorp-api; sense MONOLIT_URL (retirat a 08-01)
  # tots els serveis: OTEL_EXPORTER_OTLP_ENDPOINT http://jaeger:4317, OTEL_TRACES_SAMPLER_ARG "1.0"

Amb això, docker compose up -d --wait aixeca una vintena de contenidors (disset en execució més les tasques d'un sol ús) en uns 90 segons en un portàtil normal, i les quatre E2E de 08-02 §9 passen contra http://localhost:8080 amb un token obtingut d'http://localhost:8180. És l'entorn amb què l'alumne pot reproduir tot el curs (08-04).

  1. Ordre d'arrencada en un clúster nou

Sigui un clúster kind (plataforma/local/kind.yaml, 05-02 §4) o un de gestionat, l'ordre importa: cada capa depèn de l'anterior, i diverses peces (ESO, cert-manager, KEDA, l'operador de Prometheus) instal·len CRDs que els manifestos posteriors fan servir.

Pas Què Com Depèn de Lliçó
1 Namespaces techcorp (PSA restricted), observabilitat, infra, argocd kubectl apply -f k8s/namespace.yaml 05-02, 07-04
2 ingress-nginx, cert-manager (+ ClusterIssuer letsencrypt-prod), External Secrets Operator (+ ClusterSecretStore techcorp-vault), KEDA, kube-prometheus-stack helm upgrade --install amb k8s/infra/*/values-<env>.yaml 1 (i CRDs entre si: ESO abans que cap ExternalSecret) 05-02 §11-12, 06-04, 07-02, 07-04
3 RabbitMQ (vhost techcorp, usuaris comandes, inventari, pagaments, notificacions, clients, analitica; TLS 5671; definitions.json amb permisos per cua), PostgreSQL (init/01-esquemes-svc.sql: cinc BD, cinc usuaris svc_*), MongoDB, Redis Helm (dev) / gestionats (prod), credencials al gestor de secrets → ExternalSecret 2 (ESO) 02-04, 05-02, 07-02
4 Keycloak amb el realm techcorp importat (clients, rols, scopes, mapper clientId) Helm + ConfigMap del realm 3 (PostgreSQL de Keycloak) 07-01
5 Loki + Promtail, otel-collector, Jaeger; PrometheusRule slos-techcorp; Alertmanager per equips; dashboards kubectl apply -k k8s/infra/observabilitat 2 06-01, 06-02, 06-05
6 NetworkPolicies deny-all + DNS + per servei kubectl apply -f k8s/xarxa/ 1 07-04 §6
7 ExternalSecret de cada servei (*-db, *-rabbitmq, pagaments-passarella, notificacions-correu, clients-keycloak, gateway-keycloak) Van a la base/ de cada servei; se sincronitzen abans que el Deployment 2, 3 07-04 §4
8 Serveis en l'ordre de la saga: servei-clients i servei-cataleg (dependències síncrones de Comandes), servei-inventari, servei-pagaments, servei-notificacions, servei-comandes, consumidor-analitica, bff-mobil, gateway kubectl apply -k k8s/<servei>/overlays/<env> (o Argo CD) 3-7 05-02
9 Ingress api.techcorp.example → gateway, certificat api-techcorp-tls A la base del gateway 2, 8 05-02 §9, 07-02 §3

Sobre el pas 8: l'ordre "per la saga" no és estrictament necessari —els serveis arrenquen encara que falti un col·laborador (/health/ready no comprova Catàleg ni Clients, 04-04 §9) i les cues duradores guarden missatges— però desplegar primer els consumidors i al final Comandes i el gateway evita que les primeres comandes de la prova de fum esperin que algú declari inventari.comandes.

  1. scripts/desplegar-tot.sh i els Job de migració

A dev i en un clúster de proves s'aplica tot amb un script; a producció els passos 8-9 els fa Argo CD (app-of-apps.yaml), i l'script només es fa servir per a la infraestructura dels passos 1-7 (o Terraform/Helmfile, si Plataforma ho prefereix).

#!/usr/bin/env bash
# techcorp/plataforma/scripts/desplegar-tot.sh <entorn: dev|staging>  — idempotent: es pot tornar a llançar sencer
set -euo pipefail
ENV="${1:?entorn}"; NS=techcorp
cd "$(dirname "$0")/.."

echo "== 1. namespaces";        kubectl apply -f k8s/namespace.yaml
echo "== 2. operadors i CRDs"                                                 # helm upgrade --install és idempotent
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx >/dev/null; helm repo add jetstack https://charts.jetstack.io >/dev/null
helm repo add external-secrets https://charts.external-secrets.io >/dev/null; helm repo add kedacore https://kedacore.github.io/charts >/dev/null
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts >/dev/null; helm repo add bitnami https://charts.bitnami.com/bitnami >/dev/null; helm repo update >/dev/null
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx -n infra -f k8s/infra/ingress-nginx/values-$ENV.yaml --wait
helm upgrade --install cert-manager jetstack/cert-manager -n infra --set crds.enabled=true --wait && kubectl apply -f k8s/infra/cert-manager/
helm upgrade --install external-secrets external-secrets/external-secrets -n infra --wait && kubectl apply -f k8s/infra/external-secrets/   # ClusterSecretStore
helm upgrade --install keda kedacore/keda -n infra --wait
helm upgrade --install kube-prometheus-stack prometheus-community/kube-prometheus-stack -n observabilitat -f k8s/infra/observabilitat/prometheus-values-$ENV.yaml --wait
echo "== 3. dades i missatgeria"                                               # a prod: gestionats; l'script només aplica els ExternalSecret
helm upgrade --install rabbitmq bitnami/rabbitmq -n infra -f k8s/infra/rabbitmq/values-$ENV.yaml --set-file loadDefinition.definitions=k8s/infra/rabbitmq/definitions.json --wait
helm upgrade --install postgres bitnami/postgresql -n infra -f k8s/infra/postgres/values-$ENV.yaml --set-file primary.initdb.scripts."01-esquemes-svc\.sql"=k8s/infra/postgres/init/01-esquemes-svc.sql --wait
helm upgrade --install mongo bitnami/mongodb -n infra -f k8s/infra/mongo/values-$ENV.yaml --wait
helm upgrade --install redis bitnami/redis -n infra -f k8s/infra/redis/values-$ENV.yaml --wait
echo "== 4. identitat";         helm upgrade --install keycloak bitnami/keycloak -n infra -f k8s/infra/keycloak/values-$ENV.yaml --wait   # importa realm-techcorp.json
echo "== 5. observabilitat";    kubectl apply -k k8s/infra/observabilitat/ && kubectl apply -f k8s/slos/
echo "== 6. xarxa";             kubectl apply -f k8s/xarxa/
echo "== 7-8. serveis en l'ordre de la saga"
for s in servei-clients servei-cataleg servei-inventari servei-pagaments servei-notificacions servei-comandes consumidor-analitica bff-mobil gateway; do
  echo "   -> $s"
  kubectl -n $NS delete job "$s-migracions" --ignore-not-found                 # un Job és immutable: s'esborra i es recrea (05-03 §5)
  kubectl apply -k "k8s/$s/overlays/$ENV"                                       # ExternalSecret, ConfigMap, Job, Deployment, Service, PDB, HPA/ScaledObject, ServiceMonitor, NetworkPolicy
  if kubectl -n $NS get job "$s-migracions" >/dev/null 2>&1; then               # només els serveis amb BD tenen Job
    kubectl -n $NS wait --for=condition=complete "job/$s-migracions" --timeout=180s
  fi
  kubectl -n $NS rollout status "deploy/$s" --timeout=180s                     # no continuar fins que estigui Ready
done
echo "== 9. ingress";           kubectl -n $NS get ingress,certificate
echo "OK: $(kubectl -n $NS get pods --no-headers | grep -c Running) pods Running a $NS"

Cada servei amb base de dades porta el seu Job de migracions a la base/ (05-02 §6) amb argocd.argoproj.io/hook: PreSync per a producció (05-03 §6): servei-comandes-migracions, servei-inventari-migracions, servei-pagaments-migracions, servei-notificacions-migracions, servei-clients-migracions (Catàleg té servei-cataleg-llavor només a dev; Notificacions sí que té BD per a enviaments). Tots executen node scripts/migrar.js des de la imatge del servei i són idempotents, per això el delete + apply és segur. I tots són expand-only (05-04 §3): l'script mai no necessita "esperar que no quedi cap pod vell" per aplicar una migració.

  1. Verificació i prova de fum

kubectl -n techcorp get pods                                        # tots Running i READY 1/1 (o 2/2 amb otel sidecar, si n'hi hagués); Jobs Completed
kubectl -n techcorp get externalsecret                              # SecretSynced / READY True a tots
kubectl -n techcorp get hpa,scaledobject,pdb                        # cataleg 2/20; inventari 2/10; PDB minAvailable 1 a tots
kubectl -n techcorp get networkpolicy | wc -l                       # 11: deny-all, dns, gateway, 6 serveis, bff, analitica
for s in clients cataleg inventari pagaments notificacions comandes; do
  kubectl -n techcorp exec deploy/servei-$s -- wget -qO- http://localhost:$(kubectl -n techcorp get svc servei-$s -o jsonpath='{.spec.ports[0].port}')/health/ready
done                                                                # {"estat":"ok","comprovacions":{"postgres":"ok","rabbitmq":"ok"}} a cadascun

La prova de fum és l'E2E de 04-05 feta a mà a través de l'Ingress, amb un token de debò. scripts/token-keycloak.sh n'obté un per a la usuària de proves amb password grant (activat només al client proves-e2e del realm de dev/staging; a producció aquest client no existeix, 07-01 ex. 1):

TOKEN=$(scripts/token-keycloak.sh https://auth.techcorp.example techcorp proves-e2e ana.ruiz 'clau-de-proves')   # JWT amb clientId=c-1024, rol client
API=https://api.techcorp.example/api/v1

curl -sf "$API/productes?ids=p-501,p-777" | jq -r '.dades[].nom'                          # públic: Auriculars BT X200 / Cable USB-C 2 m
COM=$(curl -sf -X POST "$API/comandes" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H "Idempotency-Key: fum-$(date +%s)" \
      -d '{"clientId":"c-1024","linies":[{"producteId":"p-501","quantitat":1},{"producteId":"p-777","quantitat":2}],"adrecaEnviament":{"carrer":"Gran Vía 12","codiPostal":"28013","ciutat":"Madrid","pais":"ES"}}' | jq -r .id)
echo "comanda $COM"                                                                      # com-… (202)
for i in $(seq 1 15); do E=$(curl -sf "$API/comandes/$COM" -H "Authorization: Bearer $TOKEN" | jq -r .estat); echo "$i: $E"; [ "$E" = CONFIRMADA ] && break; sleep 1; done
# 1: PENDENT  2: ESTOC_RESERVAT  3: ESTOC_RESERVAT  4: CONFIRMADA   ← la saga completa amb els sis serveis reals
curl -s "$API/comandes/com-0000" -H "Authorization: Bearer $TOKEN" -o /dev/null -w '%{http_code}\n'   # 404 (no existeix: mateix codi que "aliena", 07-01)
curl -s "$API/comandes/$COM" -o /dev/null -w '%{http_code}\n'                                          # 401 sense token

I les tres comprovacions d'observabilitat, que són la raó d'haver muntat els mòduls 6 i 7: (1) a Jaeger (kubectl -n observabilitat port-forward svc/jaeger-query 16686), cercar servei=gateway, etiqueta comandaId=$COM: una traça amb els spans del gateway, Comandes, Clients, Catàleg, PostgreSQL, i —enllaçats per links des del traceparent de l'outbox (06-02 §6)— Inventari, Pagaments (amb l'span de la passarel·la) i Notificacions; (2) a Grafana, panell "Saga de comandes": comandes_creades_total +1, saga_durada_segons amb una observació de ~3 s, outbox_pendents a 0 als quatre serveis amb outbox; (3) a Loki, {namespace="techcorp"} | json | comandaId="com-…" retorna les línies dels sis serveis ordenades per temps, amb el mateix requestId a la part síncrona.

Si l'estat es queda en ESTOC_RESERVAT, la taula de l'apartat 10 diu on mirar (gairebé sempre: pagaments.estoc sense consumidor, o PASSARELLA_URL malament al ConfigMap de Pagaments).

  1. Operació: un desplegament normal de servei-comandes

El Luis fusiona un PR que afegeix la cancel·lació pel client (exercici 1 de 08-02). El que passa, amb cada peça a la seva lliçó:

flowchart LR
    PR[PR a servei-comandes] --> CI[ci.yml → servei-node-ci.yml@v1<br/>lint, unit, component, integració Testcontainers,<br/>pactes consumidor publicats]
    CI --> IMG[imatge ghcr.io/…/servei-comandes:sha-4b7e9c1<br/>Trivy, cosign, SBOM]
    IMG --> STG[cd.yml: staging<br/>Job migracions 007, rollout, E2E, record-deployment]
    STG --> CID[can-i-deploy servei-comandes sha-4b7e9c1 --to-environment prod<br/>verifiquen Catàleg, Clients, Inventari, Pagaments, Notificacions?]
    CID --> TAG[git tag v1.6.0 → mateixa imatge etiquetada 1.6.0]
    TAG --> PRP[PR a plataforma: overlays/prod newTag 1.6.0<br/>revisió humana: és Comandes]
    PRP --> ARGO[Argo CD sync<br/>PreSync: Job migracions]
    ARGO --> CAN[Ingress comandes-canary weight 10 %<br/>+ X-Canary per a l'equip]
    CAN --> OBS[30 min: burn rate ComandesErrorBudget,<br/>RED canary vs estable, saga_durada]
    OBS -->|bé| FULL[weight 100 % → Deployment estable 1.6.0, canary a 0]
    OBS -->|malament| BACK[canary-weight 0 + revert del commit]
Pas Eina Temps típic Lliçó
CI complet amb Testcontainers i publicació de pactes GitHub Actions, servei-node-ci.yml@v1 6 min 04-05, 05-03 §3, §11
Imatge, Trivy (bloqueja CRITICAL/HIGH), cosign, SBOM docker/build-push-action, aquasecurity/trivy-action, sigstore/cosign 3 min 05-01 §5, 07-04 §2, §9
Staging: kustomize edit set image, Job 007, rollout status, E2E, record-deployment cd.yml 4 min 05-03 §5
can-i-deploy a prod: els cinc consumidors/proveïdors han verificat aquesta versió Pact Broker segons 05-03 §4, 08-02 §9
Etiqueta v1.6.0 (mateixa imatge); PR de promoció a plataforma; revisió humana (Comandes i Pagaments l'exigeixen) peter-evans/create-pull-request, revisió del Luis o d'un parell 10-60 min (persona) 05-03 §7-8
Argo CD aplica overlays/prod: PreSync amb servei-comandes-migracions (007-cancellacio-client-expand.sql: columna cancellada_per, DEFAULT NULL), després el Deployment canary Argo CD 2 min 05-03 §6, 05-04 §3
Canary al 10 % durant 30 min mirant slo:comandes_error_ratio per version (etiqueta service.version de 06-02) i la latència del canary davant de l'estable Ingress NGINX canary-weight, Grafana 30 min 05-04 §5, 06-05 §5
100 %: newTag de l'estable a 1.6.0, canary-weight: 0 PR (o Argo Rollouts, quan arribi) 3 min 05-04

Total: uns 50 minuts de rellotge, dels quals uns 12 són màquina; la resta és la revisió humana i l'observació del canary, i són deliberats. Davant del dijous a la nit de 01-05 (dues hores de desplegament, quatre reversions de dotze), això passa a les 11 del matí d'un dimarts, onze vegades al dia en el conjunt de serveis, i si el burn rate puja durant el canary, canary-weight: 0 deixa el 100 % a l'estable en segons: MTTR de minuts sense que ningú hagi escrit un kubectl a mà.

  1. Operació: la campanya de Black Friday

La campanya es prepara amb una llista que Plataforma i els quatre equips repassen tres setmanes abans (la primera versió es va escriure per al Black Friday de 2025, 08-01 §3; avui és un issue amb plantilla a plataforma):

Quan Què Com Lliçó
T−3 setmanes Prova de càrrega k6 sobre staging al ×20 de navegació i ×4 de comandes; es corregeixen requests, pools i índexs proves/carrega/cataleg.js i comandes.js 06-04 §5
T−1 setmana Overlay temporal overlays/prod-bf/: Catàleg minReplicas: 6 (HPA fins a 20), Inventari minReplicaCount: 4 (KEDA fins a 10), Comandes i Pagaments 4 rèpliques fixes, gateway 4; PDB revisats; Cluster Autoscaler amb nodes de reserva PR a plataforma, Argo CD 06-04 §2-4
T−1 setmana Pressupost d'error: es comprova que tots els SLOs tenen > 50 % restant; si Comandes està per sota, es dedica la setmana a fiabilitat, no a funcionalitat Panell d'SLOs 06-05 §11
T−48 h Congelació de desplegaments tret de correccions (Argo CD continua sincronitzant, però no es fusionen PRs de promoció); guàrdia reforçada amb secundari per equip Regla d'equip, #estat-plataforma 06-05 §8
T−24 h Memòria cau de Catàleg preescalfada; TTL de Redis pujat de 30 s a 120 s per flag; rate limit del gateway ajustat per ruta ConfigMap + rollout restart de Catàleg 06-04 §6, 03-04
Dia D Sala de campanya: panells "RED per servei", "Saga de comandes", "Cues"; alertes d'SLO amb llindars normals (no es relaxen: si cremen, és real) Grafana, Alertmanager 06-01, 06-05
D+3 Es retira l'overlay prod-bf, es descongela, es revisen mètriques i cost, breu postmortem encara que no hi hagi hagut incident PR, reunió de 30 min 06-05 §10

El que es veu el dia D en un bon any (com el 2025): Catàleg entre 6 i 16 rèpliques seguint la corba de navegació; Inventari a 4-7 per longitud d'inventari.comandes; saga_durada_segons p95 en 4 s (la passarel·la també va més lenta); pressupost d'error consumit en el dia: 6 % del mensual. I el que la llista impedeix: un kubectl scale manual que Argo CD reverteixi al cap de tres minuts (05-03 §6, selfHeal: true), per això l'escalat de campanya també va per PR.

  1. Operació: un incident a pagaments.estoc.dlq, pas a pas

Un dimarts de setembre de 2026, 15:20. Diferent d'INC-2031 (missatge verinós per un bug): aquest cop la causa és externa i les eines de 06-03/06-05 ja existeixen. Segueix la cronologia com si fossis la persona de guàrdia:

  1. 15:31 — Alerta DlqAmbMissatges{cua="pagaments.estoc.dlq"} (warning: 10 min amb missatges, 06-05 §12) arriba al canal de l'equip de Pagaments; no desperta ningú (no és critical), però és horari laboral i la guàrdia la reconeix i obre #inc-2047-pagaments-dlq.
  2. 15:33 — Runbook comu/dlq: "quants, des de quan, de quin tipus?". kubectl -n infra exec rabbitmq-0 -- rabbitmqctl list_queues name messages | grep dlqpagaments.estoc.dlq 14. A la consola de RabbitMQ, els 14 són estoc.reservat amb x-intents: 5: van esgotar els reintents, no són verinosos.
  3. 15:36 — Loki: {app="servei-pagaments"} | json | nivell="error" | line_format "{{.esdevenimentId}} {{.err.codi}} {{.missatge}}"DEPENDENCIA_NO_DISPONIBLE passarella: timeout des de les 15:12; CircuitObert{dependencia="passarella"} també és en warning des de les 15:15 (06-03 §4). Jaeger: l'span POST passarella/cobraments amb error=true i 5.000 ms exactes: la passarel·la no respon, no rebutja.
  4. 15:38 — Impacte: panell "Saga de comandes": 41 comandes en ESTOC_RESERVAT amb més de 5 minuts; el vigilant començarà a cancel·lar-les per TIMEOUT_PAGAMENT als 10 (06-03 §9). Severitat SEV2 (part del flux trencat, sense workaround per al client); s'avisa a #estat-plataforma: "des de les 15:12 els cobraments no es completen per un problema del proveïdor de pagaments; les comandes s'estan retenint; propera actualització a les 16:00".
  5. 15:40 — Estabilitzar abans que entendre: la pàgina d'estat de la passarel·la confirma incident. Decisió de Pagaments: pujar temporalment el límit del vigilant de 10 a 30 minuts (ConfigMap VIGILANT_LIMIT_MIN de Comandes + rollout restart; PR exprés a plataforma amb l'etiqueta incident, perquè Argo no ho reverteixi) i així no cancel·lar 41 comandes amb estoc reservat per una caiguda de 20 minuts aliena. Les reserves caduquen als 15 min per expira_en… tret que es pugi també RESERVA_TTL_S; es puja a 2.400 s de la mateixa manera. Dos canvis de configuració, zero codi.
  6. 15:52 — La passarel·la es recupera. El breaker passa a semiobert i tanca; els missatges de pagaments.estoc.reintent (els que encara tenien intents) es cobren sols en 2 minuts. Queden els 14 de la DLQ.
  7. 15:55 — Reprocessar: kubectl -n techcorp run reprocessar --rm -it --image=ghcr.io/techcorp/servei-pagaments:1.4.2 --env-from=secret/pagaments-rabbitmq -- node scripts/reprocessarDlq.js --cua pagaments.estoc --max 50 (06-03 §8): 14 missatges tornen a la cua amb x-intents: 0 i x-reproces; cobrarComanda els troba EN_CURS (pas 1 de 08-02 §4) i consulta per clau d'idempotència abans de cobrar: 3 ja s'havien cobrat al primer intent (la passarel·la va cobrar i el timeout va arribar abans que la resposta) i es marquen CAPTURAT sense cobrar dos cops; 11 es cobren ara. pagament.confirmat ×14, sagues tancades.
  8. 16:02 — Panell: 0 comandes en ESTOC_RESERVAT antigues; es reverteixen els dos canvis de configuració (PR de tornada); es tanca l'incident. Durada: 50 min des de la primera fallada, 31 des de l'alerta; 0 comandes cancel·lades, 0 cobraments duplicats.
  9. Postmortem breu (SEV2, 06-05 §10): causa externa; va funcionar tot el que s'havia dissenyat (reintents, breaker, DLQ, alerta, runbook, idempotència de la passarel·la); accions: (a) l'alerta DlqAmbMissatges va trigar 10 min per disseny, però CircuitObert va saltar als 3: afegir al runbook de CircuitObert{passarella} el pas "pujar el vigilant"; (b) fer VIGILANT_LIMIT_MIN i RESERVA_TTL_S flags recarregables en calent (04-03 §7) per no reiniciar; (c) preguntar a la passarel·la pel seu SLA. I la pregunta fixa: "quina alerta ho hauria detectat abans?" —CircuitObert ja ho va fer; el que faltava era l'enllaç entre aquesta alerta i l'acció.

Compara amb INC-2031 (40 min, 61 comandes afectades, 38 cancel·lades): mateix símptoma ("la saga no avança"), la meitat d'impacte i cap acció destructiva, perquè el sistema ja tenia reintents diferits, DLQ, alertes per símptoma, runbook i un script. És exactament per a això que es va escriure 06-05.

  1. Operació: rotar un secret, actualitzar Node i evolucionar un contracte

Rotar comandes-db. És el procediment de 07-04 §4, executat el primer dilluns de cada semestre per Plataforma amb l'equip amo: (1) nova contrasenya de svc_comandes a Vault (techcorp/prod/comandes/db), amb l'usuari svc_comandes_b per tenir-ne les dues vàlides; (2) kubectl -n techcorp annotate externalsecret comandes-db force-sync=$(date +%s) → el Secret canvia; (3) kubectl -n techcorp rollout restart deploy/servei-comandes (rolling maxUnavailable: 0, 05-04) i rollout status; (4) comandes-migracions no cal; (5) revocar l'antiga i vigilar password authentication failed a Loki durant una hora. Quinze minuts, sense finestra de manteniment; es repeteix per a *-db i *-rabbitmq dels sis serveis el mateix matí amb un bucle a scripts/rotar-secrets.sh. L'auditoria de qui va rotar què és el git log del PR i l'audit log de Vault.

Node 20 → 22. Node 20 acaba el seu manteniment l'abril de 2026; l'actualització es fa un sol cop a la plantilla i es propaga per la regla del Luis: (1) plantilla-servei-node: FROM node:22-alpine a les dues etapes del Dockerfile (05-01 §3), engines.node: ">=22", @types/lint; (2) servei-node-ci.yml@v2: actions/setup-node amb node-version: 22 i una matriu temporal [20, 22] perquè cada servei comprovi totes dues abans de canviar; (3) @techcorp/comu-http es publica i es prova en 22 (05-03 §10); (4) cada equip, quan vol dins d'un termini (un mes), obre un PR al seu servei amb dues línies: uses: …/servei-node-ci.yml@v2 i el FROM; CI, Testcontainers, pactes i staging validen; producció amb l'estratègia habitual (canary a Comandes i Pagaments, rolling a la resta); (5) al cap d'un mes, @v1 es retira. Sis PRs de dues línies en lloc d'una migració coordinada; i si Notificacions s'endarrereix una setmana, ningú més no espera.

v2 de productes amb preu: { import, moneda }. És el canvi incompatible de 03-06 §10, ara executat de punta a punta:

Setmana Catàleg (proveïdor) Consumidors Tècnica
0 OpenAPI de /v2/productes; preu objecte, imatgeUrl obligatori; revisió amb Comandes, BFF i socis 03-06 §10
1-2 Serveix /v1/ i /v2/ des del mateix codi; el model intern ja és el nou i una capa tradueix cap enrere per a v1 (preu: import, moneda); Deprecation i Sunset (+6 mesos) a /v1/; pactes: els de v1 continuen verificant 03-06, 04-05
2 Migració MongoDB expand: preu numèric coexisteix amb preuDetallat fins que tot el codi llegeixi el nou (un script idempotent omple; 05-04 §3 aplicat a documents) 05-04 §3
3-4 Comandes: nou pacte contra /v2/productes (preu.import, preu.moneda); traductorProducte (ACL de 04-04) mapeja {import, moneda}preuUnitari + moneda; linies_comanda.moneda ja existia des de 005-linies-moneda-expand.sql i 006-…-contract.sql (05-04); comanda.creada passa a version: 2 amb preuUnitari: {import, moneda} i un upcaster als consumidors (03-06 §6) per tolerar v1 a la DLQ; BFF: Preu a l'esquema GraphQL, moneda @deprecated (03-06) canary de Comandes, can-i-deploy
5-6 Inventari, Pagaments, Notificacions: accepten comanda.creada v1 i v2 (eachLike als seus pactes de missatges, 08-02 §9); Pagaments fa servir moneda de l'esdeveniment en lloc d''EUR' fix rolling
+6 mesos Retira /v1/ (la capçalera Sunset ho va anunciar); mètrica http_requests_total{ruta="/v1/productes"} a 0 durant un mes abans No en queda cap 03-06 §7

Cap pas no talla res: en cada moment conviuen dues versions del contracte, de l'esdeveniment i de l'esquema, i cada equip desplega quan vol dins del termini. És la mateixa disciplina que a l'apartat 6, aplicada a un canvi que al monòlit hauria estat "cercar preu a tot el repositori i creuar els dits el dijous".

  1. Runbook ràpid d'operacions freqüents

Necessito… Ordre / recurs Lliçó
Veure l'estat general kubectl -n techcorp get pods,hpa,scaledobject,externalsecret · panell "RED per servei" 05-02, 06-01
Logs d'una comanda a tots els serveis Loki: {namespace="techcorp"} | json | comandaId="com-…" 06-01 §5
Traça d'una comanda Jaeger: servei=gateway, tag comandaId 06-02 §8
Desplegar a producció PR a plataforma/k8s/<svc>/overlays/prod (newTag); Argo CD sincronitza 05-03 §6
Revertir un desplegament argocd app rollback <app> <id> (o revertir el commit); emergència: kubectl rollout undo deploy/<svc> 05-04 §2, 05-03
Tallar un canary kubectl -n techcorp annotate ingress <svc>-canary nginx.ingress.kubernetes.io/canary-weight=0 --overwrite 05-04 §5
Canviar configuració PR al ConfigMap de l'overlay + rollout restart (o flag en calent si n'hi ha) 04-03, 05-02 §5
Tornar a llançar migracions kubectl -n techcorp delete job <svc>-migracions && kubectl apply -k … (idempotents) 05-02 §6, 05-03 §5
Veure cues i DLQ rabbitmqctl list_queues name messages consumers · consola 15672 · panell "Cues" 03-02, 06-03 §8
Reprocessar una DLQ kubectl run … -- node scripts/reprocessarDlq.js --cua <cua> --max N [--descartar] (auditat) 06-03 §8, 07-03 §9
Comandes encallades a la saga Panell "Saga de comandes"; vigilant TIMEOUT_PAGAMENT; CronJob reconciliar-reserves (kubectl create job --from=cronjob/reconciliar-reserves ara) 06-03 §9
Escalar a mà (dev) / campanya (prod) kubectl scale (dev) · overlay prod-bf per PR (prod) 06-04, §7
Rotar un secret Vault → annotate externalsecret force-syncrollout restart 07-04 §4, §9
Afegir un servei Copiar k8s/servei-comandes/, canviar nom/port/ConfigMap; la seva NetworkPolicy a k8s/xarxa/; usuari RabbitMQ i svc_*; Application d'Argo 05-02 §12, 07-04
Veure el pressupost d'error Panell SLOs; slo:*_pressupost_restant:ratio 06-05 §3, §11
Silenciar una alerta en manteniment Alertmanager: amtool silence add alertname=… --duration=2h --comment=… 06-05 §7
Comprovar la seguretat de la plataforma SEGURETAT.md; kubectl -n techcorp get networkpolicy; Trivy al CI; cosign verify 07-04

  1. Cost mensual aproximat i com reduir-lo

Xifres fictícies, orientatives, per a un clúster gestionat de producció amb 3.000 comandes/dia (sense campanya), arrodonides a l'alça. Serveixen per tenir l'ordre de magnitud i per a l'argument de FinOps de 08-04:

Component Dimensió €/mes aprox.
Nodes del clúster (pla de control gestionat inclòs) 6 nodes de 4 vCPU/16 GB + autoescalat a 12 en campanya 900
PostgreSQL gestionat (5 BD en 2 instàncies: comandes+pagaments, resta) amb rèplica i còpies 2 × (2 vCPU/8 GB) 420
MongoDB gestionat (catàleg, rèplica de 3) M20-equivalent 180
RabbitMQ gestionat (3 nodes, TLS) petit 160
Redis gestionat (memòria cau de catàleg) 1 GB amb rèplica 60
Keycloak (al clúster) + el seu PostgreSQL 2 pods, BD compartida amb "resta" 40
Observabilitat: Prometheus/Grafana/Loki/Jaeger al clúster + emmagatzematge d'objectes (mètriques 15 d, logs 30 d, traces 7 d al 10 %) ~1,5 nodes + 400 GB 260
Balancejador, IPs, trànsit de sortida, certificats 90
Registre de contenidors, Pact Broker, gestor de secrets, GitHub Actions (minuts extra) SaaS 150
Staging (tot l'anterior a escala 1/3, apagat de nit) 500
Total ≈ 2.760 €/mes (≈ 4.100 el mes de Black Friday)

Davant del monòlit (tres servidors grans + PostgreSQL + deu servidors durant un mes l'any ≈ 1.900 €/mes de mitjana), és un 45 % més car en infraestructura pura… i un 30 % més barat en campanya, i no inclou el que s'estalvia en hores de desplegament i incidents (08-01 §10). Com reduir-lo, per ordre de retorn: (1) requests reals mesurats amb Prometheus (06-04 §4) i right-sizing de nodes: la majoria de serveis de TechCorp demanen 250 m de CPU i en fan servir 60 m; (2) apagar staging fora d'horari (ja es fa) i fer servir minReplicaCount: 0 de KEDA per a consumidors esporàdics com analítica; (3) mostreig de traces al 10 % i retenció de logs de 30 dies (ja), sample_limit i revisió trimestral de cardinalitat (08-01 §11); (4) reservar capacitat de nodes base a un any (−30 %); (5) revisar si Redis, amb la càrrega real, compensa davant de la memòria cau HTTP del gateway; (6) no estalviar en còpies de seguretat, TLS ni rèpliques de les bases de dades de Comandes i Pagaments.

Errors Comuns i Consells

  • Instal·lar la infraestructura a mà i els serveis per GitOps. Al cap de tres mesos ningú no sap quina versió de RabbitMQ hi ha ni amb quins values. Tot a k8s/infra/ i aplicat per script o per Argo, encara que sigui Helm.
  • Saltar-se l'ordre dels CRDs. Un ExternalSecret aplicat abans que ESO o un ServiceMonitor abans que l'operador fallen amb "no matches for kind": l'script espera amb --wait cada operador abans de continuar.
  • Prova de fum sense token o amb el token d'admin. Ha de fer servir un usuari amb rol client i el seu clientId: és l'única manera de comprovar la regla del propietari i el 404 de la comanda aliena.
  • Escalar a mà a producció amb Argo CD en selfHeal. Ho reverteix en minuts i, pitjor, enmig d'una campanya. Per PR, sempre.
  • Reprocessar la DLQ abans d'entendre la causa. Si el missatge és verinós, torna a la DLQ i consumeix cinc reintents; si la dependència continua caiguda, igual. Primer Loki/Jaeger, després l'script.
  • Rotar un secret sense rollout restart i creure que ha funcionat perquè kubectl get secret mostra el nou. Els pods vells continuen amb la contrasenya antiga fins que reinicien (o fins que es llegeix com a fitxer).
  • Consell: executa desplegar-tot.sh dev en un clúster kind net un cop al mes. Si triga més de 15 minuts o falla en algun pas, alguna cosa de l'arbre de plataforma s'ha desactualitzat; millor descobrir-ho un dimarts que en la recuperació davant de desastres.

Exercicis

Exercici 1: Recuperació davant de desastres

El clúster de producció es perd del tot (regió caiguda). Amb el repositori plataforma, les còpies de les bases de dades gestionades i les imatges a ghcr.io, escriu la seqüència de recuperació en un clúster nou en una altra regió, indicant quins passos de l'apartat 3 canvien, què cal restaurar abans de què, quines dades es podrien perdre (pensa en l'outbox i les cues) i una estimació de temps. Què caldria tenir preparat per endavant perquè fos d'una hora en lloc d'un dia?

Exercici 2: El canary que menteix

Durant el canary al 10 % de servei-comandes 1.6.0 el burn rate és perfecte, però en passar al 100 % saga_durada_segons p95 puja de 3 a 40 s. Explica quin tipus de fallada no detecta un canary per pes de trànsit HTTP en un servei que a més consumeix esdeveniments, com ho hauries detectat durant el canary (mètriques i etiquetes concretes), i què canviaries en el procediment de l'apartat 6.

Exercici 3: Reduir la factura

La Marta demana abaixar la factura mensual un 25 % sense tocar la disponibilitat de Comandes ni de Pagaments. Amb la taula de l'apartat 11, proposa una llista concreta de mesures amb el seu estalvi estimat i el seu risc, i digues quines no acceptaries encara que estalviïn.

Solucions

Exercici 1. Seqüència: passos 1-2 iguals (namespaces, operadors) al clúster nou; el pas 3 canvia: restaurar les bases de dades gestionades des de còpia (PostgreSQL de Comandes i Pagaments primer, amb point-in-time recovery a l'instant més proper; MongoDB del catàleg; RabbitMQ no es restaura: es crea buit amb definitions.json, i els missatges en vol es perden); pas 4 Keycloak des de la seva BD restaurada; 5-6 iguals; 7 els ExternalSecret apunten al mateix Vault (que ha de ser fora de la regió o replicat); 8-9 iguals, canviant el DNS d'api.techcorp.example al nou balancejador i esperant el certificat. Dades en risc: els esdeveniments que eren a RabbitMQ sense consumir (cues duradores d'un clúster perdut) i els segons entre l'última còpia i la caiguda. L'outbox és la salvació parcial: en arrencar, els relays dels quatre serveis republiquen tot el que tingui publicat_en IS NULL, així que els esdeveniments generats i no publicats es recuperen; els ja publicats i no consumits es perden → les comandes en ESTOC_RESERVAT o PENDENT sense avançar les tanca el vigilant (TIMEOUT_PAGAMENT) i la reconciliació de reserves (06-03 §9), i cobrarComanda consulta la passarel·la abans de cobrar (EN_CURS), de manera que no hi ha cobraments dobles. Temps: amb tot a mà i provat, 2-4 hores; sense pràctica, un dia. Per a una hora: clúster secundari "calent" amb la infraestructura dels passos 1-7 ja aplicada i Argo CD apuntant al mateix repositori (només canviar la destinació), rèpliques de les BD gestionades a l'altra regió, Vault multiregió, i un exercici de recuperació trimestral (el consell de l'apartat anterior).

Exercici 2. El canary per pes de l'Ingress reparteix peticions HTTP, però les rèpliques canary també consumeixen comandes.saga en igualtat de condicions amb les estables (cues competidores, 06-04 §9): al 10 % de trànsit HTTP, el canary ja processa potser el 33 % dels esdeveniments (1 rèplica de 3), i si el seu consumidor és lent (una consulta sense índex sobre comandes afegida a 1.6.0), l'efecte es dilueix entre les estables i apareix "de cop" al 100 %. Detecció durant el canary: mirar mètriques etiquetades per version (l'etiqueta service.version de 06-02, que les mètriques de comu-http inclouen): histogram_quantile(0.95, sum by (le, version) (rate(saga_durada_segons_bucket[5m]))), la durada de processarUnCop per versió, pg_stat_statements o la latència de les consultes del consumidor, i x-intents/missatges a comandes.saga sense ack per pod. Canvi de procediment: afegir a l'apartat 6 un panell "canary vs estable" que compari també les mètriques de consumidors i de base de dades per version, no només RED HTTP; i, per a serveis amb consumidors, allargar el canary o fer canary per rèplica amb una cua de prova (o mesh, quan arribi) —és un dels senyals que 02-05 va fixar per replantejar l'orquestració de la saga i que 05-05 va deixar com a argument a favor del mesh.

Exercici 3. Mesures: (1) requests reals i nodes de 6 a 4 fora de campanya (l'autoescalat continua): −250 €, risc baix si els PDB i l'HPA estan bé; (2) staging a 1/4 i només en horari laboral amb arrencada sota demanda: −200 €, risc baix (l'E2E de cd.yml triga 5 min més si és apagat); (3) capacitat reservada un any per a 4 nodes base: −180 €, risc de compromís; (4) traces al 5 % amb mostreig per cua d'errors al 100 %, logs de Catàleg a info (avui debug en un overlay oblidat), 15 dies de logs: −80 €, risc mitjà en depuració; (5) MongoDB a la instància inferior amb la memòria cau de Redis (que ja absorbeix el 85 %): −60 €, risc baix, mesurar amb k6; (6) KEDA a zero per a analítica i bff-mobil de nit: −30 €. Total ≈ −800 € (29 %). No acceptaria: treure la rèplica o les còpies de PostgreSQL de Comandes/Pagaments, passar RabbitMQ a un node, treure el TLS intern de RabbitMQ/BD, o reduir minReplicas de Comandes/Pagaments a 1 (un sol pod trenca el rolling sense talls de 05-04 i el PDB): estalvien 100-200 € i posen en joc l'SLO del 99,9 % i la seguretat de 07-02.

Conclusió

El sistema de TechCorp ja no és només codi: viu en un clúster i s'opera amb procediments escrits. Hem recorregut el repositori techcorp/plataforma complet (bases i overlays per servei, k8s/xarxa/, k8s/infra/ amb els values de Helm de RabbitMQ, PostgreSQL, MongoDB, Redis, Keycloak i l'observabilitat, SLOs, Argo CD, dashboards, runbooks, proves E2E i de càrrega, el workflow reutilitzable i SEGURETAT.md), el compose.yaml que aixeca els sis serveis amb Keycloak, Jaeger, Prometheus, Grafana i Loki en un portàtil, l'ordre d'arrencada d'un clúster nou (namespaces → operadors i CRDs → dades i missatgeria → identitat → observabilitat → xarxa → secrets → serveis en l'ordre de la saga → Ingress) plasmat a desplegar-tot.sh amb els seus Job de migració idempotents, i la verificació amb una prova de fum real —un token de Keycloak, una comanda a través de l'Ingress que arriba a CONFIRMADA, la seva traça a Jaeger, la seva marca al panell "Saga de comandes" i els seus logs a Loki—. I hem operat: un desplegament de servei-comandes de PR a 100 % passant per CI, pactes, imatge signada, staging, can-i-deploy, revisió humana, Argo CD i canary observat; la llista de Black Friday amb overlay de campanya, KEDA, pressupost d'error i congelació; l'incident INC-2047 a pagaments.estoc.dlq resolt amb alerta, runbook, Loki, Jaeger, dos canvis de configuració i reprocessarDlq.js sense cap cobrament duplicat; la rotació de comandes-db amb ESO, l'actualització a Node 22 propagada per plantilla i workflow reutilitzable, i la v2 de productes amb preu {import, moneda} aplicada de punta a punta amb expand/contract; més el runbook ràpid i una factura mensual amb les seves palanques.

Amb això, la història de TechCorp és completa: monòlit, migració, implementació, desplegament i operació. Queda el més valuós d'un cas d'estudi, que és destil·lar-lo: què vam fer, què va sortir malament i què faríem diferent en cada dimensió, quins antipatrons vam aprendre a reconèixer, una llista consolidada de bones pràctiques amb la seva lliçó de referència, què faria TechCorp a la seva fase 2 i com pots aplicar tot això al teu propi context. És l'última lliçó del curs.

Curs de Microserveis

Mòdul 1: Introducció als Microserveis

Mòdul 2: Disseny de Microserveis

Mòdul 3: Comunicació entre Microserveis

Mòdul 4: Implementació de Microserveis

Mòdul 5: Desplegament i Orquestració

Mòdul 6: Monitoratge i Manteniment

Mòdul 7: Seguretat en Microserveis

Mòdul 8: Casos d'Estudi i Exemples Pràctics

© Copyright 2026. Tots els drets reservats