Na Volterra, o trabalho da equipe de SRE é operar uma plataforma de ponta global baseada em SaaS . Temos que resolver vários desafios no gerenciamento de um grande número de clusters de aplicativos em vários estados (ou seja, online, offline, inatividade do administrador, etc.) e fazemos isso aproveitando o ecossistema e as ferramentas do Kubernetes com um modelo declarativo baseado em pull usando o GitOps.
Neste blog, descreveremos:
Usando o GitOps para gerenciar e monitorar com eficiência uma grande frota de infraestrutura (hosts físicos ou em nuvem) e clusters K8s
Vamos nos aprofundar nas lições aprendidas em escala (3.000 sites de ponta), o que também foi abordado na minha palestra recente no Cloud Native Rejekts em San Diego.
O diagrama de arquitetura (Figura 1) acima mostra a conectividade lógica entre nossos REs e CEs, onde cada CE estabelece conexões redundantes (IPSec ou SSL VPN) com o RE mais próximo.
Quando começamos a projetar nossa plataforma há cerca de 2 anos, nossa equipe de produto nos pediu para resolver os seguintes desafios:
Considerando nossos requisitos e desafios de operar um sistema altamente distribuído, decidimos definir vários princípios que precisávamos que nossa equipe de SRE seguisse para reduzir problemas posteriores:
Como parte do gerenciamento do ciclo de vida do site de ponta, tivemos que resolver como provisionar o sistema operacional host, fazer configurações básicas (por exemplo, gerenciamento de usuários, autoridade de certificação, hugepages, etc.), ativar K8s, implantar cargas de trabalho e gerenciar alterações de configuração contínuas.
Uma das opções que consideramos, mas eventualmente rejeitamos, foi usar KubeSpray+Ansible (para gerenciar o sistema operacional e implantar K8s) e Helm/Spinnaker (para implantar cargas de trabalho). O motivo pelo qual rejeitamos isso foi que isso exigiria que gerenciássemos 2 a 3 ferramentas de código aberto e, então, fizéssemos modificações significativas para atender aos nossos requisitos, que continuaram a crescer à medida que adicionávamos mais recursos, como dimensionamento automático de clusters de borda, suporte para módulos TPM seguros, atualizações diferenciais, etc.
Como nosso objetivo era manter a simplicidade e minimizar o número de componentes em execução diretamente no sistema operacional (fora do Kubernetes), decidimos escrever um daemon Golang leve chamado Volterra Platform Manager (VPM). Este é o único contêiner Docker do systemd no sistema operacional e atua como um canivete suíço que executa muitas funções:
O VPM é responsável por gerenciar o ciclo de vida do sistema operacional host, incluindo instalação, atualizações, patches, configuração, etc. Há muitos aspectos que precisam ser configurados (por exemplo, alocação de hugepages, /etc/hosts, etc)
Gerenciamento para fornecer ciclo de vida para manifesto do Kubernetes. Em vez de usar o Helm, decidimos usar a biblioteca K8s client-go, que integramos ao VPM e usamos vários recursos desta biblioteca:
Otimista = crie recursos e não espere pelo status. É muito semelhante ao comando apply do kubernetes , onde você não sabe se os pods reais são iniciados com sucesso.
Pessimista = aguardar o estado do recurso do Kubernetes. Por exemplo, a implantação aguarda até que todos os pods estejam prontos. Isso é semelhante ao novo comando kubectl wait .
Além das configurações relacionadas aos manifestos do K8s, também precisamos configurar vários serviços do Volterra por meio de suas APIs. Um exemplo são as configurações de VPN IPsec/SSL — o VPM recebe a configuração do nosso plano de controle global e as programa em nós individuais.
Esse recurso nos permite redefinir uma caixa remotamente para o estado original e fazer todo o processo de instalação e registro novamente. É um recurso muito crítico para recuperar um site que precisa de acesso físico/de console.
Embora o gerenciamento do ciclo de vida do K8 possa parecer um grande tópico de discussão para muitas pessoas, para nossa equipe ele provavelmente representa apenas 40–50% do volume geral de trabalho.
O provisionamento zero-touch do site de ponta em qualquer local (nuvem, local ou ponta nômade) é uma funcionalidade crítica, pois não podemos esperar ter acesso a sites individuais nem queremos contratar tantos especialistas em Kubernetes para instalar e gerenciar sites individuais. Simplesmente não é possível dimensionar para milhares.
O diagrama a seguir (Figura 2) mostra como o VPM está envolvido no processo de registro de um novo site:
Como você pode ver, todo o processo é totalmente automatizado e o usuário não precisa saber nada sobre configuração detalhada ou executar nenhuma etapa manual. Leva cerca de 5 minutos para que todo o dispositivo fique online e pronto para atender aos aplicativos e solicitações dos clientes.
A atualização é uma das coisas mais complicadas que tivemos que resolver. Vamos definir o que está sendo atualizado em sites de ponta:
Existem dois métodos conhecidos que podem ser usados para fornecer atualizações para sites de ponta:
Nosso objetivo com a atualização era maximizar a simplicidade e a confiabilidade — semelhante às atualizações padrão de celulares. Além disso, há outras considerações que a estratégia de atualização precisa satisfazer: o contexto de atualização pode ser apenas com o operador do site, ou o dispositivo pode ficar offline ou indisponível por algum tempo devido a problemas de conectividade, etc. Esses requisitos poderiam ser mais facilmente atendidos com o método pull e, portanto, decidimos adotá-lo para atender às nossas necessidades.
Além disso, escolhemos o GitOps porque ele facilitou o fornecimento de um modelo operacional padrão para gerenciar clusters, fluxos de trabalho e alterações de auditoria do Kubernetes para nossa equipe de SRE.
Para resolver os problemas de dimensionamento de milhares de sites, criamos a arquitetura para SRE mostrada na Figura 3:
Primeiro, quero enfatizar que não usamos o Git apenas para armazenar estados ou manifestos. O motivo é que nossa plataforma precisa não apenas lidar com manifestos do K8s, mas também com configurações contínuas de API, versões do K8s, etc. No nosso caso, os manifestos do K8s representam cerca de 60% de toda a configuração declarativa. Por esse motivo, tivemos que criar nossa própria abstração DSL sobre ele, que é armazenada no git. Além disso, como o git não fornece uma API ou qualquer recurso de mesclagem de parâmetros, tivemos que desenvolver daemons Golang adicionais para SRE: API de configuração, Executor e Controlador VP.
Vamos analisar o fluxo de trabalho de lançamento de uma nova versão de software na ponta do cliente usando nossa plataforma SaaS:
Você pode assistir a uma demonstração de todo o fluxo de trabalho aqui:
Nas seções anteriores, descrevemos como nossas ferramentas são usadas para implantar e gerenciar o ciclo de vida de sites de ponta. Para validar nosso design, decidimos construir um ambiente de grande escala com três mil sites de ponta do cliente (conforme mostrado na Figura 4)
Usamos o Terraform para provisionar 3.000 VMs na AWS, Azure, Google e nossa própria nuvem bare metal local para simular escala. Todas essas VMs eram CEs (sites de ponta do cliente) independentes que estabeleciam túneis redundantes para nossos sites de ponta regionais (também conhecidos como PoPs).
A captura de tela abaixo é do nosso painel SRE e mostra números de arestas em locais representados pelo tamanho do círculo. No momento em que fizemos a captura de tela, tínhamos cerca de 2.711 sites de ponta saudáveis e 356 não saudáveis.
Como parte do dimensionamento, encontramos alguns problemas na configuração e no lado operacional que exigiram que fizéssemos modificações em nossos daemons de software. Além disso, tivemos muitos problemas com um provedor de nuvem que levaram à abertura de vários tíquetes de suporte — por exemplo, latência de resposta da API, incapacidade de obter mais de 500 VMs em uma única região, etc.
A observabilidade em um sistema distribuído apresentou um conjunto muito maior de desafios à medida que dimensionávamos o sistema.
Inicialmente, para métricas, começamos com a federação do Prometheus — o Prometheus central no controle global federando o Promethei em bordas regionais (REs), que extrai suas métricas de serviço e federa métricas de seus CEs conectados. O Prometheus de nível superior avaliou alertas e serviu como uma fonte de métricas para análises posteriores. Atingimos rapidamente os limites dessa abordagem (cerca de 1000 CEs) e tentamos minimizar o impacto do número crescente de CEs. Começamos a gerar séries pré-calculadas para histogramas e outras métricas de alta cardinalidade. Isso nos poupou um ou dois dias e depois tivemos que empregar listas de permissões para métricas. No final, conseguimos reduzir o número de métricas de séries temporais de cerca de 60.000 para 2.000 para cada local de CE.
Por fim, após continuar expandindo para mais de 3.000 locais CE e permanecer em produção por muitos dias, ficou claro que isso não era escalável e tivemos que repensar nossa infraestrutura de monitoramento. Decidimos abandonar o Prometheus de nível superior (no controle global) e dividir o Prometheus em cada RE em duas instâncias separadas. Um é responsável por coletar métricas de serviços locais e o segundo por federar métricas de CE. Ambos geram alertas e enviam métricas para o armazenamento central no Cortex. O Cortex é usado para fonte analítica e de visualização e não faz parte do fluxo de alerta de monitoramento principal. Testamos diversas soluções de métricas centralizadas, como Thanos e M3db, e descobrimos que o Cortex era o mais adequado às nossas necessidades.
A captura de tela a seguir (Figura 7) mostra o consumo de memória da extração de prometheus-cef no momento de 3.000 endpoints. O interessante é que os 29,7 GB de RAM foram consumidos, o que não é tanto considerando o tamanho do sistema. Ele pode ser otimizado ainda mais dividindo a raspagem em várias delas ou movendo a gravação remota para o Cortex diretamente na própria borda.
A próxima captura de tela (Figura 8) mostra quanta memória e recursos de CPU precisamos para ingestores Cortex (máx. 19 GB de RAM) e distribuidores nessa escala. A maior vantagem do Cortex é o dimensionamento horizontal, que nos permite adicionar mais réplicas em comparação ao Prometheus, onde o dimensionamento precisa ocorrer verticalmente.
Para infraestrutura de registro em CEs e REs, usamos serviços Fluentbit por nó, que coletam os eventos de registro de serviço e sistema e os encaminham para o Fluentd no RE conectado. O Fluentd encaminha os dados para o ElasticSearch presente no RE. Os dados do ElasticSearch são avaliados pelo Elastalert e regras são definidas para criar alertas do Alertmanager. Estamos usando nossa integração personalizada do Elastalert com o Alertmananger para produzir alertas com os mesmos rótulos produzidos pelo Prometheus.
Os pontos-chave em nossa jornada de monitoramento:
- Inicialmente tínhamos cerca de 50.000 séries temporais por CE com uma média de 15 rótulos
- Nós otimizamos para 2000 por CE em média
Listas while simples para nomes de métricas e listas negras para nomes de rótulos
- O Prometheus centralizado removeu todos os Prometheus REs e CEs
- Em 1000 EC, tornou-se insustentável gerir a quantidade de métricas
- Atualmente temos Prometheus em cada RE (federando-se aos Promethei dos CEs conectados) com RW para Cortex
- Arquitetura de registro descentralizada
- Fluentbit como coletor em cada nó encaminha logs para Fluentd (agregador) em RE
- O ElasticSearch é implantado em cada RE usando pesquisa de cluster remoto para consultar logs de uma única instância do Kibana
Espero que este blog lhe dê uma ideia de tudo o que precisa ser considerado para gerenciar milhares de sites e clusters de ponta implantados ao redor do mundo. Embora tenhamos conseguido atender e validar a maioria dos nossos requisitos iniciais de design, ainda há muitas melhorias pela frente…