Observabilidade

Prometheus

Referência completa de Prometheus: PromQL, instrumentação, alertas, exporters e configurações para ambientes de produção

Conceitos Fundamentais

O Modelo Pull

Prometheus usa pull model: ele mesmo coleta métricas dos alvos em intervalos configurados (scraping). Isso simplifica a descoberta e evita que alvos sobrecarreguem o servidor com pushes.

┌─────────────┐    HTTP GET /metrics    ┌─────────────────┐
│  Prometheus │ ──────────────────────► │  Target (app)   │
│   Server    │ ◄──────────────────────  │  :8080/metrics  │
└─────────────┘    exposition format    └─────────────────┘


┌─────────────┐
│    TSDB     │  (Time Series Database local)
└─────────────┘

Vantagens do pull model:

  • Simples de depurar (basta acessar /metrics manualmente)
  • Prometheus controla o ritmo de coleta
  • Facilita descoberta de alvos mortos
  • Sem necessidade de abrir portas no firewall para o app

TSDB — Time Series Database

Prometheus armazena dados como séries temporais: pares (timestamp, valor float64) identificados por nome e labels.

# Série temporal: nome{label1="valor1", label2="valor2"} → [(t1, v1), (t2, v2), ...]
http_requests_total{method="GET", status="200", job="api"} → [(1700000000, 1042), (1700000015, 1053), ...]

Características do TSDB:

  • Blocos imutáveis de 2 horas no disco
  • Compactação em background
  • Retenção padrão: 15 dias
  • Formato Prometheus TSDB (não compatível com outras ferramentas diretamente)
  • Write-ahead log (WAL) para durabilidade

Formato de Exposição (Exposition Format)

# HELP http_requests_total Total de requisições HTTP recebidas
# TYPE http_requests_total counter
http_requests_total{method="GET",status="200"} 1027
http_requests_total{method="POST",status="201"} 43
http_requests_total{method="GET",status="404"} 12

# HELP process_memory_bytes Uso de memória em bytes
# TYPE process_memory_bytes gauge
process_memory_bytes 2.4e+08

# HELP http_request_duration_seconds Duração das requisições
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.005"} 847
http_request_duration_seconds_bucket{le="0.01"} 923
http_request_duration_seconds_bucket{le="0.025"} 1001
http_request_duration_seconds_bucket{le="0.05"} 1020
http_request_duration_seconds_bucket{le="+Inf"} 1027
http_request_duration_seconds_sum 5.23
http_request_duration_seconds_count 1027

Tipos de Métricas

Counter

O que é: Valor que só aumenta (ou reseta ao reiniciar o processo).

Quando usar:

  • Número total de requisições
  • Número de erros
  • Bytes enviados/recebidos
  • Tarefas completadas
# Exemplo em Python
from prometheus_client import Counter

requests_total = Counter(
    'http_requests_total',
    'Total de requisições HTTP',
    ['method', 'status']
)

# Incrementar
requests_total.labels(method='GET', status='200').inc()
requests_total.labels(method='POST', status='500').inc(1)

PromQL com counters — use rate() ou increase():

# Taxa de requisições por segundo (média nos últimos 5 minutos)
rate(http_requests_total[5m])

# Aumento absoluto nos últimos 10 minutos
increase(http_requests_total[10m])

Gauge

O que é: Valor que pode subir e descer livremente.

Quando usar:

  • Temperatura atual
  • Número de goroutines/threads ativas
  • Uso de memória
  • Itens em uma fila
  • Conexões abertas
from prometheus_client import Gauge

memory_usage = Gauge('process_memory_bytes', 'Uso de memória em bytes')
queue_size = Gauge('job_queue_size', 'Itens na fila de jobs')

# Setar valor
memory_usage.set(1024 * 1024 * 256)  # 256 MB

# Incrementar/decrementar
queue_size.inc()
queue_size.dec()

# Medir duração de função (gauge de tempo)
@memory_usage.track_inprogress()
def process_job():
    pass

PromQL com gauges — use o valor direto ou avg_over_time():

# Valor atual
process_memory_bytes

# Média nas últimas 5 minutos
avg_over_time(process_memory_bytes[5m])

# Máximo nas últimas 1 hora
max_over_time(process_memory_bytes[1h])

Histogram

O que é: Amostra observações e as distribui em buckets configuráveis. Calcula soma e contagem automaticamente.

Quando usar:

  • Latência de requisições HTTP
  • Tamanho de payloads
  • Duração de queries SQL
  • Qualquer distribuição que precise de percentis

O que gera automaticamente:

  • <nome>_bucket{le="N"} — contagem de observações ≤ N
  • <nome>_sum — soma de todas observações
  • <nome>_count — número total de observações
from prometheus_client import Histogram

request_duration = Histogram(
    'http_request_duration_seconds',
    'Duração das requisições HTTP',
    ['method', 'endpoint'],
    buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]
)

# Observar um valor
request_duration.labels(method='GET', endpoint='/api/users').observe(0.042)

# Usar como context manager
with request_duration.labels(method='POST', endpoint='/api/orders').time():
    process_order()

PromQL com histograms:

# Percentil 99 de latência (nos últimos 5 minutos)
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

# Percentil 95 por endpoint
histogram_quantile(0.95,
  sum by (endpoint, le) (rate(http_request_duration_seconds_bucket[5m]))
)

# Latência média
rate(http_request_duration_seconds_sum[5m])
/ rate(http_request_duration_seconds_count[5m])

Summary

O que é: Similar ao Histogram, mas calcula percentis no lado do cliente. Mais preciso, porém não agregável.

Quando usar:

  • Quando você precisa de percentis precisos por instância
  • Quando não precisa agregar entre instâncias

Diferença chave vs Histogram:

HistogramSummary
Percentis calculadosNo servidor (PromQL)No cliente
Agregável entre instânciasSimNão
Uso de memóriaBaixoAlto
Requer buckets pré-definidosSimNão
from prometheus_client import Summary

request_latency = Summary(
    'http_request_latency_seconds',
    'Latência das requisições',
    ['method']
)

with request_latency.labels(method='GET').time():
    handle_request()

PromQL — Linguagem de Query

Seletores e Matchers

# Seletor de série exata
http_requests_total

# Filtrar por label com igualdade
http_requests_total{job="api", status="200"}

# Filtrar com regex
http_requests_total{status=~"2.."}          # Status 2xx
http_requests_total{status!~"5.."}          # Excluir 5xx
http_requests_total{endpoint!="/health"}    # Excluir health check
http_requests_total{job=~"api|gateway"}     # Múltiplos jobs

