Notre découverte de services a détecté sa propre panne et s'est désactivée toute seule
Nous avions un cluster à trois réplicas qui était constamment en désaccord avec lui-même. Les tâches d'arrière-plan s'exécutaient deux et trois fois de suite. La réponse n'était pas dans les logs ; elle était dans notre propre code. La routine de découverte de pairs comportait un bloc catch, et le commentaire à l'intérieur disait, en gros : « multicast échoué, découverte désactivée. » Notre découverte de services détectait sa propre panne et s'éteignait silencieusement, et elle faisait exactement cela en production depuis le premier jour.
Voici l'histoire de pourquoi un protocole de clustering correct sur un serveur en rack est le mauvais outil dès qu'on le met sur une plateforme managée, et pourquoi la solution a été de le supprimer plutôt que de le réparer.
À quoi sert réellement le cluster
Un serveur d'authentification s'exécute en plus d'un réplica pour la disponibilité. Les réplicas sont en grande partie indépendants : n'importe lequel peut valider un token ou vérifier un mot de passe. Mais quelques tâches doivent s'exécuter une seule fois, pas une fois par réplica. Le balayage de rétention des données qui supprime les enregistrements expirés. La passe qui déclenche les webhooks clients. Tout ce qui agit vers l'extérieur et produit un effet de bord. Pour cela, il faut deux choses sur lesquelles le cluster doit s'accorder : qui sont les membres, et lequel d'entre eux est le leader qui exécute le travail singleton.
Notre réponse d'origine était la classique : gossip. Chaque nœud bavarde avec ses pairs, l'appartenance est une propriété émergente de qui est joignable, et le groupe élit un leader parmi l'ensemble convenu. C'est un design magnifique. Il suppose aussi que les nœuds peuvent se trouver les uns les autres, et la façon dont l'ancien code les trouvait était le multicast : crier sur le segment réseau local et voir qui répond.
Pourquoi ça a cassé, en silence
Le Kubernetes managé ne transporte pas le multicast. Pas plus que presque n'importe quel réseau cloud que vous ne construisez pas vous-même. Le cri partait et rien ne revenait, à chaque fois. Alors la découverte « échouait », le bloc catch se déclenchait, et chaque nœud concluait qu'il était seul au monde.
À un réplica, c'est inoffensif. À trois, c'est du split-brain par construction : trois nœuds, chacun certain d'être le seul membre, chacun s'élisant lui-même leader, chacun exécutant le travail singleton. Le balayage de rétention s'exécutait sur les trois. La passe de webhooks déclenchait le webhook de chaque client trois fois. Rien de tout cela ne levait d'erreur, car du point de vue de chaque nœud solitaire, tout allait bien. Le bug n'était pas un plantage ; c'était trois programmes se comportant chacun parfaitement correctement dans un monde qu'ils percevaient mal.
La solution a été d'arrêter complètement de découvrir des pairs
Voici le recadrage qui a fait s'effondrer tout cela en quelques lignes : nous essayions de reconstruire, avec un protocole bavard, un fait que la plateforme stocke déjà pour nous avec une forte cohérence. Nous tournons sur un cloud qui nous fournit un magasin fortement cohérent. L'élection du leader n'a pas besoin de consensus entre pairs s'il existe déjà un seul endroit sur lequel tout le monde peut s'accorder.
Ainsi, le leadership est devenu un blob lease. Un blob, un lease. Celui qui détient le lease est le leader. Le lease a un timeout, donc un leader qui meurt cesse de le renouveler et le lease se libère pour le prochain preneur. Il n'y a pas de découverte de pairs, pas de calcul de quorum, pas de multicast, et pas de bloc catch attendant de le désactiver. La coordination et les événements transitent par une petite table qui sert de bus entre les nœuds.
La propriété que je ne m'attendais pas à adorer : on peut ouvrir Storage Explorer et voir l'esprit du cluster. Qui détient le lease en ce moment. Ce qui est en file d'attente sur le bus. L'appartenance a cessé d'être quelque chose que l'on déduit du comportement d'un protocole pour devenir une ligne que l'on peut lire. Quand ce qui décide qui exécute vos tâches destructrices est une valeur que vous pouvez regarder, le débogage cesse d'être de l'archéologie.
Le passage où nous avons supprimé une chose ingénieuse à dessein
Pendant que nous y étions, nous avons supprimé un limiteur de débit distribué bâti sur un conflict-free replicated data type, un CRDT qui fusionnait les compteurs par nœud en un total global sans coordination. C'était réellement élégant. Cela résolvait aussi un problème que nous avions déplacé. La limite de débit globale a sa place à la périphérie, là où les requêtes arrivent, et non reconstruite à l'intérieur du cluster avec une structure de données qui nécessite un paragraphe pour s'expliquer. Dehors.
Supprimer du code qui fonctionne ressemble à une perte jusqu'à ce qu'on compte ce qu'on cesse de maintenir. Nous avons retiré la couche gossip, l'hypothèse du multicast, le bloc catch qui se désactivait lui-même, et le CRDT, et nous avons remplacé tout cela par un lease et une table. Le nombre de lignes a baissé, et le nombre d'états dans lesquels le système peut se trouver a baissé avec lui.
La leçon, mise en facteur
L'appartenance par gossip et multicast est le bon outil sur du bare metal ou un réseau plat que vous contrôlez. Ce sont les mauvais outils sur une plateforme dont le réseau ne transporte pas le mécanisme même dont ils dépendent, et le mode de défaillance n'est pas un plantage bruyant ; c'est un repli silencieux qui a l'air de fonctionner. Avant de porter un schéma de systèmes distribués sur une infrastructure managée, demandez-vous ce qu'il suppose du réseau, et demandez-vous ce que la plateforme vous offre déjà gratuitement. La nôtre exécutait déjà un magasin fortement cohérent. Un lease dans ce magasin était une élection de leader plus simple et plus correcte que le protocole que nous traînions, et c'en est une que nous pouvons observer.
Si vous exploitez de l'authentification, les parties qui détiennent vos clés et décident qui exécute vos tâches destructrices devraient être les choses les plus ennuyeuses et les plus inspectables que vous possédez. Nous avons rendu les nôtres ennuyeuses. C'était une amélioration.