Por que e como criamos um controlador de admissão para tornar os drenos de nós mais seguros ao executar aplicativos com estado no Kubernetes.
A execução de aplicativos com estado no Kubernetes é cada vez mais comum, e eles geralmente são gerenciados usando recursos e operadores personalizados. No entanto, a natureza dinâmica do Kubernetes significa que os pods, especialmente aqueles em cargas de trabalho com estado, não têm garantia de vida longa. Eventos como manutenção ou pressão de recursos podem desencadear despejos de pods, que correm o risco de interromper os serviços se não forem tratados com cuidado.
O Eviction Reschedule Event é um projeto de código aberto que visa resolver esse problema usando os controladores de admissão do Kubernetes para interceptar e rejeitar solicitações de despejo de pods gerenciados pelo operador e, ao mesmo tempo, notificar o operador de que um pod precisa ser movido. Seu objetivo é ajudar a preservar a disponibilidade do serviço e reduzir a interrupção durante eventos como a manutenção de nós.
Continue lendo enquanto eu falo sobre o assunto:
-
- O problema que estamos enfrentando
- Por que as opções existentes não são suficientes
- Uma introdução ao projeto de reprogramação do hook
Embora haja alguma familiaridade com o Kubernetes Couchbase O Operador Autônomo também será referenciado como Operador para fins de brevidade, e a maioria dos exemplos e demonstrações envolverá recursos do Couchbase de algum tipo.
Embora tenha sido desenvolvido originalmente com a plataforma de dados Couchbase em mente, o projeto do gancho de reprogramação foi concebido para ser flexível e pode ser configurado para proteger outros aplicativos com estado gerenciados pelo operador.
Parte 1 - Por que os despejos são um desafio para aplicativos com estado gerenciados pelo operador
Entendendo os despejos de cápsulas
No Kubernetes, o despejo de um pod envolve o acionamento da função PreStop
e depois enviar um gancho SIGTERM
após sua conclusão. Se o pod não tiver saído após um período de carência, isso será seguido por um SIGKILL
. Os despejos ocorrem voluntária e involuntariamente por vários motivos, desde a pressão do nó até o dimensionamento automático, mas este projeto se concentra nos despejos voluntários acionados ao drenar os nós. Esse é o processo pelo qual os pods são removidos dos nós usando a API Kubernetes Eviction, que geralmente é necessária para abrir caminho para operações como manutenção ou atualizações.
Em execução drenagem do kubectl
é uma maneira comum de preparar um nó para manutenção no Kubernetes. Ele marca o nó como não programável e envia solicitações de despejo simultaneamente para todos os pods em execução nesse nó.
Por que os aplicativos com estado são desafiadores
As cargas de trabalho com estado aumentam a complexidade do tratamento de despejo:
Tempos de desligamento imprevisíveis: Essas cargas de trabalho podem envolver processos de longa duração, como consultas em voo, que precisam ser concluídas antes que um pod possa ser encerrado com segurança
Coordenação de aplicativos: O próprio aplicativo geralmente precisa ser notificado antes da remoção do pod para garantir a consistência dos dados e o reequilíbrio adequado
Gerenciamento de operadores: Os operadores que gerenciam o aplicativo precisam de tempo para coordenar a remoção do pod com o estado interno do operador
Movimento de volume: A migração de volumes do Kubernetes de um nó para outro pode levar um tempo considerável, que excede o tempo de vida útil do pod. terminationGracePeriodSeconds
Um Operador automatiza o ciclo de vida de aplicativos complexos, reconciliando continuamente seu estado real com o estado desejado definido em recursos personalizados.
Tomemos o Couchbase como exemplo. Um cluster do Couchbase é composto de vários pods, cada um executando uma instância do Couchbase Server, normalmente espalhados por diferentes nós e zonas de disponibilidade. O Operator garante que o estado do cluster corresponda ao estado definido em um CouchbaseCluster
recurso. Quando um pod precisa ser removido, o Operador deve notificar o cluster para reequilibrar os dados e lidar com todos os processos em execução de forma elegante.
Desafios organizacionais
Outro desafio comum é a separação de responsabilidades em organizações maiores:
-
- Os administradores do Kubernetes gerenciam a infraestrutura
- As equipes de aplicativos gerenciam aplicativos com estado em execução nessa infraestrutura
Isso significa que os administradores do Kubernetes precisam de maneiras de drenar com segurança os nós para realizar a manutenção sem exigir coordenação constante com os proprietários de aplicativos ou afetar a disponibilidade ou a integridade do aplicativo subjacente.
Proteções existentes do Kubernetes - por que elas não são suficientes
O Kubernetes fornece ferramentas como Ganchos PreStop e Orçamentos de interrupção de pods para ajudar com o desligamento gracioso e a disponibilidade do aplicativo durante os despejos.
Ganchos PreStop executar scripts dentro do contêiner antes que o SIGTERM
dando ao pod a chance de se desligar normalmente. Para aplicativos sem estado, isso geralmente é suficiente. Mas para aplicativos com estado, os operadores geralmente precisam coordenar a remoção do pod antes do encerramento para evitar os problemas descritos acima, o que é difícil de fazer dentro dos hooks preStop.
Uma abordagem engenhosa que vimos envolve a adição de um script preStop aos modelos de pod usados pelo Operador. Esse script faz com que o pod adicione a anotação de reprogramação (discutida mais tarde) a si mesmo para notificar o Operador de que deve ser movido e, em seguida, faça um loop até que tenha sido ejetado com segurança do cluster.
Esboço básico do roteiro, não exato:
1 2 3 4 5 6 7 8 9 |
preStop: executar: comando: - /caixa/sh /mnt/kubectl anotar cápsula $SELF_POD_NAME cao.couchbase.com/reprogramar=verdadeiro \\ enquanto /optar/couchbase/caixa/couchbase-cli servidor-lista -c bases de sofá://localhost\\ fazer \\ dormir 1; \\ feito |
No entanto, isso requer a montagem de kubectl
e couchbase-cli
em cada pod, aumentando a complexidade e o tamanho da imagem. Problemas de rede ou outros problemas também podem interromper o script e fazer com que o pod seja encerrado prematuramente.
Orçamentos de interrupção de pods (PDBs) garantem que um número mínimo de pods permaneça disponível durante interrupções voluntárias. Mas eles limitam apenas o número de pods que podem ser removidos simultaneamente, o que significa que ainda estamos lidando com o cenário em que um operador não é capaz de lidar com a remoção de pods de forma graciosa.
O que acontecerá se os pods forem despejados hoje?
O Operator cria PDBs para o Couchbase para limitar quantos pods podem ser despejados de uma vez. Embora isso garanta um nível mínimo de disponibilidade para o aplicativo, os pods que forem despejados ainda serão removidos de forma insegura do cluster do Couchbase por meio de failover.
Também podemos ver como o Operator lida com isso observando o que acontece na UI do administrador do Couchbase.
Mesmo quando os despejos acontecem um pod por vez, quando um pod é reiniciado em um novo nó, o Kubernetes pode permitir despejos no próximo pod antes que o novo pod tenha sido adicionado com segurança ao aplicativo com estado. No Couchbase, evitamos isso com uma porta de prontidão nos pods.
Para ajudar com esses problemas, introduzimos o cao.couchbase.com/reschedule
no CAO v2.7.0. Quando adicionada a um pod, ela informa ao Operador que ele deve ser recriado. No entanto, isolar manualmente os nós e adicionar essa anotação é tedioso, não é bem dimensionado e afeta apenas os pods do Couchbase - outros pods ainda exigem a drenagem normal.
Por que não lidar com isso inteiramente dentro do Operador?
Consideramos incorporar a lógica de validação de despejo dentro do próprio Operador, mas:
-
-
-
- É um antipadrão no design do operador do Kubernetes. Os operadores trabalham por meio de loops de reconciliação, não por controladores de admissão
- A adição de endpoints de controle de admissão às operadoras complica a implementação e a manutenção
-
- Os webhooks de validação são recursos com escopo de cluster e, portanto, exigem permissões com escopo de cluster. O Operator tem escopo de namespace e exigiria uma grande expansão de suas permissões necessárias
-
Parte 2 - Aproveitamento dos Webhooks do Kubernetes para despejos inteligentes de pods
O Eviction Reschedule Hook é um controlador de admissão do Kubernetes de código aberto projetado para melhorar a forma como os despejos são tratados durante a drenagem de nós para evitar os problemas descritos na parte 1. Trabalhando em conjunto com um Operator, ele automatiza o reagendamento do pod interceptando as solicitações de despejo antes que elas cheguem ao pod. Essas solicitações são rejeitadas seletivamente de uma forma que ainda permite que o padrão drenagem do kubectl
para funcionar como esperado.
Em vez de encerrar imediatamente os pods ou testar os limites do orçamento de interrupção do pod (PDB), o controlador de admissão notifica o Operador de que um pod precisa ser movido usando a função cao.couchbase.com/reschedule
anotação.
Entendendo o controle de admissão
O controle de admissão é um estágio importante no ciclo de vida da solicitação da API do Kubernetes que ocorre depois que uma solicitação foi autenticada e autorizada, mas antes de ser mantida no cluster.
Os controladores de admissão podem validar ou alterar essas solicitações com base na lógica personalizada.
Quando um nó é drenado no Kubernetes, cada tentativa de despejo de um pod aciona um CRIAR
solicitação para o pods/eviction
sub-recurso. É aqui que o nosso controlador de admissão entra em ação. Ele intercepta essas solicitações de despejo antes de chegarem aos pods e determina se elas devem ser permitidas. No nosso caso, a lógica de admissão envolve sinalizar para o Operador que um pod deve ser reprogramado com segurança adicionando a anotação de reprogramação.
Estamos usando um webhook de validação em vez de um webhook de mutação porque nosso objetivo principal é avaliar se a solicitação de despejo é permitida. Embora possamos anotar o pod como parte do processo de decisão, o webhook em si está executando a validação, e não a mutação, na solicitação de despejo.
Manuseio de drenos de nós
Como o título deste artigo deixa claro, o objetivo principal deste projeto é permitir o manuseio adequado dos drenos de nós. Com o Eviction Reschedule Hook implementado, o fluxo padrão de drenagem de nós é modificado para oferecer suporte à migração mais segura de pods.
As solicitações de despejo são interceptadas antes que o PreStop
é executado. Em vez de o pod ser encerrado, a solicitação de despejo é rejeitada e o pod é anotado. O Operador verifica se há a anotação de reprogramação durante cada loop de reconciliação. Se a anotação aparecer, ele tratará da recriação segura do pod.
Como a drenagem de um nó o mancha com node.kubernetes.io/unschedulable
Se houver outro nó no cluster do Kubernetes que corresponda aos requisitos de agendamento do pod, quando o Operador criar o novo pod, ele existirá em outro nó.
É importante ressaltar que o controlador de admissão foi projetado para filtrar seletivamente apenas os pods relevantes. Todas as outras cargas de trabalho no nó continuam a seguir o processo de despejo padrão.
Trabalhando com o Kubectl
Para integrar-se facilmente com kubectl
é importante entender o que acontece nos bastidores quando você executa kubectl drain
. Olhando para o drenar.go
fonteDepois que o nó é isolado, uma goroutine separada é gerada para cada pod nesse nó para lidar com seu despejo.
Se kubectl
A tentativa contínua de despejar um pod depende da resposta do controlador de admissão. Se a solicitação de expulsão for bem-sucedida (ou seja, nenhum erro for retornado), o loop no qual as solicitações de expulsão são enviadas será encerrado. Em seguida, há uma verificação de acompanhamento em que kubectl
aguarda a exclusão do pod. No entanto, para os pods gerenciados pelo Operator, queremos kubectl
para continuar tentando até que tenham sido reprogramados com segurança, momento em que devemos encerrar a goroutine antes de qualquer outra verificação.
Por design, kubectl
trata um 429 TooManyRequests
resposta à solicitação de despejo na goroutine como um sinal para fazer uma pausa de 5 segundos antes de tentar novamente. Podemos aproveitar esse comportamento em nosso controlador de admissão: após a lógica de seleção de pod, retornamos um 429
se o pod estiver sendo anotado para reprogramação ou se já estiver anotado e aguardando para ser movido.
Depois que o pod for reprogramado com êxito, ele terá um nome diferente e, portanto, o controlador de admissão não poderá mais localizá-lo por nome e namespace. Nesse ponto, podemos retornar um 404 NotFound
para sair da goroutine.
Reprogramação de pods no local
Em alguns casos, o Operador pode excluir e recriar um pod usando o mesmo nome.
Na Couchbase, o upgradeProcess
em um campo CouchbaseCluster
pode ser alterado para controle como o Operador substitui os pods. O padrão é SwapRebalanceque diz ao Operador para criar um novo pod com um nome diferente, reequilibrá-lo no cluster e, em seguida, excluir o pod antigo. InPlaceUpgrade é uma alternativa mais rápida, mas menos flexível, na qual o Operador excluirá o pod e o recriará com o mesmo nome, reutilizando os volumes existentes. Isso cria um desafio para o controlador de admissão.
As solicitações de despejo enviadas pela goroutine em kubectl
incluem apenas o nome e o namespace do pod. Devido a esse contexto limitado, o controlador de admissão não pode presumir que um pod sem uma anotação de reagendamento precisa ser movido - ele pode ser simplesmente uma instância recém-recriada de um pod que já foi reagendado.
Para lidar com isso, o controlador de admissão mantém um mecanismo de rastreamento leve que é usado quando os pods serão reprogramados com o mesmo nome. Quando um pod é anotado para reprogramação, também anotamos outro recurso, denominado recurso de rastreamento, com o chave reschedule.hook/.
.
Se uma solicitação de despejo subsequente for interceptada para um pod sem a anotação de reprogramação, a presença dessa anotação no recurso de rastreamento indicará que o pod já foi reprogramado. Quando isso ocorrer, o webhook limpará a anotação de rastreamento para esse pod antes de retornar um 404 NotFound
para sair da goroutine.
Embora o tipo de recurso de rastreamento tenha como padrão CouchbaseCluster
o webhook também suporta o uso de um pod Namespace
. Se os pods nunca forem reprogramados com o mesmo nome, esse recurso poderá ser desativado.
Execução do gancho de reprogramação de despejo
A implementação do Eviction Reschedule Hook e de seus componentes de suporte é simples. O LEIAME fornece instruções detalhadas para criar a imagem e implantar a pilha completa.
Os principais componentes que compõem o gancho de reagendamento são:
ValidatingWebhookConfiguration: Diz ao servidor da API do Kubernetes para encaminhar CRIAR
solicitações para o pods/eviction
para nosso serviço de webhook
Serviço: Encaminha as solicitações de webhook de entrada para o pod do controlador de admissão
Controlador de Admissão: Executa o gancho de reprogramação dentro de um contêiner, normalmente implantado como uma implantação do Kubernetes
ServiceAccount, ClusterRole e ClusterRoleBinding: Eles concedem ao controlador de admissão as permissões necessárias. No mínimo, obter
e remendo
são necessárias permissões para pods. Se estiver usando um recurso de rastreamento, obter
e remendo
as permissões para o recurso de rastreamento também devem ser adicionadas
Depois de implantado, a drenagem de um nó no qual residem pods do Couchbase demonstra como o gancho de reprogramação intercepta e rejeita solicitações de despejo até que o operador recrie e equilibre com segurança os pods de volta ao cluster:
O tratamento gracioso das solicitações de despejo também pode ser visto na interface de usuário do administrador do Couchbase. Os pods não sofrem mais falhas e são substituídos com tempo de inatividade zero:
Experimente, participe
O Eviction Reschedule Hook torna os drenos de nós mais seguros e previsíveis para aplicativos com estado em execução no Kubernetes. Ao coordenar o manuseio de despejo com um Operator, ele permite o reagendamento sem interrupções do aplicativo subjacente.
O projeto é de código aberto. Dê uma olhada no repositório no GitHub para começar e dar uma olhada no arquivo contribuindo se você estiver interessado em ajudar. Comentários, problemas e pull requests são bem-vindos!