Introdução ao Monitoramento Customizado com Python e Prometheus
O monitoramento de infraestrutura moderna vai muito além da coleta básica de métricas de CPU, memória e disco. Em ambientes complexos, onde a aplicação é tão crítica quanto o servidor subjacente, é essencial expor dados específicos do negócio ou do estado interno da aplicação. O Prometheus se consolidou como o padrão da indústria para essa tarefa, oferecendo uma arquitetura pull-based robusta e flexível. No entanto, os exporters nativos nem sempre cobrem todas as necessidades de um ambiente heterogêneo.
Neste tutorial técnico, demonstraremos como criar um exporter customizado utilizando Python. A abordagem escolhida é a mais direta e didática: expor métricas no formato textual do Prometheus diretamente através de uma interface HTTP simples. Isso elimina a dependência de bibliotecas externas pesadas e permite que qualquer script Python, seja ele uma aplicação web, um daemon de sistema ou um script de automação, se torne uma fonte de dados para o seu ecossistema de observabilidade.
Ao final deste guia, você será capaz de implementar um endpoint HTTP seguro, estruturar suas métricas seguindo as boas práticas do Prometheus e integrar essa solução em pipelines de automação para sysadmins e desenvolvedores que buscam visibilidade granular sobre seus serviços.
Pré-requisitos e Ambiente
Antes de iniciar a codificação, certifique-se de ter o ambiente preparado. Esta abordagem utiliza Python 3.x e o servidor HTTP embutido na biblioteca padrão, garantindo que você não precise instalar pacotes adicionais via pip, o que facilita a manutenção e reduz a superfície de ataque.
Você precisará de:
- Uma instância do Prometheus configurada ou em planejamento.
- Acesso SSH ao servidor onde o script rodará.
- Conhecimento básico de sintaxe Python e conceitos de HTTP.
Crie um diretório dedicado para o projeto. Por exemplo:
mkdir ~/prometheus-custom-exporter
cd ~/prometheus-custom-exporter
Passo 1: Estruturando a Lógica de Coleta de Métricas
O primeiro passo é definir o que será monitorado. Para fins didáticos, vamos criar um exporter que expõe duas métricas:
- Um gauge (medidor): O número atual de usuários logados no sistema.
- Um counter (contador): O total de requisições processadas pelo script até o momento.
Crie um arquivo chamado custom_exporter.py. A estrutura inicial deve importar os módulos necessários para manipulação de strings e criação do servidor HTTP:
import http.server
import socketserver
from io import BytesIO
import time
import threading
Defina uma classe para gerenciar o estado das métricas. É crucial usar variáveis thread-safe se o exporter for acessado concorrentemente, embora para um script simples, um lock básico seja suficiente.
class MetricsState:
def __init__(self):
self.lock = threading.Lock()
self.request_count = 0
self.logged_in_users = 0
def increment_requests(self):
with self.lock:
self.request_count += 1
def set_logged_in_users(self, count):
with self.lock:
self.logged_in_users = count
Inicialize uma instância global deste estado:
metrics_state = MetricsState()
Passo 2: Formatando a Resposta no Formato Prometheus
O Prometheus espera que as métricas sejam retornadas no formato Text Content-Type. A estrutura é simples, mas rigorosa. Cada linha deve seguir o padrão:
# HELP metric_name Uma breve descrição da métrica.
# TYPE metric_name tipo_da_metrica (counter, gauge, histogram, etc.)
metric_name{label="valor"} valor_numérico
Crie uma função que gera essa string formatada dinamicamente:
def generate_metrics():
# Leitura segura das métricas
with metrics_state.lock:
req_count = metrics_state.request_count
users = metrics_state.logged_in_users
# Construção do payload de texto
output = []
# Métrica 1: Total de requisições (Counter)
output.append("# HELP http_requests_total Total number of processed requests")
output.append("# TYPE http_requests_total counter")
output.append(f"http_requests_total {{server=\"custom-exporter\"}} {req_count}")
# Métrica 2: Usuários Logados (Gauge)
output.append("# HELP system_logged_in_users Number of currently logged-in users")
output.append("# TYPE system_logged_in_users gauge")
output.append(f"system_logged_in_users {{host=\"production-01\"}} {users}")
# Nova linha no final é obrigatória para o parser do Prometheus
output.append("")
return "\n".join(output)
Note o uso de chaves {} para labels. Labels permitem filtrar e agrupar métricas no Grafana ou PromQL. Definir labels estáticos como server ou host é uma boa prática para identificar a origem dos dados em ambientes escalados.
Passo 3: Implementando o Servidor HTTP
Agora, implementamos o manipulador de requisições. O servidor deve responder à rota /metrics com o código HTTP 200 e o cabeçalho correto.
class MetricsHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/metrics':
metrics_state.increment_requests() # Conta a própria solicitação
content = generate_metrics()
self.send_response(200)
self.send_header('Content-type', 'text/plain; charset=utf-8')
self.end_headers()
self.wfile.write(content.encode('utf-8'))
else:
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
# Opcional: Desativar logs padrão para reduzir ruído no stdout/stderr
pass
A classe BaseHTTPRequestHandler gerencia automaticamente o parsing da URL e os cabeçalhos HTTP. Ao sobrescrever do_GET, definimos a lógica específica para métodos de leitura.
Passo 4: Executando o Exporter em Background
Para rodar este script como um serviço, utilizaremos o módulo socketserver.TCPServer. Diferente de servidores web completos como Gunicorn ou Nginx, esta abordagem é leve e focada apenas na entrega das métricas.
PORT = 9091
def run_server():
with socketserver.TCPServer(("", PORT), MetricsHandler) as httpd:
print(f"Serving at port {PORT}")
httpd.serve_forever()
if __name__ == "__main__":
# Simulação de atualização de dados externos
def simulate_data():
while True:
# Simula variação no número de usuários entre 0 e 100
import random
metrics_state.set_logged_in_users(random.randint(0, 100))
time.sleep(5)
# Inicia a simulação em uma thread separada
threading.Thread(target=simulate_data, daemon=True).start()
run_server()
Execute o script no terminal:
python3 custom_exporter.py
O servidor estará rodando na porta 9091. Você pode testar a saída manualmente usando curl:
curl http://localhost:9091/metrics
Você deverá ver uma saída textual similar ao exemplo abaixo:
# HELP http_requests_total Total number of processed requests
# TYPE http_requests_total counter
http_requests_total {server="custom-exporter"} 1
# HELP system_logged_in_users Number of currently logged-in users
# TYPE system_logged_in_users gauge
system_logged_in_users {host="production-01"} 42
Passo 5: Configurando o Prometheus para Scrape
Agora que o exporter está ativo, precisamos instruir o Prometheus a coletar esses dados. Edite o arquivo de configuração principal do Prometheus, geralmente localizado em /etc/prometheus/prometheus.yml.
Adicione uma nova entrada na seção scrape_configs. É fundamental definir um job name descritivo e a URL correta do endpoint.
global:
scrape_interval: 15s
scrape_configs:
# ... suas configurações existentes ...
- job_name: 'custom-python-exporter'
static_configs:
- targets: ['localhost:9091']
labels:
environment: 'production'
service: 'app-monitoring'
Importante: Se o exporter estiver em uma máquina diferente do Prometheus, substitua localhost pelo IP ou hostname adequado e garanta que as regras de firewall (iptables, ufw ou Security Groups) permitam a comunicação na porta 9091.
Reinicie o serviço do Prometheus para aplicar as mudanças:
sudo systemctl restart prometheus
Passo 6: Validação e Verificação no Grafana
Após a reinicialização, verifique se o Prometheus reconheceu o novo target. Acesse a interface web do Prometheus (geralmente na porta 9090) e vá em Status > Targets. Você deve ver seu job custom-python-exporter com o status UP.
Se o status for DOWN, verifique os logs do Prometheus (journalctl -u prometheus) para identificar erros de conexão ou parsing. Problemas comuns incluem:
- Bloqueio de firewall na porta 9091.
- Má formatação no texto retornado (falta de nova linha final).
- Cabeçalhos HTTP incorretos.
Para visualizar os dados, abra o Grafana. Adicione um novo painel e insira uma query PromQL. Por exemplo:
system_logged_in_users
Você verá a linha do tempo atualizando a cada intervalo de scrape (definido como 15s na configuração global). Isso confirma que a integração entre Python, o exporter customizado e o ecossistema Prometheus está funcional.
Boas Práticas para Produção
Embora o script acima seja funcional para testes e ambientes leves, para produção robusta, considere as seguintes melhorias:
1. Isolamento com Systemd
Nunca rode scripts Python diretamente no terminal. Crie uma unit file .service no systemd para garantir reinicialização automática em caso de falhas e gerenciamento correto de logs.
[Unit]
Description=Custom Prometheus Exporter in Python
After=network.target
[Service]
User=nobody
Group=nogroup
ExecStart=/usr/bin/python3 /home/user/prometheus-custom-exporter/custom_exporter.py
Restart=always
[Install]
WantedBy=multi-user.target
2. Segurança e Autenticação
Expôr métricas internamente é seguro, mas se o exporter precisar ser acessado externamente ou se os dados forem sensíveis, implemente autenticação básica ou use um proxy reverso como Nginx para validar tokens antes de repassar a requisição ao Python.
3. Uso de Bibliotecas Especializadas
Para projetos maiores, considere usar bibliotecas como prometheus-client do lado do cliente. Elas abstraem a geração de strings e oferecem tipos de métricas mais avançados (Histograms, Summaries) com precisão estatística superior, embora adicionem uma dependência externa ao seu projeto.
4. Tratamento de Erros
Sempre envolva a lógica de coleta de dados (como contar usuários ou consultar banco de dados) em blocos try/except. Se a fonte de dados falhar, o exporter não deve retornar erro HTTP 500; ele deve retornar as últimas métricas conhecidas ou zero, garantindo que o Prometheus mantenha o histórico contínuo sem gaps.
Conclusão
A criação de exporters customizados em Python é uma habilidade essencial para sysadmins e desenvolvedores que desejam ter controle total sobre a observabilidade de suas aplicações. Ao invés de depender exclusivamente de ferramentas genéricas, você pode expor métricas de negócio, filas de processamento ou estados específicos de hardware de forma leve e eficiente.
Esta abordagem, baseada na biblioteca padrão do Python, oferece um equilíbrio perfeito entre simplicidade e funcionalidade. Ela permite que scripts existentes se integrem instantaneamente ao ecossistema Prometheus, facilitando a automação de monitoramento sem complexidade desnecessária. Com as métricas sendo coletadas corretamente, você está pronto para criar dashboards no Grafana que realmente importam para a saúde e performance da sua infraestrutura.
Lembre-se: o monitoramento não é apenas sobre alertar quando algo quebra, mas sobre entender como seu sistema se comporta sob carga. Comece simples, itere e expanda conforme suas necessidades de automação crescem.