Coletando registros de Loki

Coletando registros de Loki

No Badoo, monitoramos constantemente novas tecnologias e avaliamos se vale a pena usá-las em nosso sistema. Gostaríamos de compartilhar um desses estudos com a comunidade. É dedicado ao Loki, um sistema de agregação de logs.

Loki é uma solução para armazenar e visualizar logs, e essa pilha também fornece um sistema flexível para analisá-los e enviar dados ao Prometheus. Em maio, foi lançada outra atualização, promovida ativamente pelos criadores. Estávamos interessados ​​no que o Loki pode fazer, quais recursos ele oferece e até que ponto ele pode atuar como uma alternativa ao ELK, a pilha que usamos agora.

O que é Loki

Grafana Loki é um conjunto de componentes para um sistema completo de trabalho com logs. Ao contrário de outros sistemas semelhantes, o Loki é baseado na ideia de indexar apenas metadados de log - rótulos (os mesmos do Prometheus) e compactar os próprios logs em pedaços separados.

Homepage, GitHub

Antes de entrarmos no que você pode fazer com o Loki, quero esclarecer o que queremos dizer com “a ideia de indexar apenas metadados”. Vamos comparar a abordagem Loki e a abordagem de indexação em soluções tradicionais como Elasticsearch, usando o exemplo de uma linha do log nginx:

172.19.0.4 - - [01/Jun/2020:12:05:03 +0000] "GET /purchase?user_id=75146478&item_id=34234 HTTP/1.1" 500 8102 "-" "Stub_Bot/3.0" "0.001"

Os sistemas tradicionais analisam a linha inteira, incluindo campos com um grande número de valores exclusivos de user_id e item_id, e armazenam tudo em grandes índices. A vantagem dessa abordagem é que você pode executar consultas complexas rapidamente, já que quase todos os dados estão no índice. Mas isso tem um custo, pois o índice se torna grande, o que se traduz em requisitos de memória. Como resultado, o índice de log de texto completo é comparável em tamanho aos próprios logs. Para pesquisá-lo rapidamente, o índice deve ser carregado na memória. E quanto mais logs, mais rápido o índice cresce e mais memória ele consome.

A abordagem Loki exige que apenas os dados necessários sejam extraídos de uma string, cujo número de valores é pequeno. Dessa forma, obtemos um pequeno índice e podemos pesquisar os dados filtrando-os por tempo e por campos indexados e, em seguida, verificando o restante com expressões regulares ou pesquisa de substring. O processo não parece dos mais rápidos, mas Loki divide a solicitação em diversas partes e as executa em paralelo, processando uma grande quantidade de dados em pouco tempo. O número de fragmentos e solicitações paralelas neles é configurável; assim, a quantidade de dados que pode ser processada por unidade de tempo depende linearmente da quantidade de recursos fornecidos.

Essa compensação entre um índice grande e rápido e um índice de força bruta pequeno e paralelo permite que Loki controle o custo do sistema. Pode ser configurado e expandido de forma flexível de acordo com as necessidades.

A pilha Loki consiste em três componentes: Promtail, Loki, Grafana. Promtail coleta logs, processa-os e envia-os para Loki. Loki os mantém. E o Grafana pode solicitar dados do Loki e exibi-los. Em geral, o Loki pode ser usado não apenas para armazenar logs e pesquisá-los. A pilha inteira oferece grandes oportunidades para processar e analisar dados recebidos usando o método Prometheus.
Uma descrição do processo de instalação pode ser encontrada aqui.

Pesquisar por registros

Você pode pesquisar os logs em uma interface especial do Grafana - Explorer. As consultas utilizam a linguagem LogQL, que é muito semelhante ao PromQL utilizado no Prometheus. Em princípio, pode ser pensado como um grep distribuído.

A interface de pesquisa é semelhante a esta:

Coletando registros de Loki

