Al llarg del mòdul hem anat posposant una decisió: què és "anar bé". Tenim mètriques (06-01), traces (06-02), patrons de resiliència (06-03) i autoescalat (06-04), però ningú no ha dit encara quants errors a POST /v1/comandes són acceptables, quant pot trigar la saga abans que algú hagi de mirar, ni a qui desperta el telèfon quan la DLQ s'omple a les tres de la matinada. Sense aquestes respostes, l'observabilitat és una col·lecció de panells bonics que es consulten després del desastre. Aquesta lliçó la converteix en un sistema d'operació: SLOs que tradueixen l'experiència del client a números, alertes que sonen només quan aquests números perillen, guàrdies i runbooks que saben què fer, i una manera de gestionar incidents i aprendre'n sense buscar culpables.

Contingut

  1. SLI, SLO i SLA: definicions i pressupost d'error
  2. Triar els SLIs de TechCorp
  3. SLOs concrets i el seu pressupost mensual
  4. Expressar els SLIs en PromQL amb regles de gravació
  5. Alertes basades en SLO: burn rate multifinestra
  6. Alertes per símptoma i per què no alertar per causes
  7. Alertmanager: rutes per equip, agrupació, silencis i inhibició
  8. Guàrdies i runbooks
  9. Gestió d'incidents: rols, severitats i comunicació
  10. Postmortem sense culpa: INC-2031
  11. Revisió d'SLOs i política de pressupost d'error
  12. Taula resum de les alertes de TechCorp

  1. SLI, SLO i SLA: definicions i pressupost d'error

Terme Què és Qui el fixa Exemple TechCorp
SLI (Service Level Indicator) Una mesura de la qualitat del servei, normalment una proporció d'esdeveniments bons sobre el total Enginyeria Proporció de POST /v1/comandes que no retornen 5xx
SLO (Service Level Objective) Un objectiu intern sobre un SLI en una finestra Enginyeria + producte 99,9 % dels POST /v1/comandes sense 5xx en 30 dies
SLA (Service Level Agreement) Un contracte amb conseqüències (penalitzacions) si s'incompleix Negoci/legal "Disponibilitat de l'API del 99,5 % mensual" amb els marketplaces que integren TechCorp

Regles: l'SLA és sempre més lax que l'SLO (si l'SLO és 99,9 %, l'SLA 99,5 %: el marge és el que evita pagar penalitzacions); un SLI s'expressa com a proporció (0-1) per poder sumar, comparar i calcular pressupostos; i un SLO del 100 % no existeix: cada nou addicional multiplica el cost i cap dependència (el núvol, el PSP) no l'ofereix.

El pressupost d'error és la part de l'SLO que es permet fallar: 1 − SLO. Amb un SLO del 99,9 % en 30 dies, el 0,1 % de les peticions poden fallar; expressat en temps, si el servei estigués totalment caigut, 43 minuts al mes. És la idea més útil de la lliçó: converteix "fiabilitat" en una moneda que es gasta en desplegaments, experiments i accidents, i que quan s'esgota obliga a parar (apartat 11).

  1. Triar els SLIs de TechCorp

Un bon SLI mesura el que el client percep, no el que a enginyeria li resulta còmode. L'Ana Ruiz no sap què és la CPU; sap si la seva comanda s'ha creat, si la pàgina de productes carrega ràpid i si li arriba la confirmació. Els quatre SLIs de TechCorp, un per pregunta:

SLI Pregunta del client Definició (bo / total) Mètrica d'origen (06-01) Equip propietari
Disponibilitat de comandes "Puc comprar?" POST /v1/comandes amb codi ≠ 5xx / tots els POST /v1/comandes http_requests_total mesurada al gateway Comandes
Latència de catàleg "Carrega ràpid la botiga?" GET /v1/productes en < 300 ms / tots http_request_duration_seconds_bucket{le="0.3"} Experiència de compra
Saga a temps "Em confirmen la comanda de seguida?" Comandes confirmades en < 60 s des de la seva creació / comandes que arriben a resoldre's saga_durada_segons_bucket{le="60"} Comandes (i Pagaments, Inventari)
Frescor de l'outbox (intern: prediu l'anterior) Instants en què outbox_pendents < 100 / instants mesurats outbox_pendents Comandes

Notes de disseny:

  • L'SLI de disponibilitat es mesura al gateway (el que el client veu), no a Comandes: si Comandes està sa però el gateway no encamina, el client pateix igual. Els 4xx no compten com a fallada: un 422 per dades invàlides és culpa del client; un 503 per Catàleg caigut (06-03) que compta, encara que sigui "d'un altre".
  • El de latència es defineix com a proporció de peticions sota un llindar, no com "el p95 < 300 ms". La proporció és un SLI net (bo/total) i aprofita directament el bucket le="0.3" de l'histograma; el p95 es continua mirant a Grafana.
  • El de la saga necessita l'histograma de 06-01 i exclou del denominador les comandes cancel·lades per SENSE_ESTOC o PAGAMENT_REBUTJAT (resoldre's ràpid amb un rebuig també és "a temps": es compta saga_durada_segons en tancar la comanda en qualsevol estat final).
  • El de l'outbox és un SLI intern: no el veu el client, però quan falla el de la saga fallarà minuts després. Un bon sistema d'SLOs té pocs SLIs de cara al client i alguns d'interns que avisen abans.

  1. SLOs concrets i el seu pressupost mensual

