Imagens prontas para produção para k8s

Esta história é sobre como usamos contêineres em um ambiente de produção, especificamente Kubernetes. O artigo é dedicado à coleta de métricas e logs de contêineres, bem como à construção de imagens.

Imagens prontas para produção para k8s

Somos da empresa fintech Exness, que desenvolve serviços de comércio online e produtos fintech para B2B e B2C. Nossa P&D tem muitas equipes diferentes, o departamento de desenvolvimento tem mais de 100 funcionários.

Representamos a equipe responsável pela plataforma para que nossos desenvolvedores coletem e executem código. Em particular, somos responsáveis ​​por coletar, armazenar e relatar métricas, logs e eventos de aplicativos. Atualmente, operamos aproximadamente três mil contêineres Docker em um ambiente de produção, mantemos 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 está a lareira? É difícil entender. Quando foi que pegou fogo? Você pode descobrir, mas não imediatamente. 

Imagens prontas para produção para k8s

Por que alguns contêineres estão parados enquanto outros caíram? Qual contêiner foi o culpado? Afinal, a parte externa dos containers é igual, mas por dentro cada um tem seu Neo.

Imagens prontas para produção para k8s

Nossos desenvolvedores são caras competentes. Eles prestam bons serviços que trazem lucro para a empresa. Mas há falhas quando containers com aplicações se extraviam. Um contêiner consome muita CPU, outro consome a rede, um terceiro consome operações de E/S e o quarto não está completamente claro o que faz com os soquetes. Tudo cai e o navio afunda. 

Agentes

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

Imagens prontas para produção para k8s

Esses agentes são programas de restrição que mantêm os contêineres em tal estado que não quebram uns aos outros. Os agentes são padronizados e isso permite uma abordagem padronizada para atender contêineres. 

No nosso caso, os agentes devem fornecer logs em formato padrão, marcados e limitados. Eles também devem nos fornecer métricas padronizadas que sejam extensíveis do ponto de vista da aplicação de negócios.

Agentes também significam utilitários de operação e manutenção que podem funcionar em diferentes sistemas de orquestração que suportam diferentes imagens (Debian, Alpine, Centos, etc.).

Por fim, os agentes devem oferecer suporte a CI/CD simples que inclua arquivos Docker. Caso contrário, o navio desmoronará, pois os contêineres começarão a ser entregues ao longo de trilhos “tortos”.

Processo de construção e dispositivo de imagem de destino

Para manter tudo padronizado e gerenciável, algum tipo de processo de construção padrão precisa ser seguido. Portanto, decidimos coletar contêineres por contêineres - isso é recursão.

Imagens prontas para produção para k8s

Aqui os contêineres são representados por contornos sólidos. Ao mesmo tempo, decidiram colocar neles kits de distribuição para que “a vida não pareça framboesa”. Por que isso foi feito, explicaremos a seguir.
 
O resultado é uma ferramenta de construção – um contêiner específico de versão que faz referência a versões de distribuição específicas e versões de script específicas.

Como podemos usá-lo? Temos um Docker Hub que contém um contêiner. Nós o espelhamos dentro do nosso sistema para nos livrarmos de dependências externas. O resultado é um contêiner marcado em amarelo. Criamos um modelo para instalar todas as distribuições e scripts necessários no contêiner. Depois disso, montamos uma imagem pronta para uso: os desenvolvedores colocam nela código e algumas de suas próprias dependências especiais. 

O que há de bom nessa abordagem? 

  • Primeiro, controle total de versão das ferramentas de construção - versões de contêiner de construção, script e distribuição. 
  • Em segundo lugar, alcançamos a padronização: criamos templates, imagens intermediárias e prontas para uso da mesma forma. 
  • Terceiro, os contêineres nos proporcionam portabilidade. Hoje usamos Gitlab, e amanhã mudaremos para TeamCity ou Jenkins e poderemos executar nossos containers da mesma forma. 
  • Quarto, minimizando dependências. Não foi por acaso que colocamos kits de distribuição no container, pois isso nos permite evitar baixá-los sempre da Internet. 
  • Em quinto lugar, a velocidade de construção aumentou - a presença de cópias locais das imagens permite evitar perda de tempo com downloads, uma vez que existe uma imagem local. 

Em outras palavras, conseguimos um processo de montagem controlado e flexível. Usamos as mesmas ferramentas para construir qualquer contêiner totalmente versionado. 

Como funciona nosso procedimento de construção

Imagens prontas para produção para k8s

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

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

Imagens prontas para produção para k8s

Pensamos por muito tempo se deveríamos instalar um supervisor. No final, decidimos que precisávamos dele. Escolhemos S6. O supervisor fornece gerenciamento de contêiner: permite conectar-se a ele se o processo principal travar e fornece gerenciamento manual do contêiner sem recriá-lo. Logs e métricas são processos executados dentro do contêiner. Eles também precisam ser controlados de alguma forma, e fazemos isso com a ajuda de um supervisor. Por fim, o S6 cuida da limpeza, processamento de sinais e outras tarefas.