A solicitação em si consiste em duas partes: seletor e filtro. Seletor é uma pesquisa que usa metadados indexados (rótulos) atribuídos a logs, e filtro é uma string de pesquisa ou regexp que filtra os registros definidos pelo seletor. No exemplo dado: Entre chaves existe um seletor, tudo depois é um filtro.

{image_name="nginx.promtail.test"} |= "index"

Devido à forma como o Loki funciona, você não pode fazer consultas sem um seletor, mas os rótulos podem ser tão gerais quanto você desejar.

Um seletor é um valor-chave entre chaves. Você pode combinar seletores e especificar diferentes condições de pesquisa usando os operadores =, != ou expressões regulares:

{instance=~"kafka-[23]",name!="kafka-dev"} 
// Найдёт логи с лейблом instance, имеющие значение kafka-2, kafka-3, и исключит dev 

Um filtro é um texto ou regexp que filtrará todos os dados recebidos pelo seletor.

É possível obter gráficos ad-hoc com base nos dados recebidos no modo métrico. Por exemplo, você pode descobrir com que frequência uma entrada contendo o índice de string aparece nos logs do nginx:

Coletando registros de Loki

Uma descrição completa dos recursos pode ser encontrada na documentação LogQL.

Análise de log

Existem várias maneiras de coletar logs:

  • Usando Promtail, um componente padrão da pilha para coleta de logs.
  • Diretamente do contêiner docker usando Driver de registro Loki Docker.
  • Use Fluentd ou Fluent Bit, que pode enviar dados para Loki. Ao contrário do Promtail, eles possuem analisadores prontos para quase qualquer tipo de log e também podem lidar com logs multilinhas.

Normalmente o Promtail é usado para análise. Ele faz três coisas:

  • Encontra fontes de dados.
  • Anexa etiquetas a eles.
  • Envia dados para Loki.

Atualmente o Promtail pode ler logs de arquivos locais e do diário do systemd. Ele deve ser instalado em cada máquina da qual os logs são coletados.

Há integração com Kubernetes: o Promtail automaticamente, por meio da API REST do Kubernetes, reconhece o estado do cluster e coleta logs de um nó, serviço ou pod, colocando imediatamente rótulos com base nos metadados do Kubernetes (nome do pod, nome do arquivo, etc.) .

Você também pode pendurar rótulos com base nos dados do log usando Pipeline. O Pipeline Promtail pode consistir em quatro tipos de estágios. Mais detalhes em documentação oficial, notarei imediatamente algumas nuances.

  1. Estágios de análise. Este é o estágio RegEx e JSON. Nesta fase, extraímos os dados dos logs para o chamado mapa extraído. Podemos extrair do JSON simplesmente copiando os campos necessários no mapa extraído ou por meio de expressões regulares (RegEx), onde grupos nomeados são “mapeados” no mapa extraído. O mapa extraído é um armazenamento de valor-chave, onde chave é o nome do campo e valor é o valor dos logs.
  2. Estágios de transformação. Esta etapa possui duas opções: transform, onde definimos as regras de transformação, e source - a fonte de dados para transformação do mapa extraído. Caso não exista tal campo no mapa extraído, ele será criado. Desta forma é possível criar rótulos que não sejam baseados no mapa extraído. Nesta fase podemos manipular os dados no mapa extraído usando uma ferramenta bastante poderosa Modelo Golang. Além disso, devemos lembrar que o mapa extraído é carregado integralmente durante a análise, o que possibilita, por exemplo, verificar o valor nele contido: “{{if .tag}o valor da tag existe{end}}”. O modelo oferece suporte a condições, loops e algumas funções de string, como Substituir e Aparar.
  3. Etapas de ação. Neste ponto você pode fazer algo com o conteúdo extraído:
    • Crie um rótulo a partir dos dados extraídos, que será indexado pelo Loki.
    • Altere ou defina a hora do evento no log.
    • Altere os dados (texto do log) que irão para o Loki.
    • Crie métricas.
  4. Estágios de filtragem. O estágio de correspondência, onde podemos enviar entradas que não precisamos para /dev/null ou encaminhá-las para processamento posterior.