# Matchers disponíveis:
# =   igualdade exata
# !=  diferença
# =~  regex match
# !~  regex não-match

Range Queries e Vetores

# Instant vector — valor no momento atual
http_requests_total

# Range vector — valores em uma janela de tempo
http_requests_total[5m]     # últimos 5 minutos
http_requests_total[1h]     # última 1 hora
http_requests_total[30s]    # últimos 30 segundos

# Offset — query no passado
http_requests_total offset 1h         # valor de 1 hora atrás
rate(http_requests_total[5m]) offset 24h  # taxa de ontem

Funções de Taxa

# rate() — taxa por segundo, suavizada, lida resets de counter
# SEMPRE use com counters. Janela mínima recomendada: 4x o intervalo de scrape
rate(http_requests_total[5m])

# irate() — taxa instantânea (últimos 2 pontos)
# Mais responsivo a picos, menos suavizado. Use para dashboards de alertas
irate(http_requests_total[5m])

# increase() — aumento total na janela
# Equivale a rate() * duração_da_janela
increase(http_requests_total[1h])

# Diferença entre rate e irate:
# rate   → média dos últimos 5min (suavizado)
# irate  → derivada instantânea (sensível a spikes)

Funções de Agregação no Tempo

# Média ao longo do tempo
avg_over_time(process_memory_bytes[1h])

# Máximo ao longo do tempo
max_over_time(http_active_connections[30m])

# Mínimo ao longo do tempo
min_over_time(node_filesystem_free_bytes[6h])

# Desvio padrão ao longo do tempo
stddev_over_time(http_request_duration_seconds_sum[1h])

# Percentil ao longo do tempo (0.0 a 1.0)
quantile_over_time(0.95, http_request_duration_seconds[1h])

# Contagem de amostras na janela
count_over_time(up[5m])

Operadores de Agregação

# Somar todas as séries
sum(http_requests_total)

# Somar agrupando por label
sum by (status) (http_requests_total)
sum by (job, method) (http_requests_total)

# Somar excluindo labels específicas (equivalente)
sum without (instance) (http_requests_total)

# Contagem de séries
count(http_requests_total)
count by (job) (http_requests_total)

# Média
avg(process_memory_bytes)
avg by (job) (process_memory_bytes)

# Top N valores
topk(5, http_requests_total)
topk(3, sum by (endpoint) (rate(http_requests_total[5m])))

# Bottom N valores
bottomk(5, node_filesystem_free_bytes)

# Máximo / Mínimo entre séries
max by (job) (process_memory_bytes)
min by (instance) (node_cpu_seconds_total)

# Percentil entre séries (0.0 a 1.0)
quantile(0.5, http_request_duration_seconds)

Operadores Binários e Correspondência de Labels

# Operadores aritméticos
node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes

# Percentual de memória usada
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)
  / node_memory_MemTotal_bytes * 100

# Comparação (retorna séries que satisfazem a condição)
process_memory_bytes > 1e9        # mais de 1 GB
http_requests_total > bool 1000   # retorna 0 ou 1

# Correspondência de labels em operações binárias
# on() — apenas os labels listados devem corresponder
# ignoring() — ignorar esses labels na correspondência
method_code:http_errors:rate5m / ignoring(code)
  group_left method:http_requests:rate5m

# group_left / group_right para many-to-one
# group_left = lado esquerdo tem mais séries
sum(rate(http_errors_total[5m])) by (method, code)
  / ignoring(code) group_left()
  sum(rate(http_requests_total[5m])) by (method)

Queries Práticas

# Error rate (porcentagem de erros 5xx)
sum(rate(http_requests_total{status=~"5.."}[5m]))
  / sum(rate(http_requests_total[5m])) * 100

# Latência P99 por endpoint
histogram_quantile(0.99,
  sum by (endpoint, le) (rate(http_request_duration_seconds_bucket[5m]))
)

# Disponibilidade do serviço (uptime %)
avg_over_time(up{job="api"}[24h]) * 100

# CPU usage por instância (%)
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# Memória livre em GB
node_memory_MemAvailable_bytes / 1024 / 1024 / 1024

# Disco usado em %
(node_filesystem_size_bytes - node_filesystem_avail_bytes)
  / node_filesystem_size_bytes * 100

# Saturation: conexões ativas vs máximo
pg_stat_activity_count / pg_settings_max_connections * 100

# Apdex score (latência < 0.1s = satisfeito, < 0.4s = tolerado)
(
  sum(rate(http_request_duration_seconds_bucket{le="0.1"}[5m]))
  + sum(rate(http_request_duration_seconds_bucket{le="0.4"}[5m]))
) / 2 / sum(rate(http_request_duration_seconds_count[5m]))

Labels e Cardinalidade

Boas Práticas de Labels

# BOM: labels de baixa cardinalidade
http_requests_total{
  method="GET",          # 4-5 valores possíveis
  status="200",          # ~10 valores possíveis
  endpoint="/api/users"  # dezenas de valores
}

# RUIM: labels de alta cardinalidade — EXPLODEM o TSDB
http_requests_total{
  user_id="12345",       # MILHÕES de valores possíveis!
  request_id="abc-123",  # ÚNICO por requisição!
  ip="192.168.1.1"       # alto número de valores
}

Regras para Labels

  1. Cardinalidade máxima sugerida: cada label deve ter menos de ~100 valores distintos
  2. Nunca use como label: IDs de usuário, IDs de transação, timestamps, UUIDs
  3. Prefira enumerações finitas: status HTTP (200, 404, 500), método HTTP (GET, POST), ambiente (prod, staging)
  4. Labels são parte da identidade da série — mudar um label cria uma nova série
# Verificar cardinalidade atual
# Total de séries ativas
prometheus_tsdb_head_series

# Séries por job
count by (job) ({__name__=~".+"})

# Top métricas por número de séries
topk(10, count by (__name__) ({__name__=~".+"}))

prometheus.yml — Configuração Principal

# prometheus.yml

global:
  # Com que frequência coletar métricas (padrão: 1m)
  scrape_interval: 15s
  # Timeout para cada scrape (deve ser < scrape_interval)
  scrape_timeout: 10s
  # Com que frequência avaliar regras de alerta
  evaluation_interval: 15s
  # Labels adicionados a todas as séries coletadas
  external_labels:
    cluster: 'prod-us-east-1'
    environment: 'production'

# Caminho para arquivos de regras de alerta e recording rules
rule_files:
  - "rules/*.yml"
  - "alerts/*.yml"

