Prometheus — Monitorando a saúde da sua aplicação

O monitoramento de aplicações e servidores é uma parte importante do dia-a-dia do desenvolvedor de software. Isso inclui diversos tipos de análises, desde o monitoramento contínuo de possíveis exceções até o uso de CPU, memória e armazenamento do servidor. Outro fator importante do monitoramento é a capacidade de configurar alarmes, por exemplo, você pode querer receber uma notificação sempre que a CPU ou a memória alcançar determinado pico. Melhor ainda, receber notificações quando a sua aplicação parar de responder para que você possa agir rapidamente.

Existem diversas ferramentas que auxiliam no monitoramento, algumas pagas outras open source — inclusive no passado eu já falei sobre duas da Amazon: Cloudwatch e XRay. Neste post, vamos dar uma olhada no Prometheus.

O que é o Prometheus?

Conforme descrito no próprio github da ferramenta, o Prometheus é um sistema de monitoramento para serviços e aplicações. Ele coleta as métricas de seus alvos em determinados intervalos, avalia expressões de regras, exibe os resultados e também pode acionar alertas se alguma condição for observada como verdadeira.

Dentre muitas, estas são principais características do Prometheus:

  • É um modelo de dados multi-dimensional (time series).
  • Possui uma linguagem própria(PromQL) para queries de dados em formato time series.
  • Totalmente autônomo, sem dependência de armazenamento externo.
  • A coleta das métricas ocorre com um modelo pull e via HTTP.
  • Também é possível enviar métricas através de um gateway intermediário.
  • A definição dos serviços a serem monitorados pode ser feita através de uma configuração estática ou através de descoberta.
  • Possui vários modos de suporte a gráficos e painéis.

O objetivo deste post é focar no entendimento sobre como configurar a coleta de métricas — utilizando o modelo pull via HTTP — na sua aplicação e também a visualização delas. Sendo assim, aqui não serão abordados assuntos como configuração e implantação do Prometheus. Contudo, para ter uma ideia do funcionamento geral da ferramenta, é interessante entender como funcionam os componentes internos do Prometheus.

Componentes do Prometheus

O Prometheus possui um componente central chamado Prometheus Server. Este componente possui como responsabilidade monitorar, ele pode ser monitorar um servidor Linux, um servidor Apache HTTP, um processo específico, um banco de dados ou até uma aplicação desenvolvida por você.

Sendo assim, quando falamos de métricas, podemos estar falando de qualquer característica presente nos exemplos citados. Por exemplo, uma métrica pode ser o status da CPU, o uso de memória de um servidor, a quantidade de requisições de sua aplicação, ou qualquer outra unidade que seja possível quantificar.

Em resumo, o funcionamento do Prometheus acontece da seguinte forma:

Dado um intervalo de tempo, o Prometheus Server coleta métricas de seus alvos através do protocolo HTTP. Após coletadas, as métricas são armazenadas num banco de dados time-series para posteriormente serem consultadas. Os alvos e a configuração de intervalo para coleta são definidos em um arquivo chamado prometheus.yml .

O Prometheus possui uma linguagem para consulta das métricas armazenadas, a PromQL. Utilizando esta linguagem, é possível consultar os detalhes das métricas em um dado instante da linha de tempo.

Outra característica importante é que o Prometheus disponibiliza diversas bibliotecas em várias linguagens que são utilizadas para monitorar as aplicações que desenvolvemos. Além disso, existem também os exporters, eles servem para monitorar os sistemas de terceiros (Linux, MySQL, etc). Um exporter é um software que coleta as métricas existentes de um sistema de terceiros e as exporta para o formato métrico que o Prometheus server pode entender.

Figura 1 — Overview do Prometheus

Visualização com Grafana

O Grafana é uma ferramenta open source de visualização que pode ser utilizada para exibir dados de várias fontes diferentes, as mais comuns são: Graphite, InfluxDB, ElasticSearch e Prometheus.

Figura 2 — Grafana e suas fontes de dados (fonte)

Com o Grafana, ao invés de escrever as queries e visualizar as métricas diretamente no Prometheus server, é possível criar dashboard para suas métricas e visualizar elas em um único lugar.

Na prática

Agora que entendemos o que é o Prometheus e o funcionamento dos seus componentes, vamos ver um pouco dele na prática. A ideia aqui é aprender como utilizar o client de Python para exportar métricas da sua aplicação e também entender sobre as diferentes métricas. Sendo assim, toda a parte de configuração do Prometheus Server — onde os alvos de coleta e exporters são definidos — não será abordada neste post.

Pré-requisitos

Antes de começar, é necessário instalar as ferramentas que serão utilizadas, que são o Docker, o Docker Compose e o Git.

Dica: No caso do Docker e Docker Compose, não instale através de instaladores de pacotes ( por exemplo o apt get). Pois há chances da versão do pacote estar desatualizada (isso já aconteceu comigo).

Segue abaixo o link das instalações oficiais:

Rodando a aplicação

