Introdução ao Prometheus em Kubernetes
Prometheus é uma solução open-source de monitoramento e alertas que se tornou padrão na indústria para ambientes Cloud Native e Kubernetes. Diferentemente de ferramentas legadas que usam polling ou agentes push, Prometheus funciona sob o modelo pull: ele periodicamente conecta nos endpoints de suas aplicações e coleta métricas no formato Prometheus Text Format. Em um cluster Kubernetes, a força real do Prometheus emerge quando você o combina com o Operator e o ServiceMonitor, criando um sistema declarativo, dinâmico e autoexplicativo.
O maior diferencial do Prometheus em Kubernetes não é apenas a coleta de métricas, mas sua integração profunda com o modelo declarativo do próprio Kubernetes. Você define ServiceMonitor como um Custom Resource Definition (CRD), e o Prometheus Operator automaticamente sincroniza a configuração sem necessidade de recarregar o Prometheus. Isso elimina o tradicional problema de editar arquivos YAML de scrape configs e fazer rolling restart de pods.
Prometheus Operator e ServiceMonitor
O que é o Prometheus Operator?
O Prometheus Operator é um controlador Kubernetes que gerencia a lifecycle completa do Prometheus através de Custom Resources. Em vez de gerenciar ConfigMaps e StatefulSets manualmente, você descreve o estado desejado via CRDs como Prometheus, ServiceMonitor, PrometheusRule e AlertManager. O Operator observa essas definições e automaticamente gera e atualiza os objetos Kubernetes necessários, incluindo o arquivo de configuração do Prometheus.
A instalação do Operator é feita tipicamente via Helm. Aqui está um exemplo prático:
# Adicionar o repositório Prometheus Community
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# Instalar o kube-prometheus-stack (que inclui Operator, Prometheus, Grafana, etc)
helm install prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--values values.yaml
Aqui está um arquivo values.yaml mínimo e funcional:
# values.yaml para kube-prometheus-stack
prometheus:
prometheusSpec:
retention: 15d
storageSpec:
volumeClaimTemplate:
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi
# Importante: define qual ServiceMonitor o Prometheus vai scrappear
serviceMonitorSelectorNilUsesHelmValues: false
grafana:
adminPassword: "admin123"
persistence:
enabled: true
size: 10Gi
alertmanager:
enabled: true
Entendendo ServiceMonitor
ServiceMonitor é um CRD que funciona como um contrato entre sua aplicação e o Prometheus. Em vez de adicionar endereços IP ou nomes de serviço diretamente na configuração do Prometheus, você cria um ServiceMonitor que diz: "monitore todos os pods com o label app=minha-app na porta 8080 do endpoint /metrics". O Prometheus Operator descobre esses ServiceMonitors e dinamicamente adiciona as configurações de scrape.
A seleção funciona através de label matching. Você define em qual namespace buscar, quais labels de Service selecionar e quais labels de Pod selecionar. Isso é poderoso porque permite que diferentes times criem seus próprios ServiceMonitors sem precisar acessar a configuração central do Prometheus.
Vamos a um exemplo prático. Suponha que você tem uma aplicação Node.js que expõe métricas em localhost:3000/metrics. Primeiro, você cria um Deployment:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-app
namespace: production
spec:
replicas: 2
selector:
matchLabels:
app: api-app
template:
metadata:
labels:
app: api-app
version: v1
spec:
containers:
- name: api
image: myregistry/api-app:1.0.0
ports:
- name: metrics
containerPort: 3000
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
Depois, você cria um Service:
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: api-app
namespace: production
labels:
app: api-app
spec:
ports:
- name: metrics
port: 3000
targetPort: metrics
protocol: TCP
selector:
app: api-app
E finalmente, o ServiceMonitor que conecta tudo:
# servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: api-app-monitor
namespace: production
labels:
release: prometheus # Importante: deve corresponder ao seletor do Prometheus
spec:
selector:
matchLabels:
app: api-app
endpoints:
- port: metrics
interval: 30s
path: /metrics
scrapeTimeout: 10s
Note o label release: prometheus no ServiceMonitor. Isso é crucial: no spec do Prometheus (via values.yaml do Helm), você configurou serviceMonitorSelector para selecionar ServiceMonitors com esse label. Sem isso, o Prometheus nunca vai descobrir seu ServiceMonitor.
PromQL Avançado
Conceitos Fundamentais de PromQL
PromQL é a linguagem de consulta do Prometheus, e dominar PromQL avançado é o que separa um operador iniciante de um especialista. PromQL trabalha com séries temporais, onde cada métrica é identificada por seu nome e um conjunto de labels. Uma consulta PromQL retorna um conjunto de pontos de dados em um intervalo de tempo.
Antes de mergulhar em construções complexas, é essencial entender os tipos de dados: vetores instantâneos (um valor por série no momento atual), vetores de intervalo (múltiplos valores por série em um período) e escalares (um único número). Funções diferentes operam sobre esses tipos e, frequentemente, o erro em uma consulta PromQL ocorre quando você tenta aplicar uma função esperando um tipo de dado que ela não retorna.
Operadores e Funções Essenciais
A maioria das queries PromQL começa simples: http_requests_total retorna todas as séries com esse nome. Mas conforme você monitora ambientes complexos, você precisa filtrar, agrupar e agregar. Aqui estão as operações mais poderosas:
Filtros: http_requests_total{job="api-app", method="GET"} seleciona apenas requisições GET.
Operadores Aritméticos: rate(http_requests_total[5m]) calcula a taxa de requisições por segundo nos últimos 5 minutos. Este é talvez o operador mais importante em PromQL.
Operadores Booleanos: http_requests_total{status=~"5.."} usa regex para selecionar status 5xx.
Funções de Agregação: sum(rate(http_requests_total[5m])) by (job) soma as taxas agrupadas por job.
Vamos a um exemplo real. Suponha que você quer alertar quando a latência P95 de uma API fica acima de 500ms. Primeiro, sua aplicação precisa exportar um histograma:
# Em sua aplicação (exemplo com Prometheus client Python)
from prometheus_client import Histogram, start_http_server
request_latency = Histogram(
'http_request_duration_seconds',
'HTTP request latency in seconds',
buckets=(0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0)
)
@app.route('/api/users')
def get_users():
with request_latency.time():
# seu código
return jsonify(users)
A métrica exportada terá nomes como http_request_duration_seconds_bucket, http_request_duration_seconds_sum e http_request_duration_seconds_count. Para calcular o P95:
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
Essa função calcula o percentil 95 dos latências nos últimos 5 minutos. Mas isso agrega globalmente. Se você quer P95 por endpoint:
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket{job="api-app"}[5m])) by (le)
Não, espera. O by (le) está errado aqui. Você quer agrupar por endpoint, não por bucket label. A query correta é:
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{job="api-app"}[5m])) by (handler, le))
Isso é PromQL avançado: você primeiro agrega os buckets por handler (nome do endpoint), mantendo o label le (limite de bucket) necessário para a função histogram_quantile funcionar, e depois calcula o percentil.
Operações com Offset e Comparações Temporais
Um padrão poderoso é comparar o comportamento atual com o comportamento passado. Se você quer verificar se o tráfego atual está 50% acima da média dos últimos 7 dias:
rate(http_requests_total[5m]) > (avg_over_time(rate(http_requests_total[5m])[7d:5m]) * 1.5)
Quebra-se assim: avg_over_time(...[7d:5m]) calcula a média de pontos de 5 minutos ao longo de 7 dias. O :5m é essencial — ele diz "use pontos a cada 5 minutos" em vez de recuperar milhões de pontos.
Outra técnica é o offset:
rate(http_requests_total[5m]) - rate(http_requests_total[5m] offset 1h)
Isso compara o tráfego atual com o de 1 hora atrás. Útil para detectar mudanças súbitas.
Subqueries e Funções Complexas
PromQL 2.7+ suporta subqueries, que permitem usar o resultado de uma query como entrada para outra. Por exemplo, para encontrar jobs com mais de 100 requisições por segundo no P99:
topk(3, (histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (job, le))) > 0.5)
Aqui, histogram_quantile é a "subquery" interna, e topk(3, ...) retorna os 3 piores jobs.
Funções de utilidade incluem predict_linear(v range-vector, t scalar) que extrapola uma tendência linear, útil para alertas proativos:
predict_linear(disk_free_bytes[1h], 3600) < 1073741824 # Alerta se disco vai encher em 1 hora
PrometheusRule e Alertas Práticos
Estrutura de PrometheusRule
PrometheusRule é o CRD que define regras de gravação (recording rules) e alertas. Uma recording rule pré-calcula uma expressão PromQL e a armazena como uma nova série temporal, reduzindo a carga de queries custosas. Um alerta define uma condição PromQL e a ação correspondente (enviar para AlertManager).
Aqui está um exemplo completo de PrometheusRule para uma aplicação em produção:
# prometheusrule.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: api-app-rules
namespace: production
labels:
prometheus: prometheus-stack
spec:
groups:
- name: api-app.rules
interval: 30s
rules:
# Recording rule: pré-calcula taxa de requisições por endpoint
- record: job:http_requests:rate5m
expr: sum(rate(http_requests_total[5m])) by (job, handler)
# Alert: taxa de erro acima de 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: critical
team: backend
annotations:
summary: "High error rate detected for {{ $labels.job }}"
description: "Error rate is {{ $value | humanizePercentage }} for job {{ $labels.job }}"
# Alert: latência P95 acima de 500ms
- alert: HighLatencyP95
expr: |
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket[5m])) by (job, le)
) > 0.5
for: 10m
labels:
severity: warning
team: backend
annotations:
summary: "High P95 latency for {{ $labels.job }}"
description: "P95 latency is {{ $value | humanizeDuration }}"
# Alert: tráfego anormalmente baixo (possível outage)
- alert: AbnormallyLowTraffic
expr: |
rate(http_requests_total[5m])
< (avg_over_time(rate(http_requests_total[5m])[7d:5m]) * 0.5)
for: 15m
labels:
severity: warning
annotations:
summary: "Traffic is abnormally low for {{ $labels.job }}"
description: "Current rate: {{ $value | humanize }}/s"
Note alguns detalhes importantes:
for: 5msignifica que o alerta só dispara se a condição permanecer verdadeira por 5 minutos. Isso evita alertas fluttuantes causados por picos momentâneos.labelssão adicionados ao alerta e usados pelo AlertManager para roteamento e silenciamento.annotationsusam templates Golang com$labelse$valuepara contextualizar o alerta.humanizePercentageehumanizeDurationsão funções de formatação que transformam valores brutos em formatos legíveis.
Correlacionando Múltiplas Métricas
Um passo além é correlacionar múltiplas métricas para reduzir false positives. Por exemplo, em vez de alertar por alto uso de CPU simplesmente, você pode alertar apenas se CPU está alta E memória está alta E I/O de disco está elevado:
- alert: SystemUnderStress
expr: |
(node_cpu_seconds_total > 0.8)
and (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes < 0.2)
and (rate(node_disk_io_time_seconds_total[5m]) > 0.5)
for: 10m
labels:
severity: critical
Isso reduz significativamente alertas falsos porque é muito mais provável que um nó realmente tenha problemas se múltiplas métricas estão ruins simultaneamente.
Integração Prática: Do Zero ao Monitoramento
Passo a Passo Completo
Vamos criar um cenário real: você tem uma aplicação Python Flask e quer monitore-a com Prometheus em Kubernetes. Começa-se pelos pré-requisitos:
- Cluster Kubernetes rodando com
kube-prometheus-stackinstalado (conforme mostrado antes). - Sua aplicação exportando métricas Prometheus.
Aqui está a aplicação Flask instrumentada:
# app.py
from flask import Flask, jsonify
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
import time
app = Flask(__name__)
# Métricas
request_count = Counter(
'http_requests_total',
'Total HTTP requests',
['method', 'endpoint', 'status']
)
request_duration = Histogram(
'http_request_duration_seconds',
'HTTP request duration in seconds',
['method', 'endpoint'],
buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0)
)
db_query_duration = Histogram(
'db_query_duration_seconds',
'Database query duration',
['query_type']
)
@app.before_request
def before_request():
app.request_start_time = time.time()
@app.after_request
def after_request(response):
duration = time.time() - app.request_start_time
request_duration.labels(
method=request.method,
endpoint=request.path
).observe(duration)
request_count.labels(
method=request.method,
endpoint=request.path,
status=response.status_code
).inc()
return response
@app.route('/health')
def health():
return jsonify({'status': 'ok'}), 200
@app.route('/api/users')
def get_users():
# Simular query ao banco
with db_query_duration.labels(query_type='select').time():
time.sleep(0.05)
return jsonify([{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]), 200
@app.route('/metrics')
def metrics():
return generate_latest(), 200, {'Content-Type': CONTENT_TYPE_LATEST}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000)
Dockerfile:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 3000
CMD ["python", "app.py"]
requirements.txt:
Flask==3.0.0
prometheus-client==0.19.0
Agora, os manifests Kubernetes:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-app
namespace: production
labels:
app: flask-app
spec:
replicas: 3
selector:
matchLabels:
app: flask-app
template:
metadata:
labels:
app: flask-app
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "3000"
prometheus.io/path: "/metrics"
spec:
containers:
- name: app
image: myregistry/flask-app:1.0.0
imagePullPolicy: Always
ports:
- name: http
containerPort: 3000
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: flask-app
namespace: production
labels:
app: flask-app
spec:
type: ClusterIP
ports:
- name: http
port: 3000
targetPort: http
protocol: TCP
selector:
app: flask-app
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: flask-app
namespace: production
labels:
release: prometheus
spec:
selector:
matchLabels:
app: flask-app
endpoints:
- port: http
interval: 30s
path: /metrics
scrapeTimeout: 10s
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: flask-app-alerts
namespace: production
labels:
prometheus: prometheus-stack
spec:
groups:
- name: flask-app.rules
interval: 30s
rules:
- record: flask:request_rate:5m
expr: sum(rate(http_requests_total{job="flask-app"}[5m])) by (endpoint, status)
- alert: HighRequestErrorRate
expr: |
(
sum(rate(http_requests_total{job="flask-app", status=~"5.."}[5m])) by (endpoint)
/
sum(rate(http_requests_total{job="flask-app"}[5m])) by (endpoint)
) > 0.1
for: 5m
labels:
severity: warning
app: flask-app
annotations:
summary: "High error rate on {{ $labels.endpoint }}"
description: "Error rate is {{ $value | humanizePercentage }}"
- alert: HighP95Latency
expr: |
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket{job="flask-app"}[5m])) by (endpoint, le)
) > 0.5
for: 10m
labels:
severity: warning
app: flask-app
annotations:
summary: "Slow responses on {{ $labels.endpoint }}"
description: "P95 latency is {{ $value | humanizeDuration }}"
Após aplicar esses manifests com kubectl apply -f, o Prometheus Operator detectará o ServiceMonitor e o PrometheusRule, sincronizará com o Prometheus, e os alertas estarão ativos automaticamente.
Verificação e Debug
Para verificar se tudo está funcionando:
# Verificar se o ServiceMonitor foi descoberto
kubectl get servicemonitor -n production
# Ver targets descobertos no Prometheus
kubectl port-forward -n monitoring svc/prometheus-operated 9090:9090
# Acesso em http://localhost:9090/targets
# Consultar logs do Prometheus Operator
kubectl logs -n monitoring -l app.kubernetes.io/name=prometheus-operator -f
# Testar a aplicação
kubectl port-forward -n production svc/flask-app 3000:3000
curl http://localhost:3000/metrics
Conclusão
Dominar Prometheus em Kubernetes significa entender três pilares: o Prometheus Operator, que torna a configuração declarativa e dinâmica; o ServiceMonitor, que permite descoberta automática e desacoplamento entre aplicações e monitoramento; e PromQL avançado, que transforma dados brutos em insights acionáveis. O verdadeiro valor emerge quando você combina esses três: usar PromQL para escrever queries que capturam o comportamento real de suas aplicações, expressar essas queries como PrometheusRules para automatizar detecção de problemas, e usar ServiceMonitor para garantir que novas aplicações sejam monitoradas sem necessidade de reconfiguração manual. A chave é começar simples, com métricas bem instrumentadas e alertas bem baseados em dados, e iterativamente aumentar a sofisticação conforme você entende os padrões de seu sistema.