Plongée approfondie dans les problèmes de connexion Kafka ZooKeeper

Résolvez les échecs de connexion Kafka ZooKeeper avec des vérifications pratiques de configuration, réseau, délais d'attente, journaux et charge des courtiers.

Plongée approfondie dans les problèmes de connexion Kafka ZooKeeper

Les problèmes de connexion Kafka ZooKeeper affectent principalement les anciens clusters Kafka et ceux qui n'ont pas migré vers le mode KRaft. Les déploiements Kafka plus récents peuvent fonctionner sans ZooKeeper, mais de nombreux systèmes de production en dépendent encore. Si vos courtiers utilisent zookeeper.connect dans server.properties, ZooKeeper fait toujours partie de votre plan de contrôle et mérite la même attention que Kafka lui-même.

Lorsqu'un courtier Kafka ne peut pas maintenir sa session ZooKeeper, les symptômes peuvent sembler plus graves qu'un simple problème de connexion. Les courtiers peuvent redémarrer. Les élections du contrôleur peuvent se répéter. Les partitions peuvent devenir indisponibles. Les journaux peuvent montrer une expiration de session, une démission du contrôleur ou des tentatives de reconnexion répétées. Les producteurs et consommateurs peuvent seulement voir l'effet en aval : erreurs de métadonnées, délais d'attente ou leaders instables.

Commencez par le rôle que joue ZooKeeper. Dans les clusters Kafka basés sur ZooKeeper, les courtiers s'y enregistrent, l'élection du contrôleur en dépend, et la coordination des métadonnées du cluster passe par lui. Si un courtier perd sa session ZooKeeper assez longtemps pour que la session expire, Kafka considère ce courtier comme disparu du cluster. Même si le processus du courtier est toujours en cours, le cluster peut déplacer la direction loin de lui.

La première vérification est banale et attrape souvent le problème : vérifiez zookeeper.connect sur chaque courtier.

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

La chaîne de connexion doit lister les membres de l'ensemble que Kafka peut atteindre. Si vous utilisez un chemin chroot tel que /kafka, incluez-le de manière cohérente sur chaque courtier. Ne configurez pas la moitié des courtiers avec /kafka et l'autre moitié sans ; ils se comporteront comme s'ils parlaient à différents clusters Kafka. Si vous utilisez un chroot, créez-le d'abord ou confirmez son existence avec les outils ZooKeeper.

Vérifiez également le DNS ainsi que le texte de la configuration. Un nom d'hôte qui se résout correctement depuis votre ordinateur portable peut échouer depuis un sous-réseau de courtier. Exécutez les vérifications depuis l'hôte du courtier Kafka, pas depuis un bastion à moins que le bastion ait le même chemin réseau.

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

Une connexion TCP réussie ne prouve pas que ZooKeeper est sain, mais une connexion échouée suffit pour continuer à creuser les pare-feu, les groupes de sécurité, le routage, le DNS ou la configuration des écouteurs. Testez chaque courtier Kafka vers chaque nœud ZooKeeper. Une connectivité partielle est pire qu'une panne propre car la défaillance peut n'apparaître que lorsqu'un courtier tente de se connecter à un membre spécifique de l'ensemble.

Les commandes à quatre lettres de ZooKeeper peuvent aider lorsqu'elles sont activées. De nombreuses installations les restreignent, donc ne supposez pas qu'elles fonctionnent. Si autorisé, ruok devrait retourner imok, et mntr peut montrer des statistiques utiles du serveur.

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

Si ces commandes sont désactivées, utilisez les outils d'administration pris en charge ou votre pile de surveillance à la place. Le but est de répondre à une question simple : ZooKeeper écoute-t-il, participe-t-il à l'ensemble et répond-il rapidement ?

