Imagens prontas para produção para k8s

Esta história é sobre como usamos contêineres no ambiente de supermercado, especialmente no Kubernetes. O artigo é dedicado à coleta de métricas e logs de contêineres, bem como à criação de imagens.

imagem

Somos da empresa fintech Exness, que atua no desenvolvimento de serviços para comércio on-line e produtos fintech para B2B e B2C. Existem muitas equipes diferentes em nossa pesquisa e desenvolvimento, no departamento de desenvolvimento de mais de 100 funcionários.

Representamos a equipe responsável pela plataforma de coleta e execução de código por nossos desenvolvedores. Em particular, somos responsáveis ​​por coletar, armazenar e fornecer métricas, logs e eventos de aplicativos. Atualmente, operamos em torno de três mil contêineres Docker no ambiente do produto, suportamos nosso armazenamento de big data de 50 TB e fornecemos soluções arquitetônicas construídas em torno de nossa infraestrutura: Kubernetes, Rancher e vários provedores de nuvem pública. 

Nossa motivação


O que está queimando? Ninguém pode responder. Onde fica a lareira? Difícil de entender. Quando pegou fogo? Você pode descobrir, mas não imediatamente. 



Por que alguns contêineres ficam em pé enquanto outros caem? Qual recipiente foi o culpado? De fato, fora dos contêineres são os mesmos, mas dentro de cada um tem seu próprio Neo.



Nossos desenvolvedores são caras alfabetizados. Eles fazem bons serviços que fazem a empresa lucrar. Mas há fakapy quando contêineres com aplicativos vão aleatoriamente. Um contêiner consome muita CPU, o outro consome rede, o terceiro consome operações de E / S e o quarto geralmente não está claro o que faz com soquetes. Tudo isso cai e o navio afunda. 

Agentes


Para entender o que está acontecendo lá dentro, decidimos colocar agentes diretamente em contêineres.



Esses agentes são programas de contenção que mantêm os contêineres em um estado em que não se quebram. Os agentes são padronizados, e isso permite uma abordagem padronizada para o manuseio de contêineres. 

No nosso caso, os agentes devem fornecer logs em um formato padrão, marcados e com trote. Eles também devem nos fornecer métricas padronizadas, extensíveis em termos de aplicativos de negócios.

Agentes também significam utilitários para operação e manutenção, capazes de trabalhar em diferentes sistemas de orquestração, suportando imagens diferentes (Debian, Alpine, Centos, etc.).

Por fim, os agentes devem oferecer suporte a um CI / CD simples, incluindo arquivos do Docker. Caso contrário, o navio desmoronará, porque os contêineres começarão a ser entregues em trilhos "curvos".

Processo de montagem e imagem do dispositivo de destino


Para que tudo seja padronizado e gerenciável, você deve aderir a algum processo de montagem padrão. Portanto, decidimos coletar contêineres por contêineres - tal recursão.



Aqui os contêineres são representados por contornos sólidos. Ao mesmo tempo, eles decidiram distribuir neles para que "a vida não parecesse framboesa". Por que isso foi feito, descreveremos abaixo.
 
O resultado é uma ferramenta de construção - um contêiner de uma determinada versão, que se refere a certas versões de distribuições e certas versões de scripts.

Como usamos? Temos um Docker Hub no qual o contêiner está. Nós o espelhamos dentro do nosso sistema para nos livrarmos de dependências externas. O recipiente resultante está marcado em amarelo. Criamos um modelo para instalar no contêiner todas as distribuições e scripts que precisamos. Depois disso, coletamos uma imagem pronta para operação: os desenvolvedores colocam o código e algumas dependências especiais. 

Por que essa abordagem é boa? 

  • Em primeiro lugar, controle total da versão das ferramentas de construção - construa versões de contêiner, scripts e distribuições. 
  • Em segundo lugar, alcançamos a padronização: da mesma maneira que criamos modelos, intermediários e prontos para a operação. 
  • Em terceiro lugar, os contêineres nos fornecem portabilidade. Hoje usamos o Gitlab, e amanhã mudaremos para o TeamCity ou o Jenkins e, da mesma forma, poderemos lançar nossos contêineres. 
  • Quarto, minimizando dependências. Não é por acaso que colocamos distribuições no contêiner, pois isso nos permite não baixá-las sempre da Internet. 
  • Em quinto lugar, a velocidade da montagem aumentou - a disponibilidade de cópias locais de imagens permite que você não perca tempo fazendo o download, pois existe uma imagem local. 

Em outras palavras, alcançamos um processo de montagem controlado e flexível. Usamos as mesmas ferramentas para criar qualquer contêiner com versão completa. 

Como nosso procedimento de compilação funciona




A montagem é iniciada com um comando, o processo é realizado na imagem (destacada em vermelho). O desenvolvedor possui um arquivo Docker (destacado em amarelo); nós o renderizamos substituindo as variáveis ​​por valores. E, ao longo do caminho, adicionamos cabeçalhos e rodapés - esses são nossos agentes. 

O cabeçalho adiciona distribuições das imagens correspondentes. E o rodapé instala nossos serviços dentro, configura o lançamento da carga de trabalho, o log e outros agentes, substitui o ponto de entrada, etc. 



Pensamos por um longo tempo sobre a possibilidade de definir um supervisor. No final, eles decidiram que precisávamos dele. Escolha S6. O supervisor fornece controle do contêiner: permite conectar-se a ele em caso de queda no processo principal e fornece controle manual do contêiner sem recriá-lo. Logs e métricas são processos executados dentro de um contêiner. Eles também precisam ser controlados de alguma forma, e fazemos isso com a ajuda de um supervisor. Finalmente, o S6 cuida de tarefas domésticas, processamento de sinais e outras tarefas.

