Os três pilares e o vocabulário

17 min
Público
iniciante, lição 01 lida
Duração
35 a 45 min
Módulo
1/7
Competência visada
reconhecer os quatro tipos de métricas Prometheus nas linhas reais de /metrics da API do labo, entender os labels e a cardinalidade, distinguir pull e push, ler uma linha de log JSON campo a campo, e situar os dez serviços do labo pela sua porta e sua URL

Em uma imagem

Retome o painel de controle do carro da lição 01. O odômetro só aumenta: é um contador (counter), como http_requetes_total. O medidor de combustível sobe ao encher e desce ao rodar: é um gauge (gauge), como requetes_en_cours. O caderno de manutenção que anota "12 trajetos de menos de 10 km, 30 de menos de 50 km, 3 de mais de 100 km" encaixa cada trajeto em uma faixa: é um histograma, como http_duree_requete_seconds. E a caixa-preta, que escreve uma linha por evento com a hora exata, é o log JSON da API. O Prometheus não recebe nada: é ele que vem ler os contadores a cada 15 segundos, como um fiscal que passa para conferir os medidores de cada carro da frota.

Como funciona

Os quatro tipos de métricas, nas linhas reais do labo

Uma métrica Prometheus sempre tem um nome, eventualmente labels, e um valor numérico. O tipo diz como ler esse valor no tempo. O Prometheus conhece quatro: o contador, o gauge, o histograma e o summary. Os três primeiros são usados pela API de catálogo; o quarto não é, e o motivo é instrutivo. Todas as linhas abaixo foram copiadas de http://localhost:8000/metrics na máquina do curso; os valores serão diferentes na sua.

Contador (counter). Um contador só aumenta, ou volta a zero se o serviço reiniciar. Ele conta eventos acumulados. A API expõe http_requetes_total, com três labels: o código de resposta, o método HTTP e a rota:

text
# HELP http_requetes_total Nombre de requêtes HTTP reçues, par méthode, route normalisée et code de réponse.
# TYPE http_requetes_total counter
http_requetes_total{code="200",methode="GET",route="/cours"} 3247.0
http_requetes_total{code="201",methode="POST",route="/inscriptions"} 855.0
http_requetes_total{code="404",methode="GET",route="/cours/{id}"} 210.0
http_requetes_total{code="500",methode="GET",route="/cours"} 32.0

O que você nunca faz com um contador: ler seu valor bruto para dizer "há 3247 requisições agora". Um contador nunca diminui; o que interessa é sua velocidade de crescimento, calculada com a função rate() (módulo 2). Documentação: Prometheus — Metric types, Counter.

Gauge (gauge). Um gauge sobe e desce livremente: um valor em um dado instante. A API expõe requetes_en_cours, o número de requisições em processamento no instante exato da coleta:

text
# HELP requetes_en_cours Nombre de requêtes HTTP en cours de traitement à cet instant.
# TYPE requetes_en_cours gauge
requetes_en_cours 1.0

Uma requisição está em curso no instante da coleta; no instante seguinte, esse número pode ser 0 ou 4. A API também expõe gauges de configuração, cujo valor é mais uma informação do que uma medida: api_info{version="1.0.0"} 1.0 (a versão está no label, o valor sempre é 1) e api_panne_taux_erreurs 0.01 (a proporção de requisições que a API faz falhar propositalmente: 1% em funcionamento normal). Documentação: Prometheus — Metric types, Gauge.

Histograma (histogram). Um histograma mede a distribuição de um valor, como a duração de uma requisição HTTP: quantas requisições levaram menos de 5 ms, menos de 10 ms, menos de 25 ms…? A API expõe http_duree_requete_seconds. Diferente de um contador ou um gauge, um histograma publica várias linhas para uma única métrica: um contador por faixa (bucket), mais uma contagem total e uma soma. Aqui estão as doze linhas da rota /cours:

text
# HELP http_duree_requete_seconds Durée de traitement des requêtes HTTP, en secondes, par route normalisée.
# TYPE http_duree_requete_seconds histogram
http_duree_requete_seconds_bucket{le="0.005",route="/cours"} 32.0
http_duree_requete_seconds_bucket{le="0.01",route="/cours"} 74.0
http_duree_requete_seconds_bucket{le="0.025",route="/cours"} 1566.0
http_duree_requete_seconds_bucket{le="0.05",route="/cours"} 3248.0
http_duree_requete_seconds_bucket{le="0.1",route="/cours"} 3276.0
http_duree_requete_seconds_bucket{le="0.25",route="/cours"} 3278.0
http_duree_requete_seconds_bucket{le="0.5",route="/cours"} 3279.0
http_duree_requete_seconds_bucket{le="1.0",route="/cours"} 3279.0
http_duree_requete_seconds_bucket{le="2.0",route="/cours"} 3279.0
http_duree_requete_seconds_bucket{le="+Inf",route="/cours"} 3279.0
http_duree_requete_seconds_count{route="/cours"} 3279.0
http_duree_requete_seconds_sum{route="/cours"} 85.42293146001248

