원래 블로그에 게시됨
Kubernetes 표준 라이브러리는 생태계의 일부인 다양한 하위 패키지에 숨겨져 있는 보석으로 가득 차 있습니다. 최근 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 생태계에서 세 가지 방식으로 사용됩니다.
이제 리더 선택 시나리오에서 Lease를 어떻게 사용할 수 있는지 보여주는 샘플 프로그램을 작성하여 Lease의 두 번째 사용 사례를 살펴보겠습니다.
이 코드 예제에서는 리더 선택 패키지를 사용하여 리더 선택 및 임대 조작 세부 사항을 처리합니다.
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()) }
리더 선택 패키지의 좋은 점은 리더 선택을 처리하기 위한 콜백 기반 프레임워크를 제공한다는 것입니다. 이런 방식으로 특정 상태 변경에 대해 세부적인 방식으로 조치를 취하고 새 리더가 선출되면 리소스를 적절하게 해제할 수 있습니다. 별도의 고루틴에서 이러한 콜백을 실행함으로써 패키지는 Go의 강력한 동시성 지원을 활용하여 머신 리소스를 효율적으로 활용합니다.
이를 테스트하려면 종류를 사용하여 테스트 클러스터를 가동해 보겠습니다.
$ kind create cluster
샘플 코드를 main.go에 복사하고, 새 모듈을 생성하고(go mod init Leaderelectiontest) 정리하여(go mod tidy) 종속성을 설치합니다. 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!
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>
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
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!
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.
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.
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!
위 내용은 Go 앱에 Kubernetes 기반 리더 선택을 추가하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!