Um Mergulho Profundo nos Problemas de Conexão do Kafka com o ZooKeeper

Solucione falhas de conexão do Kafka com o ZooKeeper com verificações práticas de configuração, rede, timeouts, logs e carga dos brokers.

Um Mergulho Profundo nos Problemas de Conexão do Kafka com o ZooKeeper

Os problemas de conexão do Kafka com o ZooKeeper afetam principalmente clusters Kafka mais antigos e clusters que não migraram para o modo KRaft. Implantações mais recentes do Kafka podem funcionar sem o ZooKeeper, mas muitos sistemas de produção ainda dependem dele. Se seus brokers usam zookeeper.connect em server.properties, o ZooKeeper ainda faz parte do seu plano de controle e merece o mesmo cuidado que o próprio Kafka.

Quando um broker Kafka não consegue manter sua sessão com o ZooKeeper, os sintomas podem parecer maiores do que um simples problema de conexão. Os brokers podem reiniciar. As eleições do controlador podem se repetir. As partições podem ficar indisponíveis. Os logs podem mostrar expiração de sessão, renúncia do controlador ou tentativas repetidas de reconexão. Produtores e consumidores podem ver apenas o efeito downstream: erros de metadados, timeouts ou líderes instáveis.

Comece pelo papel que o ZooKeeper desempenha. Em clusters Kafka baseados em ZooKeeper, os brokers se registram nele, a eleição do controlador depende dele e a coordenação de metadados do cluster passa por ele. Se um broker perder sua sessão com o ZooKeeper por tempo suficiente para a sessão expirar, o Kafka trata esse broker como removido do cluster. Mesmo que o processo do broker ainda esteja em execução, o cluster pode mover a liderança para longe dele.

A primeira verificação é simples e frequentemente pega o problema: verifique zookeeper.connect em cada broker.

zookeeper.connect=zk01.example.com:2181,zk02.example.com:2181,zk03.example.com:2181/kafka
zookeeper.connection.timeout.ms=18000
zookeeper.session.timeout.ms=18000

A string de conexão deve listar os membros do ensemble que o Kafka pode alcançar. Se você usar um caminho chroot como /kafka, inclua-o consistentemente em todos os brokers. Não configure metade dos brokers com /kafka e a outra metade sem; eles se comportarão como se estivessem falando com clusters Kafka diferentes. Se você usar um chroot, crie-o primeiro ou confirme que ele existe com as ferramentas do ZooKeeper.

Verifique também o DNS, além do texto da configuração. Um nome de host que resolve corretamente no seu laptop pode falhar em uma sub-rede do broker. Execute as verificações a partir do host do broker Kafka, não de um bastion, a menos que o bastion tenha o mesmo caminho de rede.

getent hosts zk01.example.com
nc -vz zk01.example.com 2181
nc -vz zk02.example.com 2181
nc -vz zk03.example.com 2181

Uma conexão TCP bem-sucedida não prova que o ZooKeeper está saudável, mas uma conexão falha é suficiente para continuar investigando firewalls, grupos de segurança, roteamento, DNS ou configuração do listener. Teste cada broker Kafka para cada nó ZooKeeper. A conectividade parcial é pior do que uma falha completa, pois a falha pode aparecer apenas quando um broker tenta se conectar a um membro específico do ensemble.

Os comandos de quatro letras do ZooKeeper podem ajudar quando estão habilitados. Muitas instalações os restringem, então não presuma que funcionam. Se permitido, ruok deve retornar imok, e mntr pode mostrar estatísticas úteis do servidor.

echo ruok | nc zk01.example.com 2181
echo mntr | nc zk01.example.com 2181

Se esses comandos estiverem desabilitados, use as ferramentas administrativas suportadas ou sua pilha de monitoramento. O objetivo é responder a uma pergunta simples: o ZooKeeper está ouvindo, participando do ensemble e respondendo rapidamente?

Em seguida, inspecione a saúde do ensemble ZooKeeper. Um ensemble de três nós pode tolerar um nó ZooKeeper inativo. Não pode tolerar dois. Um ensemble de cinco nós pode tolerar dois. Evite ensembles de tamanho par, pois eles aumentam o custo sem melhorar o quorum da maneira que as pessoas esperam. Três e cinco são escolhas comuns.

No lado do ZooKeeper, verifique zoo.cfg. Confirme clientPort, tickTime, initLimit, syncLimit e as linhas do servidor. Certifique-se de que os nomes de host do servidor anunciados sejam acessíveis entre os nós do ZooKeeper, não apenas a partir dos brokers Kafka. Os pares do ZooKeeper precisam de suas próprias portas de quorum e eleição de líder. Um broker Kafka pode alcançar a porta 2181 enquanto o próprio ensemble ZooKeeper está não saudável porque o tráfego entre pares está bloqueado.