Cada _bucket{le="…"} (le de less or equal) conta as requisições mais rápidas ou iguais a esse limite, de forma cumulativa: as 32 requisições de menos de 5 ms também são contadas nas 74 de menos de 10 ms, e nas 3279 do bucket +Inf (todas). _count é o número total de observações (3279, igual ao bucket +Inf), _sum a soma de todas as durações (85,4 segundos no total, ou seja, 26 ms em média por requisição). É isso que permite reconstruir um quantil depois, com histogram_quantile() (módulo 2). Leitura direta: 3248 requisições de 3279 levaram menos de 50 ms, ou seja, 99%. Documentação: Prometheus — Metric types, Histogram.

Summary (summary), e por que o labo não o usa. Um summary também mede uma distribuição, mas calcula seus quantis diretamente no programa observado, antes de publicá-los: uma linha {quantile="0.5"}, uma linha {quantile="0.9"}, mais _sum e _count. O problema: um quantil calculado no lado do cliente não pode ser recombinado com o de outra instância. Se o labo executasse três cópias da API, você não poderia fazer a média dos três quantile="0.9" para obter o verdadeiro percentil 90 do conjunto. Um histograma publica contadores brutos por faixa: o Prometheus pode somá-los entre instâncias antes de calcular o quantil. Por isso não existe nenhuma linha summary no /metrics da API do labo. Documentação: Prometheus — Histograms and summaries.

TipoO que medeNo laboPergunta que responde
ContadorUm acumulado que só aumentahttp_requetes_total, inscriptions_total, cours_consultes_total"Quantas requisições por segundo?" (com rate)
GaugeUm valor instantâneo que sobe e descerequetes_en_cours, api_disque_libre_octets, api_info"Quantas neste momento?"
HistogramaUma distribuição, por faixas cumulativashttp_duree_requete_seconds"95% das requisições levam menos de quanto?"
SummaryUma distribuição, quantis calculados no clientenenhumA mesma, mas sem poder agregar entre instâncias

Série temporal, labels e cardinalidade

Cada combinação única de nome de métrica e labels forma uma série temporal: uma sequência de pares (timestamp, valor) que o Prometheus armazena e consulta. http_requetes_total{code="200",methode="GET",route="/cours"} é uma série; http_requetes_total{code="201",methode="POST",route="/inscriptions"} é outra. Na máquina do curso, http_requetes_total conta 14 séries (14 combinações de código, método e rota que surgiram desde a inicialização). Os labels permitem filtrar e agrupar sem mudar o nome: "os erros de servidor, só em /inscriptions" se lê http_requetes_total{route="/inscriptions",code=~"5.."}. Documentação: Prometheus — Data model.

A cardinalidade de uma métrica é o número de séries distintas que ela produz. http_requetes_total{code,methode,route}, com cinco códigos, dois métodos e sete rotas em template, dá algumas dezenas de séries no máximo. Olhe o log da etapa 2 da lição 01: a requisição chamou /cours/C0038, mas a métrica traz route="/cours/{id}", o template da rota tal como o FastAPI a declarou. Se o label contivesse o identificador real, cada curso consultado criaria uma série: 64 cursos × 5 códigos × 2 métodos. Em um catálogo real com dezenas de milhares de cursos, a métrica explodiria e o Prometheus ficaria lento. A regra: um label deve ter um número limitado e razoável de valores; um identificador único, um endereço IP ou um timestamp não têm nada a fazer em um label. Uma rota que não existe é contada em route="inconnue", nunca no seu caminho real, pelo mesmo motivo.

O labo contém propositalmente uma exceção instrutiva: inscriptions_total{cours_id="C0028"} e cours_consultes_total{cours_id="…"} têm um label por curso. Com 64 cursos, isso continua limitado (64 séries cada). Com um milhão de cursos, seria um erro. O labo, aliás, tem um alerta PrometheusTropDeSeries que soa acima de 100.000 séries. Documentação: Prometheus — Instrumentation, cardinality.