SLO Objectiu Finestra Pressupost d'error En minuts de "caiguda total" al mes
Disponibilitat POST /v1/comandes 99,9 % 30 dies 0,1 % 43 min
Latència GET /v1/productes < 300 ms 99,5 % 30 dies 0,5 % 216 min
Saga confirmada < 60 s 99,5 % 30 dies 0,5 % 216 min
Outbox fresc (< 100 pendents) 99,9 % 30 dies 0,1 % 43 min

Per què aquests números i no uns altres: 99,9 % en comandes perquè cada minut sense vendre costa diners mesurables (Black Friday, 01-05) i perquè la infraestructura de 05-02/05-04 (2 rèpliques, rolling sense talls) ho fa assolible; 99,5 % en latència i saga perquè un 0,5 % de pàgines lentes o confirmacions tardanes és molest però no impedeix comprar, i perquè depenen del PSP i de MongoDB en pic. El compte de minuts: 30 dies × 24 h × 60 min × (1 − SLO); per a 99,9 %: 43.200 × 0,001 = 43,2 min. I una advertència: els minuts són una intuïció; el pressupost real es mesura en peticions. Una caiguda del 100 % durant 43 minuts a les 4 de la matinada consumeix molt menys pressupost (poques peticions) que un 5 % d'errors durant 8 hores en horari comercial.

  1. Expressar els SLIs en PromQL amb regles de gravació

Calcular histogram_quantile o divisions de rate sobre 30 dies a cada consulta és car. Les regles de gravació (recording rules) precalculen cada SLI en una sèrie nova, amb un nom convencional sli:<nom>:ratio_rate<finestra>, i les alertes i els panells consulten aquesta sèrie:

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: slos-techcorp
  namespace: techcorp
  labels: { release: kube-prometheus-stack }
spec:
  groups:
    - name: sli-comandes-disponibilitat
      interval: 30s
      rules:
        # proporció d'errors 5xx a POST /v1/comandes, en diverses finestres
        - record: sli:comandes_errors:ratio_rate5m
          expr: |
            sum(rate(http_requests_total{servei="gateway", ruta="/v1/comandes", metode="POST", codi=~"5.."}[5m]))
              / sum(rate(http_requests_total{servei="gateway", ruta="/v1/comandes", metode="POST"}[5m]))
        - record: sli:comandes_errors:ratio_rate30m
          expr: |
            sum(rate(http_requests_total{servei="gateway", ruta="/v1/comandes", metode="POST", codi=~"5.."}[30m]))
              / sum(rate(http_requests_total{servei="gateway", ruta="/v1/comandes", metode="POST"}[30m]))
        - record: sli:comandes_errors:ratio_rate1h
          expr: |
            sum(rate(http_requests_total{servei="gateway", ruta="/v1/comandes", metode="POST", codi=~"5.."}[1h]))
              / sum(rate(http_requests_total{servei="gateway", ruta="/v1/comandes", metode="POST"}[1h]))
        - record: sli:comandes_errors:ratio_rate6h
          expr: |
            sum(rate(http_requests_total{servei="gateway", ruta="/v1/comandes", metode="POST", codi=~"5.."}[6h]))
              / sum(rate(http_requests_total{servei="gateway", ruta="/v1/comandes", metode="POST"}[6h]))
        - record: sli:comandes_errors:ratio_rate30d
          expr: |
            sum(rate(http_requests_total{servei="gateway", ruta="/v1/comandes", metode="POST", codi=~"5.."}[30d]))
              / sum(rate(http_requests_total{servei="gateway", ruta="/v1/comandes", metode="POST"}[30d]))
    - name: sli-cataleg-latencia
      rules:
        - record: sli:cataleg_lentes:ratio_rate5m
          expr: |
            1 - (
              sum(rate(http_request_duration_seconds_bucket{servei="servei-cataleg", ruta="/v1/productes", le="0.3"}[5m]))
                / sum(rate(http_request_duration_seconds_count{servei="servei-cataleg", ruta="/v1/productes"}[5m]))
            )
    - name: sli-saga
      rules:
        - record: sli:saga_tardanes:ratio_rate1h
          expr: |
            1 - (sum(rate(saga_durada_segons_bucket{le="60"}[1h])) / sum(rate(saga_durada_segons_count[1h])))
    - name: pressupost-error
      rules:
        - record: slo:comandes_pressupost_restant:ratio
          expr: 1 - (sli:comandes_errors:ratio_rate30d / 0.001)      # 1 = intacte, 0 = esgotat, negatiu = excedit
  • Cada regla record desa el resultat d'expr com una sèrie nova cada interval. Les alertes de l'apartat següent combinen les finestres de 5 m, 30 m, 1 h i 6 h; el panell de "pressupost restant" fa servir la de 30 d.
  • Es registren les proporcions de fallada (errors, lentes, tardanes) en comptes de les d'èxit: els burn rates es calculen sobre la fallada.
  • El de latència fa servir 1 − (bones/total): bucket{le="0.3"} compta les que van trigar ≤ 300 ms.
  • slo:comandes_pressupost_restant:ratio és la mètrica que s'ensenya a negoci: "ens queda el 62 % del pressupost d'aquest mes".