O ajuste do timeout da sessão é outra fonte comum de confusão. O Kafka solicita um timeout de sessão ao ZooKeeper, mas o ZooKeeper impõe limites com base em sua própria configuração. No ZooKeeper, o timeout mínimo de sessão é tipicamente 2 * tickTime e o máximo é tipicamente 20 * tickTime, a menos que seja substituído por configurações específicas do servidor. Isso significa que um valor de timeout do Kafka fora do intervalo permitido pode ser ajustado pelo ZooKeeper.

Se tickTime=2000, o intervalo de sessão permitido usual é de aproximadamente 4 segundos a 40 segundos. Uma configuração do Kafka como zookeeper.session.timeout.ms=18000 se encaixa nesse intervalo. Um timeout muito baixo pode produzir falhas falsas durante pequenas pausas de rede ou pausas de coleta de lixo. Um timeout muito alto pode fazer com que falhas reais do broker demorem mais para serem detectadas. Você está escolhendo entre sensibilidade e estabilidade.

Não altere tickTime casualmente. Isso afeta o ensemble ZooKeeper, não apenas o Kafka. Se você precisar de mais tolerância para pausas do broker, geralmente é melhor começar revisando zookeeper.session.timeout.ms do Kafka, o comportamento da JVM do broker e a saúde da rede antes de alterar o temporizador do ZooKeeper.

Os logs geralmente contam a história se você os alinhar por timestamp. Nos brokers Kafka, procure mensagens relacionadas a desconexões do ZooKeeper e expiração de sessão:

rg -i "zookeeper|session|expired|controller|reconnect" /var/log/kafka/server.log

Os padrões importam mais do que uma única linha. Uma reconexão única durante uma reinicialização planejada do ZooKeeper pode ser inofensiva. Expiração repetida a cada poucos minutos aponta para instabilidade. Expiração durante a coleta de lixo aponta para pausas da JVM ou sobrecarga do broker. Expiração ao mesmo tempo em muitos brokers aponta para o ZooKeeper, a rede ou um evento de infraestrutura compartilhada.

Nos nós do ZooKeeper, verifique mudanças de líder, avisos de fsync, limitação de conexão e latência longa de requisições. O ZooKeeper é sensível à latência do disco porque escreve logs de transação. Um disco lento pode fazer o serviço parecer acessível enquanto ainda falha em responder rápido o suficiente para sessões estáveis.

A latência de rede e a perda de pacotes são mais importantes do que a largura de banda bruta para o ZooKeeper. Os brokers Kafka não precisam de grande throughput para o ZooKeeper, mas precisam de comunicação confiável e de baixa latência. Se os brokers e o ZooKeeper estiverem divididos em redes distantes, espere problemas. Mantenha-os próximos. Em ambientes de nuvem, evite rotear o tráfego broker-ZooKeeper através de NAT desnecessário, firewalls sobrecarregados ou caminhos entre regiões.

A contenção de recursos no broker Kafka pode parecer exatamente um problema do ZooKeeper. Se a JVM parar o mundo por uma longa pausa de coleta de lixo, o broker pode perder heartbeats. Se a CPU estiver saturada, o manuseio de heartbeats pode ser atrasado. Se o host estiver preso em alta espera de I/O, o Kafka pode não acompanhar o trabalho de coordenação. Verifique as métricas do broker nos mesmos timestamps das desconexões do ZooKeeper.

Perguntas úteis do lado do broker incluem: o uso de heap aumentou antes da desconexão, o tempo de pausa do GC aumentou, a espera de I/O do disco estava alta, as retransmissões de rede aumentaram e houve grandes reassignações de partição ou movimentos de líder ao mesmo tempo? Um broker afogado sob carga pode precisar de menos líderes de partição, melhor disco, ajuste de JVM ou uma mudança de tráfego. Aumentar os timeouts do ZooKeeper pode esconder o sintoma sem corrigir a causa.

A consistência da configuração é fácil de ignorar. Todos os brokers Kafka no mesmo cluster devem usar a mesma string de conexão do ZooKeeper e chroot. Eles também devem ter valores únicos de broker.id. Um ID de broker duplicado pode causar comportamento de registro confuso porque dois processos estão tentando representar o mesmo broker.

Se você alterou recentemente nomes de host do ZooKeeper, certificados, regras de firewall ou configurações do broker Kafka, compare o broker que funciona com o que está falhando. Pequenas diferenças são comuns: um sufixo DNS antigo, um caminho chroot ausente, um grupo de segurança anexado a dois brokers, mas não ao terceiro, ou um erro de digitação em um arquivo de ambiente do systemd.

A recuperação depende do que quebrou. Se uma regra de firewall estava faltando, corrija-a e reinicie o broker afetado se ele não se reconectar corretamente. Se o ZooKeeper perdeu o quorum, restaure o quorum primeiro antes de reiniciar os brokers Kafka. Se um broker expirou porque estava sobrecarregado, reiniciá-lo pode trazê-lo de volta temporariamente, mas o problema retornará a menos que você remova a pressão.

Use reinicializações rolantes. Reiniciar todos os brokers Kafka de uma vez porque o ZooKeeper estava instável pode transformar uma falha parcial em uma completa. Restaure a saúde do ZooKeeper, depois reinicie ou recupere os brokers um de cada vez enquanto observa a estabilidade do controlador e a liderança das partições.

