元々はブログで公開されました
Kubernetes 標準ライブラリには、エコシステムの一部であるさまざまなサブパッケージの多くに隠された宝石がたくさんあります。そのような例の 1 つは、私が最近発見した k8s.io/client-go/tools/leaderelection です。これは、Kubernetes クラスター内で実行されているアプリケーションにリーダー選出プロトコルを追加するために使用できます。この記事では、リーダー選挙とは何か、それがこの Kubernetes パッケージでどのように実装されるかについて説明し、独自のアプリケーションでこのライブラリを使用する方法の例を示します。
リーダー選挙は、高可用性ソフトウェアの中核となる構成要素である分散システムの概念です。これにより、複数の同時プロセスが相互に調整し、単一の「リーダー」プロセスを選択できるようになり、そのプロセスがデータ ストアへの書き込みなどの同期アクションの実行を担当します。
これは、ハードウェアやネットワーク障害に対する冗長性を確保するために複数のプロセスが実行されているが、データの一貫性を確保するためにストレージに同時に書き込むことができない分散データベースやキャッシュなどのシステムで役立ちます。将来のある時点でリーダー プロセスが応答しなくなった場合、残りのプロセスによって新しいリーダーの選挙が開始され、最終的にリーダーとして機能する新しいプロセスが選択されます。
この概念を使用すると、単一のリーダーと複数のスタンバイ レプリカを備えた高可用性ソフトウェアを作成できます。
Kubernetes では、コントローラー ランタイム パッケージはリーダー選挙を使用してコントローラーの高可用性を実現します。コントローラーのデプロイメントでは、リソースの調整は、プロセスがリーダーであり、他のレプリカがスタンバイで待機している場合にのみ発生します。リーダー ポッドが応答しなくなった場合、残りのレプリカは新しいリーダーを選択して後続の調整を実行し、通常の操作を再開します。
このライブラリは、プロセスによって取得できる Kubernetes リース (分散ロック) を使用します。リースは、単一の ID によって一定期間保持されるネイティブ Kubernetes リソースであり、更新オプションも付いています。 以下はドキュメントの仕様例です:
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"
リースは、k8s エコシステムによって 3 つの方法で使用されます:
次に、リーダー選出シナリオでリースを使用する方法を示すサンプル プログラムを作成して、リースの 2 番目の使用例を調べてみましょう。
このコード例では、リーダー選挙とリース操作の詳細を処理するためにリーダー選挙パッケージを使用しています。
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()) }
leaderelection パッケージの優れている点は、リーダー選挙を処理するためのコールバック ベースのフレームワークを提供していることです。こうすることで、特定の状態の変化にきめ細かく対応し、新しいリーダーが選出されたときにリソースを適切に解放できます。これらのコールバックを個別の goroutine で実行することにより、パッケージは Go の強力な同時実行サポートを利用して、マシン リソースを効率的に利用します。
これをテストするには、kind を使用してテスト クラスターを起動しましょう。
$ kind create cluster
サンプル コードを main.go にコピーし、新しいモジュールを作成し (go mod init leaderelectiontest)、整理して (go mod tiny) 依存関係をインストールします。 go run main.go を実行すると、次のような出力が表示されるはずです:
$ 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!
これは、この記事を書いている時点で私のコンピュータ上で実行されていたプロセスの PID にすぎないため、正確なリーダー ID は例 (138) のものとは異なります。
テスト クラスターで作成されたリースは次のとおりです:
$ 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:
「ホルダー ID」がプロセスの PID 138 と同じであることを確認してください。
次に、別のターミナルを開いて、同じ main.go ファイルを別のプロセスで実行しましょう:
$ 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
この 2 番目のプロセスは、最初のプロセスが応答しなくなるまで永遠に待機します。最初のプロセスを強制終了して、15 秒ほど待ちます。最初のプロセスがリースの請求を更新していないため、.spec.renewTime フィールドは更新されなくなります。リースの更新時期がその期間よりも古いため、最終的には 2 番目のプロセスで新しいリーダーの選挙がトリガーされます。このプロセスは現在実行されている唯一のプロセスであるため、それ自体が新しいリーダーとして選出されます。
the leader is 604 I0716 11:48:51.904732 604 leaderelection.go:260] successfully acquired lease default/my-lock I am the leader!
最初のリーダーが終了した後も複数のプロセスが実行中の場合、リースを取得する最初のプロセスが新しいリーダーとなり、残りは引き続きスタンバイ状態になります。
このパッケージは、「1 つのクライアントだけがリーダー (別名フェンシング) として機能することを保証しない」という点で、絶対確実というわけではありません。たとえば、リーダーが一時停止され、そのリースが期限切れになると、別のスタンバイ レプリカがリースを取得します。その後、元のリーダーが実行を再開すると、自分がまだリーダーであると考え、新しく選出されたリーダーと一緒に作業を続行します。このようにして、最終的に 2 つのリーダーを同時に実行することができます。
これを修正するには、サーバーへの各リクエストにリースを参照するフェンシング トークンを含める必要があります。フェンシング トークンは事実上、リースの所有者が変わるたびに 1 ずつ増加する整数です。したがって、古いフェンシング トークンを持つクライアントは、そのリクエストをサーバーによって拒否されます。このシナリオでは、古いリーダーがスリープから目覚め、新しいリーダーがすでにフェンシング トークンをインクリメントしている場合、古いリーダーのリクエストはすべて拒否されます。これは、サーバーがサーバーから認識したものよりも古い (小さい) トークンを送信しているためです。新しいリーダー。
各リースに対応するフェンシング トークンを考慮してコア API サーバーを変更しない限り、Kubernetes にフェンシングを実装することは困難です。ただし、複数のリーダー コントローラーを持つリスクは、k8s API サーバー自体によってある程度軽減されます。古いオブジェクトへの更新は拒否されるため、オブジェクトの最新バージョンを持つコントローラーのみがそのオブジェクトを変更できます。したがって、複数のコントローラー リーダーを実行できますが、コントローラーが別のリーダーによる変更を見逃しても、リソースの状態が古いバージョンに戻ることはありません。代わりに、両方のリーダーが最新バージョンで動作していることを確認するためにリソースの内部状態を更新する必要があるため、調整時間が増加します。
ただし、このパッケージを使用して、別のデータ ストアを使用してリーダー選出を実装している場合、これは注意すべき重要な注意事項です。
リーダーの選出と分散ロックは、分散システムの重要な構成要素です。フォールトトレラントで可用性の高いアプリケーションを構築しようとする場合、このようなツールを自由に使えることが重要です。 Kubernetes 標準ライブラリは、そのプリミティブを中心に実戦テストされたラッパーを提供し、アプリケーション開発者が独自のアプリケーションにリーダー選挙を簡単に組み込めるようにします。
この特定のライブラリを使用すると、アプリケーションを Kubernetes 上にデプロイできるように制限されますが、最近は世界がそのようになっているようです。実際にそれが問題である場合は、もちろん、ライブラリをフォークして、ACID 準拠の高可用性データストアに対して動作するように変更することができます。
今後も k8s ソースの詳細をご覧ください!
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3