# Configuração do Alertmanager
alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - 'alertmanager:9093'
      # Timeout para envio de alertas
      timeout: 10s

# Configuração de scrape (onde coletar métricas)
scrape_configs:

  # Prometheus auto-monitora a si mesmo
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  # Aplicação com configuração estática
  - job_name: 'api-backend'
    scrape_interval: 10s            # sobrescreve o global
    metrics_path: '/actuator/prometheus'  # Spring Boot Actuator
    scheme: 'https'
    tls_config:
      insecure_skip_verify: false
    static_configs:
      - targets:
          - 'api-1.prod.internal:8080'
          - 'api-2.prod.internal:8080'
        labels:
          env: 'production'
          region: 'us-east-1'

  # Node Exporter (métricas do SO)
  - job_name: 'node-exporter'
    static_configs:
      - targets:
          - 'server-1:9100'
          - 'server-2:9100'
          - 'server-3:9100'

  # Descoberta via Docker (containers em execução)
  - job_name: 'docker-containers'
    docker_sd_configs:
      - host: 'unix:///var/run/docker.sock'
        refresh_interval: 30s
    relabel_configs:
      # Apenas containers com label prometheus.io/scrape=true
      - source_labels: [__meta_docker_container_label_prometheus_io_scrape]
        action: keep
        regex: 'true'
      # Usar porta customizada do label
      - source_labels: [__meta_docker_container_label_prometheus_io_port]
        action: replace
        target_label: __address__
        regex: (.+)
        replacement: '${1}'

  # Descoberta via Kubernetes
  - job_name: 'kubernetes-pods'
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      # Manter apenas pods com anotação prometheus.io/scrape: "true"
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: 'true'
      # Usar path customizado da anotação
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
        action: replace
        target_label: __metrics_path__
        regex: (.+)
      # Construir endereço a partir de IP e porta
      - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
        action: replace
        regex: ([^:]+)(?::\d+)?;(\d+)
        replacement: $1:$2
        target_label: __address__
      # Copiar namespace como label
      - source_labels: [__meta_kubernetes_namespace]
        target_label: namespace
      # Copiar nome do pod como label
      - source_labels: [__meta_kubernetes_pod_name]
        target_label: pod

Relabeling

Relabeling é executado antes do scrape (sobre metadata da descoberta):

relabel_configs:
  # keep — manter apenas séries que fazem match
  - source_labels: [__meta_kubernetes_pod_ready]
    action: keep
    regex: 'true'

  # drop — remover séries que fazem match
  - source_labels: [__meta_kubernetes_namespace]
    action: drop
    regex: 'kube-system'

  # replace — transformar label
  - source_labels: [__meta_kubernetes_pod_name]
    target_label: instance
    action: replace

  # labelmap — copiar metadata labels para labels finais
  - action: labelmap
    regex: __meta_kubernetes_pod_label_(.+)

  # labeldrop — remover labels do resultado final
  - action: labeldrop
    regex: __meta_kubernetes_.*

Alertmanager

Regras de Alerta (alert rules)

# rules/alerts.yml

groups:
  - name: api_alerts
    interval: 30s   # frequência de avaliação deste grupo
    rules:

      # Alerta se o serviço estiver down
      - alert: ServiceDown
        expr: up{job="api-backend"} == 0
        for: 1m   # precisa persistir por 1 minuto para disparar
        labels:
          severity: critical
          team: platform
        annotations:
          summary: "Serviço {{ $labels.job }} está down"
          description: "{{ $labels.instance }} está inacessível há mais de 1 minuto."
          runbook: "https://wiki.internal/runbooks/service-down"

      # Error rate > 5%
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
          / sum(rate(http_requests_total[5m])) by (job)
          > 0.05
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Alta taxa de erros em {{ $labels.job }}"
          description: "Error rate de {{ humanizePercentage $value }} nos últimos 5 minutos."

      # Latência P99 > 500ms
      - alert: HighLatencyP99
        expr: |
          histogram_quantile(0.99,
            sum by (job, le) (rate(http_request_duration_seconds_bucket[5m]))
          ) > 0.5
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Latência P99 alta em {{ $labels.job }}"
          description: "P99 = {{ humanizeDuration $value }}"

      # Memória acima de 90%
      - alert: HighMemoryUsage
        expr: |
          (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)
          / node_memory_MemTotal_bytes * 100 > 90
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "Uso de memória crítico em {{ $labels.instance }}"
          description: "{{ $value | printf \"%.1f\" }}% de memória usada."

      # Disco acima de 85%
      - alert: DiskSpaceLow
        expr: |
          (node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}
           - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"})
          / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"} * 100 > 85
        for: 30m
        labels:
          severity: warning
        annotations:
          summary: "Pouco espaço em disco em {{ $labels.instance }}"
          description: "Disco {{ $labels.mountpoint }} com {{ $value | printf \"%.1f\" }}% cheio."

Configuração do Alertmanager

# alertmanager.yml

global:
  # Tempo que um alerta fica em "resolved" antes de ser removido
  resolve_timeout: 5m
  # Configuração SMTP global
  smtp_smarthost: 'smtp.gmail.com:587'
  smtp_from: 'alertas@empresa.com'
  smtp_auth_username: 'alertas@empresa.com'
  smtp_auth_password_file: '/etc/alertmanager/smtp_password'

# Templates customizados
templates:
  - '/etc/alertmanager/templates/*.tmpl'

# Rota raiz — todas as rotas herdam desta
route:
  receiver: 'default-receiver'
  group_by: ['alertname', 'job', 'severity']
  group_wait: 30s        # espera antes de enviar primeiro alerta do grupo
  group_interval: 5m     # espera para enviar alertas novos no mesmo grupo
  repeat_interval: 4h    # quanto tempo antes de re-notificar um alerta ativo

  routes:
    # Alertas críticos vão para PagerDuty + Slack
    - matchers:
        - severity = critical
      receiver: 'critical-alerts'
      repeat_interval: 1h

    # Alertas de infraestrutura vão para canal específico
    - matchers:
        - team = infra
      receiver: 'infra-slack'

    # Silenciar alertas de staging fora do horário comercial
    - matchers:
        - environment = staging
      mute_time_intervals:
        - outside-business-hours