Para estabilidade de longo prazo, monitore ambos os lados. No ZooKeeper, observe a latência de requisições, requisições pendentes, mudanças de líder, status de sincronização dos seguidores, espaço em disco e reinicializações de processo. No Kafka, observe mudanças de controlador, partições offline, partições sub-replicadas, reinicializações de broker e logs mencionando expiração de sessão do ZooKeeper. Alerte sobre padrões repetidos, não apenas a morte total do processo.

A correção mais limpa, para equipes planejando uma atualização maior, pode ser a migração do ZooKeeper para o modo KRaft do Kafka. Isso é um projeto, não uma etapa de resposta a incidentes. Requer planejamento de versão, verificações de compatibilidade e trabalho de migração cuidadoso. Até lá, trate o ZooKeeper como infraestrutura de produção. Mantenha-o pequeno, próximo ao Kafka, configurado consistentemente, monitorado e chato.

Um padrão prático de runbook é construir uma pequena matriz durante o incidente. Coloque os brokers Kafka em um eixo e os nós ZooKeeper no outro. Preencha cada célula com o resultado de nc -vz host 2181 e, se disponível, uma verificação simples de saúde do ZooKeeper. Isso transforma relatos vagos de "Kafka não consegue alcançar o ZooKeeper" em um padrão visível. Se todos os brokers falharem ao alcançar zk02, investigue zk02 ou seu caminho de rede. Se apenas broker-4 falhar ao alcançar todos os nós ZooKeeper, investigue o host desse broker, tabela de roteamento, DNS ou firewall.

A sincronização de tempo também pode ser importante. Os mecanismos de sessão do ZooKeeper não exigem relógios perfeitamente idênticos para cada operação, mas relógios muito distorcidos tornam os logs mais difíceis de interpretar e podem quebrar automação, certificados e monitoramento ao redor. Mantenha NTP ou chrony saudáveis nos nós Kafka e ZooKeeper. Quando os timestamps discordam durante uma falha, as pessoas perdem tempo perseguindo a sequência errada de eventos.

Tenha cuidado com implantações de ZooKeeper em contêineres ou orquestradas. O ZooKeeper armazena identidade e dados em disco. Se os pods se moverem e perderem a identidade persistente, ou se a descoberta de serviço apontar os clientes para nós que não estão prontos, o Kafka pode ver comportamento de conexão instável. Identidade no estilo StatefulSet, volumes persistentes, DNS estável e verificações de prontidão são importantes. Um ensemble ZooKeeper não deve se comportar como um conjunto de pods web descartáveis e sem estado.

As configurações de segurança adicionam outra camada. Se SASL, TLS ou política de rede foi introduzida recentemente, as falhas de conexão podem parecer problemas simples de acessibilidade no início. Verifique se os logs do Kafka mostram falhas de autenticação, falhas de handshake ou erros de autorização, em vez de timeouts TCP. Uma porta pode estar aberta enquanto a sessão ainda falha porque o broker não pode autenticar no ZooKeeper.

Após o incidente, mantenha um breve registro do sintoma exato, causa raiz e correção. Os problemas do ZooKeeper geralmente se repetem porque o reparo original foi local: uma regra de firewall, uma reinicialização de broker, um aumento de timeout. Uma boa nota pós-incidente deve dizer se o cluster tinha quorum, quais brokers perderam sessões, se o ISR encolheu, se o controlador mudou e qual monitoramento pegará isso mais cedo na próxima vez.

Se você estiver solucionando problemas a partir do Kubernetes ou de outro escalonador, verifique também onde as cargas de trabalho do Kafka e do ZooKeeper foram colocadas. Um problema de rede no nível do nó, problema de disco ou evento de falta de CPU pode afetar apenas os pods agendados lá. Mover um pod pode parecer corrigir o problema, mas o problema real pode ser o host. Compare eventos e métricas do nó antes de declarar o aplicativo reparado.

Backups e snapshots merecem cautela. Os diretórios de dados do ZooKeeper não devem ser copiados casualmente enquanto o processo está ativo, a menos que seu método de backup seja projetado para isso. Para metadados do Kafka, um estado do ZooKeeper danificado ou desatualizado pode ser extremamente disruptivo. Siga as práticas de backup suportadas pelo ZooKeeper e teste os procedimentos de restauração longe da produção. Um backup que ninguém restaurou é apenas um arquivo esperançoso.

A melhor medida preventiva é manter o ZooKeeper chato. Não o coloque junto com brokers Kafka pesados, se puder evitar. Mantenha seus discos confiáveis. Mantenha o dimensionamento do heap conservador e monitorado. Limite quem pode alterar a associação do ensemble. A maioria dos incidentes do ZooKeeper que vi não foi causada por bugs exóticos; eles vieram de desvios comuns de infraestrutura em torno de um pequeno serviço que todos esqueceram que era crítico.