Para abstrair a parte da configuração do servidor e deixar o tutorial focado em métricas, preparei um repositório que já possui todo o ambiente configurado.

  1. Clone o repositório
git clone https://github.com/evandroferreiras/prometheus_tutorial.git

2. Entre na pasta clonada e rode o Docker Compose

cd prometheus_tutorial/
docker-compose up --build

Ao rodar este comando no seu terminal, algo como na imagem abaixo deve aparecer:

Figura 3 — Executando o docker-compose up

Pode demorar alguns minutos, pois, agora o Docker está fazendo o download das imagens do Prometheus e Grafana e também o build da imagem da aplicação que desenvolvi para testar as métricas.

Após tudo processado corretamente, o seu terminal deverá mostrar algo assim:

Figura 4 — Grafana, Prometheus Server e aplicação rodando

Para testar se está tudo correto, acesse essas três URLs no seu browser:

Figura 5 — Prometheus Server: http://localhost:9090
Figura 6 — Grafana: http://localhost:3000
Figura 7 — Aplicação : http://localhost:5000

Exportando métricas

Como é possível notar na Figura 7, a aplicação desenvolvida já está retornando as métricas. Este é o formato que a biblioteca client do Prometheus exporta. Por ser somente um exemplo, as métricas estão sendo retornadas na raiz da API, mas normalmente elas serão retornadas em um endpoint especifico para isso, por exemplo, http://localhost:5000/metrics .

O arquivo da nossa aplicação de exemplo é bem simples, vamos dar uma analisada nele:

O código é basicamente um loop infinito que a cada iteração aguarda um número randômico de segundos. A ideia é simular requisições de uma API, cada iteração é uma requisição ocorrendo.

Quais as métricas podemos extrair desta aplicação?

  • Métricas relacionadas ao tempo de espera das requisições
  • Métricas relacionadas a quantidade de requisições efetuadas

Neste exemplo estamos extraindo as seguintes métricas:

REQUEST_TIME_HIST = Histogram('request_latency_seconds',     
'Histogram')

Nesta linha estamos definindo uma métrica que extrairá um histograma (Histogram) para expor a latência de cada requisição.

COUNTER = Counter('requests_greater_than_half_sec_total',
'number of waits greater than half a second')

Na linha acima, estamos extraindo um contador (Counter). A lógica de incremento é a seguinte: para cada requisição, caso ela demore mais que 0.5 segundos ele é incrementado.

Consultando as métricas

