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
- SLI, SLO i SLA: definicions i pressupost d'error
- Triar els SLIs de TechCorp
- SLOs concrets i el seu pressupost mensual
- Expressar els SLIs en PromQL amb regles de gravació
- Alertes basades en SLO: burn rate multifinestra
- Alertes per símptoma i per què no alertar per causes
- Alertmanager: rutes per equip, agrupació, silencis i inhibició
- Guàrdies i runbooks
- Gestió d'incidents: rols, severitats i comunicació
- Postmortem sense culpa: INC-2031
- Revisió d'SLOs i política de pressupost d'error
- Taula resum de les alertes de TechCorp
- 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).
- 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) sí 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_ESTOCoPAGAMENT_REBUTJAT(resoldre's ràpid amb un rebuig també és "a temps": es comptasaga_durada_segonsen 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.
- 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.
- 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
recorddesa el resultat d'exprcom una sèrie nova cadainterval. 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ó.
- 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 | 6× | 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"expramband: 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:severitydecideix el canal (apartat 7);equipdecideix el destinatari;sloagrupa.annotations: el que llegeix la persona de guàrdia a les tres de la matinada. Semprerunbookidashboard; 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.
- 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.
- 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
severityiequip:criticalva a PagerDuty (la guàrdia) i a l'Slack de l'equip (continue: true);warningnomé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 enCrashLoopBackOff, 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.
- 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.
- 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-plataformaamb: 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").
- 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.
- 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.
- 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=comandesenvia 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
- 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