Pull, scrape, target, exporter, job, instance

O Prometheus funciona em pull (ele vai buscar): ele mesmo consulta, a cada 15 segundos no labo (scrape_interval: 15s em prometheus/prometheus.yml), a URL /metrics de cada serviço que monitora. É o inverso de um sistema em push, onde o serviço observado envia suas métricas para um coletor. O pull tem uma vantagem direta: se um serviço parar de responder, o Prometheus sabe imediatamente, a métrica up passa para 0, sem depender do serviço em falha para sinalizar sua própria ausência. É exatamente isso que a prática deste módulo faz você provocar com casser api.

TermoO que éNo labo
scrapeUma leitura de /metrics pelo PrometheusA cada 15 s, em cada um dos 8 targets
target (alvo)Uma URL /metrics que o Prometheus lêhttp://api:8000/metrics, http://node-exporter:9100/metrics
exporterUm programa que expõe no formato Prometheus as métricas de um sistema que não fala esse formato nativamentenode-exporter (a máquina hospedeira), cadvisor (os contêineres)
jobUm grupo de targets que fazem o mesmo trabalhojob="api", job="prometheus", job="loki"… 8 jobs
instanceUm target individual dentro de um jobinstance="api:8000"
upA métrica que o Prometheus mesmo fabrica em cada scrape: 1 se o target respondeu, 0 caso contrárioup{job="api"} vale 1 em funcionamento normal

Os nomes api:8000, node-exporter:9100 são os da rede interna do Docker Compose: é o Prometheus, dentro do seu contêiner, falando com a API dentro do dela. Da sua máquina, a mesma página é http://localhost:8000/metrics. Documentação: Prometheus — Configuration, scrape_config e Prometheus — Jobs and instances.

Os logs estruturados: uma linha JSON por evento

Um log estruturado é escrito em um formato que a máquina decompõe sem adivinhar (JSON, na maioria das vezes), em vez de uma frase livre. A API escreve na sua saída padrão uma linha JSON por requisição processada. Aqui está uma linha real, lida por .\labo.ps1 journal api na máquina do curso:

json
{"horodatage": "2026-09-15T19:33:25.839+00:00", "niveau": "INFO", "id_requete": "46bb533b33e9", "methode": "GET", "route": "/cours/{id}", "code": 200, "duree_ms": 13.9, "message": "GET /cours/C0038 -> 200"}
CampoExemploO que é
horodatage2026-09-15T19:33:25.839+00:00O instante exato do evento, no formato ISO 8601, em UTC (+00:00)
niveauINFOA gravidade: INFO (2xx), WARNING (404, 422), ERROR (500). Três valores no labo
id_requete46bb533b33e9Um identificador único de 12 caracteres gerado para essa requisição, devolvido ao cliente no cabeçalho HTTP x-id-requete
methodeGETO método HTTP
route/cours/{id}O template da rota, o mesmo que na métrica
code200O código de resposta HTTP
duree_ms13.9A duração do processamento, em milissegundos
messageGET /cours/C0038 -> 200A frase legível: é aqui, e só aqui, que aparece o identificador real do curso

O que é preciso ver: route mantém o template /cours/{id} (como a métrica), mas message contém C0038, o identificador real. Um log pode se permitir esse detalhe: o Loki não indexa o texto da mensagem, ele indexa apenas quatro labels (service, conteneur, niveau, code) extraídos pelo Alloy. O módulo 5 mostra como o Alloy lê o JSON e fabrica esses labels. Documentação: Grafana Loki — Labels.

Os outros serviços do labo não escrevem todos em JSON. O webhook, na inicialização, escreve texto livre: INFO: Uvicorn running on http://0.0.0.0:8090 (Press CTRL+C to quit). O serviço charge escreve JSON, mas com outros campos: a cada 30 segundos, um resumo {"niveau": "INFO", "message": "résumé des 30 dernières secondes", "requetes": {"200": 214, "total": 262, "201": 26, "500": 4, "404": 16, "422": 2}}. Um log estruturado não é um formato universal: é uma decisão tomada serviço por serviço.

Os traços: menção, sem ferramenta neste labo

Um traço acompanha uma única requisição por vários serviços: cada etapa (um span) registra seu nome, sua duração, e sua relação pai-filho com as outras. Ele responde a "a requisição levou 800 ms, em qual serviço esse tempo foi gasto?". O labo não instala nenhuma ferramenta de rastreamento: a API de catálogo e o gerador de carga são os dois únicos serviços de aplicação, ligados por uma simples chamada HTTP, o que limita o interesse pedagógico de um traço distribuído aqui. O id_requete do log já é o primeiro pedaço de um traço: é ele que o OpenTelemetry chamaria de trace id. O módulo 7 fala um pouco mais sobre isso. Documentação: OpenTelemetry — Traces.

