Ursprünglich veröffentlicht am von Blog
Die Kubernetes-Standardbibliothek ist voller Juwelen, die in vielen der verschiedenen Unterpakete versteckt sind, die Teil des Ökosystems sind. Ein solches Beispiel habe ich kürzlich unter k8s.io/client-go/tools/leaderelection entdeckt und kann verwendet werden, um jeder Anwendung, die in einem Kubernetes-Cluster ausgeführt wird, ein Leader-Wahlprotokoll hinzuzufügen. In diesem Artikel wird erläutert, was eine Führungswahl ist, wie sie in diesem Kubernetes-Paket implementiert wird und ein Beispiel dafür bereitgestellt wird, wie wir diese Bibliothek in unseren eigenen Anwendungen verwenden können.
Leaderwahl ist ein verteiltes Systemkonzept, das einen Kernbaustein hochverfügbarer Software darstellt. Es ermöglicht mehreren gleichzeitigen Prozessen, sich untereinander zu koordinieren und einen einzigen „Leiter“-Prozess zu wählen, der dann für die Ausführung synchroner Aktionen wie das Schreiben in einen Datenspeicher verantwortlich ist.
Dies ist in Systemen wie verteilten Datenbanken oder Caches nützlich, in denen mehrere Prozesse ausgeführt werden, um Redundanz gegen Hardware- oder Netzwerkausfälle zu schaffen, aber nicht gleichzeitig in den Speicher schreiben können, um die Datenkonsistenz sicherzustellen. Wenn der Anführerprozess irgendwann in der Zukunft nicht mehr reagiert, werden die verbleibenden Prozesse eine neue Anführerwahl einleiten und schließlich einen neuen Prozess als Anführer auswählen.
Mit diesem Konzept können wir hochverfügbare Software mit einem einzigen Leader und mehreren Standby-Replikaten erstellen.
In Kubernetes verwendet das Controller-Runtime-Paket die Leader-Wahl, um Controller hochverfügbar zu machen. In einer Controller-Bereitstellung findet der Ressourcenabgleich nur statt, wenn ein Prozess der Anführer ist und andere Replikate im Standby-Modus warten. Wenn der Leader-Pod nicht mehr reagiert, wählen die verbleibenden Replikate einen neuen Leader, der nachfolgende Abstimmungen durchführt und den normalen Betrieb wieder aufnimmt.
Diese Bibliothek verwendet eine Kubernetes-Lease oder verteilte Sperre, die von einem Prozess abgerufen werden kann. Leases sind native Kubernetes-Ressourcen, die von einer einzelnen Identität für einen bestimmten Zeitraum mit einer Verlängerungsoption gehalten werden. Hier ist eine Beispielspezifikation aus den Dokumenten:
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"
Mietverträge werden vom k8s-Ökosystem auf drei Arten genutzt:
Lassen Sie uns nun diesen zweiten Anwendungsfall von Leases untersuchen, indem wir ein Beispielprogramm schreiben, um zu demonstrieren, wie Sie sie in Szenarios zur Wahl von Führungskräften verwenden können.
In diesem Codebeispiel verwenden wir das Paket „leaderelection“, um die Besonderheiten der Leader-Wahl und der Lease-Manipulation zu verwalten.
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()) }
Das Schöne am Leaderelection-Paket ist, dass es ein Callback-basiertes Framework für die Abwicklung von Leader-Wahlen bietet. Auf diese Weise können Sie granular auf bestimmte Zustandsänderungen reagieren und Ressourcen ordnungsgemäß freigeben, wenn ein neuer Anführer gewählt wird. Durch die Ausführung dieser Rückrufe in separaten Goroutinen nutzt das Paket die starke Parallelitätsunterstützung von Go, um Maschinenressourcen effizient zu nutzen.
Um dies zu testen, starten wir einen Testcluster mit kind.
$ kind create cluster
Kopieren Sie den Beispielcode in main.go, erstellen Sie ein neues Modul (Go Mod Init Leaderelectiontest) und räumen Sie es auf (Go Mod Tidy), um seine Abhängigkeiten zu installieren. Sobald Sie go run main.go ausführen, sollten Sie eine Ausgabe wie diese sehen:
$ 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!
Die genaue Führungsidentität wird sich von der im Beispiel (138) unterscheiden, da dies nur die PID des Prozesses ist, der zum Zeitpunkt des Schreibens auf meinem Computer ausgeführt wurde.
Und hier ist der Lease, der im Testcluster erstellt wurde:
$ 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:
Achten Sie darauf, dass die „Inhaberidentität“ mit der PID des Prozesses, 138, übereinstimmt.
Jetzt öffnen wir ein anderes Terminal und führen dieselbe main.go-Datei in einem separaten Prozess aus:
$ 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
Dieser zweite Prozess wird ewig warten, bis der erste nicht mehr reagiert. Lassen Sie uns den ersten Prozess beenden und etwa 15 Sekunden warten. Da nun der erste Prozess seinen Anspruch auf den Mietvertrag nicht verlängert, wird das Feld „.spec.renewTime“ nicht mehr aktualisiert. Dies wird schließlich dazu führen, dass der zweite Prozess eine neue Wahl des Anführers auslöst, da die Verlängerungszeit des Mietvertrags älter als seine Laufzeit ist. Da dieser Prozess der einzige ist, der derzeit läuft, wird er sich selbst zum neuen Anführer wählen.
the leader is 604 I0716 11:48:51.904732 604 leaderelection.go:260] successfully acquired lease default/my-lock I am the leader!
Wenn nach dem Ausscheiden des ursprünglichen Anführers noch mehrere Prozesse ausgeführt würden, wäre der erste Prozess, der die Lease erwirbt, der neue Anführer, und der Rest wäre weiterhin im Standby-Modus.
Dieses Paket ist nicht narrensicher, da es „nicht garantiert, dass nur ein Kunde als Anführer fungiert (auch bekannt als Fechten)“. Wenn beispielsweise ein Anführer angehalten wird und seine Lease ablaufen lässt, übernimmt ein anderes Standby-Replikat die Lease. Sobald der ursprüngliche Anführer dann die Ausführung wieder aufnimmt, wird er denken, dass er immer noch der Anführer ist, und weiterhin an der Seite des neu gewählten Anführers arbeiten. Auf diese Weise kann es passieren, dass zwei Anführer gleichzeitig laufen.
Um dies zu beheben, muss in jede Anfrage an den Server ein Fencing-Token aufgenommen werden, der auf den Mietvertrag verweist. Ein Fechtmarker ist praktisch eine Ganzzahl, die sich jedes Mal um 1 erhöht, wenn ein Pachtvertrag den Besitzer wechselt. Daher werden die Anfragen eines Clients mit einem alten Fencing-Token vom Server abgelehnt. Wenn in diesem Szenario ein alter Anführer aus dem Schlaf erwacht und ein neuer Anführer den Fencing-Token bereits erhöht hat, werden alle Anfragen des alten Anführers abgelehnt, da er einen älteren (kleineren) Token sendet, als der Server ihn gesehen hat neuerer Anführer.
Die Implementierung von Fencing in Kubernetes wäre schwierig, ohne den Kern-API-Server zu ändern, um entsprechende Fencing-Tokens für jeden Mietvertrag zu berücksichtigen. Das Risiko mehrerer führender Controller wird jedoch durch den k8s-API-Server selbst etwas gemindert. Da Aktualisierungen veralteter Objekte abgelehnt werden, können nur Controller mit der aktuellsten Version eines Objekts dieses ändern. Während wir also mehrere Controller-Leader ausführen könnten, würde der Status einer Ressource niemals auf ältere Versionen zurückgehen, wenn ein Controller eine von einem anderen Leader vorgenommene Änderung übersieht. Stattdessen würde sich die Abstimmungszeit verlängern, da beide Führungskräfte ihre eigenen internen Ressourcenzustände aktualisieren müssen, um sicherzustellen, dass sie auf der Grundlage der aktuellsten Versionen handeln.
Wenn Sie dieses Paket jedoch verwenden, um die Wahl des Anführers mithilfe eines anderen Datenspeichers zu implementieren, ist dies ein wichtiger Vorbehalt, den Sie beachten sollten.
Leader-Wahl und verteiltes Sperren sind wichtige Bausteine verteilter Systeme. Wenn Sie versuchen, fehlertolerante und hochverfügbare Anwendungen zu erstellen, ist die Verfügbarkeit solcher Tools von entscheidender Bedeutung. Die Kubernetes-Standardbibliothek bietet uns einen kampferprobten Wrapper um ihre Grundelemente, damit Anwendungsentwickler die Leader-Wahl problemlos in ihre eigenen Anwendungen integrieren können.
Während die Verwendung dieser speziellen Bibliothek Sie auf die Bereitstellung Ihrer Anwendung auf Kubernetes beschränkt, scheint sich die Welt in letzter Zeit so zu entwickeln. Wenn das tatsächlich ein Dealbreaker ist, können Sie die Bibliothek natürlich abzweigen und so modifizieren, dass sie mit jedem ACID-kompatiblen und hochverfügbaren Datenspeicher funktioniert.
Bleiben Sie dran für weitere Einblicke in die K8s-Quelle!
Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.
Copyright© 2022 湘ICP备2022001581号-3