Prometheus na prática - scraping, o endpoint /metrics e uma mini app Python expondo métricas

Prometheus na prática - scraping, o endpoint /metrics e uma mini app Python expondo métricas

1. Retomando: da stack vazia à primeira fonte de dados No primeiro artigo desta série, subimos Prometheus, Loki e Grafana com Docker Compose, mas sem nenhuma aplicação real enviando dados — o Prometheus só monitorava a si mesmo. Este artigo resolve isso: vamos entender como o Prometheus efetivamente coleta métricas de uma aplicação (scraping) e construir uma mini aplicação em Python puro, sem nenhum framework, que expõe métricas nesse formato. 2. Pull vs push: o modelo de scraping do Prometheus Existem duas formas de uma ferramenta de métricas obter dados de uma aplicação: Push — a aplicação envia ativamente os valores para um servidor central sempre que quiser (ou em intervalos definidos por ela mesma). StatsD funciona assim. Pull (scraping) — a aplicação apenas expõe seus valores atuais em um endpoint HTTP, e é o servidor de métricas que periodicamente visita esse endpoint e lê o estado atual. É o modelo do Prometheus. O Prometheus escolheu pull deliberadamente, por algumas razões práticas: Descoberta centralizada de falhas — se o Prometheus não consegue fazer scraping de um alvo, ele mesmo já sabe e registra isso (métrica up == 0), sem depender da aplicação perceber que parou de enviar dados. Facilidade de testar manualmente — como o endpoint é só HTTP, dá para abrir /metrics num navegador ou com curl e ver exatamente o que será coletado, sem precisar simular o lado do Prometheus. Controle central do intervalo de coleta — quem decide a frequência é quem consome os dados (o Prometheus), não cada aplicação individualmente. A exceção fica por conta de jobs de curta duração (um script batch que roda e termina em segundos, sem ficar no ar para ser "scrapeado") — para esses casos existe o Pushgateway, um componente auxiliar que aceita push e fica exposto para o Prometheus fazer pull dele. Para aplicações de vida longa como as desta série (servidores HTTP), o modelo pull padrão é suficiente. 3. O formato de exposição: o que vive em /metrics Um endpoint /metrics do Prometheus é simplesmente texto plano, em um formato específico chamado exposition format. Cada métrica segue esta estrutura: # HELP app_requests_total Total de requisições recebidas # TYPE app_requests_total counter app_requests_total 42 Enter fullscreen mode Exit fullscreen mode A linha # HELP é uma descrição legível por humanos (opcional, mas boa prática). A linha # TYPE declara o tipo da métrica — counter, gauge, histogram ou summary (o artigo 3 desta série detalha cada tipo). A linha de dados é nome_da_metrica{labels_opcionais="valor"} valor. Nenhuma biblioteca é estritamente necessária para gerar esse texto — é só formatação de string. Isso torna o formato fácil de implementar em qualquer linguagem, mesmo sem um cliente oficial, o que é exatamente o que esta mini aplicação vai fazer. 4. Uma mini app em Python puro com http.server Usando apenas a biblioteca padrão do Python (módulo http.server, sem Flask, Django ou qualquer outro framework), uma aplicação mínima que conta requisições e expõe essa contagem em /metrics: # app.py import time from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer start_time = time.time() request_count = 0 class Handler(BaseHTTPRequestHandler): def do_GET(self): global request_count if self.path == "/metrics": uptime = time.time() - start_time body = ( "# HELP app_requests_total Total de requisicoes recebidas\n" "# TYPE app_requests_total counter\n" f"app_requests_total {request_count}\n" "# HELP app_uptime_seconds Tempo desde que o processo iniciou\n" "# TYPE app_uptime_seconds gauge\n" f"app_uptime_seconds {uptime:.2f}\n" ) self.send_response(200) self.send_header("Content-Type", "text/plain; version=0.0.4") self.end_headers() self.wfile.write(body.encode("utf-8")) return request_count += 1 self.send_response(200) self.send_header("Content-Type", "text/plain") self.end_headers() self.wfile.write(b"ola do app em python\n") def log_message(self, format, *args): pass # silencia o log padrao do http.server no stdout if __name__ == "__main__": server = ThreadingHTTPServer(("0.0.0.0", 8000), Handler) print("app rodando em http://0.0.0.0:8000") server.serve_forever() Enter fullscreen mode Exit fullscreen mode Rodando localmente: python3 app.py Enter fullscreen mode Exit fullscreen mode Cada requisição a qualquer path diferente de /metrics incrementa o contador app_requests_total — importante notar que a própria requisição ao /metrics não conta como requisição de negócio, propositalmente, para não distorcer a métrica. Testando: curl http://localhost:8000/ curl http://localhost:8000/ curl http://localhost:8000/metrics Enter fullscreen mode Exit fullscreen mode A última chamada deve mostrar app_requests_total 2. 5. Configurando o prometheus.yml para coletar a nova app Com o endpoint funcionando, o próximo passo é adicionar um job ao prometheus.yml do artigo anterior, apontando para essa aplicação: # prometheus.yml global: scrape_interval: 15s scrape_configs: - job_name: "prometheus" static_configs: - targets: ["localhost:9090"] - job_name: "python-app" static_configs: - targets: ["python-app:8000"] Enter fullscreen mode Exit fullscreen mode E incluir a aplicação no docker-compose.yml, na mesma rede dos outros serviços (por isso o hostname python-app funciona no target acima — é o nome do serviço no Compose): # docker-compose.yml (trecho adicionado ao do artigo anterior) services: python-app: image: python:3.12-slim working_dir: /app volumes: - ./app.py:/app/app.py:ro command: ["python3", "app.py"] ports: - "8000:8000" Enter fullscreen mode Exit fullscreen mode Depois de docker compose up -d, o Prometheus passa a fazer scraping da aplicação a cada 15 segundos (o scrape_interval global). Para confirmar que o alvo está saudável, em http://localhost:9090/targets o job python-app deve aparecer com estado UP. 6. Consultando os dados no Prometheus Com alguns minutos de coleta (gerando tráfego manualmente com curl em loop, se necessário), já é possível consultar no campo de expressão do Prometheus (http://localhost:9090/graph): # valor bruto do contador, acumulado desde que o processo subiu app_requests_total # taxa de requisições por segundo, calculada sobre a janela de 1 minuto rate(app_requests_total[1m]) Enter fullscreen mode Exit fullscreen mode rate() é a função mais usada sobre contadores no PromQL: como um contador só cresce (e zera se o processo reiniciar), o valor bruto não diz muito sozinho — o que importa é a velocidade de crescimento, que é exatamente o que rate() calcula, já lidando corretamente com o caso de reinício do contador. 7. Conclusão e próximos passos Vimos por que o Prometheus escolhe pull em vez de push, como o formato de exposição em /metrics é apenas texto simples — implementável sem biblioteca alguma —, construímos uma mini aplicação Python que expõe uma métrica real, e configuramos o Prometheus para coletá-la periodicamente. No próximo artigo, a mini aplicação passa a ser em PHP puro, e o foco vai para os três tipos de métrica — counters, gauges e histograms — aplicados a métricas de negócio, não só contagem de requisições. Imagem de capa: Logo oficial do Prometheus — repositório prometheus/prometheus, licença Apache 2.0 (convertido para PNG via wsrv.nl) Referências: Prometheus — Exposition Formats Prometheus — Configuration Prometheus — Querying Basics Python Docs — http.server

Original Source

Read the full article at Dev →

KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.