Os dez serviços: papel, porta, URL

ServiçoPapelPortaURL na sua máquina
prometheusLê os targets, armazena as séries, avalia as regras9090http://localhost:9090
alertmanagerRecebe os alertas do Prometheus, os agrupa e os roteia9093http://localhost:9093
grafanaExplora e visualiza Prometheus, Loki e Alertmanager3000 (GRAFANA_PORT)http://localhost:3000 (admin / aiopsatlas2026)
lokiArmazena os logs, indexados por labels3100http://localhost:3100/ready
alloyDescobre os contêineres e transporta seus logs para o Loki12345http://localhost:12345
node-exporterMétricas da máquina hospedeira9100http://localhost:9100/metrics
cadvisorMétricas de cada contêiner8080http://localhost:8080
apiO serviço observado8000http://localhost:8000/cours · http://localhost:8000/metrics
webhookRecebe e exibe os alertas8090http://localhost:8090
chargeGera tráfego para a APInenhumanenhuma: não tem interface, só um log

Nove portas, dez serviços: charge não escuta em nenhuma. Oito targets Prometheus, dez serviços: charge e webhook não expõem /metrics.

Passo a passo

Este passo a passo assume o labo iniciado (lição 04). Se você está lendo esta lição antes, guarde-o para depois: cada etapa é feita no navegador ou em um terminal, apenas em modo de leitura.

  1. Abra http://localhost:8000/metrics no navegador. É a página bruta que o Prometheus lê a cada 15 segundos: texto, uma linha por série, precedida de suas linhas # HELP e # TYPE. Na máquina do curso, ela tem 271 linhas. Compare com http://localhost:9100/metrics (node-exporter: 1578 linhas) e http://localhost:8080/metrics (cAdvisor: 3444 linhas, para dez contêineres).

    O que é preciso ver: as linhas que começam com python_ e process_ no topo da página da API não são escritas pelo labo. A biblioteca prometheus_client as adiciona sozinha (memória do processo, coletor de lixo do Python). As métricas do curso começam em http_requetes_total.

  2. Procure os quatro # TYPE da API. Na página, procure (Ctrl+F) por # TYPE http_: você encontra counter para http_requetes_total e histogram para http_duree_requete_seconds. Procure # TYPE requetes_en_cours: gauge. Procure summary: nenhum resultado.

    O que é preciso ver: os três tipos usados, e a ausência proposital do quarto.

  3. Conte as séries de http_requetes_total no Prometheus. Abra http://localhost:9090, digite http_requetes_total no campo de query e execute. Na máquina do curso, a tabela exibe 14 linhas, entre elas:

    text
    http_requetes_total{code="200", instance="api:8000", job="api", methode="GET", route="/cours", service="api"}   3241
    http_requetes_total{code="500", instance="api:8000", job="api", methode="GET", route="/cours", service="api"}   32

    O que é preciso ver: o Prometheus adicionou três labels aos da página /metrics: job="api" e instance="api:8000" (que identificam o target) e service="api" (adicionado pela configuração do job em prometheus.yml). O valor 3241 é um pouco menor que o 3247 lido na etapa 1: o Prometheus mostra o último scrape, que aconteceu entre 0 e 15 segundos atrás.

  4. Leia uma linha de log da API no terminal. Na pasta lab3:

    powershell
    .\labo.ps1 journal api

    Na máquina do curso, as últimas linhas se parecem com:

    text
    labo-api  | {"horodatage": "2026-09-15T19:33:25.916+00:00", "niveau": "INFO", "id_requete": "5ccaee4d1a2a", "methode": "GET", "route": "/sante", "code": 200, "duree_ms": 1.0, "message": "GET /sante -> 200"}
    labo-api  | {"horodatage": "2026-09-15T19:33:26.217+00:00", "niveau": "INFO", "id_requete": "e64206564a68", "methode": "GET", "route": "/cours/{id}", "code": 200, "duree_ms": 8.2, "message": "GET /cours/C0043 -> 200"}

    O que é preciso ver: GET /sante a cada poucos segundos, é o Docker verificando a saúde do contêiner (o healthcheck); o resto vem do serviço charge. O prefixo labo-api | é adicionado pelo Docker Compose, não faz parte do JSON.

  5. Verifique os labels que o Loki conhece. Abra http://localhost:3100/loki/api/v1/labels:

    json
    {"status":"success","data":["code","conteneur","niveau","service"]}

    Depois http://localhost:3100/loki/api/v1/label/niveau/values:

    json
    {"status":"success","data":["ERROR","INFO","WARNING"]}

    O que é preciso ver: quatro labels, não oito. O Loki não indexa id_requete, nem duree_ms, nem message: esses campos ficam no texto da linha, onde o LogQL pode extraí-los quando necessário com | json (módulo 5). Os três valores de niveau confirmam a tabela acima.

