O que fazer quando um cacho se transforma em abóbora?

Quando a evolução da plataforma dBrain.cloud atingiu os limites declarados considerados normais em nuvens públicas, eles tiveram que lidar com os problemas que surgiram com o crescimento dos volumes de cluster por conta própria. Afinal, as empresas gigantes que operam o Kubernetes não revelam esses segredos.

Por exemplo, para clusters EKS (Elastic Kubernetes Service) na AWS (Amazon Web Services), o limite oficial de armazenamento é de 8 GB. Ao mesmo tempo, os desenvolvedores do EKS sugerem que os usuários monitorem apenas o tamanho do etcd (métrica apiserver_storage_db_total_size_in_bytes) e não ultrapassem esse limite.

De acordo com a política do Google Cloud Platform (GCP) para clusters do Google Kubernetes Engine (GKE), o tamanho máximo do banco de dados etcd é de 6 GB, e o tamanho total de todos os objetos de cada tipo de recurso não deve exceder 800 MB. Por exemplo, você pode criar 750 MB de instâncias de pod e 750 MB de segredos, mas não pode criar 850 MB de segredos. Existem outras restrições também.

O site do Kubernetes afirma que é capaz de rodar em clusters de até 5 mil hosts e até 150 mil pods. Esses são os valores que a comunidade testou, mas em nenhum lugar é dito como atingir esses números.

Mesmo em clusters menores, tivemos problemas devido ao fato de que o etcd e o kube-api congelaram, houve mudanças frequentes no líder do etcd e os servidores da API consumiram todos os recursos alocados a eles, tentando armazenar em cache a avalanche de solicitações. Como resultado, os pods foram criados lentamente no cluster e as conexões com os servidores de API foram constantemente interrompidas.

Todas as solicitações que voam para kube-api vão para o mesmo cluster etcd, o que torna mais lento e reduz o desempenho. Portanto, o primeiro passo do dBrain para a construção de grandes clusters foi dividir o etcd. O que isso significa?

Divisão

Por padrão, as implantações do Kubernetes usam um etcd. Todos nós conhecemos a arquitetura do Kubernetes.

Fonte da imagem kubernetes.io/

Fonte da imagem kubernetes.io/

O resultado final é o seguinte: os servidores de API do Kubernetes acessam o etcd e armazenam todos os dados imediatamente.

O etcd é um armazenamento de chave-valor distribuído, cujo desempenho depende do tempo de resposta das gravações no disco. Isso significa que o Qps (volume de solicitações) depende do tamanho da chave, da taxa de transferência e do tempo de resposta. Não há como resolver o problema escalando adicionando réplicas: quanto mais réplicas, mais lenta é a gravação no etcd, porque cada réplica precisa confirmar sua gravação devido ao mecanismo Raft. Assim, se houver mais réplicas, mais delas precisarão confirmar o registro. Isso incorre em custos porque as réplicas estão em servidores diferentes e se comunicam entre si pela rede. Com uma única base etcd, mesmo em unidades locais NVME dedicadas rápidas, chegamos ao ponto em que os servidores de API levavam 20 segundos para responder a algumas solicitações e não processavam todas.

No etcd, há um limite no tamanho do banco de dados (8 GB por padrão). No entanto, em clusters grandes, há muitos objetos por banco de dados que ocupam mais do que o tamanho definido, mesmo se você definir revisionHistoryLimit=1. Isso não significa que o banco de dados pare de funcionar, mas ainda não é recomendado fazer isso, porque o etcd não pode armazenar informações em cache normalmente e lê do disco o tempo todo.

As transações que vão para o etcd do servidor de API contêm muitos dados. Além do fato de que o valor por chave em si é grande, o etcd mantém todo o histórico de chaves até o último pacote.

Em configurações pequenas, é muito fácil lidar com isso: você precisa mover o etcd para um disco físico separado. O etcd faz todas as gravações sequencialmente - em um thread, e a velocidade depende do tempo que leva para gravar no disco. Para garantir latência mínima, discos separados são alocados para gravação, além do etcd. Mas quando os clusters ficam maiores, isso não ajuda: o etcd ainda se baseia em seu registro e, portanto, o tempo de gravação e resposta aumenta.

No servidor de api, usamos uma opção especial etcd-servers-overrides, que especifica quais objetos do Kubernetes armazenar em qual etcd. (Infelizmente, isso só funciona para recursos padrão, as Definições de Recursos Personalizados (CRD) não podem ser removidas dessa maneira.) Assim, é possível substituir o armazenamento de implantações em outro etcd.

Como resultado, no dBrain, criamos uma arquitetura em que temos três clusters etcd com três réplicas etcd cada. O primeiro etcd continua sendo o principal, ele armazena quase todos os objetos do Kubernetes. No segundo, trouxemos eventos e concessões do Kubernetes (esta é uma recomendação clássica). Os arrendamentos são usados como um mecanismo através do qual os componentes do Kubernetes (e não apenas) “concordam” com quem será o líder, por exemplo, controlador-gerente, agendador. Para uma troca rápida, esse ponto de extremidade deve ser o mais rápido possível. No terceiro etcd, movemos toda a carga de trabalho - as abstrações padrão do Kubernetes para a carga de trabalho: pods, implantações, statefullset, replicaset, daemonset, etc.

Distribuímos a carga de um para três clusters etcd, cada um dos quais pode usar discos dedicados e/ou nós separados. Fizemos uma espécie de dimensionamento horizontal do etcd distribuindo o conjunto total de dados em diferentes etcds. Assim, a largura de banda paralela total aumentou. Sim, as implantações são lidas na mesma velocidade, a gravação não acelerou, mas agora essas ações não se correlacionam entre si, não entram em conflito e algumas solicitações não esperam por outras.

Desafios da migração

Para passar de um etcd para três, infelizmente, você não pode simplesmente marcar a caixa de seleção do servidor de API (etcd-servers-overrides).

Portanto, criamos um utilitário que pega as chaves especificadas da fonte do etcd e as transfere para outro etcd. As atualizações são relativamente indolores: os servidores de API são desativados para não prejudicar a consistência dos dados, então é lançado um utilitário que transfere dados do etcd principal para dois novos. A etapa final é o lançamento dos servidores de API da nova revisão.

É claro que o caminho para grandes clusters não se limitou à divisão do etcd. Nos artigos a seguir, mostraremos como foi realizada a separação de funções entre controlador e gerente. Além disso, temos outro caso para você - dimensionamento automático de servidores de API.


Artigo Original