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
- El repositori
techcorp/plataformacomplet compose.yaml: el sistema sencer en un portàtil- Ordre d'arrencada en un clúster nou
scripts/desplegar-tot.shi elsJobde migració- Verificació i prova de fum
- Operació: un desplegament normal de
servei-comandes - Operació: la campanya de Black Friday
- Operació: un incident a
pagaments.estoc.dlq, pas a pas - Operació: rotar un secret, actualitzar Node i evolucionar un contracte
- Runbook ràpid d'operacions freqüents
- Cost mensual aproximat i com reduir-lo
- El repositori
techcorp/plataforma complet
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ó.
compose.yaml: el sistema sencer en un portàtil
compose.yaml: el sistema sencer en un portàtilEl 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).
- 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.
scripts/desplegar-tot.sh i els Job de migració
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ó.
- 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 cadascunLa 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 tokenI 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).
- Operació: un desplegament normal de
servei-comandes
servei-comandesEl 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à.
- 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.
- Operació: un incident a
pagaments.estoc.dlq, pas a pas
pagaments.estoc.dlq, pas a pasUn 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:
- 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. - 15:33 — Runbook
comu/dlq: "quants, des de quan, de quin tipus?".kubectl -n infra exec rabbitmq-0 -- rabbitmqctl list_queues name messages | grep dlq→pagaments.estoc.dlq 14. A la consola de RabbitMQ, els 14 sónestoc.reservatambx-intents: 5: van esgotar els reintents, no són verinosos. - 15:36 — Loki:
{app="servei-pagaments"} | json | nivell="error" | line_format "{{.esdevenimentId}} {{.err.codi}} {{.missatge}}"→DEPENDENCIA_NO_DISPONIBLE passarella: timeoutdes de les 15:12;CircuitObert{dependencia="passarella"}també és en warning des de les 15:15 (06-03 §4). Jaeger: l'spanPOST passarella/cobramentsamberror=truei 5.000 ms exactes: la passarel·la no respon, no rebutja. - 15:38 — Impacte: panell "Saga de comandes": 41 comandes en
ESTOC_RESERVATamb més de 5 minuts; el vigilant començarà a cancel·lar-les perTIMEOUT_PAGAMENTals 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". - 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_MINde Comandes +rollout restart; PR exprés aplataformaamb l'etiquetaincident, 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 perexpira_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. - 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. - 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 ambx-intents: 0ix-reproces;cobrarComandaels trobaEN_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 marquenCAPTURATsense cobrar dos cops; 11 es cobren ara.pagament.confirmat×14, sagues tancades. - 16:02 — Panell: 0 comandes en
ESTOC_RESERVATantigues; 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. - 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
DlqAmbMissatgesva trigar 10 min per disseny, peròCircuitObertva saltar als 3: afegir al runbook deCircuitObert{passarella}el pas "pujar el vigilant"; (b) ferVIGILANT_LIMIT_MINiRESERVA_TTL_Sflags 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?" —CircuitObertja 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.
- 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".
- 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-sync → rollout 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 |
- 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 ak8s/infra/i aplicat per script o per Argo, encara que sigui Helm. - Saltar-se l'ordre dels CRDs. Un
ExternalSecretaplicat abans que ESO o unServiceMonitorabans que l'operador fallen amb "no matches for kind": l'script espera amb--waitcada operador abans de continuar. - Prova de fum sense token o amb el token d'admin. Ha de fer servir un usuari amb rol
clienti el seuclientId: é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 restarti creure que ha funcionat perquèkubectl get secretmostra 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 deven 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 deplataformas'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
- Conceptes Bàsics de Microserveis
- Avantatges i Desavantatges dels Microserveis
- Comparació amb l'Arquitectura Monolítica
- Quan Adoptar Microserveis: Criteris de Decisió
- El Cas Pràctic del Curs: la Botiga Online de TechCorp
Mòdul 2: Disseny de Microserveis
- Principis de Disseny de Microserveis
- Descomposició d'Aplicacions Monolítiques
- Definició de Bounded Contexts
- Gestió de Dades: una Base de Dades per Servei
- Consistència Distribuïda: Sagues, CQRS i Event Sourcing
Mòdul 3: Comunicació entre Microserveis
- APIs RESTful
- Missatgeria Asíncrona
- Protocols de Comunicació: gRPC, GraphQL
- API Gateway i Backend for Frontend
- Descobriment de Serveis i Balanceig de Càrrega
- Contractes i Versionat d'APIs
Mòdul 4: Implementació de Microserveis
- Elecció de Tecnologies i Eines
- Desenvolupament d'un Microservei Simple
- Gestió de Configuració
- Integració Pràctica: Consumir APIs i Publicar Esdeveniments
- Proves en Microserveis: Unitàries, d'Integració i de Contracte
Mòdul 5: Desplegament i Orquestració
- Contenidors i Docker
- Orquestració amb Kubernetes
- CI/CD per a Microserveis
- Estratègies de Desplegament: Rolling, Blue-Green i Canary
- Service Mesh: Istio i Linkerd
Mòdul 6: Monitoratge i Manteniment
- Monitoratge i Logging
- Traçabilitat Distribuïda amb OpenTelemetry
- Gestió d'Errors i Recuperació
- Escalabilitat i Rendiment
- SLOs, Alertes i Gestió d'Incidents
Mòdul 7: Seguretat en Microserveis
- Autenticació i Autorització
- Seguretat en la Comunicació
- Pràctiques de Seguretat
- Seguretat en Contenidors i Kubernetes
