La lliçó anterior va acabar amb un problema que no admet ajornament: totes les mètriques de CicloUrbana viuen en memòria i moren amb el procés. Cada desplegament automàtic de 08-05 les esborra; /actuator/metrics retorna l'acumulat d'ara mateix i no té història; no hi ha cap gràfic, no hi ha comparació amb la setmana passada i, sobretot, no hi ha ni una sola alerta. Els SLO que vam definir amb RED i USE estan escrits i ningú no els vigila.
Aquesta lliçó tanca aquest buit amb les dues eines que s'han convertit en l'estàndard de facto: Prometheus, que recull i emmagatzema sèries temporals, i Grafana, que les representa. Veurem per què Prometheus va a buscar les dades en lloc d'esperar-les, com exposar les mètriques de Micrometer en el seu format, com llegir aquest format de text —incloses les cubetes d'histograma que vam configurar a 09-03—, PromQL de debò fins a poder calcular el p95 real de la xarxa de Ribalta agregant les tres instàncies, el quadre de comandament de CicloUrbana panell a panell i, el que de debò canvia la vida de l'equip, les regles d'alerta que converteixen un número en una trucada de telèfon quan —i només quan— cal.
Contingut
- Per què cal un sistema extern
- El model pull davant del push
- L'arquitectura de monitoratge de CicloUrbana
- Exposar
/actuator/prometheus - Llegir el format de text
- El model de dades de Prometheus
prometheus.ymlper a CicloUrbana- Descobriment d'objectius a Kubernetes
- Retenció i emmagatzematge
- PromQL: el que cal saber
- Les consultes de CicloUrbana, una a una
- Aixecar la pila amb
docker-compose - Grafana: origen de dades i quadres de comandament importats
- El quadre de comandament de CicloUrbana, panell a panell
- Regles d'alerta
- Alertmanager: rutes, agrupació i silencis
- Bones pràctiques d'alertes
- Alternatives gestionades
- Errors Comuns i Consells
- Exercicis
- Per què cal un sistema extern
El MeterRegistry de 09-03 és un conjunt de comptadors en memòria. Això implica quatre mancances que cap configuració d'Actuator no pot resoldre:
| Mancança | Conseqüència pràctica |
|---|---|
| Sense persistència | Cada reinici o desplegament posa els comptadors a zero |
| Sense història | No es pot respondre «això passava la setmana passada?» |
| Sense agregació | Tres instàncies donen tres respostes diferents i ningú no les suma |
| Sense avaluació contínua | Ningú no mira /actuator/metrics a les quatre de la matinada |
Un sistema de monitoratge extern resol les quatre: recull periòdicament, emmagatzema amb retenció, agrega entre instàncies i avalua regles per alertar. L'aplicació només ha d'exposar els seus números en un format que aquest sistema entengui, i amb Micrometer això costa una dependència.
- El model pull davant del push
Prometheus va a buscar les dades: cada 15 segons fa una petició HTTP a cada instància i desa el que li retorna. És la decisió de disseny que més el distingeix de sistemes com Datadog o StatsD, on és l'aplicació la que empeny les seves mètriques cap a un col·lector.
| Pull (Prometheus) | Push (StatsD, Datadog) | |
|---|---|---|
| Qui inicia | El servidor de monitoratge | L'aplicació |
| Configuració de destinacions | Centralitzada, en un fitxer | A cada aplicació |
| La instància és viva? | Se sap de franc: up == 0 si no respon |
Cal deduir-ho de l'absència de dades |
| Sobrecàrrega a l'aplicació | Mínima: respondre un text | Un fil emissor i una cua |
| Processos efímers (lots) | Problema: poden morir abans de la recollida | Natural |
| Xarxes amb NAT o tallafocs | El servidor ha d'arribar a l'aplicació | L'aplicació només necessita sortida |
| Depuració | Obres la URL al navegador i ho veus | Cal mirar la destinació |
L'avantatge decisiu a la pràctica és el tercer: amb pull, Prometheus genera automàticament una mètrica up per objectiu, de manera que «l'aplicació està caiguda» és una consulta, no una inferència. I per al cas que el model no cobreix —tasques efímeres com la importació nocturna de 07-03, que pot acabar entre dues recollides— existeix el Pushgateway, un intermediari on el procés diposita les seves mètriques i del qual Prometheus estira després. És l'excepció, i convé tractar-la com a tal: fer-lo servir per a serveis normals anul·la la detecció de caigudes.
- L'arquitectura de monitoratge de CicloUrbana
flowchart LR
subgraph K8s["Kubernetes · 08-04"]
A1[ciclourbana-1<br/>:8081/actuator/prometheus]
A2[ciclourbana-2]
A3[ciclourbana-3]
end
P[(Prometheus<br/>TSDB local)]
A1 -->|scrape 15s| P
A2 -->|scrape 15s| P
A3 -->|scrape 15s| P
PG[PostgreSQL<br/>postgres_exporter] --> P
P --> G[Grafana<br/>quadres de comandament]
P -->|regles disparades| AM[Alertmanager]
AM --> S[Slack de l'equip]
AM --> O[Guardia · PagerDuty]
G -.consulta PromQL.-> P
Cinc peces i una responsabilitat cadascuna. L'aplicació només exposa un endpoint de text. Prometheus recull, emmagatzema i avalua les regles: fixa't que les alertes es disparen a Prometheus, no a Grafana. Alertmanager rep les alertes disparades i decideix a qui avisar, agrupant, silenciant i evitant repeticions. Grafana només consulta i dibuixa: no emmagatzema res. I els exporters —com postgres_exporter— publiquen en el mateix format les mètriques de sistemes que no parlen Prometheus, cosa que interessa perquè a 09-01 va quedar clar que bona part dels problemes de CicloUrbana són a la base de dades.
- Exposar
/actuator/prometheus
/actuator/prometheus<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>Només amb aquesta dependència, Micrometer registra un PrometheusMeterRegistry i Actuator afegeix l'endpoint. Falta exposar-lo:
management:
endpoints.web.exposure.include: health,info,loggers,metrics,caches,prometheus
server.port: 8081 # el port de gestio de 07-01
metrics.tags:
application: ciclourbana
entorn: ${SPRING_PROFILES_ACTIVE:local}
prometheus.metrics.export.enabled: trueSeguretat: la cadena EndpointRequest.toAnyEndpoint() de 07-01 ja cobreix aquest endpoint i exigeix ADMIN, així que Prometheus necessitarà credencials (apartat 7). És el correcte: /actuator/prometheus revela noms d'endpoints, versions i el volum de negoci de Ribalta. I continua vivint al port 8081, que no surt de la xarxa interna.
- Llegir el format de text
# HELP ciclourbana_lloguers_iniciats_total Lloguers iniciats
# TYPE ciclourbana_lloguers_iniciats_total counter
ciclourbana_lloguers_iniciats_total{application="ciclourbana",entorn="prod",estacio="1",tarifa="ESTANDARD",} 18422.0
ciclourbana_lloguers_iniciats_total{application="ciclourbana",entorn="prod",estacio="1",tarifa="ESTUDIANT",} 4310.0
# HELP ciclourbana_bicicletes_disponibles Bicicletes disponibles per estacio
# TYPE ciclourbana_bicicletes_disponibles gauge
ciclourbana_bicicletes_disponibles{application="ciclourbana",estacio="Plaça Major",} 12.0
# HELP http_server_requests_seconds Duration of HTTP server request handling
# TYPE http_server_requests_seconds histogram
http_server_requests_seconds_bucket{uri="/api/v1/lloguers",status="201",le="0.1",} 8241.0
http_server_requests_seconds_bucket{uri="/api/v1/lloguers",status="201",le="0.3",} 11903.0
http_server_requests_seconds_bucket{uri="/api/v1/lloguers",status="201",le="0.8",} 12744.0
http_server_requests_seconds_bucket{uri="/api/v1/lloguers",status="201",le="+Inf",} 12801.0
http_server_requests_seconds_count{uri="/api/v1/lloguers",status="201",} 12801.0
http_server_requests_seconds_sum{uri="/api/v1/lloguers",status="201",} 1284.31Com es llegeix, element per element. # HELP és la descripció, que ve literalment del .description(...) que vam escriure a MetriquesCicloUrbana. # TYPE declara el tipus —counter, gauge, histogram, summary—, i a partir d'ell Grafana i PromQL saben quines operacions tenen sentit. Cada línia següent és una sèrie temporal: nom, claus amb les etiquetes i valor actual; la combinació nom + etiquetes és la seva identitat, i aquí es veu per què la cardinalitat de 09-03 importa tant —cada línia és una sèrie que Prometheus indexarà i desarà per sempre—.
Els sufixos mereixen atenció especial, perquè són la traducció del que vam configurar a 09-03. Un comptador es converteix en _total per convenció de Prometheus. Un histograma produeix tres famílies: _count (quantes observacions), _sum (la suma de totes, en segons) i _bucket amb l'etiqueta le (less or equal), una sèrie per llindar. Fixa't que les cubetes són acumulatives: le="0.3" val 11.903 i inclou les 8.241 de le="0.1". L'última, le="+Inf", coincideix sempre amb _count. I aquests llindars 0,1 / 0,3 / 0,8 surten exactament del slo: que vam configurar a 09-03.
Amb _count i _sum s'obté la mitjana (_sum / _count = 1284,31 / 12801 = 100 ms), i amb les cubetes s'obté el percentil real: això és l'apartat 10.
- El model de dades de Prometheus
Una mostra és un valor float64 amb una marca de temps, i pertany a una sèrie temporal identificada per un nom de mètrica i un conjunt d'etiquetes. Res més: no hi ha taules, ni esquemes, ni tipus complexos.
ciclourbana_lloguers_iniciats_total{entorn="prod", tarifa="ESTANDARD", estacio="1"}
└─────────────── nom ───────────────┘ └────────────── etiquetes ──────────────┘Els quatre tipus: counter (monòton creixent; es consulta amb rate, mai pel seu valor absolut), gauge (puja i baixa; el seu valor sí que significa alguna cosa), histogram (cubetes acumulatives, agregable) i summary (percentils precalculats; el que produeix percentiles de 09-03, no agregable).
Prometheus afegeix a més unes etiquetes pròpies a cada mostra: job (el nom del grup d'objectius), instance (host:port) i les que es defineixin a la configuració. Per això, encara que les tres rèpliques publiquin el mateix comptador, les seves sèries són distingibles.
prometheus.yml per a CicloUrbana
prometheus.yml per a CicloUrbanaglobal:
scrape_interval: 15s # cada quant es recullen els objectius
evaluation_interval: 15s # cada quant s'avaluen les regles d'alerta
external_labels:
cluster: ribalta-prod # identifica l'origen en alertes i federacio
rule_files:
- /etc/prometheus/regles/*.yml
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]
scrape_configs:
- job_name: ciclourbana
metrics_path: /actuator/prometheus # NO es el /metrics per defecte
scheme: http
basic_auth: # la cadena de seguretat de 07-01
username: prometheus
password_file: /etc/prometheus/secrets/clau
scrape_interval: 15s
scrape_timeout: 10s # ha de ser menor que l'interval
static_configs:
- targets: ["ciclourbana-1:8081", "ciclourbana-2:8081", "ciclourbana-3:8081"]
labels:
entorn: prod
- job_name: postgres
static_configs: [{ targets: ["postgres-exporter:9187"] }]
- job_name: prometheus # Prometheus es vigila a si mateix
static_configs: [{ targets: ["localhost:9090"] }]Les decisions que importen. metrics_path és obligatori perquè Prometheus busca /metrics per defecte i Actuator publica a /actuator/prometheus: és l'error de configuració número u. L'interval de 15 segons és un compromís —més freqüent dóna més resolució i més emmagatzematge; per sota de 10 s rarament compensa, i per sobre de 60 s es perden els pics curts—. scrape_timeout menor que scrape_interval, o les recollides se solapen. L'autenticació és la contrapartida d'haver assegurat Actuator, amb la contrasenya en un fitxer i no al YAML. I el port 8081, que és el de gestió: Prometheus mai no toca el port del trànsit dels ciutadans.
- Descobriment d'objectius a Kubernetes
La llista estàtica deixa de servir tan bon punt l'HPA de 08-04 escala de tres a set rèpliques: els pods nous no apareixerien. La solució és el descobriment dinàmic:
- job_name: ciclourbana-k8s
kubernetes_sd_configs:
- role: pod
relabel_configs:
# 1. Nomes els pods que s'anuncien amb l'anotacio
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: "true"
# 2. La ruta ve de l'anotacio del pod
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
# 3. El port tambe
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
# 4. Etiquetes utils per consultar despres
- source_labels: [__meta_kubernetes_pod_name]
target_label: podI al Deployment de 08-04, la plantilla del pod s'anota:
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/path: "/actuator/prometheus"
prometheus.io/port: "8081"El mecanisme és elegant: Prometheus pregunta a l'API de Kubernetes quins pods existeixen i el relabel_configs filtra i transforma aquesta llista. Cada pod nou que neixi amb aquestes anotacions comença a ser recollit al cicle següent, i cada pod que mori deixa de ser-ho, sense tocar la configuració. Aquest és el motiu real pel qual Prometheus encaixa tan bé amb Kubernetes.
- Retenció i emmagatzematge
Prometheus desa les dades a la seva pròpia base de sèries temporals, en disc local, amb blocs de dues hores comprimits. Els paràmetres d'arrencada:
--storage.tsdb.retention.time=30d --storage.tsdb.retention.size=50GB --storage.tsdb.path=/prometheus
Una estimació útil: cada mostra ocupa aproximadament 1-2 bytes un cop comprimida. Amb 15.000 sèries i una recollida cada 15 segons, surten uns 86 milions de mostres al dia, és a dir, de l'ordre de 100-170 MB diaris i uns 3-5 GB al mes. La conclusió operativa: el consum depèn del nombre de sèries, no del trànsit, cosa que retorna un altre cop a la cardinalitat de 09-03 —duplicar les etiquetes duplica el disc i la memòria, encara que el nombre de peticions no canviï—.
Trenta dies són suficients per operar. Per a informes anuals a l'ajuntament cal emmagatzematge de llarga durada —Thanos, Cortex o Mimir—, que se situen darrere de Prometheus i desen els blocs en emmagatzematge d'objectes. I un avís important: el volum de Prometheus ha de ser persistent (un PersistentVolumeClaim a Kubernetes); si viu al sistema de fitxers del contenidor, cada reinici esborra la història i tornem al problema de l'apartat 1.
- PromQL: el que cal saber
Selectors i comparadors. La forma bàsica és el nom de la mètrica amb un filtre d'etiquetes entre claus:
http_server_requests_seconds_count{entorn="prod", uri="/api/v1/lloguers"}
http_server_requests_seconds_count{status!="200"} # diferent de
http_server_requests_seconds_count{status=~"5.."} # coincideix amb l'expressio
http_server_requests_seconds_count{uri!~"/actuator.*"} # no coincideixEls quatre comparadors són =, !=, =~ i !~; els dos últims són expressions regulars ancorades a la cadena completa, així que status=~"5.." capta exactament els 5xx.
rate(): la funció més important. Un comptador només creix, així que el seu valor absolut no diu res útil —«hem servit 18.422 lloguers des que va arrencar el procés» no es pot comparar amb una altra instància ni amb ahir—. El que interessa és a quin ritme creix:
rate calcula l'increment per segon dins de la finestra i, crucialment, gestiona els reinicis: si el comptador es posa a zero perquè l'aplicació s'ha reiniciat, rate ho detecta i no produeix un valor negatiu absurd. És exactament el que fa usable el model de comptadors monòtons.
irate() fa servir només els dos últims punts: reacciona molt de pressa i és molt sorollós. La regla pràctica: rate per a gràfics i alertes, irate només per depurar un pic en directe. I una condició que s'oblida: la finestra ha de contenir almenys quatre mostres, així que amb recollida cada 15 s, mai menys de [1m].
Agregació. Els operadors (sum, avg, max, min, count, topk) col·lapsen sèries:
sum(rate(http_server_requests_seconds_count[5m])) # total del servei
sum by (uri) (rate(http_server_requests_seconds_count[5m])) # desglossat per endpoint
sum without (instance) (rate(...)) # suma les 3 repliques
topk(5, sum by (uri) (rate(http_server_requests_seconds_count[5m]))) # els 5 mes usatssum by (...) conserva només les etiquetes anomenades; sum without (...) conserva totes menys aquestes. La segona és sovint més pràctica: without (instance) agrega les rèpliques mantenint la resta del context.
histogram_quantile(): el p95 real. Aquí es cobra la decisió de 09-03 de fer servir histogrames:
Es llegeix de dins cap enfora: rate sobre les cubetes dóna el ritme d'observacions per cubeta; sum by (le, uri) suma les tres instàncies conservant el llindar i l'endpoint; histogram_quantile interpola el percentil. El le dins del by és obligatori: sense ell es destrueix l'estructura de l'histograma i el resultat no significa res. I aquest és el p95 agregat que era impossible amb percentils calculats a la JVM.
Altres funcions útils. increase(x[1h]) dóna l'increment total a la finestra —increase és rate × segons, i serveix per a «quants lloguers en l'última hora»—. avg_over_time(x[10m]) fa la mitjana d'un gauge en el temps. changes(x[1h]) compta canvis de valor, útil per detectar reinicis. I absent(x) val 1 quan la sèrie no existeix, que és la manera d'alertar que alguna cosa ha deixat de publicar-se.
Operacions entre sèries. Es poden dividir dues consultes si les seves etiquetes casen, que és com es calcula una proporció:
sum(rate(http_server_requests_seconds_count{outcome="SERVER_ERROR"}[5m]))
/
sum(rate(http_server_requests_seconds_count[5m]))Amb sum(...) a banda i banda el resultat és un escalar i no hi ha problema d'aparellament; sense agregar, cal fer servir on(...) o ignoring(...) per indicar per quines etiquetes casen les sèries.
- Les consultes de CicloUrbana, una a una
| Què respon | Consulta PromQL |
|---|---|
| Peticions per segon, per endpoint | sum by (uri) (rate(http_server_requests_seconds_count{entorn="prod"}[5m])) |
| Taxa d'error 5xx (proporció) | sum(rate(http_server_requests_seconds_count{outcome="SERVER_ERROR"}[5m])) / sum(rate(http_server_requests_seconds_count[5m])) |
| Latència p95 per endpoint | histogram_quantile(0.95, sum by (le, uri) (rate(http_server_requests_seconds_bucket[5m]))) |
| Latència p99 de l'inici de lloguer | histogram_quantile(0.99, sum by (le) (rate(http_server_requests_seconds_bucket{uri="/api/v1/lloguers"}[5m]))) |
| Lloguers per minut i tarifa | sum by (tarifa) (rate(ciclourbana_lloguers_iniciats_total[5m])) * 60 |
| Lloguers en l'última hora | sum(increase(ciclourbana_lloguers_iniciats_total[1h])) |
| Bicicletes disponibles per estació | ciclourbana_bicicletes_disponibles{entorn="prod"} |
| Estacions buides ara | count(ciclourbana_bicicletes_disponibles == 0) |
| Ús del pool d'HikariCP | hikaricp_connections_active / hikaricp_connections_max |
| Fils esperant connexió | hikaricp_connections_pending |
| Pauses de GC (temps en pausa per segon) | rate(jvm_gc_pause_seconds_sum[5m]) |
| Memòria de heap usada | sum by (instance) (jvm_memory_used_bytes{area="heap"}) |
| Taxa d'encerts de memòria cau (09-02) | sum by (cache) (rate(cache_gets_total{result="hit"}[5m])) / sum by (cache) (rate(cache_gets_total[5m])) |
| Durada mitjana d'un trajecte | histogram_quantile(0.5, sum by (le) (rate(ciclourbana_lloguer_durada_bucket[30m]))) |
| Instàncies caigudes | up{job="ciclourbana"} == 0 |
| Estat del tallacircuits (07-06) | resilience4j_circuitbreaker_state{state="open"} == 1 |
Cinc d'elles mereixen comentari perquè amaguen una idea:
La taxa d'error s'expressa com a proporció, no com a número absolut: 20 errors per minut és una catàstrofe amb 100 peticions i soroll amb 100.000. Un detall pràctic: al numerador convé sumar outcome="SERVER_ERROR" i no status=~"5..", perquè outcome ja distingeix els errors nostres dels 4xx del client —un 409 per intentar un segon lloguer és la regla de negoci funcionant, no una fallada—.
El p95 per endpoint és la consulta que justifica tot el mòdul: agrega les tres instàncies, conserva el desglossament per endpoint i dóna el número que es va prometre com a SLO a 09-03.
Els lloguers per minut i tarifa són el pols del negoci, i el * 60 és només per llegir-los en unitats humanes: rate sempre retorna per segon.
L'ús del pool és una utilització (USE), i n'hi ha prou de comparar amb 0,8. hikaricp_connections_pending és la saturació, i el seu llindar és zero.
La taxa d'encerts de memòria cau tanca 09-02: la regla del by (cache) al numerador i al denominador fa que la divisió casi sèrie amb sèrie i doni un valor per memòria cau.
- Aixecar la pila amb
docker-compose
docker-composeAl costat del docker-compose.yml de 07-04, un fitxer d'observabilitat:
# docker-compose.observabilitat.yml
services:
prometheus:
image: prom/prometheus:v2.53.0
container_name: ciclourbana-prometheus
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=30d
- --web.enable-lifecycle # recarrega la config amb POST /-/reload
volumes:
- ./observabilitat/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./observabilitat/regles:/etc/prometheus/regles:ro
- prometheus-dades:/prometheus
ports: ["9090:9090"]
networks: [xarxa-ciclourbana]
alertmanager:
image: prom/alertmanager:v0.27.0
volumes: ["./observabilitat/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro"]
ports: ["9093:9093"]
networks: [xarxa-ciclourbana]
grafana:
image: grafana/grafana:11.1.0
container_name: ciclourbana-grafana
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:?falta GRAFANA_PASSWORD}
GF_USERS_ALLOW_SIGN_UP: "false"
volumes:
- ./observabilitat/grafana/provisioning:/etc/grafana/provisioning:ro
- grafana-dades:/var/lib/grafana
ports: ["3000:3000"]
depends_on: [prometheus]
networks: [xarxa-ciclourbana]
postgres-exporter:
image: prometheuscommunity/postgres-exporter:v0.15.0
environment:
DATA_SOURCE_NAME: "postgresql://ciclourbana:${POSTGRES_PASSWORD}@postgres:5432/ciclourbana?sslmode=disable"
networks: [xarxa-ciclourbana]
volumes:
prometheus-dades:
grafana-dades:S'arrenca al costat del principal amb docker compose -f docker-compose.yml -f docker-compose.observabilitat.yml up -d. Quatre detalls: els volums amb nom conserven la història i els quadres de comandament entre reinicis; --web.enable-lifecycle permet recarregar regles sense reiniciar; la contrasenya de Grafana és obligatòria —deixar admin/admin exposat és un clàssic—; i el postgres_exporter porta les mètriques de la base de dades que a 09-01 calia consultar a mà amb pg_stat_statements.
- Grafana: origen de dades i quadres de comandament importats
Grafana es configura per interfície, però la configuració que sobreviu és la aprovisionada per fitxer, versionada al repositori:
# observabilitat/grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
jsonData:
timeInterval: 15s # ha de coincidir amb el scrape_intervalImportar abans que construir. Abans de dibuixar un sol panell, convé importar dos quadres de comandament de la comunitat, indicant-ne l'identificador a Dashboards → Import:
| Id | Quadre de comandament | Què aporta |
|---|---|---|
| 4701 | JVM (Micrometer) | Memòria per àrea, GC, fils, classes, CPU. El més útil de tots |
| 12900 | Spring Boot 2.1+ Statistics | Peticions HTTP, latències, estat general |
| 11378 | Spring Boot APM | Vista amb HikariCP i endpoints |
| 9628 | PostgreSQL Database | Mètriques del postgres_exporter |
La comparació honesta: importar dóna el 80 % del valor en cinc minuts, cobreix tot el genèric —JVM, GC, pool, HTTP— i està molt més ben construït del que un faria a mà. El que no et pot donar és l'específic de Ribalta: lloguers per tarifa, bicicletes per estació, encerts de memòria cau. L'estratègia sensata és importar el genèric i construir un únic quadre de comandament propi amb el de negoci, en lloc de reinventar el gràfic de memòria de la JVM.
Un avís: els quadres importats suposen noms d'etiquetes concrets, gairebé sempre application i instance. Si les teves etiquetes comunes de 09-03 no inclouen application, els panells sortiran buits i semblarà que alguna cosa està trencada.
- El quadre de comandament de CicloUrbana, panell a panell
Un bon quadre de comandament respon en cinc segons a la pregunta «està bé el servei?» i permet aprofundir després. S'organitza en files, del que veu el ciutadà al que consumeix la màquina:
Fila 1 — Estat del servei (RED). Quatre panells: Taxa (sum(rate(http_server_requests_seconds_count[5m])), gràfic de línies), Errors (la proporció de l'apartat 11, en stat amb llindars de color: verd < 0,5 %, ambre < 1 %, vermell per sobre), Latència p50/p95/p99 (tres consultes superposades en un gràfic, amb el SLO de 800 ms dibuixat com a línia de llindar) i Disponibilitat (avg(up{job="ciclourbana"}), en stat).
Fila 2 — Per endpoint. Una taula amb topk(10, sum by (uri) (rate(...))) i un gràfic del p95 per uri. Aquesta fila respon «quin endpoint és el culpable?» immediatament després que la fila 1 digui que alguna cosa va malament.
Fila 3 — JVM i recursos (USE). Memòria de heap després de GC, rate(jvm_gc_pause_seconds_sum[5m]), fils vius, hikaricp_connections_active davant de max al mateix gràfic, i hikaricp_connections_pending com a stat amb llindar a 1. És la fila de les causes.
Fila 4 — Negoci. Lloguers per minut i tarifa (barres apilades), bicicletes disponibles per estació (mapa de calor o taula ordenada ascendentment, per veure primer les estacions buides), durada mitjana del trajecte i taxa d'encerts per memòria cau. És la fila que l'ajuntament entén sense traducció, i la que detecta un desplegament que va trencar el lloguer.
Variables de plantilla. En lloc de duplicar el quadre per entorn, es defineixen variables: entorn com a query label_values(up, entorn), i instancia com a label_values(up{entorn="$entorn"}, instance) amb l'opció All activada. Després, totes les consultes fan servir {entorn="$entorn", instance=~"$instancia"}. Un sol quadre serveix per a pre i prod, i permet aïllar una rèplica sospitosa amb un desplegable.
Bones pràctiques de visualització, que separen un quadre útil d'un de decoratiu: unitats sempre (segons, peticions/s, bytes, per cent) perquè un número sense unitat indueix a error; els percentils al mateix gràfic que la mediana, per veure la separació entre l'experiència típica i la dolenta; llindars dibuixats com a línia, de manera que el SLO sigui visible i no una dada de la documentació; escala logarítmica en latències, que solen abastar ordres de magnitud; res de gràfics de sectors per a sèries temporals; i com a màxim uns 12 panells, perquè un quadre de 40 no es mira mai.
- Regles d'alerta
Les regles s'avaluen a Prometheus, cada evaluation_interval, i viuen als fitxers de rule_files:
# observabilitat/regles/ciclourbana.yml
groups:
- name: ciclourbana-servei
interval: 30s
rules:
- alert: AplicacioCaiguda
expr: up{job="ciclourbana"} == 0
for: 2m
labels: { severity: critica, equip: plataforma }
annotations:
summary: "La instancia {{ $labels.instance }} no respon"
description: "Prometheus no aconsegueix recollir metriques des de fa 2 minuts."
runbook_url: "https://wiki.ribalta.example/runbooks/ciclourbana-caiguda"
- alert: TaxaErrorAlta
expr: |
sum(rate(http_server_requests_seconds_count{outcome="SERVER_ERROR"}[5m]))
/ sum(rate(http_server_requests_seconds_count[5m])) > 0.01
for: 5m
labels: { severity: critica, equip: plataforma }
annotations: { summary: "Taxa d'error del {{ $value | humanizePercentage }}" }
- alert: LatenciaP99Degradada
expr: |
histogram_quantile(0.99,
sum by (le) (rate(http_server_requests_seconds_bucket{uri="/api/v1/lloguers"}[5m]))
) > 1
for: 10m
labels: { severity: avis, equip: plataforma }
annotations: { summary: "p99 d'inici de lloguer per sobre d'1 s" }
- alert: PoolDeConnexionsSaturat
expr: hikaricp_connections_pending > 5
for: 5m
labels: { severity: critica, equip: plataforma }
annotations: { summary: "{{ $value }} fils esperant connexio a {{ $labels.instance }}" }
- alert: SenseLloguersEnHoraPunta
expr: |
sum(rate(ciclourbana_lloguers_iniciats_total[10m])) == 0
and on() (hour() >= 6 < 9) # 7-10 h en horari de Ribalta (UTC+1)
for: 10m
labels: { severity: critica, equip: producte }
annotations: { summary: "Cap lloguer iniciat en 10 min en hora punta" }Els quatre camps d'una regla i el seu paper. expr és una consulta PromQL: l'alerta està activa mentre retorni alguna sèrie. for és l'element més subestimat: exigeix que la condició es mantingui durant aquest temps abans de disparar, i és el que elimina el 90 % del soroll —un pic de dos segons no desperta ningú—. labels classifica l'alerta i és el que Alertmanager farà servir per encaminar. I annotations és el missatge per a l'humà, amb plantilles Go ({{ $value }}, {{ $labels.instance }}) i, molt recomanable, un runbook_url: a les quatre de la matinada, un enllaç als passos de diagnòstic val més que qualsevol gràfic.
L'última regla mereix deteniment perquè és la més valuosa i la més difícil d'escriure: alerta que no està passant una cosa que hauria de passar. Requereix acotar l'horari —hour() retorna l'hora UTC, d'aquí el desfasament— perquè no dispari a les quatre de la matinada, quan zero lloguers és el normal a Ribalta.
- Alertmanager: rutes, agrupació i silencis
Prometheus dispara; Alertmanager decideix què fer:
route:
group_by: [alertname, entorn] # agrupa les 3 instancies en un sol missatge
group_wait: 30s # espera per si arriben companyes
group_interval: 5m # cada quant informa de novetats del grup
repeat_interval: 4h # cada quant insisteix amb el ja notificat
receiver: slack-plataforma
routes:
- matchers: [severity="critica"]
receiver: guardia
continue: true # a mes, segueix al receptor per defecte
- matchers: [equip="producte"]
receiver: slack-producte
inhibit_rules:
- source_matchers: [alertname="AplicacioCaiguda"]
target_matchers: [severity="avis"]
equal: [instance] # si esta caiguda, callar els seus avisos derivats
receivers:
- name: slack-plataforma
slack_configs:
- api_url_file: /etc/alertmanager/secrets/slack
channel: "#ciclourbana-alertes"
title: "{{ .CommonAnnotations.summary }}"
- name: guardia
pagerduty_configs: [{ routing_key_file: /etc/alertmanager/secrets/pagerduty }]
- name: slack-producte
slack_configs:
- api_url_file: /etc/alertmanager/secrets/slack
channel: "#ciclourbana-producte"Tres mecanismes que eviten l'infern de notificacions. L'agrupació converteix tres alertes idèntiques de les tres rèpliques en un sol missatge. Les regles d'inhibició callen les alertes derivades: si l'aplicació està caiguda, no té sentit avisar a més que la seva latència és estranya. I els silencis, que es creen des de la interfície d'Alertmanager amb una durada i un motiu, serveixen per a una finestra de manteniment coneguda —una migració de Flyway pesada, per exemple—; convé que siguin sempre temporals, perquè un silenci permanent és una alerta esborrada sense dir-ho.
- Bones pràctiques d'alertes
Alertar sobre símptomes, no sobre causes. «La latència p99 supera 1 s» és un símptoma que el ciutadà nota; «la CPU està al 85 %» és una causa que pot ser perfectament normal. Si s'alerta de causes, l'equip rep avisos de coses que no afecten ningú i deixa de mirar-los, que és exactament com es perd una alerta important.
Cada alerta ha d'exigir una acció humana i immediata. La pregunta que decideix és: si això arriba a les quatre de la matinada, hi ha alguna cosa a fer ara? Si la resposta és «mirar-ho demà», no és una alerta: és un panell del quadre de comandament o, com a molt, un avís en horari laboral. La distinció severity: critica davant de severity: avis de l'apartat 15 és exactament això, amb destinacions diferents.
Combatre la fatiga d'alertes. Un equip que rep trenta notificacions al dia deixa de llegir-les en dues setmanes, i a partir d'aquí el sistema d'alertes no existeix encara que continuï funcionant. Les eines: for generós, agrupació, inhibició i esborrar sense pietat les alertes que mai no han portat a una acció.
Posar sempre un runbook_url. Qui rep l'alerta a les quatre de la matinada pot no ser qui va escriure el codi. Tres línies de «comprova això, mira allò, si és això fes allò altre» canvien completament el resultat.
Alertar també sobre l'absència. Els sistemes trencats de vegades deixen d'emetre en lloc d'emetre errors. absent() i la regla de «sense lloguers en hora punta» cobreixen aquest buit.
Provar les alertes. Prometheus porta promtool test rules, que permet escriure proves unitàries de les regles amb sèries simulades. Una alerta que mai no s'ha vist disparar-se és una hipòtesi, no una alerta.
- Alternatives gestionades
| Solució | Model | Avantatge principal | Inconvenient | Quan triar-la |
|---|---|---|---|---|
| Prometheus + Grafana propis | Autogestionat | Control total, sense cost de llicència, estàndard | Cal operar-lo i dimensionar-lo | CicloUrbana: volum moderat i equip capaç |
| Grafana Cloud | Gestionat | Mateix PromQL i mateixos quadres; sense operar res | Cost per sèrie ingerida | Equip petit que vol el mateix sense mantenir-ho |
| Datadog | Gestionat, push amb agent | Mètriques, logs i APM integrats i molt polits | El més car, amb diferència; efecte de bloqueig | Empresa que prioritza integració sobre cost |
| New Relic | Gestionat | APM molt fort, instrumentació automàtica | Cost; model de dades propi | Diagnòstic profund d'aplicacions |
| Elastic APM | Tots dos | Mètriques, logs i traces a la mateixa pila que 09-05 | Operar Elasticsearch no és barat | Ja es fa servir ELK per als logs |
| CloudWatch (08-03) | Gestionat a AWS | Integrat amb ECS, RDS i ALB; sense infraestructura | PromQL no; consultes i panells pobres | Tot a AWS i necessitats senzilles |
El criteri. La pregunta no és quin és millor, sinó qui operarà això i amb quin pressupost. Prometheus autogestionat és gratuït en llicències i car en atenció; els gestionats inverteixen els termes. Per a CicloUrbana —tres instàncies, unes 15.000 sèries, un equip que ja opera Kubernetes— la pila pròpia és l'elecció correcta, amb una sortida natural cap a Grafana Cloud si el manteniment comença a pesar, perquè el llenguatge de consulta i els quadres de comandament es conserven. Aquest és, de fet, l'argument estratègic més fort a favor de l'ecosistema Prometheus: no tanca portes.
I una nota que anticipa el final del mòdul: OpenTelemetry s'està convertint en l'estàndard comú per emetre mètriques, logs i traces, amb un protocol (OTLP) que gairebé totes les destinacions accepten. Prometheus ja sap ingerir OTLP, Micrometer té pont cap a ell i els backends comercials el suporten. És la direcció cap a la qual convergeix tot, i és la que prendrem a 09-06 per a la traçabilitat distribuïda.
Errors Comuns i Consells
Oblidar metrics_path: /actuator/prometheus. Prometheus busca /metrics, l'objectiu apareix en vermell i tota la resta sembla trencada. És la fallada número u: comprova-ho a Status → Targets.
Fer servir el valor absolut d'un comptador. ciclourbana_lloguers_iniciats_total sense rate() dóna un número que només creix i que es posa a zero a cada desplegament. Sobre un comptador, sempre rate o increase.
Oblidar le al sum by d'histogram_quantile. El resultat és un número sense significat que a més sembla plausible. Sempre sum by (le, ...).
Finestra de rate massa curta. Amb recollida cada 15 s, rate(x[30s]) no té mostres suficients i produeix buits. Regla: la finestra, almenys quatre vegades l'interval de recollida.
Alertar sense for. Un pic de dos segons desperta algú. Gairebé tota alerta necessita un for d'entre 2 i 15 minuts.
Alertar sobre causes. CPU al 85 %, memòria al 70 %, GC freqüent: són diagnòstics, no problemes. Alerta sobre el que pateix l'usuari i fes servir aquestes altres mètriques per investigar.
No persistir el volum de Prometheus ni el de Grafana. Un reinici esborra la història i els quadres de comandament construïts a mà. Volums amb nom i, millor encara, quadres aprovisionats per fitxer al repositori.
Deixar Grafana amb admin/admin accessible. Els quadres de comandament revelen volums de negoci, endpoints i versions. Contrasenya obligatòria i registre deshabilitat.
Consell: aprovisiona per fitxer els orígens de dades, els quadres i les regles, i versiona'ls al repositori al costat del codi. Un quadre de comandament construït a mà a la interfície és infraestructura sense control de versions.
Consell: comença important el 4701 i el 12900 i construeix un únic quadre propi amb el de negoci de Ribalta. Reinventar el gràfic de memòria de la JVM no aporta res.
Consell: escriu l'alerta alhora que la mètrica. Una mètrica de negoci sense alerta es converteix en un panell que ningú no mira.
Exercicis
Exercici 1: escriure les consultes
Escriu la consulta PromQL per a cada pregunta de l'ajuntament i explica cada funció que facis servir: (a) quants lloguers s'han iniciat avui a l'estació «Universitat»; (b) quin percentatge de les peticions a /api/v1/estacions es respon en menys de 300 ms —el SLO de 09-03—; (c) quina instància consumeix més memòria de heap; (d) la taxa d'encerts de la memòria cau estacions en l'última hora; (e) si alguna rèplica fa més de cinc minuts que té el tallacircuits de pagaments obert.
Exercici 2: l'alerta que no dispara
L'equip va definir aquesta regla i, durant una caiguda real de 20 minuts en què l'API va retornar 500 a tot, no va arribar cap notificació. Troba els tres motius possibles i explica com verificaries cadascun.
- alert: ErrorsALaApi
expr: http_server_requests_seconds_count{status="500"} > 100
for: 30m
labels: { severity: info }
annotations: { summary: "Errors a l'API" }Exercici 3: dissenyar el quadre de comandament de guàrdia
L'ajuntament demana una pantalla que es projecti a la sala d'operacions i que permeti a algú sense coneixements tècnics saber si la xarxa de Ribalta funciona. Dissenya aquest quadre de comandament: tria sis panells com a màxim, indica la consulta PromQL de cadascun, el tipus de visualització, les unitats i els llindars de color, i justifica què has deixat fora i per què.
Solucions
Solució 1.
(a) Lloguers d'avui a «Universitat»:
increase dóna l'increment total del comptador a la finestra —a diferència de rate, que dóna el ritme per segon—, i sum col·lapsa les tres rèpliques i els tres tipus de tarifa en un únic número. Advertiment honest: [24h] és «les últimes 24 hores», no «des de mitjanit»; per a això cal ajustar el rang del panell o fer servir una regla de registre diària.
(b) Percentatge per sota de 300 ms:
sum(rate(http_server_requests_seconds_bucket{uri="/api/v1/estacions", le="0.3"}[5m]))
/
sum(rate(http_server_requests_seconds_count{uri="/api/v1/estacions"}[5m]))Aquí es cobra la configuració slo: 300ms de 09-03: com que les cubetes són acumulatives, la de le="0.3" conté exactament les peticions que van complir l'objectiu, i dividir-la pel total dóna la proporció de compliment. És la manera correcta de mesurar un SLO, millor que histogram_quantile, perquè respon a la pregunta contractual: «quin percentatge va complir», no «quin és el percentil». Requisit imprescindible: que existeixi una cubeta exactament a 0.3.
(c) Instància amb més heap:
sum by (instance) agrega els diferents pools de memòria (Eden, Survivor, Old) de cada JVM, i topk(1, ...) retorna la més gran conservant l'etiqueta instance, que és la resposta buscada.
(d) Taxa d'encerts de la memòria cau:
sum(rate(cache_gets_total{cache="estacions", result="hit"}[1h]))
/
sum(rate(cache_gets_total{cache="estacions"}[1h]))El denominador inclou encerts i fallades perquè no filtra per result. Si el resultat és per sota de 0,2, cal revisar la clau o l'autoinvocació de 09-02; en un panell convé mostrar-ho com a percentatge amb llindars.
(e) Tallacircuits obert més de cinc minuts:
min_over_time sobre la finestra val 1 només si la sèrie ha valgut 1 durant tota la finestra, cosa que expressa exactament «fa cinc minuts que és obert» i no «es va obrir en algun moment». L'alternativa idiomàtica és expr: ... == 1 amb for: 5m en una regla d'alerta, que és com s'escriuria en producció.
Solució 2.
Motiu 1 — l'expressió fa servir un comptador sense rate. http_server_requests_seconds_count és acumulatiu des de l'arrencada, així que > 100 es compleix sempre en qualsevol aplicació amb trànsit... i per això, paradoxalment, no distingeix una caiguda d'un dia normal. A més, si la caiguda va provocar reinicis, el comptador es va posar a zero i va baixar de 100. El correcte és una proporció amb rate. Com verificar-ho: executar l'expressió a la interfície de Prometheus i veure que retorna un valor gran fins i tot en un moment sa.
Motiu 2 — for: 30m és més llarg que la incidència. La caiguda va durar 20 minuts, així que l'alerta va estar en estat pending tot aquest temps i es va resoldre abans de passar a firing: mai no va arribar a Alertmanager. Com verificar-ho: mirar l'estat històric a Alerts o consultar la mètrica ALERTS{alertstate="pending"}. Regla pràctica: el for ha de ser bastant menor que la durada de l'incident que es vol detectar; 5 minuts és un valor raonable.
Motiu 3 — severity: info probablement no s'encamina a cap receptor. Les rutes de l'apartat 16 encaminen critica a la guàrdia i equip=producte al seu canal; una etiqueta que no casa amb cap ruta específica cau al receptor per defecte, i si l'equip el té apuntat a un canal que ningú no llegeix, l'efecte pràctic és el mateix que no alertar. Com verificar-ho: fer servir amtool config routes test severity=info o l'eina d'encaminament de la interfície d'Alertmanager.
A més, l'anotació «Errors a l'API» no diu quants, ni on, ni què fer, i no hi ha runbook_url. Versió corregida:
- alert: TaxaErrorAlta
expr: |
sum(rate(http_server_requests_seconds_count{outcome="SERVER_ERROR"}[5m]))
/ sum(rate(http_server_requests_seconds_count[5m])) > 0.01
for: 5m
labels: { severity: critica, equip: plataforma }
annotations:
summary: "Taxa d'error {{ $value | humanizePercentage }} a {{ $labels.entorn }}"
runbook_url: "https://wiki.ribalta.example/runbooks/taxa-error"Solució 3.
Principi de disseny: l'audiència no és tècnica, així que cada panell ha de respondre una pregunta en llenguatge de servei, amb color com a informació principal i el número com a detall. Sis panells:
| Panell | Consulta | Visualització | Unitat i llindars |
|---|---|---|---|
| Està en marxa? | avg(up{job="ciclourbana"}) * 100 |
Stat gran | %; vermell < 100, verd = 100 |
| Lloguers per minut | sum(rate(ciclourbana_lloguers_iniciats_total[5m])) * 60 |
Gràfic de línies | llog/min; el pols del servei |
| Peticions amb error | proporció SERVER_ERROR × 100 |
Stat amb fons de color | %; verd < 0,5, ambre < 1, vermell ≥ 1 |
| Temps de resposta (p95) | histogram_quantile(0.95, sum by (le) (rate(http_server_requests_seconds_bucket[5m]))) |
Gràfic amb línia de llindar a 0,3 s | segons; log |
| Bicicletes disponibles a la xarxa | sum(ciclourbana_bicicletes_disponibles) i count(... == 0) |
Stat doble | unitats; vermell si hi ha > 5 estacions buides |
| Estacions amb menys bicicletes | bottomk(5, ciclourbana_bicicletes_disponibles) |
Taula ordenada | unitats |
Què es deixa fora i per què. Memòria de heap, pauses de GC, fils de Tomcat, ús del pool, taxa d'encerts de memòria cau i latència per endpoint: tot això són causes, no símptomes, i el seu públic és l'equip tècnic. Un operari de l'ajuntament no pot fer res amb «GC pause 180 ms», i la seva presència només aconsegueix que els panells que sí que importen es perdin entre soroll. Viuen al quadre de comandament tècnic de l'apartat 14 i al 4701 importat, a un clic de distància.
Dues decisions addicionals. La segona fila del quart panell dibuixa el SLO com a línia de llindar, perquè «va lent» sigui visible sense interpretar números. I el panell de lloguers per minut és deliberadament el més gran: és l'únic que detecta un servei funcionalment trencat amb tots els indicadors tècnics en verd, el cas que a 09-03 vam identificar com el que les mètriques tècniques no veuen mai. Convé a més fixar el rang temporal per defecte en 6 hores i activar el refresc automàtic cada 30 segons, coherent amb l'interval de recollida.
Conclusió
Les mètriques de CicloUrbana han sortit del procés. Entens per què calia un sistema extern —persistència, història, agregació entre instàncies i avaluació contínua— i per què Prometheus va a buscar les dades en lloc d'esperar-les, amb l'avantatge decisiu que «la instància està caiguda» es converteix en una consulta (up == 0) en lloc d'una inferència, i amb el Pushgateway com a excepció acotada per a processos efímers. Saps exposar les mètriques amb micrometer-registry-prometheus al port de gestió de 07-01, i llegir el format de text: # HELP, # TYPE, les sèries amb les seves etiquetes i els sufixos _count, _sum i _bucket amb l'etiqueta le acumulativa, que són la traducció literal dels histogrames que vas configurar a 09-03.
Tens un prometheus.yml complet per a Ribalta —amb el metrics_path que gairebé tothom oblida, l'autenticació que exigeix la cadena de seguretat d'Actuator i els intervals triats amb criteri— i saps substituir la llista estàtica per descobriment a Kubernetes amb anotacions i relabel_configs, de manera que cada rèplica que creï l'HPA de 08-04 comenci a mesurar-se sola. Coneixes el cost real de l'emmagatzematge i la seva dependència del nombre de sèries, no del trànsit, cosa que retorna una vegada més a la cardinalitat.
I saps PromQL de debò: selectors amb els seus quatre comparadors, rate sempre sobre un comptador i per què irate només serveix per depurar, sum by i sum without per agregar les tres instàncies, histogram_quantile amb el le obligatori per obtenir el p95 real que era impossible amb percentils calculats a la JVM, més increase, avg_over_time, absent i les operacions entre sèries que produeixen proporcions. Amb aquestes peces has escrit les consultes concretes de CicloUrbana: trànsit i latència per endpoint, taxa d'error com a proporció, lloguers per minut i tarifa, bicicletes per estació, pool d'HikariCP, pauses de GC i encerts de memòria cau.
Del costat operatiu, tens la pila aixecada amb docker-compose al costat de la de 07-04, amb volums persistents i el postgres_exporter; Grafana aprovisionat per fitxer, amb l'estratègia d'importar el genèric (4701, 12900) i construir només el de negoci; el quadre de comandament de Ribalta organitzat en quatre files del que veu el ciutadà al que consumeix la màquina, amb variables de plantilla per entorn i instància; i les regles d'alerta amb expr, for, labels i annotations, inclosa la més valuosa i la més difícil d'escriure —la que avisa que no està passant una cosa que hauria de passar—. Alertmanager agrupa, inhibeix i silencia, i tens les regles d'or de les alertes: símptomes i no causes, cada alerta amb una acció possible, runbook_url sempre, i esborrar sense pietat el que només produeix fatiga.
Amb això, dos dels tres pilars són drets. Les mètriques diuen que alguna cosa va malament i des de quan; els quadres de comandament ho mostren i les alertes ho avisen. Però quan l'alerta arriba a les quatre de la matinada dient que la taxa d'error de l'inici de lloguer és del 3 %, la pregunta següent —què ha fallat exactament, en quina petició, amb quin usuari, amb quin missatge— no la respon cap gràfic. La respon el pilar que fem servir des de la primera lliçó sense haver-lo tractat mai seriosament: el registre d'esdeveniments. La lliçó següent, Gestió de Registres i Logs, el converteix en una eina de debò: nivells amb criteri, logback-spring.xml per perfil, logs estructurats en JSON, el rastreId del FiltreRastreig de 03-06 propagat i consultable, el que mai no s'ha de registrar sobre els ciutadans de Ribalta, i l'agregació centralitzada que permet buscar als logs de les tres instàncies com si fossin un de sol.
Curs de Spring Boot
Mòdul 1: Introducció a Spring Boot
- Què és Spring Boot?
- Configuració del teu entorn de desenvolupament
- Creant la teva primera aplicació Spring Boot
- Entenent l'estructura del projecte
- L'arrencada i el cicle de vida de l'aplicació
Mòdul 2: Conceptes bàsics de Spring Boot
- Anotacions de Spring Boot
- Injecció de dependències a Spring Boot
- Àmbit i cicle de vida dels beans
- Configuració de Spring Boot
- Propietats de Spring Boot
- Autoconfiguració i starters per dins
Mòdul 3: Construint serveis web RESTful
- Introducció als serveis web RESTful
- Creant controladors REST
- Gestió dels mètodes HTTP
- Validació de dades d'entrada
- DTOs i mapatge entre capes
- Gestió d'excepcions a REST
- Documentar l'API amb OpenAPI
Mòdul 4: Accés a dades amb Spring Boot
- Introducció a Spring Data JPA
- Configuració de fonts de dades
- Creació d'entitats JPA
- Relacions entre entitats
- Ús de repositoris de Spring Data
- Mètodes de consulta a Spring Data JPA
- Transaccions i gestió de la persistència
- Migracions d'esquema amb Flyway
Mòdul 5: Seguretat a Spring Boot
- Introducció a Spring Security
- Configuració de Spring Security
- Autenticació i autorització d'usuaris
- Implementació d'autenticació JWT
- Seguretat a nivell de mètode i enduriment de l'API
Mòdul 6: Proves a Spring Boot
- Introducció a les proves
- Proves unitàries amb JUnit
- Simulació amb Mockito
- Proves d'integració
- Proves amb Testcontainers
Mòdul 7: Funcions avançades de Spring Boot
- Spring Boot Actuator
- Perfils de Spring Boot
- Tasques programades i execució asíncrona
- Spring Boot amb Docker
- Spring Boot i microserveis
- Comunicació entre serveis i tolerància a fallades
Mòdul 8: Desplegament d'aplicacions Spring Boot
- Introducció al desplegament
- Desplegant a Heroku
- Desplegant a AWS
- Desplegant a Kubernetes
- Integració i lliurament continus
Mòdul 9: Rendiment i monitoratge
- Ajust de rendiment
- Memòria cau amb Spring Cache
- Monitoratge amb Spring Boot Actuator
- Ús de Prometheus i Grafana
- Gestió de registres i logs
- Traçabilitat distribuïda