Ensuite, inspectez la santé de l'ensemble ZooKeeper. Un ensemble de trois nœuds peut tolérer qu'un nœud ZooKeeper soit hors service. Il ne peut pas en tolérer deux. Un ensemble de cinq nœuds peut en tolérer deux. Évitez les ensembles de taille paire car ils ajoutent des coûts sans améliorer le quorum comme les gens le pensent. Trois et cinq sont des choix courants.

Du côté de ZooKeeper, regardez zoo.cfg. Confirmez clientPort, tickTime, initLimit, syncLimit et les lignes de serveur. Assurez-vous que les noms d'hôte du serveur annoncés sont accessibles entre les nœuds ZooKeeper, pas seulement depuis les courtiers Kafka. Les pairs ZooKeeper ont besoin de leurs propres ports de quorum et d'élection de leader. Un courtier Kafka peut atteindre le port 2181 alors que l'ensemble ZooKeeper lui-même est malsain parce que le trafic entre pairs est bloqué.

Le réglage du délai d'expiration de session est une autre source courante de confusion. Kafka demande un délai d'expiration de session à ZooKeeper, mais ZooKeeper applique des limites basées sur sa propre configuration. Dans ZooKeeper, le délai d'expiration de session minimum est généralement 2 * tickTime et le maximum est généralement 20 * tickTime, sauf si des paramètres de serveur spécifiques le remplacent. Cela signifie qu'une valeur de délai d'expiration Kafka en dehors de la plage autorisée peut être ajustée par ZooKeeper.

Si tickTime=2000, la plage de session autorisée habituelle est d'environ 4 secondes à 40 secondes. Un paramètre Kafka tel que zookeeper.session.timeout.ms=18000 s'inscrit dans cette plage. Un délai d'expiration très bas peut produire de fausses défaillances lors de brèves pauses réseau ou de pauses de garbage collection. Un délai d'expiration très élevé peut rendre la détection des véritables défaillances de courtier plus longue. Vous choisissez entre sensibilité et stabilité.

Ne modifiez pas tickTime à la légère. Il affecte l'ensemble ZooKeeper, pas seulement Kafka. Si vous avez besoin de plus de tolérance pour les pauses de courtier, il est souvent préférable de commencer par examiner zookeeper.session.timeout.ms de Kafka, le comportement JVM du courtier et la santé du réseau avant de modifier le timing de ZooKeeper.

Les journaux racontent généralement l'histoire si vous les alignez par horodatage. Sur les courtiers Kafka, recherchez les messages autour des déconnexions ZooKeeper et de l'expiration de session :

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

Les motifs comptent plus qu'une seule ligne. Une reconnexion unique lors d'un redémarrage planifié de ZooKeeper peut être inoffensive. Une expiration répétée toutes les quelques minutes pointe vers une instabilité. Une expiration pendant le garbage collection pointe vers des pauses JVM ou une surcharge du courtier. Une expiration au même moment sur de nombreux courtiers pointe vers ZooKeeper, le réseau ou un événement d'infrastructure partagé.

Sur les nœuds ZooKeeper, vérifiez les changements de leader, les avertissements fsync, la limitation des connexions et la latence des requêtes longues. ZooKeeper est sensible à la latence du disque car il écrit des journaux de transactions. Un disque lent peut rendre le service apparemment accessible tout en échouant à répondre assez rapidement pour des sessions stables.

La latence réseau et la perte de paquets sont plus importantes que la bande passante brute pour ZooKeeper. Les courtiers Kafka n'ont pas besoin d'un débit énorme vers ZooKeeper, mais ils ont besoin d'une communication fiable et à faible latence. Si les courtiers et ZooKeeper sont répartis sur des réseaux distants, attendez-vous à des problèmes. Gardez-les proches. Dans les environnements cloud, évitez de router le trafic courtier vers ZooKeeper via des NAT inutiles, des pare-feu surchargés ou des chemins inter-régions.