Se travar

As mensagens abaixo foram provocadas de verdade no Prometheus, na máquina do curso. Elas voltarão no módulo 2; melhor já reconhecê-las agora.

  • Esquecer as aspas em torno de um valor de label: up{job=api} retorna

    text
    invalid parameter "query": 1:8: parse error: unexpected identifier "api" in label matching, expected string

    Um valor de label é sempre uma string entre aspas: up{job="api"}. Mesmo para um número: http_requetes_total{code=500} retorna parse error: unexpected character inside braces: '5'; é preciso escrever code="500".

  • Chamar rate() sem uma janela de tempo: rate(http_requetes_total) retorna

    text
    invalid parameter "query": 1:6: parse error: expected type range vector in call to function "rate", got instant vector

    rate() precisa de um intervalo entre colchetes: rate(http_requetes_total[1m]). Sem colchetes, você dá a ele o último valor (um instant vector), quando ele precisa de uma sequência de valores (um range vector).

  • Chamar rate() em um gauge: rate(requetes_en_cours[1m]) não gera erro, mas o Prometheus exibe um aviso:

    text
    PromQL info: metric might not be a counter, name does not end in _total/_sum/_count/_bucket: "requetes_en_cours" (1:6)

    O resultado (0.017… na máquina do curso) não tem nenhum sentido: a velocidade de crescimento de um valor que sobe e desce não é uma informação útil. rate() é reservado a contadores.

  • Errar o nome de uma métrica: http_request_total (no singular, sem o "e" francês) retorna um resultado vazio, sem mensagem de erro. O Prometheus não conhece essa métrica, ele não a corrige. O nome exato é http_requetes_total. Da mesma forma, http_requetes_total{route="/inexistant"} retorna um resultado vazio: a rota desconhecida é contada em route="inconnue", não no seu caminho real.

Para lembrar

Os quatro tipos de métricas Prometheus são o contador (http_requetes_total, que só aumenta e se lê com rate()), o gauge (requetes_en_cours, que sobe e desce), o histograma (http_duree_requete_seconds, doze linhas por rota: dez _bucket cumulativos, _count, _sum) e o summary (ausente do labo, porque seus quantis não se agregam entre instâncias). Uma série temporal é uma combinação única de nome e labels; http_requetes_total tem 14 na máquina do curso. A cardinalidade explica por que a métrica traz route="/cours/{id}" enquanto o log contém C0038 na sua mensagem. O Prometheus funciona em pull: ele lê (scrape) oito targets a cada 15 segundos, agrupados por job, identificados por instance, e fabrica ele mesmo a métrica up. Um log estruturado do labo é uma linha JSON com oito campos, incluindo id_requete, devolvido também no cabeçalho x-id-requete; o Loki indexa apenas quatro desses como labels: service, conteneur, niveau, code. Dez serviços, nove portas (charge não tem nenhuma), oito targets Prometheus (charge e webhook não expõem /metrics).

Para ir além

  • Prometheus — Exposition formats: a gramática exata da página /metrics que você abriu na etapa 1 (as linhas # HELP, # TYPE, o escape dos labels).
  • Prometheus — Metric and label naming: por que _total para um contador, _seconds para uma duração, _bytes para um tamanho; a API do labo respeita essas convenções, com uma exceção: os nomes estão em francês.
  • Prometheus — Histograms and summaries: o artigo de referência sobre a escolha entre histograma e summary, com os erros de aproximação do histogram_quantile.
  • prometheus_client (Python): a biblioteca usada por api/app.py para expor as métricas; o módulo 3 faz você adicionar sua própria métrica com ela.
  • Grafana Loki — Labels: por que poucos labels, e nunca um identificador único dentro: a mesma regra de cardinalidade do Prometheus.
  • O módulo 2 retoma cada termo na prática: escrever up, filtrar por label, calcular um rate(), ler um histograma com histogram_quantile().