Logs no Kubernetes (e não só) hoje: expectativas e realidade

Logs no Kubernetes (e não só) hoje: expectativas e realidade

Estamos em 2019 e ainda não temos uma solução padrão para agregação de logs no Kubernetes. Neste artigo gostaríamos, a partir de exemplos da prática real, de compartilhar nossas pesquisas, problemas encontrados e suas soluções.

Porém, primeiro, farei uma reserva de que diferentes clientes entendem coisas muito diferentes ao coletar logs:

  • alguém deseja ver os logs de segurança e auditoria;
  • alguém - registro centralizado de toda a infraestrutura;
  • e para alguns, basta coletar apenas logs de aplicações, excluindo, por exemplo, balanceadores.

Abaixo está o resumo sobre como implementamos várias “listas de desejos” e quais dificuldades encontramos.

Teoria: sobre ferramentas de registro

Antecedentes dos componentes de um sistema de registro

A exploração madeireira já percorreu um longo caminho, como resultado do desenvolvimento de metodologias de coleta e análise de toras, que é o que usamos hoje. Na década de 1950, Fortran introduziu um análogo dos fluxos de entrada/saída padrão, que ajudou o programador a depurar seu programa. Esses foram os primeiros registros de computador que facilitaram a vida dos programadores da época. Hoje vemos neles o primeiro componente do sistema de registro - fonte ou “produtor” de logs.

A informática não parou: surgiram redes de computadores, os primeiros clusters... Sistemas complexos compostos por vários computadores começaram a funcionar. Agora os administradores de sistema eram forçados a coletar logs de diversas máquinas e, em casos especiais, podiam adicionar mensagens do kernel do sistema operacional caso precisassem investigar uma falha do sistema. Para descrever sistemas centralizados de coleta de logs, no início dos anos 2000 foi publicado RFC 3164, que padronizou remote_syslog. Foi assim que apareceu outro componente importante: coletor de registros e seu armazenamento.

Com o aumento no volume de logs e a introdução generalizada de tecnologias da web, surgiu a questão de quais logs precisam ser mostrados de forma conveniente aos usuários. Ferramentas simples de console (awk/sed/grep) foram substituídas por outras mais avançadas visualizadores de registros - terceiro componente.

Com o aumento do volume de toras, outra coisa ficou clara: são necessárias toras, mas não todas. E diferentes toras exigem diferentes níveis de preservação: algumas podem ser perdidas em um dia, enquanto outras precisam ser armazenadas por 5 anos. Então, um componente para filtragem e roteamento de fluxos de dados foi adicionado ao sistema de registro - vamos chamá-lo filtro.

O armazenamento também deu um grande salto: de arquivos regulares para bancos de dados relacionais e, em seguida, para armazenamento orientado a documentos (por exemplo, Elasticsearch). Assim o armazenamento foi separado do coletor.

Em última análise, o próprio conceito de registo expandiu-se para uma espécie de fluxo abstrato de eventos que queremos preservar para a história. Ou melhor, caso necessite realizar uma investigação ou elaborar um relatório analítico...

Como resultado, em um período de tempo relativamente curto, a coleta de logs se desenvolveu em um importante subsistema, que pode ser justamente chamado de uma das subseções do Big Data.

Logs no Kubernetes (e não só) hoje: expectativas e realidade
Se antes as impressões comuns podiam ser suficientes para um “sistema de registro”, agora a situação mudou muito.

Kubernetes e registros

Quando o Kubernetes chegou à infraestrutura, o problema já existente de coleta de logs também não o contornou. De certa forma, tornou-se ainda mais doloroso: a gestão da plataforma de infraestrutura não só foi simplificada, como também complicada ao mesmo tempo. Muitos serviços antigos começaram a migrar para microsserviços. No contexto dos logs, isso se reflete no crescente número de fontes de log, em seu ciclo de vida especial e na necessidade de rastrear os relacionamentos de todos os componentes do sistema por meio de logs...

Olhando para o futuro, posso afirmar que agora, infelizmente, não existe uma opção de registro padronizado para o Kubernetes que se compare favoravelmente a todas as outras. Os esquemas mais populares na comunidade são os seguintes:

  • alguém desenrola a pilha SFAO (Elasticsearch, Fluentd, Kibana);
  • alguém está experimentando o lançado recentemente Loki ou usa Operador de registro;
  • nos (e talvez não só nós?..) Estou bastante satisfeito com meu próprio desenvolvimento - casa de toras...

