Publié à l'origine sur par blog
La bibliothèque standard de Kubernetes regorge de joyaux, cachés dans de nombreux sous-paquets qui font partie de l'écosystème. Un exemple que j'ai découvert récemment k8s.io/client-go/tools/leaderelection, qui peut être utilisé pour ajouter un protocole d'élection de leader à toute application exécutée dans un cluster Kubernetes. Cet article expliquera ce qu'est l'élection du leader, comment elle est implémentée dans ce package Kubernetes et fournira un exemple de la façon dont nous pouvons utiliser cette bibliothèque dans nos propres applications.
L'élection du leader est un concept de systèmes distribués qui constitue un élément essentiel des logiciels hautement disponibles. Il permet à plusieurs processus simultanés de se coordonner et d'élire un seul processus « leader », qui est ensuite responsable de l'exécution d'actions synchrones telles que l'écriture dans un magasin de données.
Ceci est utile dans les systèmes tels que les bases de données distribuées ou les caches, où plusieurs processus sont en cours d'exécution pour créer une redondance contre les pannes matérielles ou réseau, mais ne peuvent pas écrire simultanément sur le stockage pour garantir la cohérence des données. Si le processus de leader ne répond plus à un moment donné dans le futur, les processus restants lanceront une nouvelle élection de leader, choisissant finalement un nouveau processus pour agir en tant que leader.
Grâce à ce concept, nous pouvons créer des logiciels hautement disponibles avec un seul leader et plusieurs répliques de secours.
Dans Kubernetes, le package d'exécution du contrôleur utilise l'élection du leader pour rendre les contrôleurs hautement disponibles. Dans un déploiement de contrôleur, la réconciliation des ressources se produit uniquement lorsqu'un processus est le leader et que d'autres réplicas sont en attente en attente. Si le pod leader ne répond plus, les répliques restantes éliront un nouveau leader pour effectuer les rapprochements ultérieurs et reprendre le fonctionnement normal.
Cette bibliothèque utilise un bail Kubernetes, ou verrou distribué, qui peut être obtenu par un processus. Les baux sont des ressources Kubernetes natives détenues par une seule identité, pour une durée donnée, avec une option de renouvellement. Voici un exemple de spécification tiré de la documentation :
apiVersion: coordination.k8s.io/v1 kind: Lease metadata: labels: apiserver.kubernetes.io/identity: kube-apiserver kubernetes.io/hostname: master-1 name: apiserver-07a5ea9b9b072c4a5f3d1c3702 namespace: kube-system spec: holderIdentity: apiserver-07a5ea9b9b072c4a5f3d1c3702_0c8914f7-0f35-440e-8676-7844977d3a05 leaseDurationSeconds: 3600 renewTime: "2023-07-04T21:58:48.065888Z"
Les baux sont utilisés par l'écosystème k8s de trois manières :
Explorons maintenant ce deuxième cas d'utilisation des baux en écrivant un exemple de programme pour démontrer comment vous pouvez les utiliser dans des scénarios d'élection de dirigeants.
Dans cet exemple de code, nous utilisons le package leaderelection pour gérer les spécificités de l'élection du leader et de la manipulation du bail.
package main import ( "context" "fmt" "os" "time" "k8s.io/client-go/tools/leaderelection" rl "k8s.io/client-go/tools/leaderelection/resourcelock" ctrl "sigs.k8s.io/controller-runtime" ) var ( // lockName and lockNamespace need to be shared across all running instances lockName = "my-lock" lockNamespace = "default" // identity is unique to the individual process. This will not work for anything, // outside of a toy example, since processes running in different containers or // computers can share the same pid. identity = fmt.Sprintf("%d", os.Getpid()) ) func main() { // Get the active kubernetes context cfg, err := ctrl.GetConfig() if err != nil { panic(err.Error()) } // Create a new lock. This will be used to create a Lease resource in the cluster. l, err := rl.NewFromKubeconfig( rl.LeasesResourceLock, lockNamespace, lockName, rl.ResourceLockConfig{ Identity: identity, }, cfg, time.Second*10, ) if err != nil { panic(err) } // Create a new leader election configuration with a 15 second lease duration. // Visit https://pkg.go.dev/k8s.io/client-go/tools/leaderelection#LeaderElectionConfig // for more information on the LeaderElectionConfig struct fields el, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{ Lock: l, LeaseDuration: time.Second * 15, RenewDeadline: time.Second * 10, RetryPeriod: time.Second * 2, Name: lockName, Callbacks: leaderelection.LeaderCallbacks{ OnStartedLeading: func(ctx context.Context) { println("I am the leader!") }, OnStoppedLeading: func() { println("I am not the leader anymore!") }, OnNewLeader: func(identity string) { fmt.Printf("the leader is %s\n", identity) }, }, }) if err != nil { panic(err) } // Begin the leader election process. This will block. el.Run(context.Background()) }
Ce qui est bien avec le package leaderelection, c'est qu'il fournit un cadre basé sur le rappel pour gérer les élections des dirigeants. De cette façon, vous pouvez agir de manière granulaire sur des changements d’état spécifiques et libérer correctement des ressources lorsqu’un nouveau leader est élu. En exécutant ces rappels dans des goroutines distinctes, le package profite de la forte prise en charge de la concurrence de Go pour utiliser efficacement les ressources de la machine.
Pour tester cela, développons un cluster de test en utilisant kind.
$ kind create cluster
Copiez l'exemple de code dans main.go, créez un nouveau module (go mod init leaderelectiontest) et rangez-le (go mod spice) pour installer ses dépendances. Une fois que vous avez exécuté, lancez main.go, vous devriez voir un résultat comme celui-ci :
$ go run main.go I0716 11:43:50.337947 138 leaderelection.go:250] attempting to acquire leader lease default/my-lock... I0716 11:43:50.351264 138 leaderelection.go:260] successfully acquired lease default/my-lock the leader is 138 I am the leader!
L'identité exacte du leader sera différente de celle de l'exemple (138), puisqu'il s'agit simplement du PID du processus qui était en cours d'exécution sur mon ordinateur au moment de la rédaction.
Et voici le bail qui a été créé dans le cluster de test :
$ kubectl describe lease/my-lock Name: my-lock Namespace: default Labels:Annotations: API Version: coordination.k8s.io/v1 Kind: Lease Metadata: Creation Timestamp: 2024-07-16T15:43:50Z Resource Version: 613 UID: 1d978362-69c5-43e9-af13-7b319dd452a6 Spec: Acquire Time: 2024-07-16T15:43:50.338049Z Holder Identity: 138 Lease Duration Seconds: 15 Lease Transitions: 0 Renew Time: 2024-07-16T15:45:31.122956Z Events:
Vérifiez que « l'identité du titulaire » est la même que le PID du processus, 138.
Maintenant, ouvrons un autre terminal et exécutons le même fichier main.go dans un processus séparé :
$ go run main.go I0716 11:48:34.489953 604 leaderelection.go:250] attempting to acquire leader lease default/my-lock... the leader is 138
Ce deuxième processus attendra indéfiniment, jusqu'à ce que le premier ne réponde plus. Tuons le premier processus et attendons environ 15 secondes. Maintenant que le premier processus ne renouvelle pas sa réclamation sur le bail, le champ .spec.renewTime ne sera plus mis à jour. Cela finira par amener le deuxième processus à déclencher l'élection d'un nouveau leader, puisque la période de renouvellement du bail est plus ancienne que sa durée. Parce que ce processus est le seul en cours, il s’élira lui-même comme nouveau leader.
the leader is 604 I0716 11:48:51.904732 604 leaderelection.go:260] successfully acquired lease default/my-lock I am the leader!
S'il y avait plusieurs processus toujours en cours après la sortie du leader initial, le premier processus à acquérir le bail serait le nouveau leader, et les autres continueraient à être en attente.
Ce package n'est pas infaillible, dans le sens où il "ne garantit pas qu'un seul client agit en tant que leader (c'est-à-dire l'escrime)". Par exemple, si un leader est mis en pause et laisse son bail expirer, une autre réplique de secours acquerra le bail. Ensuite, une fois que le leader d'origine aura repris l'exécution, il pensera qu'il est toujours le leader et continuera à travailler aux côtés du leader nouvellement élu. De cette façon, vous pouvez vous retrouver avec deux dirigeants qui courent simultanément.
Pour résoudre ce problème, un jeton de clôture faisant référence au bail doit être inclus dans chaque requête adressée au serveur. Un jeton d'escrime est en fait un nombre entier qui augmente de 1 à chaque fois qu'un bail change de mains. Ainsi, un client possédant un ancien jeton de clôture verra ses requêtes rejetées par le serveur. Dans ce scénario, si un ancien chef se réveille et qu'un nouveau chef a déjà incrémenté le jeton d'escrime, toutes les demandes de l'ancien chef seront rejetées car il envoie un jeton plus ancien (plus petit) que celui que le serveur a vu du serveur. nouveau leader.
La mise en œuvre du fencing dans Kubernetes serait difficile sans modifier le serveur API principal pour prendre en compte les jetons de fencing correspondants pour chaque bail. Cependant, le risque d'avoir plusieurs contrôleurs principaux est quelque peu atténué par le serveur API k8s lui-même. Étant donné que les mises à jour des objets obsolètes sont rejetées, seuls les contrôleurs disposant de la version la plus récente d’un objet peuvent le modifier. Ainsi, même si plusieurs contrôleurs leaders sont en cours d'exécution, l'état d'une ressource ne régressera jamais vers les anciennes versions si un contrôleur manque une modification apportée par un autre leader. Au lieu de cela, le temps de réconciliation augmenterait car les deux dirigeants devraient actualiser leurs propres états internes de ressources pour s'assurer qu'ils agissent sur les versions les plus récentes.
Néanmoins, si vous utilisez ce package pour mettre en œuvre l'élection d'un chef à l'aide d'un autre magasin de données, il s'agit d'une mise en garde importante à prendre en compte.
L'élection du leader et le verrouillage distribué sont des éléments essentiels des systèmes distribués. Lorsque vous essayez de créer des applications tolérantes aux pannes et hautement disponibles, il est essentiel de disposer d'outils comme ceux-ci. La bibliothèque standard Kubernetes nous offre un wrapper éprouvé autour de ses primitives pour permettre aux développeurs d'applications d'intégrer facilement l'élection des dirigeants dans leurs propres applications.
Bien que l'utilisation de cette bibliothèque particulière vous limite au déploiement de votre application sur Kubernetes, cela semble être la façon dont le monde évolue récemment. S'il s'agit en fait d'un problème, vous pouvez bien sûr créer une bibliothèque et la modifier pour qu'elle fonctionne avec n'importe quelle banque de données conforme à ACID et hautement disponible.
Restez à l'écoute pour plus d'informations approfondies sur les sources K8 !
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3