Implantações de Kafka Cluster no Kubernetes
Já é de senso comum que, Kafka é um canivete suiço quando se trata em trabalhar com FastData, stream processing, realtime integrations, comunicação entre microserviços, eventsource, event hub, ... Enfim, uma série de paterns e operações podem ser efetuados. Muitas empresas grandes, medias e pequenas, desde cooporações até pequenas startups, usam o Apache Kafka. Mas da mesma forma outro senso é que, subir um cluster Kafka tem suas complicações, além da necessidade de ter um time especialista para "tocar" o ambiente. Pensando nisso podemos fazer uma combinação bem interessante para termos um ambiente estável e escalável, simplificando a operação de um cluster Kafka, através da implementação do Kafka no Kubernetes.
Nos últimos anos, o Kubernetes teve um tremendo aumento na popularidade, como mostra o gráfico do Google Trends. Não apenas startups e pequenas empresas, até mesmo as empresas agora estão se movendo lentamente em direção ao Kubernetes ou a uma versão corporativa dele. Grande parte desse crescimento é atribuível à Containerização com Docker e à agitação dos Microsserviços.
Basicamente, o Kubernetes é um scheduler. É extremamente bom em agendar contêineres da maneira mais ideal para permitir o uso eficiente dos recursos da VM. O Kubernetes, sem dúvida, provou ser o orquestrador de escolha para aplicativos sem estado. O caminho da adoção também está bem estabelecido:
- Compile e construa os artefatos.
- Altere-o de acordo com as recomendações de 12 fatores.
- Enrole-o dentro de um recipiente com o Docker
- Deixe o Kubernetes orquestrar e agendar seus contêineres.
Mas a história é muito diferente quando se trata de aplicativos com estado.
Stateful Applications
Se o seu aplicativo for algo além de um aplicativo da web, você provavelmente precisará lidar com o estado de tempo de execução. No entanto, quando um aplicativo se torna estável? Faça a si mesmo a pergunta: Posso substituir uma instância do meu aplicativo por outra? Posso equilibrar a solicitação com várias cópias do aplicativo sem erros? Antes de responder que sim, pense cuidadosamente sobre o que seu aplicativo está armazenando na memória ou no disco. Esses dados estão mudando no tempo de execução? Seu aplicativo seria interrompido se esses dados fossem perdidos no caso de substituição por outra instância? Nesse caso, você tem um aplicativo com estado.
Para orquestrar adequadamente os aplicativos com estado, o Kubernetes apresentou uma nova definição de recurso StatefulSet (anteriormente conhecida como PetSet) na versão 1.5 no final de 2016. O StatefulSet, quando usado corretamente, pode ser usado para executar aplicativos com estado no Kubernetes. Nesta postagem, tentaremos entender o que é necessário para executar o Apache Kafka (um aplicativo com estado) no Kubernetes.
Apache Kafka as Stateful Application
O Apache Kafka, uma solução de streaming em tempo real, é um aplicativo stateful complexo. Tem:
- Corretores com identidade. Substituir um corretor não é tão simples quanto substituir um pod.
- Dependência do ZooKeeper - outro aplicativo estável que potencializa o comportamento distribuído de Kafka.
- Armazenamento persistente. Devido à replicação, é possível perder esses dados e recuperar, mas isso não deve ser considerado uma operação padrão, especialmente se grandes volumes estiverem sendo manipulados.
Para garantir que todo esse cenário seja executado em harmonia, de forma resiliente e autossustentável, utiliza-se o padrão Operator do Kubernetes, uma vês que existe uma série de fatores de configuração e ações que precisam ser executados para assim manter o Apache Kafka escalável, resiliente, e indo além, sendo inclusive autossuficiente e tendo características de self-healing.
Se você está pensando em executar o Apache Kafka no Kubernetes em 2020, não precisará configurá-lo por conta própria e muito menos montar um Kubernetes operator para Apache Kafka! Existem opções disponíveis como Strimzi. Vamos ver como o Strimzi Kafka Operator facilita a execução do Kafka no Kubernetes.
Strimzi Operator
Strimzi é um Kubernetes Operator para implantar clusters Kafka. Ele define uma definição de recurso personalizado (CRD) chamada “Kafka”, que representa uma combinação do ZooKeeper e Kafka Cluster. Um exemplo muito simples da implantação do Kafka no Kubernetes seria o seguinte:
A configuração acima definirá um cluster ZooKeeper de 3 nós e um cluster Kafka de 3 corretores com um disco persistente de 100 Gi conectado. Isso se traduz efetivamente no StatefulSets for Kafka e ZooKeeper, serviço sem cabeçalho para poder acessar diretamente os corretores individuais (em vez de balancear a carga).
Operações Efetivas
Como você pode ver, é muito fácil começar a implantar um cluster Kafka no Kubernetes. Mas o que garante estabilidade e resiliência quando as atualizações e manutenção precisam ser feitas no cluster Kafka?
O Kubernetes fornece muitos recursos que podem ser usados para implantar qualquer aplicativo de uma maneira mais resiliente. Vamos ver como Strimzi faz uso deles.
Pod Disruption Budgets
Qualquer aplicativo que seja considerado crítico e não possa aceitar o tempo de inatividade (planejado ou não) deve definir um orçamento de interrupção de pod (PDB). Um PDB informa ao Kubernetes quantos pods deste aplicativo podem ser interrompidos. Quando o administrador de cluster do Kubernetes executa uma ação de manutenção como drenagem do nó (drenagem do kubectl), a ação respeita todos os PDBs configurados e remove os pods apenas se o orçamento de interrupção permitir.
Strimzi adiciona dois PDBs cada para o ZooKeeper e Kafka. Vamos dar uma olhada no PDB Kafka:
O PDB acima afirma que entre todos os pods de brokers de Kafka, apenas 1 pode estar indisponível a qualquer momento. Portanto, se você tiver 3 brokers Kafka em 3 nós distintos, qualquer tentativa de drenar mais de 1 nó será bloqueada, uma vez que vai contra o PDB definido. Isso é personalizável para um valor mais alto para clusters Kafka grandes.
Network Policies
As diretivas de rede podem ser usadas para descrever regras que determinam quais aplicativos têm permissão para se conectar a outros aplicativos em um nível de abstração mais alto. Strimzi define políticas de rede para o ZooKeeper (a ser acessado apenas pelo Kafka) e para o Kafka (diferentes portas do ouvinte acessíveis a diferentes entidades).
Abaixo está uma política de rede padrão definida por Strimzi para Kafka:
A porta de replicação 9091 é acessível apenas a outros Brokers e operadores Kafka definidos no Strimzi para executar tarefas de administração. A porta de "plain text” 9093 e a porta TLS 9094 não estão restritas a nenhum aplicativo.
Observe que o Kubernetes fornece apenas uma interface de diretiva de rede. A implementação é deixada para o administrador do cluster. Por exemplo, você pode instalar um plugin CNI como o Calico para implementar as políticas de rede em um cluster. Sem esse plug-in, essas políticas de rede não têm efeito!
Rack Awareness
A configuração correta do rack no Kafka é muito importante por vários motivos. O Kafka usa a configuração do rack para determinar onde as réplicas da partição serão finalizadas. Por exemplo, se você tiver 6 corretores Kafka em 3 zonas de disponibilidade diferentes (ou racks), distribuídas uniformemente (2 em cada zona). Quando um tópico com fator de replicação 3 é criado, o Kafka escolhe corretores que estão em zonas diferentes. Nesse caso, garantirá que nenhuma zona tenha mais de 1 réplica. Isso garante a máxima disponibilidade.
Strimzi permite configurar rack em broker Kafka como abaixo:
O topologyKey é um rótulo que deve existir em todos os nós do cluster Kubernetes. A maioria dos clusters gerenciados em nuvem como EKS, AKS e GKE fornece isso por padrão. No caso do EKS, o valor do rack passado para o corretor Kafka seria algo como eu-central-1a.
O Strimzi usa a configuração de rack acima para garantir que cada pod do broker Kafka também esteja espalhado por todas as zonas de disponibilidade. Faz isso usando um conceito Kubernetes chamado Affinity. Vamos dar uma olhada nisso.
Affinity
Como orquestrador, o Kubernetes faz agendamento. Os agendadores lidam com a pergunta básica: “Onde executar o pod?”. Às vezes, você deseja que determinados pods estejam na mesma máquina (ou diferente) que em outro pod de aplicativo. Por exemplo, dois aplicativos que comunicam muitos dados podem ser colocados no mesmo nó para manter o tráfego local. Ou você deseja que um pod de aplicativo esteja em um nó específico com CPU e memória mais altas. Esses cenários podem ser tratados no Kubernetes usando o Affinity.
Existem dois tipos de afinidade: NodeAffinity - usado para responder à pergunta "Em qual nó executar este pod?" e PodAffinity (ou PodAntiAffinity) - usado para responder à pergunta "Este pod deve ser executado no mesmo nó / diferente que este outro pod?" Ambos determinam o nó em que o pod será executado, mas as regras são baseadas nos nós anteriores e nos últimos.
O Strimzi defini afinidade de nó e pod se a configuração do rack estiver ativada. Abaixo está um exemplo de NodeAffinity:
A configuração acima pode parecer complexa, mas é simples de seguir. Ele informa ao scheduler do Kubernetes que os pods do broker Kafka devem ser agendados nos Nós que possuem um rótulo com a chave failure-domain.beta.kubernetes.io/zone. Portanto, seus nós de trabalho devem ter esse rótulo presente. Para clusters gerenciados como EKS, AKS e GKE, isso já está disponível.
O item de configuração invulgarmente longo requiredDuringSchedulingIgnoredDuringExecution implica que a regra deve ser rigorosamente aplicada ao agendar novos pods do broker Kafka (requiredDuringScheduling), mas é fácil em todos os pods do broker Kafka já em execução encontrados em um nó que não atenda a esse critério (ignoredDuringExecution).
Abaixo está um exemplo de PodAntiAffinity definido por Strimzi quando o rack está ativado:
Para explicar essa configuração, vamos agendar três pods do corretor Kafka:
Ao planejar o broker1, o scheduler tentará executar o pod em um nó na zona de disponibilidade AZ1, por exemplo. A configuração de afinidade acima diz que verifique se já existe um pod do broker Kafka em execução (com base nos rótulos do kubernetes operator) nesse nó e se esse pod não estiver em execução (AntiAffinity), programe o broker1 no AZ1, que será bem-sucedido,
O próximo planejador tenta planejar o broker2 no nó AZ1, mas desta vez encontra o pod do broker1 já em execução. Portanto, esse nó é ignorado e um nó AZ2 diferente é encontrado e o broker2 é planejado.
A etapa 2 é repetida para o broker3 ser agendado no AZ3.
E se você tivesse um quarto corretor a ser agendado também? Em qual nó isso terminaria? Aqui, o item de configuração preferidoDuranteSchedulingIgnoredDuringExecution se torna importante. Isso implica que a regra de afinidade é preferida, mas não obrigatória. Portanto, ao planejar o quarto intermediário, a regra não será atendida (supondo que haja apenas três AZs exclusivos), mas agendada em algum nó de qualquer maneira.
Health Checks
Nenhuma implantação de aplicativo no Kubernetes pode ser considerada pronta para produção sem as verificações de integridade adequadas configuradas. O Kubernetes permite configurar duas verificações - liveness probe e readiness probe.
O Liveness probe é usado para determinar quando reiniciar o pod. As soluções de análise comuns são a verificação da porta TCP ou a chamada HTTP GET. Se o pod não estiver respondendo por um determinado intervalo, ele será reiniciado.
O Readiness probe é usado para determinar quando os pods devem começar a receber tráfego. Isso é feito adicionando o pod no back-end do Serviço responsável pelo tráfego para esses pods.
No caso de Kafka, a distinção entre sondas Liveness e Readiness é importante. Kafka inicia os ouvintes, mas essa não é a confirmação de que está pronto para atender os clientes. Isso pode ocorrer devido a réplicas fora de sincronia que precisam acompanhar outros corretores. Se o broker estiver gerenciando milhares de partições, esse processo de sincronização poderá levar alguns minutos. Portanto, é importante definir o readiness probe para Kafka corretamente. Lembre-se de que as sondas Liveness e Readiness devem ser configuradas.
Liveness Probe
Normalmente para o Kafka o ideal é usar um script bash personalizado para testar o Listening na porta de replicação (9091) usando o netstat. Quando essa detecção de atividade é bem-sucedida, indica que o Kafka foi iniciado corretamente e o ouvinte de replicação está ativado.
Readiness Probe
Configurar uma sonda de prontidão para Kafka é complicado. Quando o corretor Kafka está pronto para atender os clientes? Quando o broker inicia, ele realiza muitas operações, como a obtenção de metadados de cluster do ZooKeeper, sincronização com o controlador, verificação de integridade de log, liderança de partições, sincronização de réplicas para ingressar na lista de partições ISR e muito mais. A maioria dessas atividades ocorre após o início do ouvinte, portanto, enquanto o broker está "live", ele ainda não está "ready".
Quando o broker finalmente está pronto para atender às solicitações dos clientes, ele relata isso por meio de uma métrica JMX kafka.server: type = KafkaServer, name = BrokerState. Strimzi verifica essa métrica na análise de prontidão para determinar quando o broker Kafka está pronto para receber tráfego. Isso é feito em um agente Java que pesquisa essa métrica e, uma vez atingido o estado desejado, grava um arquivo no disco que é verificado pela existência do probe Readiness.
A configuração correta dos probes garante que as atualizações sem interrupção dos corretores Kafka tenham zero tempo de inatividade.
Conclusão
Neste artigo, vimos como executar um aplicativo estável como o Kafka no Kubernetes é um desafio. Vamos recapitular o que vemos.
- Use Operators como Strimzi ao executar o Kafka no Kubernetes
- Não subestime a importância de manter e executar um aplicativo com estado no Kubernetes.
- Faça uso efetivo dos recursos do Kubernetes, como orçamentos de interrupção de pod, políticas de rede, afinidades e exames de saúde.
Com o entendimento adequado das várias construções do Kubernetes, é possível executar o Kafka com segurança e confiabilidadee podemos explorar muitas funcionalidades a partir do momento que você construa seu próprio operator. Conhecendo Kafka e suas particularidades, mais suas ferramentas de monitoramento(Cruise Control, DoctorKafka, etc.), trazendo ferramentas de alert management. Você consegue criar actions no operator para conduzir a operação do Cluster Kafka, desde troca de versão, criação e manutenção de tópicos e usuários(ACL), manutenção do ambiente e resolução de problemas de replica e particionamento. Assim criando um ambiente que precisa de pouca ou quase nenhuma intervenção humana em sua operação (falo por experiência própria, tenho alguns clusters Kafka que levantei, rodando com um operator que fiz e tem +/- 8 meses que não são abertos nenhum chamado do ambiente).
Boa sorte!