A luta por soluções de qualidade na Erlang / Elixir


@jcutrer


Hoje falaremos sobre logs de eventos, métricas quantitativas e monitoramento de tudo isso para aumentar a taxa de reação da equipe a incidentes e reduzir o tempo de inatividade do sistema de destino.


Erlang / OTP, como estrutura e ideologia para a construção de sistemas distribuídos, oferece abordagens regulamentadas para o desenvolvimento, ferramentas e implementação de componentes padrão. Digamos que usamos o potencial do OTP e fomos desde o protótipo até a produção. Nosso projeto Erlang parece ótimo em servidores de batalha, a base de códigos está em constante evolução, novos requisitos e funcionalidades aparecem, novas pessoas entram na equipe e tudo parece estar bem. Mas, às vezes, algo dá errado e problemas técnicos, multiplicados pelo fator humano, podem levar a um acidente.


Uma vez que é impossível descartar absolutamente todos os casos possíveis de falhas e problemas, ou não é economicamente viável, é necessário reduzir o tempo de inatividade do sistema em caso de falhas pelas soluções de gerenciamento e software.


Nos sistemas de informação, sempre haverá uma probabilidade de ocorrência de falhas de várias naturezas:


  • Falhas de hardware e falhas de energia
  • Falhas na rede: erros de configuração, curvas de firmware
  • Erros lógicos: de erros de codificação de algoritmos a problemas de arquitetura que surgem nos limites de subsistemas e sistemas.
  • Problemas de segurança e ataques e hacks relacionados, incluindo fraude interna.

Imediatamente distinguimos responsabilidade: o monitoramento da infraestrutura, por exemplo, organizado pela zabbix, será responsável pela operação de equipamentos de computação e redes de transmissão de dados. Muito foi escrito sobre a instalação e configuração desse monitoramento, não o repetiremos.


Do ponto de vista do desenvolvedor, o problema de acessibilidade e qualidade está no plano de detecção precoce de erros e problemas de desempenho e a rápida resposta a eles. Isso requer abordagens e meios de avaliação. Então, vamos tentar derivar métricas quantitativas, analisando quais, em diferentes estágios do desenvolvimento e operação do projeto, podemos melhorar significativamente a qualidade.


Sistemas de montagem


Deixe-me lembrá-lo mais uma vez sobre a importância da abordagem de engenharia e testes no desenvolvimento de software. O Erlang / OTP oferece duas estruturas de teste ao mesmo tempo: unidade e teste comum.


Como métricas para uma avaliação inicial do estado da base de códigos e de sua dinâmica, você pode usar o número de testes bem-sucedidos e problemáticos, o tempo de execução e a porcentagem de cobertura de código nos testes. Ambas as estruturas permitem salvar os resultados do teste no formato Junit.
Por exemplo, para rebar3 e ct, adicione as seguintes linhas ao rebar.config:


{cover_enabled, true}. {cover_export_enabled, true}. {ct_opts,[ {ct_hooks, [{cth_surefire, [{path, "report.xml"}]}]} ]}. 

O número de testes com e sem êxito permitirá que você crie um gráfico de tendências:

olhando para qual, você pode avaliar a dinâmica da equipe e a regressão dos testes. Por exemplo, em Jenkins, este gráfico pode ser obtido usando o plug-in Analisador de Resultados de Teste.


Se os testes ficarem vermelhos ou começarem a ser executados por um longo tempo, as métricas permitirão que a liberação seja finalizada mesmo no estágio de montagem e teste automático.


Métricas de aplicativo


Além das métricas do sistema operacional, o monitoramento deve incluir métricas de aplicativos, como o número de visualizações por segundo, o número de pagamentos e outros indicadores críticos.


Nos meus projetos, eu uso um modelo como ${application}.${metrics_type}.${name} para nomear as métricas. Essa nomeação permite obter listas de métricas do formulário


 messaging.systime_subs.messages.delivered = 1654 messaging.systime_subs.messages.proxied = 0 messaging.systime_subs.messages.published = 1655 messaging.systime_subs.messages.skipped = 3 

Talvez quanto mais métricas, mais fácil é entender o que está acontecendo em um sistema complexo.


Métricas da VM Erlang


