So fügen Sie Ihren Go-Apps die von Kubernetes unterstützte Führungswahl hinzu

WBOY
Freigeben: 2024-07-20 09:15:39
Original
834 Leute haben es durchsucht

How to add Kubernetes-powered leader election to your Go apps

Ursprünglich veröffentlicht auf 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.

Führerwahl

Leader-Wahl ist ein verteiltes Systemkonzept, das einen Kernbaustein hochverfügbarer Software darstellt. Es ermöglicht mehreren gleichzeitigen Prozessen, sich untereinander zu koordinieren und einen einzelnen „Leiter“-Prozess zu wählen, der dann für die Durchfü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 Leiterprozess irgendwann in der Zukunft nicht mehr reagiert, werden die verbleibenden Prozesse eine neue Leiterwahl einleiten und schließlich einen neuen Prozess als Leiter auswählen.

Mit diesem Konzept können wir hochverfügbare Software mit einem einzigen Leader und mehreren Standby-Replikaten erstellen.

In Kubernetes nutzt das Controller-Runtime-Paket die Leader-Wahl, um Controller hochverfügbar zu machen. In einer Controller-Bereitstellung erfolgt der Ressourcenabgleich nur, 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.

Kubernetes-Leasingverträge

Diese Bibliothek verwendet eine Kubernetes-Lease oder verteilte Sperre, die von einem Prozess abgerufen werden kann. Bei Leasing handelt es sich um 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"
Nach dem Login kopieren

Leasingverträge werden vom k8s-Ökosystem auf drei Arten genutzt:

  1. Knoten-Heartbeats: Jeder Knoten verfügt über eine entsprechende Lease-Ressource und aktualisiert sein renewTime-Feld fortlaufend. Wenn die Erneuerungszeit eines Mietvertrags längere Zeit nicht aktualisiert wurde, wird der Knoten als nicht verfügbar eingestuft und es werden keine weiteren Pods für ihn geplant.
  2. Leader-Wahl: In diesem Fall wird ein Lease verwendet, um mehrere Prozesse zu koordinieren, indem ein Leader die Inhaberidentität des Lease aktualisiert. Standby-Replikate mit unterschiedlichen Identitäten stecken fest und warten auf den Ablauf der Lease. Wenn der Mietvertrag tatsächlich abläuft und vom Anführer nicht erneuert wird, findet eine Neuwahl statt, bei der die verbleibenden Replikate versuchen, den Besitz des Mietvertrags zu übernehmen, indem sie dessen Inhaberidentität durch ihre eigene aktualisieren. Da der Kubernetes-API-Server Aktualisierungen veralteter Objekte nicht zulässt, kann nur ein einziger Standby-Knoten den Lease erfolgreich aktualisieren und setzt dann die Ausführung als neuer Anführer fort.
  3. API-Server-Identität: Ab Version 1.26 veröffentlicht jedes Kube-Apiserver-Replikat als Beta-Funktion seine Identität, indem es einen dedizierten Lease erstellt. Da es sich um eine relativ schlanke, neue Funktion handelt, lässt sich aus dem Lease-Objekt nicht viel anderes ableiten, als wie viele API-Server ausgeführt werden. Dies lässt jedoch Raum, um in zukünftigen k8s-Versionen weitere Metadaten zu diesen Mietverträgen hinzuzufügen.

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.

Beispielprogramm

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())

}
Nach dem Login kopieren

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.

Probieren Sie es aus

Um dies zu testen, erstellen wir einen Testcluster mit kind.

$ kind create cluster
Nach dem Login kopieren

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!
Nach dem Login kopieren

The exact leader identity will be different from what's in the example (138), since this is just the PID of the process that was running on my computer at the time of writing.

And here's the Lease that was created in the test cluster:

$ kubectl describe lease/my-lock
Name:         my-lock
Namespace:    default
Labels:       <none>
Annotations:  <none>
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:                    <none>
Nach dem Login kopieren

See that the "Holder Identity" is the same as the process's PID, 138.

Now, let's open up another terminal and run the same main.go file in a separate process:

$ 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
Nach dem Login kopieren

This second process will wait forever, until the first one is not responsive. Let's kill the first process and wait around 15 seconds. Now that the first process is not renewing its claim on the Lease, the .spec.renewTime field won't be updated anymore. This will eventually cause the second process to trigger a new leader election, since the Lease's renew time is older than its duration. Because this process is the only one now running, it will elect itself as the new leader.

the leader is 604
I0716 11:48:51.904732     604 leaderelection.go:260] successfully acquired lease default/my-lock
I am the leader!
Nach dem Login kopieren

If there were multiple processes still running after the initial leader exited, the first process to acquire the Lease would be the new leader, and the rest would continue to be on standby.

No single-leader guarantees

This package is not foolproof, in that it "does not guarantee that only one client is acting as a leader (a.k.a. fencing)". For example, if a leader is paused and lets its Lease expire, another standby replica will acquire the Lease. Then, once the original leader resumes execution, it will think that it's still the leader and continue doing work alongside the newly-elected leader. In this way, you can end up with two leaders running simultaneously.

To fix this, a fencing token which references the Lease needs to be included in each request to the server. A fencing token is effectively an integer that increases by 1 every time a Lease changes hands. So a client with an old fencing token will have its requests rejected by the server. In this scenario, if an old leader wakes up from sleep and a new leader has already incremented the fencing token, all of the old leader's requests would be rejected because it is sending an older (smaller) token than what the server has seen from the newer leader.

Implementing fencing in Kubernetes would be difficult without modifying the core API server to account for corresponding fencing tokens for each Lease. However, the risk of having multiple leader controllers is somewhat mitigated by the k8s API server itself. Because updates to stale objects are rejected, only controllers with the most up-to-date version of an object can modify it. So while we could have multiple controller leaders running, a resource's state would never regress to older versions if a controller misses a change made by another leader. Instead, reconciliation time would increase as both leaders need to refresh their own internal states of resources to ensure that they are acting on the most recent versions.

Still, if you're using this package to implement leader election using a different data store, this is an important caveat to be aware of.

Conclusion

Leader election and distributed locking are critical building blocks of distributed systems. When trying to build fault-tolerant and highly-available applications, having tools like these at your disposal is critical. The Kubernetes standard library gives us a battle-tested wrapper around its primitives to allow application developers to easily build leader election into their own applications.

While use of this particular library does limit you to deploying your application on Kubernetes, that seems to be the way the world is going recently. If in fact that is a dealbreaker, you can of course fork the library and modify it to work against any ACID-compliant and highly-available datastore.

Stay tuned for more k8s source deep dives!

Das obige ist der detaillierte Inhalt vonSo fügen Sie Ihren Go-Apps die von Kubernetes unterstützte Führungswahl hinzu. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!