# Receivers — destinos das notificações
receivers:
  - name: 'default-receiver'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/T00/B00/xxx'
        channel: '#alertas-gerais'
        title: '{{ .GroupLabels.alertname }}'
        text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'

  - name: 'critical-alerts'
    pagerduty_configs:
      - routing_key: 'sua-chave-pagerduty'
        description: '{{ .GroupLabels.alertname }}: {{ .CommonAnnotations.summary }}'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/T00/B00/yyy'
        channel: '#alertas-criticos'
        color: '{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}'
        title: '{{ if eq .Status "firing" }}ALERTA: {{ else }}RESOLVIDO: {{ end }}{{ .GroupLabels.alertname }}'
        text: |
          {{ range .Alerts }}
          *Summary:* {{ .Annotations.summary }}
          *Description:* {{ .Annotations.description }}
          *Runbook:* {{ .Annotations.runbook }}
          {{ end }}

  - name: 'infra-slack'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/T00/B00/zzz'
        channel: '#infra-alertas'

  - name: 'email-oncall'
    email_configs:
      - to: 'oncall@empresa.com'
        subject: '[ALERTA] {{ .GroupLabels.alertname }}'
        html: '{{ template "email.html" . }}'

# Inibições — suprimir alertas menos graves quando há alertas mais graves
inhibit_rules:
  # Se o nó está down, inibir alertas de serviços nesse nó
  - source_matchers:
      - alertname = NodeDown
    target_matchers:
      - alertname =~ ".*"
    equal: ['instance']

# Intervalos de silêncio
time_intervals:
  - name: outside-business-hours
    time_intervals:
      - weekdays: ['saturday', 'sunday']
      - times:
          - start_time: '00:00'
            end_time: '08:00'
          - start_time: '18:00'
            end_time: '24:00'

Recording Rules

Recording rules pré-calculam expressions PromQL caras e salvam como novas séries. Úteis para dashboards e alertas complexos.

# rules/recording-rules.yml

groups:
  - name: http_metrics
    interval: 1m
    rules:
      # Taxa de requisições por job e método
      - record: job_method:http_requests:rate5m
        expr: sum by (job, method) (rate(http_requests_total[5m]))

      # Error rate por job
      - record: job:http_error_rate:ratio5m
        expr: |
          sum by (job) (rate(http_requests_total{status=~"5.."}[5m]))
          / sum by (job) (rate(http_requests_total[5m]))

      # Latência P99 por job
      - record: job:http_request_duration_p99:5m
        expr: |
          histogram_quantile(0.99,
            sum by (job, le) (rate(http_request_duration_seconds_bucket[5m]))
          )

      # Latência P50 (mediana) por job
      - record: job:http_request_duration_p50:5m
        expr: |
          histogram_quantile(0.50,
            sum by (job, le) (rate(http_request_duration_seconds_bucket[5m]))
          )

  - name: node_metrics
    interval: 1m
    rules:
      # CPU usage por instância
      - record: instance:cpu_usage:rate5m
        expr: |
          100 - (avg by (instance)
            (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

      # Memória usada em %
      - record: instance:memory_usage:ratio
        expr: |
          1 - (node_memory_MemAvailable_bytes
               / node_memory_MemTotal_bytes)

Exporters

Node Exporter

Coleta métricas do sistema operacional Linux.

# Executar Node Exporter
docker run -d \
  --name node-exporter \
  --pid=host \
  --network=host \
  -v /proc:/host/proc:ro \
  -v /sys:/host/sys:ro \
  -v /:/rootfs:ro \
  prom/node-exporter:latest \
  --path.procfs=/host/proc \
  --path.sysfs=/host/sys \
  --collector.filesystem.ignored-mount-points="^/(sys|proc|dev|host|etc)($$|/)"

Métricas importantes do Node Exporter:

# CPU idle por core
rate(node_cpu_seconds_total{mode="idle"}[5m])

# Memória disponível
node_memory_MemAvailable_bytes

# Disco livre
node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}

# Network traffic
rate(node_network_receive_bytes_total[5m])
rate(node_network_transmit_bytes_total[5m])

# Load average
node_load1
node_load5
node_load15

JMX Exporter (Java)

# jmx-exporter-config.yml
startDelaySeconds: 0
hostPort: localhost:1099
ssl: false
lowercaseOutputName: true
lowercaseOutputLabelNames: true
whitelistObjectNames:
  - "java.lang:type=Memory"
  - "java.lang:type=GarbageCollector,*"
  - "java.lang:type=Threading"
  - "Catalina:type=ThreadPool,*"

rules:
  # Memória heap
  - pattern: 'java.lang<type=Memory><HeapMemoryUsage>used'
    name: jvm_memory_heap_used_bytes
    type: GAUGE

  # GC time
  - pattern: 'java.lang<type=GarbageCollector,name=(.*)><CollectionTime>'
    name: jvm_gc_collection_seconds_total
    labels:
      gc: "$1"
    type: COUNTER
# Na JVM, adicionar agente:
java -javaagent:/opt/jmx_prometheus_javaagent.jar=9404:/opt/jmx-config.yml \
     -jar sua-aplicacao.jar

PostgreSQL Exporter

docker run -d \
  --name postgres-exporter \
  -e DATA_SOURCE_NAME="postgresql://user:pass@host:5432/db?sslmode=disable" \
  -p 9187:9187 \
  wrouesnel/postgres_exporter

Métricas PostgreSQL úteis:

# Conexões ativas
pg_stat_activity_count{state="active"}

# Tamanho dos bancos
pg_database_size_bytes

# Queries lentas
rate(pg_stat_database_blks_hit[5m]) /
  (rate(pg_stat_database_blks_read[5m]) + rate(pg_stat_database_blks_hit[5m]))

# Replication lag
pg_replication_lag

Exporter Customizado

# custom_exporter.py
from prometheus_client import start_http_server, Gauge, Counter
import time
import requests

# Métricas customizadas
api_health = Gauge('external_api_health', 'Status da API externa (1=up, 0=down)',
                   ['api_name'])
api_latency = Gauge('external_api_response_seconds', 'Latência da API externa',
                    ['api_name'])

APIS_TO_CHECK = {
    'payment-gateway': 'https://api.pagamento.com/health',
    'cep-service': 'https://viacep.com.br/ws/01310100/json/'
}

def check_apis():
    for name, url in APIS_TO_CHECK.items():
        try:
            start = time.time()
            resp = requests.get(url, timeout=5)
            latency = time.time() - start

            api_health.labels(api_name=name).set(1 if resp.status_code < 400 else 0)
            api_latency.labels(api_name=name).set(latency)
        except Exception:
            api_health.labels(api_name=name).set(0)

if __name__ == '__main__':
    # Iniciar servidor HTTP na porta 8000
    start_http_server(8000)
    while True:
        check_apis()
        time.sleep(30)

Instrumentação em Java (Micrometer + Spring Boot)

<!-- pom.xml -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus,metrics
      base-path: /actuator
  endpoint:
    prometheus:
      enabled: true
  metrics:
    tags:
      application: ${spring.application.name}
      environment: ${spring.profiles.active:dev}
    distribution:
      percentiles-histogram:
        http.server.requests: true    # habilita histograma para requisições HTTP
      percentiles:
        http.server.requests: 0.5, 0.95, 0.99
      slo:
        http.server.requests: 50ms, 100ms, 200ms, 500ms
// Instrumentação customizada com Micrometer
import io.micrometer.core.instrument.*;

@Service
public class OrderService {

    private final Counter ordersCreated;
    private final Counter ordersFailed;
    private final Timer orderProcessingTime;
    private final Gauge pendingOrders;
    private final AtomicInteger pendingCount = new AtomicInteger(0);

    public OrderService(MeterRegistry registry) {
        // Counter
        this.ordersCreated = Counter.builder("orders.created.total")
            .description("Total de pedidos criados")
            .tag("service", "orders")
            .register(registry);

        this.ordersFailed = Counter.builder("orders.failed.total")
            .description("Total de pedidos com falha")
            .register(registry);

        // Timer (equivalente ao Histogram do Prometheus)
        this.orderProcessingTime = Timer.builder("orders.processing.duration")
            .description("Duração do processamento de pedidos")
            .publishPercentiles(0.5, 0.95, 0.99)
            .publishPercentileHistogram(true)
            .register(registry);

        // Gauge
        this.pendingOrders = Gauge.builder("orders.pending", pendingCount, AtomicInteger::get)
            .description("Pedidos aguardando processamento")
            .register(registry);
    }

    public Order createOrder(OrderRequest request) {
        pendingCount.incrementAndGet();
        return orderProcessingTime.record(() -> {
            try {
                Order order = processOrder(request);
                ordersCreated.increment();
                return order;
            } catch (Exception e) {
                ordersFailed.increment();
                throw e;
            } finally {
                pendingCount.decrementAndGet();
            }
        });
    }
}

Instrumentação em Python

# requirements: prometheus-client==0.19.0

from prometheus_client import (
    Counter, Gauge, Histogram, Summary,
    start_http_server, CollectorRegistry, push_to_gateway
)
import functools
import time

# Definir métricas no nível do módulo (escopo global)
REQUEST_COUNT = Counter(
    'http_requests_total',
    'Total de requisições HTTP',
    ['method', 'endpoint', 'http_status']
)

REQUEST_LATENCY = Histogram(
    'http_request_duration_seconds',
    'Duração das requisições HTTP',
    ['method', 'endpoint'],
    buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0]
)