Vamos acessar o Prometheus Server(http://localhost:9090) e localizar as duas métricas:

  • O Counter: requests_greater_than_half_sec_total
  • O Histogram: request_latency_seconds

Counters

O Counter somente incrementa, a métrica que criamos nos permite acompanhar o crescimento de requests que demoraram mais que meio segundo conforme o passar do tempo.

O valor absoluto de um counter é bem irrelevante e dificilmente deve ser considerado — isto por que os counters não preservam seus valores quando uma aplicação restarta. No mundo dos micro services isso é muito comum, as instâncias de serviços possuem uma vida de curta duração (por exemplo, rolling-updates, auto-scaling…), portanto, os counters muitas vezes serão redefinidos de volta para 0.

Na verdade, o proposito real dos Counters está em sua evolução ao longo do tempo, no qual pode ser obtida utilizando funções como rate() ou irate(). Estas funções corrigem o problema de resetar o counter, permitindo calcular a métrica mesmo que o serviço seja reiniciado.

A query abaixo demonstra o total de requisições que levaram mais de meio segundo. Caso o serviço pare, esta métrica irá resetar o valor.

sum(requests_greater_than_half_sec_total)

Para ilustrar este cenário, parei o container da API por alguns segundos e subi novamente. Observe como ficou o gráfico, antes de parar o counter estava com um pouco mais de 300:

Agora vamos criar uma métrica que não é afetada quando a aplicação é reiniciada. Para isto, vamos utilizar a função rate , a métrica abaixo demonstra a quantidade de requisições que levaram mais de quinhentos milissegundos por segundo:

sum(rate(requests_greater_than_half_sec_total[5m]))

Diferente da função sum() , a rate() não roda na métrica, mas sim em um range vector (criado ao usar o operador [ ]). Um range vector possui todos os dados da métrica do período especificado. No exemplo acima, [5m] irá considerar todos os dados da métrica por 5 minutos. A aplicação de rate() a um range vector calcula a taxa média de aumento por segundo durante esse tempo. Abaixo é possível visualizar o gráfico dessa métrica:

Neste gráfico, é possível notar um certo comportamento padrão da nossa aplicação. Por exemplo, na média, o sistema está tendo uma requisição que demora mais que quinhentos milissegundos por segundo. O abismo que aparece no minuto 20 é devido à parada do serviço, que ficou parado por pouco mais de 1 minuto. Em cenários de micro services, no qual os mesmos morrem e nascem rapidamente, nem daria para notar nesse gráfico, visto que ele faz a média dos últimos 5 minutos para cada segundo.

Histrogram

Quando expomos uma métrica do tipo histogram utilizando um client do Prometheus, automaticamente estamos expondo três métricas para consultar. No nosso exemplo, criamos uma métrica com o nome request_latency_seconds, sendo assim as métricas que aparecem no Prometheus são essas:

  • request_latency_seconds_bucket: contadores cumulativos para os intervalos coletados.
  • request_latency_seconds_sum: a soma total de todos os valores coletados.
  • request_latency_seconds_count: a quantidade de eventos coletados

Ao tentar observar a latência das requisições de determinado serviço, a tentação inicial pode ser trabalhar com média: elas são simples de calcular ao longo do tempo usando um total e um contador. Contudo, infelizmente as médias têm a desvantagem de ocultar a distribuição e impedir a descoberta de outliers.

Os quantis são uma medida melhor para essa métrica, eles permitem entender a distribuição. Por exemplo, se a latência da requisição do quantil de 0.5 (percentil 50) for 100ms, significa que 50% das requisições foram concluídas abaixo de 100ms. Da mesma forma, se o quantil de 0.99 (percentil 99) for 4s, isso significa que 1% das solicitações responderam em mais de 4s.

No entanto, os quantis são custosos para serem calculados com precisão, pois, eles precisam do conjunto completo de observações (ou seja, a duração de cada solicitação). Os histogramas tornam isso mais simples, agrupando as observações em um intervalo predefinido. Por exemplo, um histograma de latência de requisições pode ter blocos (buckets) para <10ms, <100ms, <1s, <10s.

O Prometheus pode usar esses buckets para inferir os quantis usando a função histogram_quantile():

histogram_quantile(0.9, rate(request_latency_seconds_bucket[5m]))

O gráfico mostrado acima está diferente dos anteriores, este está sendo apresentado pelo Grafana. Esse gráfico está combinando quatro métricas:

1 -Percentil 99 da latência das requisições

histogram_quantile(0.99, rate(request_latency_seconds_bucket[5m]))

2-Percentil 90 da latência das requisições

histogram_quantile(0.9, rate(request_latency_seconds_bucket[5m]))

3-Percentil 50 da latência das requisições

histogram_quantile(0.5, rate(request_latency_seconds_bucket[5m]))

4-Média da latência das requisições

rate(request_latency_seconds_sum[5m])
/
rate(request_latency_seconds_count[5m])

Algumas conclusões que podemos tirar analisando o gráfico:

  • 50% das requisições são concluídas abaixo de 506 milissegundos.
  • 90% da requisições são concluídas abaixo de 901 milissegundos.
  • 99% das requisições são concluídas abaixo de 992 milissegundos.
  • Numa amostragem de 100 requisições, 1 a cada 10 leva 901 milissegundos para completar.
  • Numa amostragem de 100 requisições, 1 a cada 100 leva 992 milissegundos para completar.

Tendo em vista estes valores, é possível começar a entender os padrões do seu sistema e assim identificar e alarmar picos de anomalias.

Visualizando os dados no Grafana

Agora vamos ver como montei esse gráfico no Grafana. Ao acessa-lo em http://localhost:3000/ será solicitado um usuário e senha. Abaixo está o usuário e senha padrão:

  • usuário: admin
  • senha: admin
Logo após o primeiro acesso será solicitado que você modifique a senha.

Após logar, clique no botão “New dashboard” e você será redirecionado para a criação de componentes:

Neste tutorial iremos utilizar o Graph. Logo após adicionar o gráfico, clique no titulo do painel e selecione “edit”. No rodapé da página, no campo “Data source” selecione o prometheus:

Agora é possível adicionar queries(PromQL). Adicione as quatro queries explicadas na sessão anterior e informe a descrição no campo “Legend format”. Deve ficar assim:

Note que há outras abas, não explicarei o que é cada uma delas, mas vou mostrar aqui algumas personalizações que podemos fazer:

  • Na aba “Legend”, marque os campos “As Table”, “To the right” e o “Current”.
  • Na aba “Display”, no grupo “Series overrides”, adicione a serie da média( no exemplo chamei de avg ) e selecione: Lines: false , Dashes: true , Line width: 3.

Por último, mude as cores do seu gráfico clicando na legenda:

No cabeçalho há um filtro para o intervalo de tempo, para ter uma melhor visualização, mude para os últimos 15 minutos. Pronto, você acabou de montar o seu primeiro gráfico no Grafana 😃.

Conclusão

O foco deste post foi fazer um tutorial para entender essa stack( Grafana + Prometheus). Contudo, ainda há muito o que mostrar, principalmente sobre as funcionalidades do PromQL. Caso queira se aprofundar ainda mais nos estudos, deixo abaixo uma lista de links que me ajudaram durante a elaboração do post:


Se quiser trocar uma ideia ou entrar em contato comigo, pode me achar no Twitter(@e_ferreirasouza) ou Linkedin.

Grande abraço e até a próxima!