Como usamos diferentes sistemas de orquestração, após a montagem e o lançamento, o contêiner deve entender em que ambiente está e agir de acordo com a situação. Por exemplo:
Isso nos permite coletar uma imagem e lançá-la em diferentes sistemas de orquestração, e ela será lançada levando em consideração as especificidades desse sistema de orquestração.

 

Para o mesmo contêiner, obtemos diferentes árvores de processo no Docker e no Kubernetes:



A carga útil é executada no supervisor S6. Preste atenção ao coletor e aos eventos - esses são nossos agentes responsáveis ​​por logs e métricas. O Kubernetes não os possui, mas o Docker os possui. Por quê? 

Se você observar a especificação do "forno" (doravante - pod Kubernetes), veremos que o contêiner de eventos é executado no forno, no qual existe um contêiner coletor separado que executa a função de coletar métricas e logs. Podemos usar os recursos do Kubernetes: executar contêineres em uma lareira, em um único processo e / ou espaço de rede. Na verdade, apresente seus agentes e execute algumas funções. E se o mesmo contêiner for lançado no Docker, ele receberá os mesmos recursos na saída, ou seja, poderá fornecer logs e métricas, uma vez que os agentes serão lançados no interior. 

Métricas e logs


A entrega de métricas e logs é uma tarefa difícil. Existem vários aspectos em sua decisão.
A infraestrutura é criada para atender a carga útil, e não a entrega em massa de logs. Ou seja, esse processo deve ser executado com requisitos mínimos para recursos de contêiner. Nós nos esforçamos para ajudar nossos desenvolvedores: "Pegue o contêiner do Docker Hub, inicie-o e podemos entregar os logs". 

O segundo aspecto é a limitação do volume de logs. Se em vários contêineres houver uma situação de aumento no volume de logs (o aplicativo exibe um rastreamento de pilha no loop), a carga na CPU, canais de comunicação, o sistema de processamento de logs aumenta e isso afeta a operação do host como um todo e de outros contêineres no host, às vezes isso leva a "Queda" do host. 

O terceiro aspecto - você precisa suportar o maior número possível de métodos de coleta de métricas. Da leitura de arquivos e sondagem do ponto final Prometheus ao uso de protocolos de aplicativos específicos.

E o último aspecto - você precisa minimizar o consumo de recursos.

Escolhemos uma solução Go de código aberto chamada Telegraf. Este é um conector universal que suporta mais de 140 tipos de canais de entrada (plugins de entrada) e 30 tipos de saída (plugins de saída). Nós finalizamos e agora contaremos como é usado com o Kubernetes como exemplo. 



Suponha que um desenvolvedor implante uma carga e o Kubernetes receba uma solicitação para criar uma lareira. Nesse ponto, um contêiner chamado Collector é criado automaticamente para cada pod (usamos o webhook de mutação). O coletor é nosso agente. No início, esse contêiner se configura para funcionar com o Prometheus e o sistema de coleta de logs.

  • Para fazer isso, ele usa as anotações da lareira e, dependendo do seu conteúdo, cria, por exemplo, o ponto final do Prometeu; 
  • Com base na especificação da lareira e nas configurações específicas dos contêineres, ele decide como entregar os logs.

Coletamos logs através da API do Docker: basta que os desenvolvedores os coloquem em stdout ou stderr e, em seguida, o Collector descobrirá. Os logs são coletados por partes com algum atraso para evitar possível congestionamento do host. 

As métricas são coletadas em instâncias de carga de trabalho (processos) em contêineres. Tudo é marcado: namespace, abaixo e assim por diante, e depois convertido para o formato Prometheus - e fica disponível para coleta (exceto para logs). Além disso, enviamos logs, métricas e eventos para Kafka e mais:

  • Os logs estão disponíveis no Graylog (para análise visual);
  • Logs, métricas e eventos são enviados à Clickhouse para armazenamento a longo prazo.

Da mesma forma, tudo funciona na AWS, apenas estamos substituindo o Graylog do Kafka pelo Cloudwatch. Enviamos logs para lá e tudo resulta muito conveniente: fica imediatamente claro a quem o cluster e o contêiner pertencem. O mesmo vale para o Google Stackdriver. Ou seja, nosso esquema funciona tanto no local com Kafka quanto na nuvem. 

Se não temos Kubernetes com pods, o esquema é um pouco mais complicado, mas funciona com os mesmos princípios.



Os mesmos processos são executados dentro do contêiner, eles são orquestrados usando o S6. Todos os mesmos processos estão em execução no mesmo contêiner.

Eventualmente


Criamos uma solução completa para montar e iniciar imagens em operação, com opções para coletar e fornecer logs e métricas:

  • Desenvolveu uma abordagem padronizada para a montagem de imagens, com base no desenvolvimento de modelos de IC;
  • Agentes de coleta de dados são nossas extensões ao Telegraf. Nós os executamos bem na produção;
  • Usamos o webhook de mutação para implementar contêineres com agentes nos pods; 
  • Integrado ao ecossistema Kubernetes / Rancher;
  • Podemos executar os mesmos contêineres em diferentes sistemas de orquestração e obter o resultado que esperamos;
  • Criou uma configuração de gerenciamento de contêiner totalmente dinâmica. 

Co-autor: Ilya Prudnikov

All Articles