IN_PROGRESS = Gauge(
    'http_requests_in_progress',
    'Requisições HTTP em andamento',
    ['method']
)

# Decorator para instrumentar funções
def track_requests(method, endpoint):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            IN_PROGRESS.labels(method=method).inc()
            start = time.time()
            status = '200'
            try:
                result = func(*args, **kwargs)
                return result
            except Exception as e:
                status = '500'
                raise
            finally:
                REQUEST_COUNT.labels(method=method, endpoint=endpoint,
                                     http_status=status).inc()
                REQUEST_LATENCY.labels(method=method, endpoint=endpoint)\
                               .observe(time.time() - start)
                IN_PROGRESS.labels(method=method).dec()
        return wrapper
    return decorator

# Usar com Flask
from flask import Flask
app = Flask(__name__)

@app.route('/api/users', methods=['GET'])
@track_requests('GET', '/api/users')
def list_users():
    return {'users': []}

if __name__ == '__main__':
    start_http_server(8000)  # Prometheus scrape endpoint
    app.run(port=5000)

Instrumentação em Node.js (prom-client)

// npm install prom-client

const promClient = require('prom-client');

// Criar registry customizado (ou usar o default)
const register = new promClient.Registry();

// Adicionar métricas padrão do processo (CPU, memória, event loop)
promClient.collectDefaultMetrics({ register, prefix: 'node_app_' });

// Counter
const httpRequestsTotal = new promClient.Counter({
  name: 'http_requests_total',
  help: 'Total de requisições HTTP',
  labelNames: ['method', 'route', 'status'],
  registers: [register]
});

// Histogram
const httpRequestDuration = new promClient.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duração das requisições HTTP',
  labelNames: ['method', 'route'],
  buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5],
  registers: [register]
});

// Gauge
const activeConnections = new promClient.Gauge({
  name: 'active_connections',
  help: 'Conexões WebSocket ativas',
  registers: [register]
});

// Middleware Express
function metricsMiddleware(req, res, next) {
  const start = Date.now();
  const route = req.route?.path || req.path;

  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    httpRequestsTotal.labels(req.method, route, res.statusCode).inc();
    httpRequestDuration.labels(req.method, route).observe(duration);
  });

  next();
}

// Endpoint de métricas
app.use(metricsMiddleware);
app.get('/metrics', async (req, res) => {
  res.setHeader('Content-Type', register.contentType);
  res.send(await register.metrics());
});

Pushgateway

Use o Pushgateway para jobs de curta duração (batch jobs, cron jobs) que não vivem tempo suficiente para serem scraped.

Quando usar:

  • Cron jobs que executam por segundos/minutos
  • Scripts de migração
  • Jobs de processamento em lote

Quando NÃO usar:

  • Serviços de longa duração (use scrape normal)
  • Como proxy genérico de métricas
# Iniciando o Pushgateway
docker run -d -p 9091:9091 prom/pushgateway

# Push via curl (API REST)
# POST /metrics/job/<job_name>/[<label_name>/<label_value>]*
echo 'batch_job_duration_seconds 23.5' | \
  curl --data-binary @- http://pushgateway:9091/metrics/job/backup_job/instance/server-1

# Múltiplas métricas
cat <<EOF | curl --data-binary @- http://pushgateway:9091/metrics/job/etl_pipeline
# TYPE etl_records_processed counter
etl_records_processed 15432
# TYPE etl_errors_total counter
etl_errors_total 3
# TYPE etl_duration_seconds gauge
etl_duration_seconds 127.4
EOF

# Deletar métricas de um job
curl -X DELETE http://pushgateway:9091/metrics/job/etl_pipeline/instance/server-1
# Push com Python
from prometheus_client import CollectorRegistry, Gauge, Counter, push_to_gateway