La contention de ressources sur le courtier Kafka peut ressembler exactement à un problème ZooKeeper. Si la JVM arrête le monde pour une longue pause de garbage collection, le courtier peut manquer des battements de cœur. Si le CPU est saturé, le traitement des battements de cœur peut être retardé. Si l'hôte est bloqué dans une attente d'E/S élevée, Kafka peut ne pas suivre le travail de coordination. Vérifiez les métriques du courtier aux mêmes horodatages que les déconnexions ZooKeeper.

Les questions utiles côté courtier incluent : l'utilisation du tas a-t-elle augmenté avant la déconnexion, le temps de pause GC a-t-il augmenté, l'attente d'E/S disque était-elle élevée, les retransmissions réseau ont-elles augmenté, et y avait-il de grandes réaffectations de partitions ou des mouvements de leader au même moment ? Un courtier submergé par la charge peut avoir besoin de moins de leaders de partitions, d'un meilleur disque, d'un réglage JVM ou d'un déplacement de trafic. Augmenter les délais d'expiration ZooKeeper peut masquer le symptôme sans résoudre la cause.

La cohérence de la configuration est facile à négliger. Tous les courtiers Kafka du même cluster doivent utiliser la même chaîne de connexion ZooKeeper et le même chroot. Ils doivent également avoir des valeurs broker.id uniques. Un ID de courtier dupliqué peut provoquer un comportement d'enregistrement confus car deux processus tentent de représenter le même courtier.

Si vous avez récemment modifié les noms d'hôte ZooKeeper, les certificats, les règles de pare-feu ou les configurations des courtiers Kafka, comparez le courtier fonctionnel avec celui qui échoue. Les petites différences sont courantes : un suffixe DNS ancien, un chemin chroot manquant, un groupe de sécurité attaché à deux courtiers mais pas au troisième, ou une faute de frappe dans un fichier d'environnement systemd.

La récupération dépend de ce qui s'est cassé. Si une règle de pare-feu manquait, corrigez-la et redémarrez le courtier affecté s'il ne se reconnecte pas proprement. Si ZooKeeper a perdu le quorum, restaurez d'abord le quorum avant de redémarrer les courtiers Kafka. Si un courtier a expiré parce qu'il était surchargé, le redémarrer peut le ramener temporairement, mais le problème reviendra à moins que vous ne supprimiez la pression.

Utilisez des redémarrages progressifs. Redémarrer tous les courtiers Kafka en même temps parce que ZooKeeper était instable peut transformer une panne partielle en panne complète. Rétablissez la santé de ZooKeeper, puis redémarrez ou récupérez les courtiers un par un tout en surveillant la stabilité du contrôleur et la direction des partitions.

Pour une stabilité à long terme, surveillez les deux côtés. Sur ZooKeeper, surveillez la latence des requêtes, les requêtes en attente, les changements de leader, l'état de synchronisation des suiveurs, l'espace disque et les redémarrages de processus. Sur Kafka, surveillez les changements de contrôleur, les partitions hors ligne, les partitions sous-répliquées, les redémarrages de courtiers et les journaux mentionnant l'expiration de session ZooKeeper. Alertez sur les motifs répétés, pas seulement sur la mort totale du processus.

La solution la plus propre, pour les équipes planifiant une mise à niveau plus importante, peut être la migration loin de ZooKeeper vers le mode KRaft de Kafka. C'est un projet, pas une étape de réponse à incident. Cela nécessite une planification de version, des vérifications de compatibilité et un travail de migration minutieux. Jusque-là, traitez ZooKeeper comme une infrastructure de production. Gardez-le petit, proche de Kafka, configuré de manière cohérente, surveillé et ennuyeux.

Un modèle pratique de runbook est de construire une petite matrice pendant l'incident. Mettez les courtiers Kafka sur un axe et les nœuds ZooKeeper sur l'autre. Remplissez chaque cellule avec le résultat de nc -vz host 2181 et, si disponible, une simple vérification de santé ZooKeeper. Cela transforme les rapports vagues "Kafka ne peut pas atteindre ZooKeeper" en un motif visible. Si chaque courtier échoue à atteindre zk02, enquêtez sur zk02 ou son chemin réseau. Si seul broker-4 échoue à atteindre chaque nœud ZooKeeper, enquêtez sur l'hôte de ce courtier, sa table de routage, son DNS ou son pare-feu.