Como regra, usamos os seguintes pacotes em clusters K8s (para soluções auto-hospedadas):

No entanto, não vou me alongar nas instruções para sua instalação e configuração. Em vez disso, concentrar-me-ei nas suas deficiências e em conclusões mais globais sobre a situação dos registos em geral.

Pratique com logs em K8s

Logs no Kubernetes (e não só) hoje: expectativas e realidade

“Registros do dia a dia”, quantos de vocês estão aí?..

A recolha centralizada de registos de uma infra-estrutura bastante grande requer recursos consideráveis, que serão gastos na recolha, armazenamento e processamento de registos. Durante a operação de diversos projetos, nos deparamos com diversas exigências e problemas operacionais deles decorrentes.

Vamos experimentar o ClickHouse

Vejamos um armazenamento centralizado em um projeto com um aplicativo que gera logs de forma bastante ativa: mais de 5000 linhas por segundo. Vamos começar a trabalhar com seus logs, adicionando-os ao ClickHouse.

Assim que o tempo real máximo for necessário, o servidor de 4 núcleos com ClickHouse já estará sobrecarregado no subsistema de disco:

Logs no Kubernetes (e não só) hoje: expectativas e realidade

Esse tipo de carregamento se deve ao fato de tentarmos escrever no ClickHouse o mais rápido possível. E o banco de dados reage a isso aumentando a carga do disco, o que pode causar os seguintes erros:

DB::Exception: Too many parts (300). Merges are processing significantly slower than inserts

Fato é que Tabelas MergeTree no ClickHouse (eles contêm dados de log) têm suas próprias dificuldades durante as operações de gravação. Os dados inseridos neles geram uma partição temporária, que é então mesclada com a tabela principal. Como resultado, a gravação acaba sendo muito exigente no disco e também está sujeita à limitação que recebemos acima: não mais do que 1 subpartições podem ser mescladas em 300 segundo (na verdade, são 300 inserções por segundo).

Para evitar esse comportamento, deveria escrever para ClickHouse em pedaços tão grandes quanto possível e não mais do que 1 vez a cada 2 segundos. No entanto, escrever em grandes quantidades sugere que devemos escrever com menos frequência no ClickHouse. Isso, por sua vez, pode levar a um buffer overflow e perda de logs. A solução é aumentar o buffer do Fluentd, mas o consumo de memória também aumentará.

Nota: Outro aspecto problemático da nossa solução com ClickHouse estava relacionado ao fato do particionamento no nosso caso (loghouse) ser implementado através de tabelas externas conectadas Mesclar tabela. Isso leva ao fato de que, ao amostrar grandes intervalos de tempo, é necessária muita RAM, uma vez que a metatabela itera por todas as partições - mesmo aquelas que obviamente não contêm os dados necessários. No entanto, agora esta abordagem pode ser declarada obsoleta com segurança para as versões atuais do ClickHouse (c 18.16).

Como resultado, fica claro que nem todo projeto possui recursos suficientes para coletar logs em tempo real no ClickHouse (mais precisamente, sua distribuição não será adequada). Além disso, você precisará usar acumulador, ao qual retornaremos mais tarde. O caso descrito acima é real. E naquela época, não fomos capazes de oferecer uma solução confiável e estável que atendesse ao cliente e nos permitisse coletar logs com o mínimo de atraso...

E quanto ao Elasticsearch?

O Elasticsearch é conhecido por lidar com cargas de trabalho pesadas. Vamos tentar no mesmo projeto. Agora a carga fica assim:

Logs no Kubernetes (e não só) hoje: expectativas e realidade

O Elasticsearch foi capaz de digerir o fluxo de dados, no entanto, gravar esses volumes nele utiliza bastante a CPU. Isto é decidido organizando um cluster. Tecnicamente, isso não é um problema, mas acontece que apenas para operar o sistema de coleta de logs já usamos cerca de 8 núcleos e temos um componente adicional altamente carregado no sistema...

Resumindo: esta opção pode ser justificada, mas apenas se o projeto for grande e a sua gestão estiver pronta para gastar recursos significativos num sistema de registo centralizado.

Surge então uma pergunta natural:

