Monitoramento Customizado com Prometheus e Python

10 min de leitura Automação

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:

  1. Um gauge (medidor): O número atual de usuários logados no sistema.
  2. 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.

Esse tutorial foi útil?

Comentários (0)

Seja o primeiro a comentar.

Deixe seu comentário

Seu comentário será analisado antes de ser publicado.

0/2000
WhatsApp