Els 30 dies de finestra requereixen retenció suficient a Prometheus (o Thanos/Mimir per al llarg termini, menció). Els quatre SLIs es documenten també com a text a techcorp/plataforma/observabilitat/slos.md: definició, propietari, objectiu i data de revisió.

  1. Alertes basades en SLO: burn rate multifinestra

L'alerta ingènua "taxa d'errors > 1 % durant 5 minuts" té dos problemes: dispara amb un pic de dos minuts que gairebé no gasta pressupost, i no dispara amb un 0,5 % sostingut tres dies que sí que l'esgota. L'alternativa de l'SRE book és alertar per velocitat de consum del pressupost (burn rate): 1× vol dir que al ritme actual el pressupost s'esgota exactament al final de la finestra de 30 dies; 14,4× vol dir que s'esgotaria en 2 dies (30/14,4). Es combinen dues finestres (una de llarga per assegurar que és real, una de curta perquè l'alerta s'apagui aviat en resoldre's):

Alerta Finestra llarga Finestra curta Burn rate Pressupost consumit per disparar Severitat
Ràpida 1 h 5 m 14,4× 2 % en 1 h critical (pàgina)
Lenta 6 h 30 m 5 % en 6 h warning (tiquet)

Per a l'SLO del 99,9 % (pressupost 0,001), 14,4× equival a una taxa d'errors de l'1,44 % i 6× al 0,6 %:

    - name: alertes-slo-comandes
      rules:
        - alert: ComandesErrorBudgetBurnRapid
          expr: |
            sli:comandes_errors:ratio_rate1h > (14.4 * 0.001)
              and
            sli:comandes_errors:ratio_rate5m > (14.4 * 0.001)
          for: 2m
          labels:
            severity: critical
            equip: comandes
            slo: comandes-disponibilitat
          annotations:
            resum: "POST /v1/comandes consumeix el pressupost d'error 14,4x més ràpid del previst"
            descripcio: "Taxa de 5xx {{ $value | humanizePercentage }} a l'última hora (SLO 99,9 %). Vegeu el runbook."
            runbook: "https://runbooks.techcorp.internal/comandes/error-budget-burn"
            dashboard: "https://grafana.techcorp.internal/d/red-servei?var-servei=servei-comandes"
        - alert: ComandesErrorBudgetBurnLent
          expr: |
            sli:comandes_errors:ratio_rate6h > (6 * 0.001)
              and
            sli:comandes_errors:ratio_rate30m > (6 * 0.001)
          for: 15m
          labels: { severity: warning, equip: comandes, slo: comandes-disponibilitat }
          annotations:
            resum: "POST /v1/comandes consumeix el pressupost d'error 6x més ràpid del previst"
            runbook: "https://runbooks.techcorp.internal/comandes/error-budget-burn"
  • expr amb and: les dues finestres han de superar el llindar alhora. La d'1 h evita que un pic de segons desperti ningú; la de 5 m fa que l'alerta es resolgui minuts després d'arreglar la causa (sense ella continuaria activa fins que l'hora sencera es netegés).
  • for: 2m: la condició s'ha de mantenir dos minuts abans de disparar; contra el soroll d'un sol scrape.
  • labels: severity decideix el canal (apartat 7); equip decideix el destinatari; slo agrupa.
  • annotations: el que llegeix la persona de guàrdia a les tres de la matinada. Sempre runbook i dashboard; una alerta sense runbook no es desplega (regla de Plataforma, comprovada per un linter a CI).

Les mateixes dues alertes es repliquen per a la latència de catàleg (CatalegLatenciaBurnRapid, pressupost 0,005: llindars 7,2 % i 3 % de lentes) i la saga (SagaTardanaBurnRapid, sobre sli:saga_tardanes). Amb quatre SLOs i dues alertes cadascun són vuit alertes d'SLO en total: poques, significatives i totes lligades a alguna cosa que el client nota.

  1. Alertes per símptoma i per què no alertar per causes

Les alertes d'SLO cobreixen "el client pateix". Hi ha un segon grup d'alertes per símptoma que anticipen que patirà o que alguna cosa està trencada encara que el client encara no ho noti; han de ser poques i amb llindar clar:

    - name: alertes-simptomes
      rules:
        - alert: OutboxEncallat
          expr: outbox_pendents{servei="servei-comandes"} > 100
          for: 5m
          labels: { severity: critical, equip: comandes }
          annotations: { resum: "L'outbox de Comandes acumula {{ $value }} esdeveniments sense publicar", runbook: "https://runbooks.techcorp.internal/comandes/outbox-encallat" }
        - alert: DlqAmbMissatges
          expr: rabbitmq_queue_messages_ready{queue=~".+\\.dlq"} > 0
          for: 10m
          labels: { severity: warning, equip: "{{ if eq $labels.queue \"pagaments.estoc.dlq\" }}pagaments{{ else }}comandes{{ end }}" }
          annotations: { resum: "La cua {{ $labels.queue }} té {{ $value }} missatges", runbook: "https://runbooks.techcorp.internal/comu/dlq" }
        - alert: PodEnCrashLoop
          expr: max_over_time(kube_pod_container_status_waiting_reason{namespace="techcorp", reason="CrashLoopBackOff"}[5m]) > 0
          for: 5m
          labels: { severity: warning, equip: plataforma }
          annotations: { resum: "{{ $labels.pod }} en CrashLoopBackOff", runbook: "https://runbooks.techcorp.internal/plataforma/crashloop" }
        - alert: CircuitObert
          expr: circuit_breaker_estat == 2
          for: 5m
          labels: { severity: warning, equip: comandes }
          annotations: { resum: "Circuit obert cap a {{ $labels.dependencia }} des de {{ $labels.servei }}", runbook: "https://runbooks.techcorp.internal/comu/circuit-obert" }