registry = CollectorRegistry()

duration = Gauge('batch_job_duration_seconds', 'Duração do job',
                 registry=registry)
records = Counter('batch_records_processed_total', 'Registros processados',
                  registry=registry)

# ... executar job ...
duration.set(127.4)
records.inc(15432)

push_to_gateway('pushgateway:9091', job='etl_pipeline',
                registry=registry,
                grouping_key={'instance': 'server-1'})

Armazenamento e Retenção

# prometheus.yml — flags de armazenamento (passados na linha de comando)
# --storage.tsdb.path=/prometheus          # diretório dos dados
# --storage.tsdb.retention.time=15d        # retenção por tempo
# --storage.tsdb.retention.size=50GB       # retenção por tamanho
# --storage.tsdb.wal-compression           # comprimir WAL
# Linha de comando completa
prometheus \
  --config.file=/etc/prometheus/prometheus.yml \
  --storage.tsdb.path=/var/prometheus/data \
  --storage.tsdb.retention.time=30d \
  --storage.tsdb.retention.size=100GB \
  --storage.tsdb.wal-compression \
  --web.enable-lifecycle \               # habilita reload via API
  --web.enable-admin-api                 # habilita API de administração
# Reload de configuração sem restart
curl -X POST http://localhost:9090/-/reload

# Verificar configuração
promtool check config prometheus.yml

