etcd 3.4.3: recherche sur la fiabilité et la sécurité du stockage

Remarque perev. : Le contenu de cet article n'est pas entièrement typique de notre blog. Cependant, comme beaucoup le savent, etcd est situé au cœur de Kubernetes, c'est pourquoi cette étude, menée par un consultant indépendant dans le domaine de la fiabilité, s'est avérée intéressante auprès des ingénieurs exploitant ce système. De plus, il est intéressant dans le contexte de l'amélioration des projets Open Source qui ont déjà fait leurs preuves en production, même à un niveau très "bas".



Le coffre de valeurs-clés (KV), etc. est une base de données distribuée basée sur l'algorithme de consensus Raft. Dans une analyse menée en 2014 , nous avons constaté que etcd 0.4.1 était affecté par les soi-disant lectures périmées par défaut(lire les opérations qui renvoient une ancienne valeur non pertinente en raison d'un retard de synchronisation - environ transl.) . Nous avons décidé de revenir à etcd (cette fois - à la version 3.4.3) afin d'évaluer à nouveau en détail son potentiel dans le domaine de la fiabilité et de la sécurité.

Nous avons constaté que le fonctionnement avec des paires de "valeurs-clés" strictement sérialisables et que les processus de l'observateur (montres) livraient à chaque changement de clé de commande. Cependant, les verrous dans etcd sont fondamentalement dangereux, et les risques qui leur sont associés sont exacerbés par un bogue, à la suite de quoi la pertinence du bail n'est pas vérifiée après avoir attendu le verrou. Vous pouvez lire le commentaire des développeurs etcd sur notre rapport sur le blog du projet .

L'étude a été parrainée par la Cloud Native Computing Foundation (CNCF), qui fait partie de la Linux Foundation. Elle a été réalisée dans le plein respect des politiques éthiques de Jepsen .

1. Origines


Le référentiel Etc KV est un système distribué conçu pour être utilisé comme base de coordination. Comme Zookeeper et Consul , etcd stocke de petites quantités d'états rarement mis à jour ( par défaut jusqu'à 8 Go ) sous la forme d'une carte de valeurs-clés et fournit des lectures, écritures et microtransactions strictement sérialisables dans tout l'entrepôt de données, ainsi que des primitives de coordination comme les verrous , le suivi (montres) et sélection des leaders. De nombreux systèmes distribués, tels que Kubernetes et OpenStack , utilisent etcd pour stocker des métadonnées de cluster, coordonner des vues coordonnées de données, choisir un leader, etc.

En 2014, nous avons déjà réalisé une évaluation de etcd 0.4.1 . Ensuite, nous avons constaté que par défaut, il est sujet aux lectures périmées en raison de l'optimisation. Alors que le travail sur les principes de Raft discute de la nécessité de diviser les opérations de lecture en threads et de les passer par un système de consensus pour assurer la viabilité, etcd lit localement tout leader sans vérifier un état plus actuel sur le nouveau leader. L'équipe de développement etcd a implémenté l' indicateur de quorum facultatif , et dans l'API etcd version 3.0 , la linéarisation pour toutes les opérations, à l'exception des opérations de suivi, est apparue par défaut . L'API etcd 3.0 se concentre sur une carte plate KV où les clés et les valeurs sont opaques