Atenção especial deve ser dada ao monitoramento da Erlang VM. A ideologia de deixar cair é linda e o uso adequado do OTP certamente ajudará a elevar as partes caídas do aplicativo dentro da Erlang VM. Mas não se esqueça da própria Erlang VM, porque é difícil descartá-la, mas é possível. Todas as opções são baseadas na exaustão de recursos. Listamos os principais:


  • Estouro da tabela Atom.
    Átomos são identificadores cujo objetivo principal é melhorar a legibilidade do código. Os átomos criados uma vez permanecem para sempre na memória da instância da Erlang VM, pois não são limpos pelo coletor de lixo. Por que isso está acontecendo? O coletor de lixo trabalha separadamente em cada processo com os dados desse processo, enquanto os átomos podem ser distribuídos pelas estruturas de dados de muitos processos.
    Por padrão, 1.048.576 átomos podem ser criados. Em artigos sobre como matar o Erlang VM, geralmente você pode encontrar algo assim.


     [list_to_atom(integer_to_list(I)) || I <- lists:seq(erlang:system_info(atom_count), erlang:system_info(atom_limit))] 

    como uma ilustração desse efeito. Parece que um problema artificial também é inatingível em sistemas reais, mas há casos ... Por exemplo, no manipulador de API externa, ao analisar solicitações, binary_to_atom/2 vez de binary_to_existing_atom/2 ou list_to_atom/1 vez de list_to_existing_atom/1 .
    Os seguintes parâmetros devem ser usados ​​para monitorar o estado dos átomos:


    1. erlang:memory(atom_used) - quantidade de memória usada para átomos
    2. erlang:system_info(atom_count) - o número de átomos criados no sistema. Juntamente com erlang:system_info(atom_limit) , a utilização do átomo pode ser calculada.

  • Vazamentos no processo.
    Gostaria de dizer imediatamente que quando o argumento process_limit (+ P é alcançado, o erl) erlang vm não cai, mas entra em um estado de emergência, por exemplo, provavelmente será impossível conectar-se a ele. Por fim, ficar sem memória disponível ao alocar processos vazados causará falha no erlang vm.


    1. erlang:system_info(process_count) - o número de processos ativos no momento. Juntamente com erlang:system_info(process_limit) , a utilização do processo pode ser calculada.
    2. erlang:memory(processes) - memória alocada para processos
    3. erlang:memory(processes_used) - memória usada para processos.

  • Estouro do processo de caixa de correio.
    Um exemplo típico desse problema é que o processo do remetente envia mensagens para o processo do destinatário sem aguardar confirmação, enquanto o receive no processo do destinatário ignora todas essas mensagens devido a um padrão ausente ou incorreto. Como resultado, as mensagens são acumuladas na caixa de correio. Embora o erlang tenha um mecanismo para diminuir a velocidade do remetente, caso o manipulador não possa lidar com o processamento, de qualquer maneira, após o esgotamento da memória disponível, o vm trava.
    Para entender se há problemas com excesso de caixa de correio, o etop ajudará.


     $ erl -name etop@host -hidden -s etop -s erlang halt -output text -node dest@host -setcookie some_cookie -tracing off -sort msg_q -interval 1 -lines 25 


    Como métrica para monitoramento contínuo, você pode obter o número de processos com problemas. Para identificá-los, você pode usar a seguinte função:


     top_msq_q()-> [{P, RN, L, IC, ST} || P <- processes(), { _, L } <- [ process_info(P, message_queue_len) ], L >= 1000, [{_, RN}, {_, IC}, {_, ST}] <- [process_info(P, [registered_name, initial_call, current_stacktrace]) ] ]. 

    Além disso, essa lista pode ser registrada e, ao receber uma notificação do monitoramento, a análise do problema é simplificada.


  • Binários com vazamento.
    A memória para binários grandes (mais de 64 bytes) é alocada no heap geral. O bloco alocado possui um contador de referência que mostra o número de processos que têm acesso a ele. Após reiniciar o contador, ocorre a limpeza. O sistema mais simples, mas, como se costuma dizer, existem nuances. Em princípio, existe a possibilidade de um processo gerar tanto lixo no heap que o sistema não possui memória suficiente para executar a limpeza.
    erlang:memory(binary) atua como uma métrica para o monitoramento, mostrando a memória alocada para os binários.



Portanto, os casos que levaram à queda da vm são resolvidos; além disso, é bom monitorar parâmetros não menos importantes que afetam direta ou indiretamente o funcionamento correto de seus aplicativos:


  • A memória usada pelas erlang:memory(ets) ETS: erlang:memory(ets) .
  • Memória para módulos compilados: erlang:memory(code) .
    Se suas soluções não usarem compilação de código dinâmico, essa opção poderá ser excluída.
    Também gostaria de mencionar erlydtl. Se você compilar modelos dinamicamente, a compilação criará um feixe carregado na memória vm. Também pode causar vazamento de memória.
  • Memória do sistema: erlang:memory(system) . Mostra o consumo de memória de tempo de execução erlang.
  • Memória total consumida: erlang:memory(total) . Essa é a quantidade de memória consumida por processos e tempo de execução.
  • Informações sobre reduções: erlang:statistics(reductions) .
  • Número de processos e portas prontos para execução: erlang:statistics(run_queue) .
  • O tempo de atividade da erlang:statistics(runtime) vm: erlang:statistics(runtime) permite entender se houve uma reinicialização sem análise de log.
  • Atividade de rede: erlang:statistics(io) .

Enviando métricas para o zabbix


Criaremos um arquivo contendo métricas de aplicativo e erlang vm, que atualizaremos a cada N segundos. Para cada nó erlang, o arquivo de métrica deve conter as métricas dos aplicativos em execução nele e as métricas da instância erlang vm. O resultado deve ser algo como isto:


 messaging.systime_subs.messages.delivered = 1654 messaging.systime_subs.messages.proxied = 0 messaging.systime_subs.messages.published = 1655 messaging.systime_subs.messages.skipped = 3 …. erlang.io.input = 2205723664 erlang.io.output = 1665529234 erlang.memory.binary = 1911136 erlang.memory.ets = 1642416 erlang.memory.processes = 23596432 erlang.memory.processes_used = 23598864 erlang.memory.system = 50883752 erlang.memory.total = 74446048 erlang.processes.count = 402 erlang.processes.run_queue = 0 erlang.reductions = 148412771 .... 