(L'equip amb plantilla a DlqAmbMissatges és una simplificació; a la pràctica es fa una regla per cua amb el seu equip propietari de 02-01.) Altres del mateix tipus: CertificatCaduca (probe_ssl_earliest_cert_expiry de Blackbox Exporter, < 14 dies, menció), SondaReadyFallant més de 10 minuts, RabbitMQNodeCaigut.

El que no s'alerta com a pàgina: les causes. "CPU > 80 %", "memòria > 90 %", "disc al 70 %" poden voler dir un problema o un HPA fent la seva feina (06-04); si el client no pateix i cap símptoma no apareix, no hi ha res a fer a les tres de la matinada. Es deixen com a panells i, com a molt, com a severity: info a un canal de Slack que es llegeix al matí. La disciplina és aquesta: pàgina → el client pateix o patirà en minuts; tiquet → alguna cosa està malament i s'ha d'arreglar aquesta setmana; info → convé saber-ho. Cada alerta que desperta algú i no requereix acció és una alerta que cal esborrar o degradar.

  1. Alertmanager: rutes per equip, agrupació, silencis i inhibició

Prometheus avalua les regles; Alertmanager decideix a qui i com avisar. Configuració de l'equip de Plataforma (secret alertmanager-config, versionat amb els valors del chart):

route:
  receiver: slack-plataforma                 # destí per defecte
  group_by: [alertname, equip]               # una notificació per alerta i equip, no per pod
  group_wait: 30s                            # esperar 30 s que arribin alertes relacionades abans de la primera notificació
  group_interval: 5m
  repeat_interval: 4h                        # recordatori si continua activa
  routes:
    - matchers: [severity="critical"]
      receiver: pagerduty-guardia            # pàgina a qui estigui de guàrdia
      continue: true                         # i a més continua avaluant: també arriba a Slack
    - matchers: [equip="comandes"]
      receiver: slack-comandes
    - matchers: [equip="pagaments"]
      receiver: slack-pagaments-comunicacions
    - matchers: [equip="experiencia"]
      receiver: slack-experiencia-compra
    - matchers: [equip="plataforma"]
      receiver: slack-plataforma
receivers:
  - name: pagerduty-guardia
    pagerduty_configs:
      - routing_key_file: /etc/alertmanager/secrets/pagerduty
        severity: critical
  - name: slack-comandes
    slack_configs:
      - api_url_file: /etc/alertmanager/secrets/slack
        channel: "#alertes-comandes"
        title: '{{ .CommonLabels.alertname }} ({{ .Status }})'
        text: '{{ range .Alerts }}{{ .Annotations.resum }} — <{{ .Annotations.runbook }}|runbook>{{ "\n" }}{{ end }}'
  # slack-pagaments-comunicacions, slack-experiencia-compra, slack-plataforma: anàlegs
inhibit_rules:
  - source_matchers: [alertname="RabbitMQNodeCaigut"]
    target_matchers: [alertname=~"DlqAmbMissatges|OutboxEncallat"]
    equal: [namespace]                       # si RabbitMQ està caigut, no avisar a més de les seves conseqüències
  • Routing per severity i equip: critical va a PagerDuty (la guàrdia) i a l'Slack de l'equip (continue: true); warning només a l'Slack de l'equip. Els quatre canals corresponen als quatre equips de 02-01.
  • Agrupació (group_by): si 12 pods de Catàleg entren en CrashLoopBackOff, arriba una notificació amb 12 alertes, no 12 missatges.
  • Silencis: durant un manteniment planificat (migració de RabbitMQ un diumenge) es crea un silenci a la interfície d'Alertmanager amb matchers (equip="comandes", 2 h) i un comentari; les alertes s'avaluen però no s'envien.
  • Inhibició: una alerta "arrel" (RabbitMQNodeCaigut) suprimeix les derivades (OutboxEncallat, DlqAmbMissatges) perquè la guàrdia rebi un avís, no deu.
  • Slack i PagerDuty són ficticis al curs; el patró serveix igual amb Opsgenie, Teams o correu.

  1. Guàrdies i runbooks

TechCorp, amb ~25 tècnics, munta una guàrdia setmanal per rotació entre els desenvolupadors dels quatre equips (no només Plataforma: qui construeix, opera), amb una persona primària i una altra de secundària, horari de compensació acordat i una regla: la guàrdia només rep alertes critical amb runbook. Tota la resta espera a l'horari laboral.

Un runbook és la pàgina que s'obre des de l'alerta i diu què mirar i què fer, escrita per a algú que no coneix el servei i està mig adormit. Plantilla i exemple per a ComandesErrorBudgetBurnRapid:

# Runbook: ComandesErrorBudgetBurnRapid
Severitat: critical · Equip: Comandes · Última revisió: 2026-08-01 · Propietari: Luis

## Què vol dir
POST /v1/comandes retorna 5xx a més de l'1,44 % de les peticions durant l'última hora
i els últims 5 min. Els clients no poden comprar. Cada minut compta contra l'SLO 99,9 %.

## Comprovar en 5 minuts
1. Grafana "RED per servei" (servei=gateway, ruta=/v1/comandes): quins codis? 503 → dependència; 502/504 → Comandes no respon; 500 → bug.
2. Grafana "RED per servei" (servei=servei-comandes): latència alta? pods vius?  kubectl -n techcorp get pods -l app=servei-comandes
3. Loki: {app="servei-comandes", nivell="error"} | json | line_format "{{.err.codi}} {{.missatge}}"
   - DEPENDENCIA_NO_DISPONIBLE + dependencia=cataleg → anar a "Catàleg caigut" (a sota)
   - errors de pg → anar a "Base de dades"
4. Hi ha hagut desplegament a l'última hora?  argocd app history servei-comandes
   Sí → revertir primer, investigar després: argocd app rollback servei-comandes <id-anterior>
   Canary (05-04): kubectl -n techcorp annotate ingress comandes-canary nginx.ingress.kubernetes.io/canary-weight=0
5. Jaeger: cercar servei=servei-comandes, error=true, últims 15 min: en quin span falla?

## Remeis per causa
- Catàleg caigut: comprovar pods de cataleg; el breaker (06-03) ja protegeix; si és MongoDB → escalar a Plataforma.
- Base de dades: kubectl -n techcorp get pods -l app=postgres; connexions: SELECT count(*) FROM pg_stat_activity; si són al màxim, reiniciar el pod de comandes amb més connexions ocioses.
- Pods en CrashLoop: kubectl -n techcorp logs -l app=servei-comandes --previous | head; sol ser config (04-03) → revisar l'últim canvi del ConfigMap.
- Missatges a la DLQ després de la recuperació: node scripts/reprocessarDlq.js --cua comandes.saga --max 200 (06-03) des d'un pod efímer.

## Escalar a
Luis (líder de Comandes) després de 15 min sense causa; Plataforma si és infraestructura; Marta si SEV1 (> 30 min o Black Friday).

## Després
Registrar al canal #incidents; si ha durat > 15 min o ha afectat clients, obrir postmortem (plantilla).

Cada alerta de la taula de l'apartat 12 té el seu a techcorp/plataforma/runbooks/, versionats a Git i amb data d'última revisió: un runbook amb ordres que ja no funcionen és pitjor que cap. La regla d'or és que qui resol un incident sense runbook l'escriu en acabar.

  1. Gestió d'incidents: rols, severitats i comunicació

Un incident és qualsevol situació que degrada el servei i requereix coordinació. Quan salta una pàgina, la persona de guàrdia fa tres coses: reconeix l'alerta (perquè no continuï escalant), obre un canal (#inc-2031-pagaments-estoc a Slack) i avalua la severitat:

Severitat Criteri Exemple TechCorp Resposta
SEV1 Els clients no poden comprar o hi ha pèrdua/dany de dades Gateway caigut; POST /v1/comandes amb > 50 % de 5xx; cobraments duplicats Pàgina immediata, comandant assignat, comunicació a negoci cada 30 min, tots els equips disponibles
SEV2 Degradació important o part del flux trencat, amb workaround o impacte parcial Saga encallada (comandes en ESTOC_RESERVAT sense confirmar); catàleg lent amb p95 > 1 s; les notificacions no surten Pàgina, es resol en horari ampliat, comunicació a l'inici i al tancament
SEV3 Impacte menor o intern; sense efecte visible per al client DLQ amb 3 missatges; un pod en CrashLoopBackOff amb rèpliques sanes; pressupost d'error al 20 % Tiquet, es resol en horari laboral

Els rols en un SEV1/SEV2, perquè no tothom tecleji alhora:

  • Comandant de l'incident (incident commander): coordina, decideix i assigna; no depura personalment. Sol ser la persona de guàrdia fins que algú amb més context la releva de manera explícita ("assumeixo el comandament").
  • Comunicador: manté informats negoci i suport (un missatge cada 30 min a #estat-plataforma amb: què passa, impacte, què s'està fent, propera actualització) perquè ningú no interrompi qui resol.
  • Operadors/investigadors: un o diversos; segueixen el runbook, executen canvis i anoten cada acció amb hora al canal.

Principis: estabilitzar abans que entendre (revertir el desplegament, escalar rèpliques, desactivar el canary, reprocessar després: la causa arrel es busca quan el client ja no pateix); una cronologia en temps real al canal (la reconstruirà el postmortem); i comunicació amb negoci en el seu llenguatge ("no es poden crear comandes des de les 10:14; estimem recuperació en 20 min; les comandes ja creades no es veuen afectades"), mai en el nostre ("pagaments.estoc està encallada per un missatge verinós").

  1. Postmortem sense culpa: INC-2031

Tot SEV1 i SEV2 acaba amb un postmortem la setmana següent. "Sense culpa" (blameless) vol dir que s'assumeix que les persones van actuar raonablement amb la informació que tenien, i que la pregunta és què del sistema (codi, eines, processos, alertes) va permetre que passés i que durés el que va durar. Si el postmortem busca a qui renyar, la propera vegada la informació s'amaga. Plantilla i exemple resumit:

# Postmortem INC-2031 — pagaments.estoc encallada 40 minuts per un missatge verinós
Data: 2026-07-22 · Severitat: SEV2 · Durada: 40 min (10:14–10:54) · Comandant: guàrdia (Elena, Pagaments)
Autors: equip de Pagaments i comunicacions · Estat: accions en curs

## Resum
Un esdeveniment estoc.reservat amb `linies: null` (bug a Inventari després del desplegament 2.3.0) va fer que el
consumidor de pagaments.estoc llancés TypeError. Amb prefetch(1) a Pagaments i reintent immediat amb requeue,
el missatge tornava al cap de la cua en bucle i va bloquejar la resta de cobraments. 61 comandes van quedar en
ESTOC_RESERVAT; el vigilant en va cancel·lar 38 per TIMEOUT_PAGAMENT abans de la resolució.

## Impacte
61 clients sense confirmació durant fins a 40 min; 38 comandes cancel·lades i notificades com a "problema en el pagament"
(23 van tornar a comprar). Pressupost d'error de la saga consumit: 31 % del mes.

## Cronologia (UTC+2)
10:03 Desplegament servei-inventari 2.3.0 (canary 10 % → 100 % a les 10:10).
10:14 Primer TypeError a pagaments.estoc (Loki). Sense alerta: no existia DlqAmbMissatges ni SagaTardanaBurn.
10:31 Suport avisa a Slack: clients sense confirmació. La guàrdia obre #inc-2031.
10:38 Panell de la saga: 40 comandes en ESTOC_RESERVAT; pagaments.estoc amb 58 missatges llestos i cap de consumit.
10:44 S'identifica el missatge verinós a Loki per esdevenimentId; es purga manualment de la cua.
10:46 Cua desencallada; els cobraments pendents es processen en 2 min.
10:50 Es reverteix inventari a 2.2.4 (argocd rollback).
10:54 Saga normal. Es tanca l'incident.

## Causes (5 perquès, resumit)
1. Inventari va publicar un esdeveniment invàlid → manca de validació de sortida contra el contracte (03-06); el test de Pact
   no cobria línies nul·les.
2. Pagaments reintentava en bucle → nack amb requeue immediat i prefetch(1) (encara no aplicava 06-03).
3. Ningú no ho va veure en 17 min → sense alerta de símptoma sobre cues ni d'SLO sobre la saga; el panell existia, ningú no el mira.
4. Va trigar 13 min més a localitzar-se → sense runbook per a "cua encallada".

## Què va funcionar
El vigilant de TIMEOUT_PAGAMENT va limitar el dany (reserves alliberades, clients avisats). Els logs per esdevenimentId van permetre
localitzar el missatge en minuts un cop es va saber on mirar.

## Accions correctives (tasques amb propietari i data)
- [Pagaments] Cua de reintent amb TTL + DLQ a la primera per a errors permanents; prefetch 3.  → fet a 06-03
- [Comandes] Alertes DlqAmbMissatges i SagaTardanaBurnRapid/Lent.                              → aquesta lliçó
- [Inventari] Validar esdeveniments de sortida amb l'esquema del contracte abans d'escriure a l'outbox; test de Pact.
- [Plataforma] Runbook "cua encallada / DLQ" i script reprocessarDlq.js.                       → fet a 06-03
- [Tots] Afegir "quina alerta ho hauria detectat?" com a pregunta fixa del postmortem.

Les accions es converteixen en tasques al backlog amb propietari i data, i es revisen a la següent reunió d'operacions; un postmortem les accions del qual no s'executen és un document inútil. I es comparteix amb tota l'empresa: els incidents són la font d'aprenentatge més cara que existeix; desaprofitar-la és doble pèrdua.

  1. Revisió d'SLOs i política de pressupost d'error

Els SLOs no són eterns. Cada trimestre, cada equip revisa amb producte: l'SLI mesura el que el client nota? L'objectiu és massa ambiciós (sempre en vermell, ningú no se'l creu) o massa lax (sempre al 100 %, no diu res)? Han canviat les expectatives (un marketplace nou exigeix més)? S'ajusten objectius, es retiren SLIs inútils i s'afegeixen els que falten.

I el pressupost d'error es fa servir amb una política escrita, acordada entre la Marta i els equips:

Pressupost restant al mes Política
> 50 % Normal: desplegaments, experiments, game days (06-03)
20-50 % Precaució: es revisen els desplegaments de risc amb una altra persona; canary més llarg (05-04)
< 20 % Només canvis de fiabilitat i correccions; es congelen les funcionalitats fins que el pressupost es recuperi (la finestra de 30 dies avança)
Esgotat / negatiu Congelació total tret de correccions; l'equip dedica l'sprint a fiabilitat; postmortem obligatori de cada contribuïdor

És l'equilibri que faltava al debat de velocitat de 05-03: les mètriques DORA empenyen a desplegar més sovint; el pressupost d'error diu fins quan. Un equip amb pressupost sobrant hauria d'atrevir-se a més (més desplegaments, més experiments), no a menys: no gastar el pressupost també és un malbaratament.

  1. Taula resum de les alertes de TechCorp

Alerta Condició Severitat Equip Runbook
ComandesErrorBudgetBurnRapid 5xx a POST /v1/comandes > 1,44 % en 1 h i 5 m critical Comandes comandes/error-budget-burn
ComandesErrorBudgetBurnLent > 0,6 % en 6 h i 30 m warning Comandes comandes/error-budget-burn
CatalegLatenciaBurnRapid / Lent GET /v1/productes > 300 ms en > 7,2 % (1 h/5 m) / > 3 % (6 h/30 m) critical / warning Experiència de compra cataleg/latencia
SagaTardanaBurnRapid / Lent Sagues > 60 s en > 7,2 % / > 3 % critical / warning Comandes comandes/saga-encallada
OutboxEncallat outbox_pendents > 100 durant 5 m critical Comandes comandes/outbox-encallat
DlqAmbMissatges Qualsevol *.dlq amb missatges 10 m warning Propietari de la cua comu/dlq
CircuitObert circuit_breaker_estat == 2 durant 5 m warning Propietari del servei comu/circuit-obert
PodEnCrashLoop CrashLoopBackOff a techcorp 5 m warning Plataforma plataforma/crashloop
RabbitMQNodeCaigut Node del clúster de RabbitMQ no disponible critical Plataforma plataforma/rabbitmq
CertificatCaduca Certificat TLS < 14 dies warning Plataforma plataforma/certificats
PressupostErrorBaix slo:*_pressupost_restant:ratio < 0.2 info Cada equip comu/politica-pressupost

Onze alertes (més les variants per cua). Si en tres mesos alguna no ha requerit mai cap acció, es degrada o s'elimina; si un incident no va ser detectat per cap, s'afegeix la que ho hauria fet.

Errors Comuns i Consells

  • Cent alertes. Cadascuna sembla raonable; juntes produeixen fatiga i la guàrdia les ignora totes. Començar per les d'SLO i quatre símptomes; afegir-ne només des dels postmortems.
  • Alertar per llindar fix d'errors ("> 1 % durant 5 m"). Soroll amb pics, silenci amb degradacions lentes. Burn rate multifinestra.
  • SLO del 99,99 % "perquè sona bé". Són 4 minuts al mes: incompatible amb rolling de dependències, amb el PSP i amb el pressupost de l'empresa. Triar el que el negoci necessita i l'arquitectura permet.
  • Mesurar l'SLI on convé i no on és el client. Comandes al 100 % amb el gateway caigut és un SLO complert i una botiga tancada.
  • Alertes sense runbook. Despertar algú perquè comenci de zero. Runbook o no es desplega.
  • Postmortem amb noms i adjectius ("X va desplegar sense provar"). La propera vegada ningú no explicarà el que va passar. Sistema, no persones.
  • Accions del postmortem sense propietari ni data. S'obliden en una setmana. Tasques al backlog, revisades.
  • La guàrdia només a Plataforma. Els desenvolupadors no veuen les conseqüències del seu codi. Qui construeix, opera (amb formació i compensació).
  • Consell: muntar un panell únic "estat d'SLOs" amb les quatre proporcions i el pressupost restant, i posar-lo a la pantalla de l'equip; és la conversa amb producte que faltava.
  • Consell: provar les alertes: amtool alert add ComandesErrorBudgetBurnRapid severity=critical equip=comandes envia una alerta de prova i comprova l'encaminament sense esperar un incident real.

Exercicis

Exercici 1: SLO i pressupost per a Notificacions

Defineix un SLI i un SLO per a servei-notificacions ("el client rep el correu de confirmació"), indica de quina mètrica sortiria (pots proposar-ne una de nova coherent amb 06-01), calcula el pressupost mensual i justifica una severitat per a la seva alerta.

Exercici 2: l'alerta de la saga

Escriu la regla SagaTardanaBurnRapid completa (expressió amb les regles de gravació necessàries, for, labels, annotations) per a l'SLO del 99,5 %, i explica per què el seu llindar és 7,2 % i no 1,44 %.

Exercici 3: classificar i actuar

Són les 02:10. Salta OutboxEncallat (outbox_pendents = 340) i, alhora, DlqAmbMissatges a inventari.comandes.dlq amb 2 missatges. POST /v1/comandes respon 202 amb normalitat. Classifica la severitat, decideix quina alerta atens primer i descriu els cinc primers minuts seguint l'estil del runbook.

Solucions

Exercici 1

SLI: proporció de correus de confirmació lliurats al proveïdor en menys de 5 minuts des de comanda.confirmada, sobre el total de comanda.confirmada rebuts. Mètrica: histograma notificacio_latencia_segons{tipus="confirmacio"} observat a Notificacions des d'ocorregut_en de l'esdeveniment fins a la resposta 2xx del proveïdor, amb buckets [1, 5, 30, 60, 300, 900]; SLI = rate(..._bucket{le="300"}) / rate(..._count). SLO: 99 % en 30 dies (un correu tardà és molest, no impedeix comprar; el proveïdor extern no garanteix més). Pressupost: 1 % ≈ 432 minuts de "caiguda total" al mes, o unes 900 confirmacions tardanes de 90.000. Alerta: NotificacionsTardanesBurnRapid amb severity: warning (no critical): no desperta ningú; els correus es reintenten des de la cua (06-03) i arriben quan el proveïdor torna. Si el retard superés hores passaria a SEV3/SEV2 per impacte en suport, però no a pàgina.

Exercici 2

Regles de gravació necessàries: sli:saga_tardanes:ratio_rate1h (ja definida) i sli:saga_tardanes:ratio_rate5m (mateixa expressió amb [5m]).

- alert: SagaTardanaBurnRapid
  expr: |
    sli:saga_tardanes:ratio_rate1h > (14.4 * 0.005)
      and
    sli:saga_tardanes:ratio_rate5m > (14.4 * 0.005)
  for: 2m
  labels: { severity: critical, equip: comandes, slo: saga-a-temps }
  annotations:
    resum: "Les sagues triguen més de 60 s en {{ $value | humanizePercentage }} de les comandes"
    descripcio: "El pressupost d'error de l'SLO 'saga < 60 s' (99,5 %) es consumeix 14,4x més ràpid del previst. Sol ser Pagaments, Inventari o RabbitMQ."
    runbook: "https://runbooks.techcorp.internal/comandes/saga-encallada"
    dashboard: "https://grafana.techcorp.internal/d/saga-comandes"

El llindar és burn rate × pressupost: 14,4 × 0,005 = 0,072 (7,2 %). Amb un SLO del 99,5 % el pressupost és cinc vegades més gran que amb 99,9 %, i per tant la taxa de fallada que l'esgota "en dos dies" també és cinc vegades més gran. El burn rate és el mateix (14,4×), el percentatge absolut depèn de l'SLO.

Exercici 3

Severitat: POST /v1/comandes continua acceptant comandes, però cap no avança (els esdeveniments no surten de l'outbox): la saga està aturada per a tothom qui compri ara, i en 10 minuts el vigilant no actuarà (les comandes són en PENDENT, no en ESTOC_RESERVAT), així que s'acumularan comandes sense confirmar. És un SEV2 (flux trencat amb impacte creixent; no SEV1 perquè no hi ha pèrdua de dades: l'outbox ho conserva tot). S'atén primer OutboxEncallat: és critical, és la causa més "aigües amunt" i la DLQ d'Inventari amb 2 missatges és warning i pot esperar (a més podria ser conseqüència: si RabbitMQ va malament, totes dues alertes encaixen). Cinc minuts: (1) reconèixer a PagerDuty, obrir #inc-XXXX-outbox; (2) Grafana "Saga de comandes": outbox_pendents puja en línia recta des de les 01:55, comandes_creades_total normal, rabbitmq_queue_messages_ready{queue="inventari.comandes"} = 0 → el relay no publica; (3) kubectl -n techcorp get pods -l app=servei-comandes → 2 pods Running; Loki {app="servei-comandes"} | json | missatge=~"relay.*|rabbit.*" → "canal tancat pel broker" a les 01:55 i cap "lot publicat" des de llavors: el relay va perdre el canal i no va reconnectar (un bug de reconnexió); (4) remei immediat: kubectl -n techcorp rollout restart deployment/servei-comandes (els pods nous obren canal, el relay buida l'outbox en un minut; el rolling de 05-04 no talla el POST); comprovar que outbox_pendents baixa; (5) anotar la cronologia, veure els 2 missatges d'inventari.comandes.dlq (probablement relacionats: esdeveniments publicats a mitges abans del tall; es reprocessen amb reprocessarDlq.js un cop el relay va), missatge al comunicador/#estat-plataforma i, al matí, postmortem amb acció "el relay ha de reconnectar amb backoff i /health/ready ha de fallar si el canal està tancat" (06-03).

Conclusió

Amb aquesta lliçó TechCorp deixa de "mirar panells" per operar amb objectius. Quatre SLIs diuen el que el client nota (disponibilitat de POST /v1/comandes, latència de GET /v1/productes sota 300 ms, sagues confirmades en menys de 60 s, frescor de l'outbox), amb SLOs del 99,9 %/99,5 % i el seu pressupost d'error en minuts i en peticions; les regles de gravació sli:*:ratio_rate<finestra> els precalculen; les alertes de burn rate multifinestra (ComandesErrorBudgetBurnRapid a 14,4× en 1 h/5 m, …Lent a 6× en 6 h/30 m) i un grapat d'alertes per símptoma (OutboxEncallat, DlqAmbMissatges, CircuitObert, PodEnCrashLoop) arriben a través d'Alertmanager a l'equip propietari i, només si són critical, a la guàrdia; cadascuna amb runbook; els incidents es gestionen amb severitats SEV1-SEV3, comandant i comunicador, i acaben en un postmortem sense culpa les accions del qual són tasques; i la política de pressupost d'error decideix quan es desplega i quan es para. Amb això acaba el mòdul de monitoratge i manteniment: els serveis de TechCorp emeten logs estructurats i mètriques RED i de negoci (06-01), traces que uneixen la petició HTTP amb la saga (06-02), sobreviuen a les fallades amb timeouts, reintents, circuit breakers, cues de reintent i el vigilant de la saga (06-03), escalen sols i amb proves de càrrega (06-04) i s'operen contra SLOs amb alertes, guàrdies i postmortems (06-05). El sistema ja és observable, resilient, escalable i operable; el que encara no és, tret de les mencions al JWT i a la redacció de dades, és segur. El mòdul 7 comença per aquí: autenticació i autorització amb JWT/OAuth2 i Keycloak (07-01), seguretat en la comunicació entre serveis (07-02), pràctiques de seguretat al codi i les dades (07-03) i seguretat en contenidors i Kubernetes (07-04).

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