des tableaux d'octets ( opaques ) . À l'aide de requêtes de plage, vous pouvez simuler des clés hiérarchiques. Les utilisateurs peuvent lire, écrire et supprimer des clés, ainsi que surveiller le flux des mises à jour pour une seule clé ou une plage de clés. La boîte à outils etcd est complétée par des baux (objets variables avec une durée de vie limitée, qui sont maintenus à l'état actif par les demandes de pulsation du client), des verrous (objets nommés dédiés liés aux baux) et le choix des leaders.

Dans la version 3.0, etcd propose une API transactionnelle limitéepour les opérations atomiques avec de nombreuses clés. Dans ce modèle, une transaction est une expression conditionnelle avec un prédicat, une vraie branche et une fausse branche. Un prédicat peut être une conjonction de plusieurs comparaisons de clés: égalité ou diverses inégalités, selon les versions d'une clé, révision globale, etc., ou la valeur de clé actuelle. Les vraies et fausses branches peuvent inclure plusieurs opérations de lecture et d'écriture; tous sont appliqués atomiquement en fonction du résultat de l'estimation du prédicat.

1.1 Garanties de cohérence dans la documentation


Depuis octobre 2019, la documentation etcd de l'API indique que «tous les appels d'API démontrent une cohérence cohérente - la forme la plus solide de garantie de cohérence disponible sur les systèmes distribués». Ce n'est pas le cas: la cohérence cohérente est strictement plus faible que la linéarisationet la linéarisation est certainement réalisable dans les systèmes distribués. En outre, la documentation indique que «pendant l'opération de lecture, etcd ne garantit pas le transfert de la valeur [la plus récente (mesurée par l'horloge externe après la fin de la requête)] disponible sur n'importe quel membre du cluster». C'est aussi une déclaration trop conservatrice: si etcd fournit la linéarisation, les opérations de lecture sont toujours associées à l'état validé le plus récent dans l'ordre de linéarisation.

La documentation affirme également que etcd garantit une isolation sérialisable: toutes les opérations (même celles qui affectent plusieurs touches) sont effectuées dans un ordre général. Les auteurs décrivent l'isolement sérialisable comme «le plus haut niveau d'isolement disponible dans les systèmes distribués». Cela (selon ce que vous entendez par «niveau d'isolement») n'est pas vrai non plus; la sérialisabilité stricte est plus forte que la sérialisation simple, tandis que la première est également réalisable dans les systèmes distribués.

La documentation indique que toutes les opérations (sauf le suivi) dans etcd sont linéarisables par défaut. Dans ce cas, la linéarisation est définie comme la cohérence avec des horloges globales faiblement synchronisées. Il convient de noter qu'une telle définition n'est pas seulement incompatible avec la définition de la linéarisationHerlihy & Wing, mais implique également une violation de la causalité: les nœuds avec des heures de pointe vont essayer de lire les résultats d'opérations qui n'ont même pas commencé. Nous supposons que etcd n'est toujours pas une machine à remonter le temps, et comme il est basé sur l'algorithme Raft, la définition généralement acceptée de la linéarisation devrait être appliquée.

Puisque les opérations KV dans etcd sont sérialisables et linéarisables, nous pensons qu'en fait, etcd fournit une sérialisation stricte par défaut . Cela a du sens, car toutes les clés etcd sont dans une seule machine d'état, et Raft fournit un ordre complet de toutes les opérations sur cette machine d'état. En fait, l'ensemble de données etcd est un seul objet linéarisable.

Le drapeau optionnel serializable s'abaisseNiveau des opérations de lecture, de la cohérence sérialisable stricte à régulière, permettant la lecture d'un état validé périmé. Notez que le drapeau serializablen'affecte pas la sérialisation de l'histoire; Les opérations KV, etc. sont sérialisables dans tous les cas.

2. Développement de tests


Pour créer une suite de tests, nous avons utilisé la bibliothèque Jepsen appropriée. La version etcd 3.4.3 (la dernière en date d'octobre 19) a été analysée, travaillant sur des clusters Debian Stretch composés de 5 nœuds. Nous avons implémenté un certain nombre de défauts dans ces clusters, notamment les partitions réseau, l'isolement des nœuds individuels, le partitionnement du cluster en majorité et en minorité, ainsi que les partitions non transitives à majorité chevauchante. Ils ont «abandonné» et suspendu des sous-ensembles aléatoires de nœuds, ainsi que des dirigeants délibérément handicapés. Des distorsions temporelles pouvant atteindre plusieurs centaines de secondes ont été introduites, à des intervalles de plusieurs secondes et à des millisecondes ("scintillement" rapide). Étant donné que etcd prend en charge la modification dynamique du nombre de composants, nous avons ajouté et supprimé des nœuds au hasard pendant les tests.

Les charges de test comprenaient des registres, des ensembles et des tests transactionnels pour vérifier les opérations sur KV, ainsi que des charges spécialisées pour les serrures et les montres.

2.1 Registres


Pour évaluer la fiabilité de etcd pendant les opérations KV, un test de registre a été développé au cours duquel des opérations aléatoires de lecture, d'écriture, de comparaison et de définition ont été effectuées sur des clés d'unité. Les résultats ont été évalués à l'aide de l' outil de linéarisation Knossos à l'aide du modèle de registre de comparaison / d'installation et des informations de version.

2.2 Ensembles


Pour quantifier les lectures périmées, un test a été développé qui a utilisé une transaction de comparaison et de définition pour lire un ensemble d'entiers à partir d'une seule clé, puis ajouter une valeur à cet ensemble. Pendant le test, nous avons également effectué une lecture parallèle de l'ensemble complet. Après l'achèvement du test, les résultats ont été analysés pour la survenue de cas où l'élément, qui était connu pour être présent dans l'ensemble, était absent dans les résultats de lecture. Ces cas ont été utilisés pour quantifier les lectures périmées et les mises à jour perdues.

2.3 Ajouter un test


Pour vérifier la sérialisabilité stricte, un test d'ajout a été développé au cours duquel les transactions ont été lues en parallèle et ont ajouté des valeurs à des listes constituées d'ensembles uniques d'entiers. Chaque liste a été stockée dans une clé etcd, et des ajouts ont été effectués au sein de chaque transaction, en lisant chaque clé qui devait être modifiée en une seule transaction, puis ces clés ont été écrites et des lectures ont été effectuées dans la deuxième transaction, qui était protégéepour s'assurer qu'aucune clé enregistrée n'a changé depuis la première lecture. À la fin du test, nous avons tracé la relation entre les transactions en fonction de la priorité en temps réel et la relation des opérations de lecture et d'ajout. La vérification de ce graphique pour les boucles a permis de déterminer si les opérations étaient strictement sérialisables.

Alors que etcd empêche les transactions d'écrire plusieurs fois la même clé, vous pouvez créer des transactions avec jusqu'à un enregistrement par clé. Nous nous sommes également assurés que les opérations de lecture dans la même transaction reflétaient les opérations d'écriture précédentes de la même transaction.

2.4 Serrures


En tant que service de coordination, etcd promet un support intégré pour le verrouillage distribué . Nous avons étudié ces verrous de deux manières. Au début, des demandes de verrouillage et de déverrouillage aléatoires ont été générées , recevant un bail pour chaque verrou et le laissant ouvert à l'aide du client etc. intégré dans le client Java keepalivejusqu'à sa libération . Nous avons testé les résultats avec Knossos pour voir s'ils forment une implémentation linéarisée du service de verrouillage.

Pour un test plus pratique (et pour quantifier la fréquence des échecs de verrouillage), nous avons utilisé des verrous et etcd pour organiser l'exclusion mutuelle lors des mises à jour de l'ensemble en mémoireet recherché les mises à jour perdues dans cet ensemble. Ce test nous a permis de confirmer directement si les systèmes utilisant etcd comme mutex peuvent mettre à jour en toute sécurité l'état interne.

La troisième version du test de verrouillage impliquait des gardes sur la clé de bail pour modifier l'ensemble stocké dans etcd.

2.5 Suivi


Afin de vérifier que les surveillances fournissent des informations sur chaque mise à jour de clé, une clé a été créée dans le cadre du test et des valeurs entières uniques aveugles ont été attribuées . Pendant ce temps, les clients ont partagé cette clé pendant plusieurs secondes à la fois. Chaque fois après le début de la montre, le client a commencé la révision sur laquelle il s'était arrêté la dernière fois.

À la fin de ce processus, nous nous sommes assurés que chaque client a observé la même séquence de changements clés.

3. Résultats


3.1 Suivi à partir de la 0e révision


Lors du suivi d'une clé, les clients peuvent spécifier une révision initiale , qui est «une révision facultative avec laquelle le suivi démarre (inclusivement)». Si l'utilisateur veut voir chaque opération avec une certaine clé, il peut spécifier la première révision de etcd. Qu'est-ce que cet audit? Le modèle de données et le glossaire ne fournissent pas de réponse à cette question; les révisions sont décrites comme augmentant de façon monotone les compteurs 64 bits, mais il n'est pas clair si etcd démarre à partir de 0 ou 1. Il est raisonnable de supposer que le compte à rebours est à partir de zéro (juste au cas où).

Hélas, c'est faux. La demande de la 0e révision oblige etcd à démarrer la diffusion des mises à jour, en commençant par la révision actuelle sur le serveur plus un, mais pas avec le tout premier. La demande de 1ère révision donne toutes les modifications. Ce comportement n'est documenté nulle part .

Nous pensons qu'en pratique, cette subtilité est peu susceptible d'entraîner des problèmes de production, car la plupart des clusters ne s'attardent pas sur la première révision. De plus, etcd compresse l'histoire de toute façon au fil du temps, donc dans les applications du monde réel, très probablement, en tout cas, il ne nécessite pas de lire toutes les versions, à partir de la 1ère révision. Un tel comportement est justifié, mais il ne nuirait pas à la description correspondante dans la documentation.

3.2 Serrures mythiques


La documentation de l'API pour les verrous indique qu'une clé verrouillée "peut être utilisée conjointement avec des transactions pour garantir que les mises à jour dans etcd se produisent uniquement lorsque le verrou est détenu". Étrange, mais il ne fournit aucune garantie pour les serrures elles-mêmes et leur fonction n'est pas expliquée.

Cependant, dans d'autres documents, les responsables de la maintenance, etc. partagent toujours des informations sur l'utilisation des verrous. Par exemple, l'annonce de la sortie de etcd 3.2 décrit une application etcdctlpour bloquer les changements de partage de fichiers sur un disque. De plus, dans un problème sur GitHub avec une question sur le but spécifique des verrous, l' un des développeurs etcd a répondu ce qui suit:

etcd , ( ) , ( etcd), - :

  1. etcd;
  2. - ( , etcd);
  3. .

Un tel exemple est donné dans etcdctl: un verrou a été utilisé pour protéger l'équipe put, mais n'a pas lié la clé de verrouillage à la mise à jour.

Hélas, ce n'est pas sûr car cela permet à plusieurs clients de détenir simultanément le même verrou. Le problème est aggravé par la suspension des processus, les pannes de réseau ou les partitions, mais il peut également se produire dans des clusters complètement sains sans aucune défaillance externe. Par exemple, dans cette exécution de test, le processus numéro 3 définit correctement le verrou et le processus 1 obtient le même verrou en parallèle avant même que le processus 3 n'ait la possibilité de le supprimer:



La violation du mutex était plus visible sur les baux avec des TTL courts: les TTL de 1, 2 et 3 secondes n'étaient pas en mesure de fournir l'exclusion mutuelle après seulement quelques minutes de test (même dans des grappes saines). Les suspensions de processus et les partitions réseau ont entraîné des problèmes encore plus rapidement.

Dans l'une de nos variantes de test de verrouillage, des mutex etcd ont été utilisés pour protéger les mises à jour conjointes d'un ensemble d'entiers (comme le suggère la documentation etcd). Chaque mise à jour lit la valeur d'exemple en mémoire actuelle et, après environ une seconde, réécrit cette collection avec l'ajout d'un élément unique. Avec des baux avec un TTL de deux secondes, cinq processus parallèles et une pause de processus toutes les cinq secondes, nous avons pu provoquer une perte régulière d'environ 18% des mises à jour confirmées.

Ce problème a été exacerbé par le mécanisme de verrouillage interne dans etcd. Si un client attendait qu'un autre client le déverrouille, a perdu son bail et après que le verrou a été libéré, le serveur n'a pas revérifié le bail pour s'assurer qu'il est toujours valide avant d'informer le client que le verrou est maintenant derrière lui.

L'inclusion d'un chèque de bail supplémentaire, ainsi que la sélection de TTL plus longs et la définition minutieuse des délais d' expiration des élections , réduiront la fréquence de ce problème. Cependant, les violations de mutex ne peuvent pas être complètement éliminées, car les verrous distribués sont fondamentalement dangereux dans les systèmes asynchrones. Le Dr Martin Kleppmann le décrit de manière convaincante dans son articleÀ propos des verrous distribués. Selon lui, les services de blocage doivent sacrifier l'exactitude afin de maintenir la viabilité dans les systèmes asynchrones: si le processus se bloque lors du contrôle du blocage, le service de blocage a besoin d'un moyen pour forcer le déverrouillage du blocage. Cependant, si le processus n'est pas tombé, mais s'exécute simplement lentement ou est temporairement indisponible, le déverrouiller peut entraîner son maintien à plusieurs endroits en même temps.

Mais même si le service de blocage distribué utilise, disons, une sorte de détecteur de défaillance magique et peut effectivement garantir l'exclusion mutuelle, dans le cas d'une ressource non locale, son utilisation sera toujours dangereuse. Supposons que le processus A envoie un message à la base de données D tout en maintenant un verrou. Après cela, le processus A se bloque et le processus B reçoit un verrou et envoie également un message à la base D. Le problème est qu'un message du processus A (en raison de l'asynchronie) peut venir après un message du processus B, violant l'exception mutuelle que le verrou était censé fournir. .

Pour éviter ce problème, il est nécessaire de s'appuyer sur le fait que le système de stockage lui - même prendra en charge l'exactitude des transactions ou, si le service de verrouillage fournit un tel mécanisme, utilisezJeton « Clôture» qui sera inclus dans toutes les opérations effectuées par le détenteur de la serrure. Cela garantira qu'aucune opération du détenteur de verrou précédent ne se produira soudainement entre les opérations du propriétaire de verrou actuel. Par exemple, dans le service de blocage Chubby de Google , ces jetons sont appelés séquenceurs . Dans etcd, vous pouvez utiliser la révision de la clé de verrouillage comme un jeton de blocage ordonné globalement.

De plus, les clés de verrouillage dans etcd peuvent être utilisées pour protéger les mises à jour transactionnelles dans etcd lui-même. Vérification de la version de la clé de verrouillage dans le cadre de la transaction, les utilisateurs peuvent empêcher une transaction si le verrou n'est plus maintenu (c'est-à-dire que la version de la clé de verrouillage est supérieure à zéro). Dans nos tests, cette approche nous a permis d'isoler avec succès les opérations de lecture-modification-écriture dans lesquelles l'écriture était la seule transaction protégée par verrouillage. Cette approche fournit un isolement similaire aux jetons de barrage, mais (comme les jetons de barrage) ne garantit pas l'atomicité: un processus peut se bloquer ou perdre un mutex lors d'une mise à jour comprenant de nombreuses opérations, laissant etcd dans un état logiquement incohérent.

Les résultats du travail sur les enjeux du projet:

4. Discussion


Dans nos tests, etcd 3.4.3 a répondu aux attentes concernant les opérations KV: nous avons observé une cohérence strictement sérialisable des transactions en lecture, écriture et même multi-clés, malgré la suspension des processus, les plantages, la manipulation de l'horloge et du réseau, ainsi qu'un changement dans le nombre de membres du cluster . Un comportement strictement sérialisable a été implémenté par défaut dans les opérations KV; la performance des lectures avec l'indicateur serializablea entraîné l'apparition de lectures périmées (comme décrit dans la documentation).

Moniteur (montres) pour fonctionner correctement - au moins sur les touches individuelles. Jusqu'à ce que la compression de l'historique détruise les anciennes données, la montre a émis avec succès chaque mise à jour de clé.

Cependant, il s'est avéré que les verrous dans etcd (comme tous les verrous distribués) ne fournissent pas d'exclusion mutuelle. Différents processus peuvent maintenir le verrou en même temps - même dans des grappes saines avec des horloges parfaitement synchronisées. La documentation avec l'API de verrouillage n'a rien dit à ce sujet et les exemples de verrous présentés n'étaient pas sûrs. Cependant, certains des problèmes avec les verrous ont dû disparaître après la publication de ce correctif .

Suite à notre collaboration, l'équipe etcd a apporté un certain nombre d'amendements à la documentation (ils sont déjà apparus sur GitHub et seront publiés dans les futures versions du site web du projet). La page API des garanties GitHub indique maintenant que par défaut etcd est strictement sérialisableet l'affirmation selon laquelle le sériel et le sérialisable sont les niveaux de cohérence les plus solides disponibles dans les systèmes distribués a été supprimée. En ce qui concerne les révisions, il est maintenant indiqué que le début doit être à partir de l'unité (1) , bien que la documentation de l'API ne dise toujours pas qu'une tentative de démarrage à partir de la 0e révision entraînera «des événements de sortie qui se sont produits après la révision actuelle plus 1». au lieu de "l'envoi de tous les événements". La documentation des problèmes de sécurité des verrous est en cours d'élaboration .

Certaines modifications de la documentation, telles que la description du comportement spécial de etcd lors de la lecture, en commençant par une révision zéro, nécessitent toujours une attention particulière.

Comme d'habitude, nous soulignons que Jepsen préfère une approche expérimentale de la vérification de la sécurité: nous pouvons confirmer la présence de bugs, mais pas leur absence. Des efforts considérables sont déployés pour trouver des problèmes, mais nous ne pouvons pas prouver l'exactitude générale de etcd.

4.1 Recommandations


Si vous utilisez des verrous dans etcd, demandez-vous si vous en avez besoin pour la sécurité ou simplement pour augmenter les performances en limitant la simultanéité de manière probabiliste. Les verrous Etcd peuvent être utilisés pour augmenter les performances, mais leur utilisation à des fins de sécurité peut être risquée.

En particulier, si vous utilisez le verrou etcd pour protéger une ressource partagée telle qu'un fichier, une base de données ou un service, cette ressource doit garantir la sécurité sans blocage. Une façon d'y parvenir est d'utiliser un jeton de barrage monotone . Il peut s'agir, par exemple, d'une révision etcd associée à la clé de verrouillage en cours. La ressource partagée doit s'assurer qu'une fois que le client a utilisé le jetonypour effectuer une opération, toute opération avec un jeton x < ysera rejetée. Cette approche ne garantit pas l'atomicité, mais elle garantit que les opérations dans le cadre du verrouillage sont effectuées dans l'ordre et non par intermittence.

Nous pensons que les utilisateurs ordinaires ne rencontreront probablement pas ce problème. Mais si vous comptez toujours sur la lecture de toutes les modifications de etcd, en commençant par la première révision, n'oubliez pas que vous devez passer 1, pas 0 comme paramètre. Nos expériences montrent qu'une révision nulle dans ce cas signifie "révision actuelle", pas "Au plus tôt."

Enfin, les verrous et etcd (comme tous les verrous distribués) induisent les utilisateurs en erreur: ils voudront peut-être les utiliser comme des verrous ordinaires, mais ils seront très surpris lorsqu'ils se rendront compte que ces verrous ne procurent pas d'exclusion mutuelle. La documentation de l'API, les articles de blog, les problèmes sur GitHub ne disent rien sur ce risque. Nous vous recommandons d'inclure dans la documentation etcd des informations indiquant que les verrous ne fournissent pas d'exclusion mutuelle et de fournir des exemples d'utilisation de jetons de barrage pour mettre à jour le statut des ressources partagées au lieu d'exemples pouvant entraîner la perte de mises à jour.

4.2 Plans futurs


Le projet etcd est considéré comme stable depuis plusieurs années: l'algorithme Raft basé sur lui a bien fonctionné, l'API pour les opérations KV est simple et directe. Bien que certaines fonctionnalités supplémentaires aient récemment reçu une nouvelle API, sa sémantique est relativement simple. Nous pensons que nous avons déjà étudié suffisamment de commandes de base comme getet put, les transactions, le blocage et le suivi. Cependant, d'autres tests doivent être effectués.

Pour le moment, nous n'avons pas procédé à une évaluation suffisamment détaillée des suppressions.: Il peut y avoir des cas limites associés aux versions et révisions, lorsque les objets sont constamment créés et supprimés. Lors de futurs tests, nous comptons soumettre les opérations de retrait à une étude plus approfondie. Nous n'avons pas non plus testé les requêtes de plage ou les opérations de suivi avec plusieurs clés, bien que nous suspections que leur sémantique soit similaire aux opérations avec des clés uniques.

Dans les tests, nous avons utilisé la suspension des processus, les plantages, les manipulations avec l'horloge, le réseau a été divisé et la composition du cluster a changé; dans les coulisses, il y avait des problèmes comme des dommages au disque et d'autres échecs byzantins au niveau d'un nœud. Ces opportunités pourraient être explorées dans de futures recherches.

Le travail a été soutenu par la Cloud Native Computing Foundation., qui fait partie de la Fondation Linux , et respecte les politiques éthiques de Jepsen . Nous tenons à remercier l'équipe etcd pour son aide, et les représentants suivants en particulier: Chris Aniszczyk, Gyuho Lee, Xiang Li, Hitoshi Mitake, Jingyi Hu et Brandon Philips.

PS du traducteur


Lisez aussi dans notre blog:

Source: https://habr.com/ru/post/undefined/


All Articles