Quais registros são realmente necessários?

Logs no Kubernetes (e não só) hoje: expectativas e realidade Vamos tentar mudar a abordagem em si: os logs devem ser simultaneamente informativos e não cobrir каждое evento no sistema.

Digamos que temos uma loja online de sucesso. Quais registros são importantes? Coletar o máximo de informações possível, por exemplo, de um gateway de pagamento, é uma ótima ideia. Mas nem todos os logs do serviço de fatiamento de imagens no catálogo de produtos são críticos para nós: apenas erros e monitoramento avançado são suficientes (por exemplo, a porcentagem de 500 erros que este componente gera).

Então chegamos à conclusão de que o registro centralizado nem sempre é justificado. Muitas vezes, o cliente deseja coletar todos os logs em um só lugar, embora, na verdade, de todo o log, sejam necessários apenas 5% condicionais de mensagens que são críticas para o negócio:

  • Às vezes é suficiente configurar, digamos, apenas o tamanho do log do contêiner e do coletor de erros (por exemplo, Sentry).
  • Muitas vezes, uma notificação de erro e um grande log local podem ser suficientes para investigar incidentes.
  • Tínhamos projetos que se contentavam exclusivamente com testes funcionais e sistemas de coleta de erros. O desenvolvedor não precisava de logs como tal - eles viam tudo, desde rastreamentos de erros.

Ilustração da vida

Outra história pode servir de bom exemplo. Recebemos uma solicitação da equipe de segurança de um de nossos clientes que já utilizava uma solução comercial desenvolvida muito antes da introdução do Kubernetes.

Foi necessário “fazer amizade” do sistema centralizado de coleta de logs com o sensor corporativo de detecção de problemas – QRadar. Este sistema pode receber logs através do protocolo syslog e recuperá-los do FTP. No entanto, não foi possível integrá-lo imediatamente com o plugin remote_syslog para fluentd (como se viu, Nós não estamos sozinhos). Os problemas com a configuração do QRadar acabaram por ocorrer por parte da equipe de segurança do cliente.

Como resultado, parte dos logs críticos para os negócios foi transferida por upload para o FTP QRadar e a outra parte foi redirecionada por meio de syslog remoto diretamente dos nós. Para isso até escrevemos gráfico simples - talvez ajude alguém a resolver um problema semelhante... Graças ao esquema resultante, o próprio cliente recebeu e analisou logs críticos (usando suas ferramentas favoritas), e conseguimos reduzir o custo do sistema de log, economizando apenas o mês passado.

Outro exemplo é bastante indicativo do que não fazer. Um de nossos clientes para processamento cada eventos vindos do usuário, feitos multilinha saída não estruturada informações no registro. Como você pode imaginar, esses logs eram extremamente inconvenientes tanto para leitura quanto para armazenamento.

Critérios para registros

Tais exemplos levam à conclusão de que, além de escolher um sistema de coleta de logs, é necessário também projete os próprios logs! Quais são os requisitos aqui?

  • Os logs devem estar em formato legível por máquina (por exemplo, JSON).
  • Os logs devem ser compactos e com capacidade de alterar o grau de registro para depurar possíveis problemas. Ao mesmo tempo, em ambientes de produção você deve executar sistemas com um nível de registro como Aviso ou erro.
  • Os logs devem ser normalizados, ou seja, em um objeto log todas as linhas devem ter o mesmo tipo de campo.

Logs não estruturados podem levar a problemas com o carregamento de logs no armazenamento e à interrupção completa de seu processamento. A título de ilustração, aqui está um exemplo com o erro 400, que muitos definitivamente encontraram nos logs do fluentd:

2019-10-29 13:10:43 +0000 [warn]: dump an error event: error_class=Fluent::Plugin::ElasticsearchErrorHandler::ElasticsearchError error="400 - Rejected by Elasticsearch"

O erro significa que você está enviando um campo cujo tipo é instável para o índice com um mapeamento pronto. O exemplo mais simples é um campo no log nginx com uma variável $upstream_status. Ele pode conter um número ou uma string. Por exemplo:

{ "ip": "1.2.3.4", "http_user": "-", "request_id": "17ee8a579e833b5ab9843a0aca10b941", "time": "29/Oct/2019:16:18:57 +0300", "method": "GET", "uri": "/staffs/265.png", "protocol": "HTTP/1.1", "status": "200", "body_size": "906", "referrer": "https://example.com/staff", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", "request_time": "0.001", "cache_status": "-", "upstream_response_time": "0.001, 0.007", "upstream_addr": "127.0.0.1:9000", "upstream_status": "200", "upstream_response_length": "906", "location": "staff"}
{ "ip": "1.2.3.4", "http_user": "-", "request_id": "47fe42807f2a7d8d5467511d7d553a1b", "time": "29/Oct/2019:16:18:57 +0300", "method": "GET", "uri": "/staff", "protocol": "HTTP/1.1", "status": "200", "body_size": "2984", "referrer": "-", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", "request_time": "0.010", "cache_status": "-", "upstream_response_time": "0.001, 0.007", "upstream_addr": "10.100.0.10:9000, 10.100.0.11:9000", "upstream_status": "404, 200", "upstream_response_length": "0, 2984", "location": "staff"}

Os logs mostram que o servidor 10.100.0.10 respondeu com um erro 404 e a solicitação foi enviada para outro armazenamento de conteúdo. Como resultado, o valor nos logs ficou assim:

"upstream_response_time": "0.001, 0.007"

Esta situação é tão comum que até merece um destaque separado referências na documentação.

E quanto à confiabilidade?

Há momentos em que todos os logs, sem exceção, são vitais. E com isso, os esquemas típicos de coleta de log para K8s propostos/discutidos acima apresentam problemas.

Por exemplo, o fluentd não pode coletar logs de contêineres de curta duração. Em um de nossos projetos, o contêiner de migração do banco de dados durou menos de 4 segundos e depois foi excluído - de acordo com a anotação correspondente:

"helm.sh/hook-delete-policy": hook-succeeded

Por conta disso, o log de execução da migração não foi incluído no armazenamento. A política pode ajudar neste caso. before-hook-creation.

Outro exemplo é a rotação de log do Docker. Digamos que haja um aplicativo que grava ativamente nos logs. Em condições normais, conseguimos processar todos os logs, mas assim que surge um problema - por exemplo, conforme descrito acima com um formato incorreto - o processamento é interrompido e o Docker gira o arquivo. O resultado é que os logs críticos para os negócios podem ser perdidos.

É por isso é importante separar os fluxos de log, incorporando o envio dos mais valiosos diretamente no aplicativo para garantir sua segurança. Além disso, não seria supérfluo criar alguns “acumulador” de logs, que pode sobreviver a uma breve indisponibilidade de armazenamento enquanto salva mensagens críticas.

Por fim, não devemos esquecer que É importante monitorar qualquer subsistema adequadamente. Caso contrário, é fácil se deparar com uma situação em que o fluentd está em um estado CrashLoopBackOff e não envia nada, e isso promete a perda de informações importantes.

Descobertas

Neste artigo, não estamos analisando soluções SaaS como o Datadog. Muitos dos problemas aqui descritos já foram resolvidos de uma forma ou de outra por empresas comerciais especializadas na coleta de logs, mas nem todos podem usar SaaS por vários motivos. (os principais são custo e conformidade com 152-FZ).

A coleta centralizada de logs à primeira vista parece uma tarefa simples, mas não é. É importante lembrar que:

  • Apenas os componentes críticos precisam ser registrados detalhadamente, enquanto o monitoramento e a coleta de erros podem ser configurados para outros sistemas.
  • Os logs de produção devem ser mantidos mínimos para não adicionar carga desnecessária.
  • Os logs devem ser legíveis por máquina, normalizados e ter um formato estrito.
  • Logs realmente críticos devem ser enviados em um fluxo separado, que deve ser separado dos principais.
  • Vale a pena considerar um acumulador de toras, que pode evitar picos de carga elevada e tornar a carga no armazenamento mais uniforme.

Logs no Kubernetes (e não só) hoje: expectativas e realidade
Estas regras simples, se aplicadas em todos os lugares, permitiriam que os circuitos descritos acima funcionassem - mesmo que faltassem componentes importantes (a bateria). Se você não aderir a esses princípios, a tarefa levará facilmente você e a infraestrutura a outro componente altamente carregado (e ao mesmo tempo ineficaz) do sistema.

PS

Leia também em nosso blog:

Fonte: habr.com

Adicionar um comentário