La synchronisation temporelle peut également compter. Les mécanismes de session ZooKeeper n'exigent pas des horloges murales parfaitement identiques pour chaque opération, mais des horloges fortement décalées rendent les journaux plus difficiles à interpréter et peuvent casser l'automatisation environnante, les certificats et la surveillance. Gardez NTP ou chrony en bonne santé sur les nœuds Kafka et ZooKeeper. Lorsque les horodatages ne concordent pas pendant une panne, les gens perdent du temps à chercher la mauvaise séquence d'événements.

Soyez prudent avec les déploiements ZooKeeper conteneurisés ou orchestrés. ZooKeeper stocke l'identité et les données sur le disque. Si les pods se déplacent et perdent leur identité persistante, ou si la découverte de service pointe les clients vers des nœuds qui ne sont pas prêts, Kafka peut voir un comportement de connexion instable. L'identité de type StatefulSet, les volumes persistants, le DNS stable et les vérifications de préparation comptent. Un ensemble ZooKeeper ne devrait pas se comporter comme un ensemble de pods web jetables sans état.

Les paramètres de sécurité ajoutent une autre couche. Si SASL, TLS ou une politique réseau a été récemment introduite, les échecs de connexion peuvent d'abord ressembler à de simples problèmes d'accessibilité. Vérifiez si les journaux Kafka montrent des échecs d'authentification, des échecs de handshake ou des erreurs d'autorisation plutôt que des délais d'attente TCP. Un port peut être ouvert alors que la session échoue toujours parce que le courtier ne peut pas s'authentifier auprès de ZooKeeper.

Après l'incident, conservez un court enregistrement du symptôme exact, de la cause racine et du correctif. Les problèmes ZooKeeper se répètent souvent parce que la réparation originale était locale : une règle de pare-feu, un redémarrage de courtier, une augmentation de délai d'attente. Une bonne note post-incident devrait indiquer si le cluster avait le quorum, quels courtiers ont perdu leurs sessions, si l'ISR a rétréci, si le contrôleur a changé, et quelle surveillance détectera cela plus tôt la prochaine fois.

Si vous résolvez des problèmes depuis Kubernetes ou un autre planificateur, vérifiez également où les charges de travail Kafka et ZooKeeper ont atterri. Un problème réseau au niveau du nœud, un problème de disque ou un événement de privation de CPU peut affecter uniquement les pods programmés là-bas. Déplacer un pod peut sembler résoudre le problème, mais le vrai problème peut être l'hôte. Comparez les événements et les métriques des nœuds avant de déclarer l'application réparée.

Les sauvegardes et les instantanés méritent de la prudence. Les répertoires de données ZooKeeper ne doivent pas être instantanés à la légère pendant que le processus est actif, à moins que votre méthode de sauvegarde ne soit conçue pour cela. Pour les métadonnées Kafka, un état ZooKeeper endommagé ou obsolète peut être extrêmement perturbateur. Suivez les pratiques de sauvegarde prises en charge par ZooKeeper et testez les procédures de restauration loin de la production. Une sauvegarde que personne n'a restaurée n'est qu'un fichier plein d'espoir.

La meilleure mesure préventive est de garder ZooKeeper ennuyeux. Ne le co-localisez pas avec de lourds courtiers Kafka si vous pouvez l'éviter. Gardez ses disques fiables. Gardez le dimensionnement du tas conservateur et surveillé. Limitez qui peut modifier l'appartenance à l'ensemble. La plupart des incidents ZooKeeper que j'ai vus n'étaient pas causés par des bugs exotiques ; ils provenaient d'une dérive d'infrastructure ordinaire autour d'un petit service que tout le monde avait oublié d'être critique.