Usando um exemplo de processamento de logs nginx regulares, mostrarei como você pode analisar logs usando o Promtail.

Para o teste, vamos tomar como nginx-proxy uma imagem nginx modificada jwilder/nginx-proxy:alpine e um pequeno daemon que pode se perguntar via HTTP. O daemon possui vários endpoints, aos quais pode fornecer respostas de diferentes tamanhos, com diferentes status HTTP e com diferentes atrasos.

Coletaremos logs de contêineres docker, que podem ser encontrados no caminho /var/lib/docker/containers/ / -json.log

Em docker-compose.yml configuramos o Promtail e especificamos o caminho para a configuração:

promtail:
  image: grafana/promtail:1.4.1
 // ...
 volumes:
   - /var/lib/docker/containers:/var/lib/docker/containers:ro
   - promtail-data:/var/lib/promtail/positions
   - ${PWD}/promtail/docker.yml:/etc/promtail/promtail.yml
 command:
   - '-config.file=/etc/promtail/promtail.yml'
 // ...

Adicione o caminho dos logs para promtail.yml (há uma opção “docker” na configuração, que faz a mesma coisa em uma linha, mas não ficaria tão claro):

scrape_configs:
 - job_name: containers

   static_configs:
       labels:
         job: containerlogs
         __path__: /var/lib/docker/containers/*/*log  # for linux only

Quando esta configuração estiver habilitada, os logs de todos os contêineres serão enviados ao Loki. Para evitar isso, alteramos as configurações do nginx de teste em docker-compose.yml - adicionamos um campo de tag de registro:

proxy:
 image: nginx.test.v3
//…
 logging:
   driver: "json-file"
   options:
     tag: "{{.ImageName}}|{{.Name}}"

Editando promtail.yml e configurando o Pipeline. A entrada inclui logs do seguinte tipo:

{"log":"u001b[0;33;1mnginx.1    | u001b[0mnginx.test 172.28.0.3 - - [13/Jun/2020:23:25:50 +0000] "GET /api/index HTTP/1.1" 200 0 "-" "Stub_Bot/0.1" "0.096"n","stream":"stdout","attrs":{"tag":"nginx.promtail.test|proxy.prober"},"time":"2020-06-13T23:25:50.66740443Z"}
{"log":"u001b[0;33;1mnginx.1    | u001b[0mnginx.test 172.28.0.3 - - [13/Jun/2020:23:25:50 +0000] "GET /200 HTTP/1.1" 200 0 "-" "Stub_Bot/0.1" "0.000"n","stream":"stdout","attrs":{"tag":"nginx.promtail.test|proxy.prober"},"time":"2020-06-13T23:25:50.702925272Z"}

Estágio de pipeline:

 - json:
     expressions:
       stream: stream
       attrs: attrs
       tag: attrs.tag

Extraímos os campos stream, attrs, attrs.tag (se existirem) do JSON recebido e os colocamos no mapa extraído.

 - regex:
     expression: ^(?P<image_name>([^|]+))|(?P<container_name>([^|]+))$
     source: "tag"

Se conseguimos colocar o campo tag no mapa extraído, então usando regexp extraímos os nomes da imagem e do container.

 - labels:
     image_name:
     container_name:

Atribuímos rótulos. Se as chaves image_name e container_name forem encontradas nos dados extraídos, seus valores serão atribuídos aos rótulos correspondentes.

 - match:
     selector: '{job="docker",container_name="",image_name=""}'
     action: drop

Descartamos todos os logs que não possuem os rótulos image_name e container_name instalados.

  - match:
     selector: '{image_name="nginx.promtail.test"}'
     stages:
       - json:
           expressions:
             row: log

Para todos os logs cujo image_name é nginx.promtail.test, extraia o campo de log do log de origem e coloque-o no mapa extraído com a chave de linha.

  - regex:
         # suppress forego colors
         expression: .+nginx.+|.+[0m(?P<virtual_host>[a-z_.-]+) +(?P<nginxlog>.+)
         source: logrow

Limpamos a linha de entrada com expressões regulares e extraímos o host virtual nginx e a linha de log nginx.

     - regex:
         source: nginxlog
         expression: ^(?P<ip>[w.]+) - (?P<user>[^ ]*) [(?P<timestamp>[^ ]+).*] "(?P<method>[^ ]*) (?P<request_url>[^ ]*) (?P<request_http_protocol>[^ ]*)" (?P<status>[d]+) (?P<bytes_out>[d]+) "(?P<http_referer>[^"]*)" "(?P<user_agent>[^"]*)"( "(?P<response_time>[d.]+)")?

Analise o log nginx usando expressões regulares.

    - regex:
           source: request_url
           expression: ^.+.(?P<static_type>jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$
     - regex:
           source: request_url
           expression: ^/photo/(?P<photo>[^/?.]+).*$
       - regex:
           source: request_url
           expression: ^/api/(?P<api_request>[^/?.]+).*$

Vamos analisar request_url. Usando regexp determinamos a finalidade da solicitação: para dados estáticos, para fotos, para API e definimos a chave correspondente no mapa extraído.

       - template:
           source: request_type
           template: "{{if .photo}}photo{{else if .static_type}}static{{else if .api_request}}api{{else}}other{{end}}"

Usando operadores condicionais no Template, verificamos os campos instalados no mapa extraído e definimos os valores necessários para o campo request_type: foto, estático, API. Atribua outro se falhar. request_type agora contém o tipo de solicitação.

       - labels:
           api_request:
           virtual_host:
           request_type:
           status:

Definimos os rótulos api_request, virtual_host, request_type e status (status HTTP) com base no que conseguimos colocar no mapa extraído.

       - output:
           source: nginx_log_row

Alterar saída. Agora, o log nginx limpo do mapa extraído vai para Loki.

Coletando registros de Loki

Depois de executar a configuração acima, você pode ver que cada entrada recebe rótulos com base nos dados do log.

Uma coisa a ter em mente é que recuperar rótulos com um grande número de valores (cardinalidade) pode desacelerar significativamente o Loki. Ou seja, você não deve colocar, por exemplo, user_id no índice. Leia mais sobre isso no artigo “Como os rótulos no Loki podem tornar as consultas de log mais rápidas e fáceis" Mas isso não significa que você não possa pesquisar por user_id sem índices. Você precisa usar filtros ao pesquisar (“capturar” os dados), e o índice aqui atua como um identificador de fluxo.

Visualização de logs

Coletando registros de Loki

Loki pode atuar como fonte de dados para gráficos Grafana usando LogQL. Os seguintes recursos são suportados:

  • taxa — número de registros por segundo;
  • contagem ao longo do tempo — o número de registros no intervalo especificado.

Existem também funções de agregação Sum, Avg e outras. Você pode construir gráficos bastante complexos, por exemplo, um gráfico do número de erros HTTP:

Coletando registros de Loki

A fonte de dados padrão Loki tem funcionalidade um pouco reduzida em comparação com a fonte de dados Prometheus (por exemplo, você não pode alterar a legenda), mas Loki pode ser conectado como uma fonte com o tipo Prometheus. Não tenho certeza se este é um comportamento documentado, mas a julgar pela resposta dos desenvolvedores “Como configurar o Loki como fonte de dados do Prometheus? · Edição #1222 · grafana/loki”, por exemplo, é totalmente legal, e Loki é totalmente compatível com PromQL.

Adicione Loki como fonte de dados com o tipo Prometheus e adicione URL /loki:

Coletando registros de Loki

E podemos fazer gráficos, como se estivéssemos trabalhando com métricas do Prometheus:

Coletando registros de Loki

Acho que a discrepância na funcionalidade é temporária e os desenvolvedores corrigirão isso no futuro.

Coletando registros de Loki

Métricas

Loki oferece a capacidade de extrair métricas numéricas de logs e enviá-las ao Prometheus. Por exemplo, o log nginx contém o número de bytes por resposta, bem como, com uma certa modificação no formato de log padrão, o tempo em segundos que levou para responder. Esses dados podem ser extraídos e enviados ao Prometheus.

Adicione outra seção ao promtail.yml:

- match:
   selector: '{request_type="api"}'
   stages:
     - metrics:
         http_nginx_response_time:
           type: Histogram
           description: "response time ms"
           source: response_time
           config:
             buckets: [0.010,0.050,0.100,0.200,0.500,1.0]
- match:
   selector: '{request_type=~"static|photo"}'
   stages:
     - metrics:
         http_nginx_response_bytes_sum:
           type: Counter
           description: "response bytes sum"
           source: bytes_out
           config:
             action: add
         http_nginx_response_bytes_count:
           type: Counter
           description: "response bytes count"
           source: bytes_out
           config:
             action: inc

A opção permite definir e atualizar métricas com base nos dados do mapa extraído. Essas métricas não são enviadas ao Loki - elas aparecem no endpoint Promtail /metrics. O Prometheus deve estar configurado para receber os dados recebidos nesta fase. No exemplo acima, para request_type=“api” coletamos uma métrica de histograma. Com este tipo de métrica é conveniente obter percentis. Para estática e foto, coletamos a soma dos bytes e o número de linhas em que recebemos bytes para calcular a média.

Leia mais sobre métricas aqui.

Abra a porta no Promtail:

promtail:
     image: grafana/promtail:1.4.1
     container_name: monitoring.promtail
     expose:
       - 9080
     ports:
       - "9080:9080"

Certifique-se de que as métricas com o prefixo promtail_custom apareçam:

Coletando registros de Loki

Configurando o Prometheus. Adicionar promessa de emprego:

- job_name: 'promtail'
 scrape_interval: 10s
 static_configs:
   - targets: ['promtail:9080']

E desenhamos um gráfico:

Coletando registros de Loki

Desta forma você pode descobrir, por exemplo, as quatro consultas mais lentas. Você também pode configurar o monitoramento para essas métricas.

Escala

Loki pode estar no modo binário único ou no modo fragmentado (modo escalonável horizontalmente). No segundo caso, ele pode salvar os dados na nuvem, e os pedaços e o índice são armazenados separadamente. A versão 1.5 introduz a capacidade de armazenamento em um só lugar, mas ainda não é recomendado usá-lo em produção.

Coletando registros de Loki

Os pedaços podem ser armazenados em armazenamento compatível com S3 e bancos de dados escaláveis ​​horizontalmente podem ser usados ​​para armazenar índices: Cassandra, BigTable ou DynamoDB. Outras partes do Loki - Distribuidores (para escrita) e Querier (para consultas) - não têm estado e também são dimensionadas horizontalmente.

Na conferência DevOpsDays Vancouver 2019, um dos participantes Callum Styan anunciou que com Loki seu projeto possui petabytes de logs com índice inferior a 1% do tamanho total: “Como Loki correlaciona métricas e registros – e economiza dinheiro".

Comparação de Loki e ELK

Tamanho do índice

Para testar o tamanho do índice resultante, peguei logs do contêiner nginx para o qual o Pipeline acima foi configurado. O arquivo de log continha 406 linhas com volume total de 624 MB. Os logs foram gerados em uma hora, aproximadamente 109 entradas por segundo.

Exemplo de duas linhas do log:

Coletando registros de Loki

Quando indexado pelo ELK, obteve um tamanho de índice de 30,3 MB:

Coletando registros de Loki

No caso do Loki, isso resultou em aproximadamente 128 KB de índice e aproximadamente 3,8 MB de dados em blocos. Vale ressaltar que o log foi gerado artificialmente e não possuía grande variedade de dados. Um simples gzip no log JSON do Docker original com dados deu uma compactação de 95,4%, e levando em consideração o fato de que apenas o log nginx limpo foi enviado para o próprio Loki, a compactação de até 4 MB é compreensível. O número total de valores exclusivos para rótulos Loki foi 35, o que explica o pequeno tamanho do índice. Para ELK, o log também foi apagado. Assim, Loki comprimiu os dados originais em 96% e ELK em 70%.

Consumo de memória

Coletando registros de Loki

Se compararmos toda a pilha do Prometheus e do ELK, Loki “come” várias vezes menos. É claro que um serviço Go consome menos que um serviço Java, e comparar o tamanho do JVM Heap Elasticsearch e a memória alocada para Loki está incorreto, mas mesmo assim é importante notar que Loki usa muito menos memória. Sua vantagem de CPU não é tão óbvia, mas também está presente.

velocidade

Loki “devora” registros mais rápido. A velocidade depende de muitos fatores - que tipo de log são, quão sofisticados somos em analisá-los, rede, disco, etc. - mas é definitivamente maior que o ELK (no meu teste - cerca de duas vezes mais). Isso se explica pelo fato de Loki colocar muito menos dados no índice e, consequentemente, gastar menos tempo na indexação. Com a velocidade de pesquisa, a situação é oposta: Loki fica visivelmente mais lento em dados maiores que vários gigabytes, enquanto a velocidade de pesquisa do ELK não depende do tamanho dos dados.

Pesquisar por registros

Loki é significativamente inferior ao ELK em termos de recursos de pesquisa de log. Grep com expressões regulares é poderoso, mas é inferior a um banco de dados maduro. A falta de consultas de intervalo, agregação apenas por rótulos, a impossibilidade de pesquisar sem rótulos - tudo isso nos limita na busca por informações de interesse em Loki. Isso não significa que nada pode ser encontrado usando o Loki, mas define o fluxo de trabalho com logs quando você encontra pela primeira vez um problema nos gráficos do Prometheus e, em seguida, usa esses rótulos para procurar o que aconteceu nos logs.

Interface.

Em primeiro lugar, é lindo (desculpe, não resisti). O Grafana tem uma interface bonita, mas o Kibana é muito mais rico em recursos.

Prós e contras de Loki

Uma das vantagens é que o Loki se integra ao Prometheus, então obtemos métricas e alertas prontos para uso. É conveniente coletar logs e armazená-los em pods do Kubernetes, pois possui descoberta de serviço herdada do Prometheus e anexa rótulos automaticamente.

A desvantagem é a documentação fraca. Algumas coisas, por exemplo, os recursos e capacidades do Promtail, descobri apenas no processo de estudo do código, felizmente é de código aberto. Outra desvantagem são os fracos recursos de análise. Por exemplo, Loki não pode analisar logs multilinhas. Outra desvantagem é que Loki é uma tecnologia relativamente jovem (a versão 1.0 foi em novembro de 2019).

Conclusão

Loki é uma tecnologia 100% interessante e adequada para projetos de pequeno e médio porte, permitindo resolver diversos problemas de agregação de logs, busca de logs, monitoramento e análise de logs.

Não usamos Loki no Badoo porque temos uma pilha ELK adequada para nós e que foi repleta de várias soluções personalizadas ao longo dos anos. Para nós, o obstáculo é pesquisar nos registros. Com quase 100 GB de logs por dia, é importante para nós conseguirmos encontrar tudo e um pouco mais e fazê-lo rapidamente. Para mapeamento e monitoramento, utilizamos outras soluções adaptadas às nossas necessidades e integradas entre si. A pilha Loki tem benefícios tangíveis, mas não nos dará mais do que já temos, e os seus benefícios certamente não compensarão o custo da migração.

E embora depois de pesquisas tenha ficado claro que não podemos usar o Loki, esperamos que este post o ajude na sua escolha.

O repositório com o código utilizado no artigo está localizado aqui.

Fonte: habr.com

Adicionar um comentário