Como utilizamos diferentes sistemas de orquestração, após construir e rodar, o container deve entender em que ambiente está e agir de acordo com a situação. Por exemplo:
Isto permite-nos construir uma imagem e executá-la em diferentes sistemas de orquestração, e será lançada tendo em conta as especificidades deste sistema de orquestração.

 Imagens prontas para produção para k8s

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

Imagens prontas para produção para k8s

A carga útil é executada sob a supervisão do S6. Preste atenção ao coletor e aos eventos – estes são nossos agentes responsáveis ​​pelos logs e métricas. O Kubernetes não os possui, mas o Docker sim. Por que? 

Se olharmos a especificação do “pod” (doravante – pod Kubernetes), veremos que o contêiner de eventos é executado em um pod, que possui um contêiner coletor separado que desempenha a função de coletar métricas e logs. Podemos usar os recursos do Kubernetes: executar contêineres em um pod, 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á todas as mesmas capacidades como saída, ou seja, poderá entregar logs e métricas, já que os agentes serão lançados internamente. 

Métricas e registros

Entregar métricas e logs é uma tarefa complexa. Existem vários aspectos em sua decisão.
A infraestrutura é criada para a execução da carga útil, e não para entrega em massa de logs. Ou seja, esse processo deve ser executado com requisitos mínimos de recursos do contêiner. Nós nos esforçamos para ajudar nossos desenvolvedores: “Obtenha um contêiner Docker Hub, execute-o e poderemos entregar os logs”. 

O segundo aspecto é limitar o volume de toras. Se ocorrer um aumento no volume de logs em vários contêineres (o aplicativo gera um rastreamento de pilha em um loop), a carga na CPU, nos canais de comunicação e no sistema de processamento de log aumenta, e isso afeta a operação do host como um contêineres inteiros e outros no host, às vezes isso leva à "queda" do host. 

O terceiro aspecto é que é necessário oferecer suporte imediato ao maior número possível de métodos de coleta de métricas. Desde a leitura de arquivos e pesquisa do endpoint Prometheus até o uso de protocolos específicos do aplicativo.

E o último aspecto é 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 canais de saída (plugins de saída). Finalizamos e agora contaremos como o usamos usando o Kubernetes como exemplo. 

Imagens prontas para produção para k8s

Digamos que um desenvolvedor implante uma carga de trabalho e o Kubernetes receba uma solicitação para criar um pod. Neste ponto, um contêiner chamado Collector é criado automaticamente para cada pod (usamos webhook de mutação). O colecionador é 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 anotações de pod e, dependendo de seu conteúdo, cria, digamos, um endpoint do Prometheus; 
  • Com base na especificação do pod e nas configurações específicas do contêiner, ele decide como entregar logs.

Coletamos logs por meio da API Docker: os desenvolvedores só precisam colocá-los em stdout ou stderr, e o Collector resolverá isso. Os logs são coletados em partes com algum atraso para evitar possível sobrecarga do host. 

As métricas são coletadas em instâncias de carga de trabalho (processos) em contêineres. Tudo é marcado: namespace, under e assim por diante, e então convertido para o formato Prometheus - e fica disponível para coleta (exceto logs). Também 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 ao Clickhouse para armazenamento de longo prazo.

Tudo funciona exatamente da mesma forma na AWS, apenas substituímos Graylog por Kafka por Cloudwatch. Enviamos os logs para lá e tudo fica muito conveniente: fica imediatamente claro a qual cluster e contêiner eles pertencem. O mesmo se aplica ao Google Stackdriver. Ou seja, nosso esquema funciona tanto on-premise com Kafka quanto na nuvem. 

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

Imagens prontas para produção para k8s

Os mesmos processos são executados dentro do container, são orquestrados usando S6. Todos os mesmos processos estão sendo executados dentro do mesmo contêiner.

Como resultado,

Criamos uma solução completa para construção e lançamento de imagens, com opções de coleta e entrega de logs e métricas:

  • Desenvolvemos uma abordagem padronizada para montagem de imagens e, com base nela, desenvolvemos modelos de CI;
  • Os agentes de coleta de dados são nossas extensões do Telegraf. Nós os testamos bem em produção;
  • Usamos webhook de mutação para implementar contêineres com agentes em pods; 
  • Integrado ao ecossistema Kubernetes/Rancher;
  • Podemos executar os mesmos containers em diferentes sistemas de orquestração e obter o resultado que esperamos;
  • Criou uma configuração de gerenciamento de contêineres completamente dinâmica. 

Coautor: Ilia Prudnikov

Fonte: habr.com

Adicionar um comentário