Usando zabbix_sender enviaremos esse arquivo para o zabbix, onde uma representação gráfica e a capacidade de criar gatilhos de automação e notificação já estarão disponíveis.


Agora que temos métricas no sistema de monitoramento e acionadores de automação e eventos de notificação criados com base em nós, temos a chance de evitar acidentes reagindo antecipadamente a todos os desvios perigosos de um estado totalmente funcional.


Recolha central de toras


Quando existem 1-2 servidores em um projeto, você provavelmente ainda pode viver sem uma coleção de logs central, mas assim que um sistema distribuído com muitos servidores, clusters, ambientes aparecer, será necessário resolver o problema de coleta e visualização conveniente dos logs.


Para escrever logs em meus projetos, uso lager. Freqüentemente, desde o protótipo até a produção, os projetos passam pelos seguintes estágios de coleta de logs:


  • Log mais simples com saída para um arquivo local ou mesmo para stdout (lager_file_backend)
  • Coleção centralizada de logs usando, por exemplo, syslogd e envio automático de logs para o coletor. Para esse esquema, lager_syslog é adequado.
    A principal desvantagem do esquema é que você precisa acessar o servidor de coleta de logs, localizar o arquivo com os logs necessários e, de alguma forma, filtrar os eventos em busca dos necessários para depuração.
  • Coleção centralizada de logs com armazenamento no repositório, com a capacidade de filtrar e pesquisar por registros.

Sobre os minuses, vantagens e métricas quantitativas que podem ser aplicadas usando o último, e falaremos no prisma de uma implementação específica - lager_clickhouse , que eu uso na maioria dos projetos desenvolvidos. Algumas palavras sobre lager_clickhouse . Este é o backend lager para salvar eventos na clickhouse. No momento, este é um projeto interno, mas há planos para torná-lo aberto. Ao desenvolver o lager_clickhouse, tive que ignorar alguns recursos do clickhouse, por exemplo, usar o buffer de eventos para evitar fazer solicitações frequentes no clickhouse. Esforço gasto pago com operação estável e bom desempenho.


A principal desvantagem da abordagem para salvar no repositório é a essência adicional - clickhouse e a necessidade de desenvolver código para salvar eventos nele e a interface do usuário para analisar e procurar eventos. Além disso, para alguns projetos, pode ser crítico usar o tcp para enviar logs.


Parece-me que os profissionais superam todas as desvantagens possíveis.


  • Pesquisa fácil e rápida de eventos:


    • Filtrar por data sem precisar procurar um arquivo / arquivos em um servidor central que contenha um intervalo de eventos.
    • Filtrando por ambiente. Logs de diferentes subsistemas e geralmente de diferentes clusters são gravados em um repositório. No momento, a separação ocorre de acordo com os rótulos definidos em cada nó do cluster.
    • Filtrar por nome do host
    • Filtrando pelo nome do módulo que enviou o evento
    • Filtragem de Eventos
    • Pesquisa de texto

    Um exemplo de exibição da interface de exibição de log é mostrado na captura de tela:


  • Capacidade de automação.
    Com a introdução do armazenamento de log, tornou-se possível em tempo real receber informações sobre o número de erros, a ocorrência de falhas críticas e a atividade do sistema. Ao introduzir certos limites, podemos gerar eventos de emergência quando o sistema sair do estado funcional, cujos manipuladores executarão ações de automação para eliminar esse estado e enviar notificações aos membros da equipe responsáveis ​​pela funcionalidade:


    • Quando ocorre um erro crítico.
    • No caso de erros de massa (a derivada do tempo aumenta mais rápido que um determinado limite).
    • Uma métrica separada é a taxa de geração de eventos, ou seja, quantos novos eventos aparecem no log de eventos. Você quase sempre pode saber a quantidade aproximada de logs gerados por um projeto por unidade de tempo. Se for excedido várias vezes, provavelmente algo está dando errado.


O desenvolvimento adicional do tópico de automação do tratamento de eventos de emergência foi o uso de scripts lua. Qualquer desenvolvedor ou administrador pode escrever um script para processar logs e métricas. Os scripts oferecem flexibilidade e permitem criar scripts e notificações de automação personalizados.


Sumário


Para entender os processos que ocorrem no sistema e investigar incidentes, é vital ter indicadores quantitativos e logs de eventos, além de ferramentas convenientes para analisá-los. Quanto mais informações coletamos sobre o sistema, mais fácil é analisar seu comportamento e corrigir problemas, mesmo no estágio de sua ocorrência. No caso de nossas medidas não funcionarem, sempre temos agendamentos e registros detalhados do incidente à nossa disposição.


E como você opera soluções em Erlang / Elixir e quais casos interessantes você encontrou na produção?

Source: https://habr.com/ru/post/pt437720/


All Articles