# Verificar regras
promtool check rules rules/*.yml

Federation

Permite que um Prometheus “global” scrape métricas de Prometheus locais (multi-datacenter, multi-cluster).

# prometheus-global.yml
scrape_configs:
  - job_name: 'federated-dc-us-east'
    scrape_interval: 30s
    honor_labels: true    # preservar labels do Prometheus de origem
    metrics_path: '/federate'
    params:
      # Quais métricas federar (regex)
      match[]:
        - '{job="api-backend"}'
        - '{__name__=~"job:.*"}'     # apenas recording rules
        - 'up'
    static_configs:
      - targets:
          - 'prometheus-us-east.internal:9090'
          - 'prometheus-eu-west.internal:9090'
        labels:
          datacenter: 'us-east'

Thanos e Cortex (HA e Retenção Longa)

Thanos — Conceitos

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│ Prometheus  │    │ Prometheus  │    │ Prometheus  │
│ + Sidecar   │    │ + Sidecar   │    │ + Sidecar   │
└──────┬──────┘    └──────┬──────┘    └──────┬──────┘
       │                  │                  │
       └──────────────────┼──────────────────┘
                          │ Thanos Store Gateway

                   ┌─────────────┐
                   │ Object Store│  (S3, GCS, MinIO)
                   │ (cold data) │
                   └─────────────┘

                   ┌─────────────┐
                   │   Querier   │  (PromQL global)
                   └─────────────┘

                   ┌─────────────┐
                   │  Compactor  │  (downsampling, deduplication)
                   └─────────────┘
# docker-compose.thanos.yml (Thanos Sidecar com Prometheus)
services:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=2h'    # Thanos assume os dados antigos
      - '--storage.tsdb.min-block-duration=2h'
      - '--storage.tsdb.max-block-duration=2h'
      - '--web.enable-lifecycle'

  thanos-sidecar:
    image: thanosio/thanos:latest
    command:
      - 'sidecar'
      - '--tsdb.path=/prometheus'
      - '--prometheus.url=http://prometheus:9090'
      - '--objstore.config-file=/etc/thanos/s3.yml'
      - '--http-address=0.0.0.0:19191'
      - '--grpc-address=0.0.0.0:10901'
    volumes:
      - prometheus-data:/prometheus
      - ./s3.yml:/etc/thanos/s3.yml

Docker Compose Completo

# docker-compose.monitoring.yml

version: '3.9'

volumes:
  prometheus-data: {}
  alertmanager-data: {}
  grafana-data: {}

networks:
  monitoring:
    driver: bridge

services:

  prometheus:
    image: prom/prometheus:v2.48.0
    container_name: prometheus
    restart: unless-stopped
    networks:
      - monitoring
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - ./rules:/etc/prometheus/rules:ro
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=30d'
      - '--web.enable-lifecycle'
      - '--web.enable-admin-api'
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:9090/-/ready"]
      interval: 30s
      timeout: 10s
      retries: 3

  alertmanager:
    image: prom/alertmanager:v0.26.0
    container_name: alertmanager
    restart: unless-stopped
    networks:
      - monitoring
    ports:
      - "9093:9093"
    volumes:
      - ./alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
      - alertmanager-data:/alertmanager
    command:
      - '--config.file=/etc/alertmanager/alertmanager.yml'
      - '--storage.path=/alertmanager'

  node-exporter:
    image: prom/node-exporter:v1.7.0
    container_name: node-exporter
    restart: unless-stopped
    networks:
      - monitoring
    pid: host
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'

  grafana:
    image: grafana/grafana:10.2.0
    container_name: grafana
    restart: unless-stopped
    networks:
      - monitoring
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
      GF_USERS_ALLOW_SIGN_UP: false
      GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH: /etc/grafana/provisioning/dashboards/overview.json
    volumes:
      - grafana-data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning:ro

  pushgateway:
    image: prom/pushgateway:v1.7.0
    container_name: pushgateway
    restart: unless-stopped
    networks:
      - monitoring
    ports:
      - "9091:9091"

Referência Rápida de PromQL

# Operadores de agregação
sum()  avg()  min()  max()  count()  stddev()  stdvar()
topk(N, expr)   bottomk(N, expr)   quantile(φ, expr)
count_values("label", expr)

# Modificadores de agregação
by (label1, label2)    # agrupar pelos labels listados
without (label1)       # agrupar por todos, exceto os listados

# Funções para counters
rate(v[d])              # taxa por segundo (recomendado)
irate(v[d])             # taxa instantânea
increase(v[d])          # aumento absoluto

# Funções para histograms
histogram_quantile(φ, v)

# Funções no tempo
avg_over_time(v[d])
max_over_time(v[d])
min_over_time(v[d])
sum_over_time(v[d])
count_over_time(v[d])
stddev_over_time(v[d])
quantile_over_time(φ, v[d])
last_over_time(v[d])

# Funções matemáticas
abs()  ceil()  floor()  round()  sqrt()
exp()  ln()  log2()  log10()
clamp_min()  clamp_max()
delta()  deriv()
predict_linear(v[d], t)   # projeção linear

# Funções de labels
label_replace(v, dst, replacement, src, regex)
label_join(v, dst, separator, src1, src2, ...)

# Funções de tempo
time()          # timestamp atual em segundos
timestamp(v)    # timestamp de cada amostra
minute()  hour()  day_of_week()  month()  year()

# Modificadores de query
offset 1h              # deslocar no tempo
@ 1700000000          # query em timestamp absoluto
subquery: v[1h:5m]    # subquery com resolução customizada

Remote Write e Remote Storage

Por que o armazenamento local não é suficiente

O TSDB local do Prometheus foi projetado para dados recentes e alta velocidade de escrita, não para retenção longa. Limitações práticas:

  • Retenção padrão: 15 dias (configurável com --storage.tsdb.retention.time, mas cresce o disco)
  • Sem redundância: se o servidor cai, os dados do WAL podem ser perdidos
  • Sem HA nativo: dois Prometheus independentes coletam dados duplicados, mas não os sincronizam
  • Escala vertical: um único servidor tem limite de cardinalidade (~10 milhões de séries ativas)

A solução é enviar os dados para um remote storage enquanto o Prometheus local continua servindo queries recentes.

Configuração remote_write

# prometheus.yml
remote_write:
  - url: "http://thanos-receive:19291/api/v1/receive"
    # ou Mimir:  http://mimir:8080/api/v1/push
    # ou Cortex: http://cortex:9009/api/prom/push

    # Autenticação básica (Mimir/Cortex multi-tenant)
    basic_auth:
      username: "tenant-id"
      password: "secret"

    # Controle de fila (tuning de performance)
    queue_config:
      capacity: 10000          # buffer in-memory de samples
      max_shards: 200          # paralelismo máximo de workers
      max_samples_per_send: 500
      batch_send_deadline: 5s

    # Filtrar métricas antes de enviar (reduzir custo/tráfego)
    write_relabel_configs:
      - source_labels: [__name__]
        regex: "go_.*|process_.*"
        action: drop            # não envia métricas internas do runtime

Configuração remote_read

# prometheus.yml
remote_read:
  - url: "http://thanos-query:9090/api/v1/read"
    read_recent: false          # true = lê do remote mesmo para dados recentes
    required_matchers:
      env: "production"         # só faz remote_read se a query contiver esse label

Com remote_read, o Prometheus transparentemente complementa dados locais com dados históricos do remote storage ao executar queries.

Thanos: Sidecar vs Receive

Duas arquiteturas distintas para integrar o Prometheus com o Thanos:

# Arquitetura Sidecar (recomendado para Prometheus existentes)
┌─────────────────────────────────────────────┐
│  Pod / VM                                   │
│  ┌──────────────┐    ┌──────────────────┐   │
│  │  Prometheus  │◄──►│  Thanos Sidecar  │   │
│  │  :9090       │    │  :10902 (HTTP)   │   │
│  └──────────────┘    │  :10901 (gRPC)   │   │
│                       └────────┬─────────┘   │
└────────────────────────────────│─────────────┘
                                 │ upload blocos a cada 2h

                         Object Storage (S3/GCS/MinIO)

# Arquitetura Receive (para remote_write direto)
Prometheus ──remote_write──► Thanos Receive ──► Object Storage

                                    ▼ (replicação)
                             Thanos Receive replica

Thanos Sidecar:

  • Copia blocos TSDB do disco do Prometheus para o object storage a cada 2 horas
  • Expõe a API StoreAPI para o Thanos Query consultar dados locais
  • Zero impacto no Prometheus (só lê do disco)

Thanos Receive:

  • Atua como endpoint de remote_write
  • Grava dados diretamente, sem depender do disco do Prometheus
  • Suporta replicação multi-réplica (fator configurável)

Compactação e Downsampling com Thanos Store/Compactor

# thanos-compactor (roda como job periódico, não contínuo)
thanos compact \
  --data-dir /tmp/thanos-compact \
  --objstore.config-file /etc/thanos/objstore.yml \
  --retention.resolution-raw=30d \   # dados brutos (1 ponto por scrape)
  --retention.resolution-5m=90d \    # downsampled: 1 ponto a cada 5 min
  --retention.resolution-1h=1y \     # downsampled: 1 ponto por hora
  --wait                              # roda em loop contínuo

Níveis de resolução:

  • raw — dados originais, 1 ponto por scrape_interval (ex: 15s)
  • 5m — gerado automaticamente pelo compactor quando dados têm >40h
  • 1h — gerado quando dados têm >10 dias
# Consultar dados históricos via Thanos Query
thanos query \
  --store thanos-sidecar:10901 \    # dados recentes (via sidecar)
  --store thanos-store:10901 \      # dados históricos (do object storage)
  --query.replica-label replica     # deduplica métricas de HA replicas

Service Discovery Avançado

Como funciona o ciclo de descoberta

Prometheus executa o ciclo de service discovery continuamente. A cada intervalo (refresh_interval, padrão 5m), ele:

  1. Consulta os providers configurados (Kubernetes API, arquivo, DNS, EC2…)
  2. Gera uma lista de targets com labels prefixados por __meta_*
  3. Aplica relabel_configs para filtrar e transformar labels
  4. Os targets resultantes entram no scraping

Labels prefixados com __ são internos e não aparecem nas métricas salvas (exceto os que forem copiados via relabeling).

kubernetes_sd_configs

# prometheus.yml — scraping de pods no Kubernetes
scrape_configs:
  - job_name: "kubernetes-pods"
    kubernetes_sd_configs:
      - role: pod                    # role: pod | service | endpoints | node | ingress
        namespaces:
          names: ["production", "staging"]
        # kubeconfig_file: /etc/prometheus/kubeconfig  # fora do cluster
        # Se dentro do cluster, usa ServiceAccount automático

    relabel_configs:
      # Scrape apenas pods com annotation prometheus.io/scrape: "true"
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: "true"

      # Porta customizada via annotation prometheus.io/port
      - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
        action: replace
        regex: "([^:]+)(?::\\d+)?;(\\d+)"
        replacement: "$1:$2"
        target_label: __address__

      # Path customizado via annotation prometheus.io/path
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
        action: replace
        target_label: __metrics_path__
        regex: "(.+)"

      # Copiar namespace como label da métrica
      - source_labels: [__meta_kubernetes_namespace]
        target_label: namespace

      # Copiar nome do pod
      - source_labels: [__meta_kubernetes_pod_name]
        target_label: pod

Roles disponíveis no Kubernetes SD:

RoleDescobre__address__ padrão
podcada PodpodIP:containerPort
servicecada ServiceclusterIP:port
endpointsendpoints de ServicespodIP:port
nodecada NodenodeIP:10250 (kubelet)
ingresscada Ingresshost do Ingress

file_sd_configs — inventário dinâmico

Ideal para ambientes fora do Kubernetes ou para integrar com ferramentas de CMDB.

# prometheus.yml
scrape_configs:
  - job_name: "file-targets"
    file_sd_configs:
      - files:
          - "/etc/prometheus/targets/*.json"
          - "/etc/prometheus/targets/*.yaml"
        refresh_interval: 1m      # recarrega arquivos periodicamente
// /etc/prometheus/targets/servers.json
[
  {
    "targets": ["10.0.1.10:9100", "10.0.1.11:9100"],
    "labels": {
      "env": "production",
      "datacenter": "us-east-1",
      "role": "database"
    }
  },
  {
    "targets": ["10.0.2.5:9100"],
    "labels": {
      "env": "staging",
      "role": "api"
    }
  }
]

Prometheus detecta mudanças nos arquivos sem precisar recarregar config (SIGHUP).

ec2_sd_configs

scrape_configs:
  - job_name: "ec2-instances"
    ec2_sd_configs:
      - region: us-east-1
        access_key: AKID...         # ou usa IAM role da instância
        secret_key: secret...
        port: 9100                   # porta padrão para node_exporter
        filters:
          - name: "tag:monitored"    # filtra por tag AWS
            values: ["true"]

    relabel_configs:
      # Usar private DNS como label
      - source_labels: [__meta_ec2_private_dns_name]
        target_label: instance
      # Tag "Name" da EC2 como label
      - source_labels: [__meta_ec2_tag_Name]
        target_label: name
      # AZ como label
      - source_labels: [__meta_ec2_availability_zone]
        target_label: az

dns_sd_configs

scrape_configs:
  - job_name: "dns-services"
    dns_sd_configs:
      - names:
          - "_prometheus._tcp.example.com"   # SRV record
        type: SRV
        refresh_interval: 30s
      - names:
          - "api.example.com"                 # A/AAAA record
        type: A
        port: 9090

__address__ vs labels __meta_*

__address__           → endereço de scrape (host:port) — usado pelo Prometheus para conectar
__scheme__            → http ou https
__metrics_path__      → caminho da rota /metrics (padrão: /metrics)
__param_<name>        → parâmetros de query string

# Labels __meta_* são gerados pelo SD provider — exemplos:
__meta_kubernetes_pod_name              → nome do pod
__meta_kubernetes_pod_namespace         → namespace
__meta_kubernetes_pod_label_<label>     → qualquer label do pod
__meta_kubernetes_pod_annotation_<ann>  → qualquer annotation do pod
__meta_ec2_instance_id                  → ID da instância EC2
__meta_ec2_tag_<tag>                    → tag da EC2

# Todos os __meta_* e __ são descartados após relabeling
# → copie para labels normais se quiser que apareçam nas métricas

Relabeling — relabel_configs

O que é e quando acontece

relabel_configs transforma os labels de um target antes do scrape acontecer. O fluxo completo é:

SD Provider gera targets com __meta_* labels

   relabel_configs (filtra e transforma)

  Targets finais com labels normais

   Prometheus scrape /metrics

metric_relabel_configs (transforma labels das métricas coletadas)

   Métricas salvas no TSDB

metric_relabel_configs funciona igual, mas é aplicado às métricas após o scrape (útil para drop de métricas caras).

Campos de uma regra de relabeling

relabel_configs:
  - source_labels: [label1, label2]   # labels de entrada (concatenados com separator)
    separator: ";"                      # separador padrão entre source_labels
    target_label: novo_label           # label de destino (para replace)
    regex: "(.*)"                       # regex aplicada ao valor concatenado
    replacement: "$1"                  # valor de substituição (suporta grupos de captura)
    action: replace                    # ação a executar (padrão: replace)
    modulus: 10                        # usado com action: hashmod

Ações disponíveis

AçãoO que faz
replaceSe regex faz match, define target_label = replacement. Padrão.
keepMantém apenas targets onde regex faz match nos source_labels
dropRemove targets onde regex faz match nos source_labels
labelmapCopia labels cujos nomes fazem match com regex para novos nomes definidos por replacement
labeldropRemove labels cujos nomes fazem match com regex
labelkeepMantém apenas labels cujos nomes fazem match com regex
hashmodDefine target_label = hash(source_labels) % modulus (para sharding)

Exemplos práticos

relabel_configs:
  # 1. Filtrar pods por annotation — manter apenas os que pedem scraping
  - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
    action: keep
    regex: "true"

  # 2. Renomear __meta_kubernetes_pod_name para label "pod"
  - source_labels: [__meta_kubernetes_pod_name]
    target_label: pod

  # 3. Renomear __meta_kubernetes_namespace para "namespace"
  - source_labels: [__meta_kubernetes_namespace]
    target_label: namespace

  # 4. Copiar todos os labels do pod (k8s) para labels da métrica via labelmap
  #    __meta_kubernetes_pod_label_app → app
  #    __meta_kubernetes_pod_label_version → version
  - action: labelmap
    regex: __meta_kubernetes_pod_label_(.+)

  # 5. Drop de targets de namespaces de sistema
  - source_labels: [__meta_kubernetes_namespace]
    action: drop
    regex: "kube-system|monitoring"

  # 6. Construir __address__ customizado combinando IP + porta de annotation
  - source_labels: [__meta_kubernetes_pod_ip, __meta_kubernetes_pod_annotation_prometheus_io_port]
    separator: ":"
    target_label: __address__

  # 7. Remover labels internos antes de salvar
  - action: labeldrop
    regex: "__meta_.*"

  # 8. Sharding: enviar apenas 1/3 dos targets para este Prometheus
  - source_labels: [__address__]
    modulus: 3
    target_label: __tmp_hash
    action: hashmod
  - source_labels: [__tmp_hash]
    action: keep
    regex: "0"    # este shard processa o módulo 0

metric_relabel_configs — remover métricas caras após scrape

scrape_configs:
  - job_name: "kubernetes-pods"
    metric_relabel_configs:
      # Drop de métricas de alta cardinalidade que não são usadas
      - source_labels: [__name__]
        regex: "go_gc_duration_seconds.*|go_memstats_.*"
        action: drop

      # Normalizar label "code" para apenas 2xx/3xx/4xx/5xx
      - source_labels: [code]
        regex: "(\\d)\\d+"
        replacement: "${1}xx"
        target_label: code_class