"일꾼이 일을 잘하려면 먼저 도구를 갈고 닦아야 한다." - 공자, 『논어』.
첫 장 > 프로그램 작성 > Go 앱에 Kubernetes 기반 리더 선택을 추가하는 방법

Go 앱에 Kubernetes 기반 리더 선택을 추가하는 방법

2024-07-30에 게시됨
검색:806

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

원래 블로그에 게시됨

Kubernetes 표준 라이브러리는 생태계의 일부인 다양한 하위 패키지에 숨겨져 있는 보석으로 가득 차 있습니다. 최근 k8s.io/client-go/tools/leaderelection을 발견한 사례 중 하나는 Kubernetes 클러스터 내에서 실행되는 모든 애플리케이션에 리더 선택 프로토콜을 추가하는 데 사용할 수 있습니다. 이 문서에서는 리더 선택이 무엇인지, 이 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 생태계에서 세 가지 방식으로 사용됩니다.

  1. 노드 하트비트: 모든 노드에는 해당 임대 리소스가 있으며 지속적으로 renewTime 필드를 업데이트합니다. 임대의 renewTime이 한동안 업데이트되지 않은 경우 노드는 사용할 수 없는 것으로 오염되고 더 이상 포드가 예약되지 않습니다.
  2. 리더 선택: 이 경우 리더가 임대의holderIdentity를 업데이트하도록 하여 여러 프로세스를 조정하는 데 임대가 사용됩니다. 다른 ID를 가진 대기 복제본이 임대가 만료되기를 기다리고 있습니다. 임대가 만료되고 리더가 갱신하지 않으면 남은 복제본이 해당holderIdentity를 자신의 것으로 업데이트하여 임대의 소유권을 얻으려고 시도하는 새로운 선택이 발생합니다. Kubernetes API 서버는 오래된 객체에 대한 업데이트를 허용하지 않으므로 단일 대기 노드만 임대를 성공적으로 업데이트할 수 있으며, 이 시점에서는 임대가 새로운 리더로 계속 실행됩니다.
  3. API 서버 ID: v1.26부터 베타 기능으로 각 kube-apiserver 복제본은 전용 임대를 생성하여 ID를 게시합니다. 이는 비교적 슬림하고 새로운 기능이므로 실행 중인 API 서버 수 외에 Lease 개체에서 파생될 수 있는 내용이 많지 않습니다. 그러나 이는 향후 k8s 버전에서 이러한 임대에 더 많은 메타데이터를 추가할 여지를 남겨둡니다.

이제 리더 선택 시나리오에서 임대를 사용하는 방법을 보여주는 샘플 프로그램을 작성하여 임대의 두 번째 사용 사례를 살펴보겠습니다.

예제 프로그램

이 코드 예제에서는 리더 선택 패키지를 사용하여 리더 선택 및 임대 조작 세부 사항을 처리합니다.

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!

정확한 리더 신원은 예시(138)에 있는 것과 다를 수 있습니다. 이는 글을 쓰는 당시 내 컴퓨터에서 실행 중이던 프로세스의 PID일 뿐이기 때문입니다.

테스트 클러스터에서 생성된 임대는 다음과 같습니다.

$ 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:                    

"Holder Identity"가 프로세스의 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

이 두 번째 프로세스는 첫 번째 프로세스가 응답하지 않을 때까지 영원히 기다립니다. 첫 번째 프로세스를 종료하고 약 15초 정도 기다리겠습니다. 이제 첫 번째 프로세스가 임대에 대한 청구를 갱신하지 않으므로 .spec.renewTime 필드는 더 이상 업데이트되지 않습니다. 임대의 갱신 시간이 해당 기간보다 오래되었기 때문에 이로 인해 결국 두 번째 프로세스가 새로운 리더 선택을 트리거하게 됩니다. 이 프로세스는 현재 실행 중인 유일한 프로세스이므로 자체적으로 새로운 리더로 선출됩니다.

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

초기 리더가 종료된 후에도 여러 프로세스가 계속 실행 중인 경우 임대를 획득하는 첫 번째 프로세스가 새 리더가 되고 나머지는 계속 대기 상태가 됩니다.

단일 리더가 보장되지 않음

이 패키지는 "단 하나의 클라이언트만 리더(일명 펜싱)로 작동함을 보장하지 않습니다"라는 점에서 완벽하지는 않습니다. 예를 들어, 리더가 일시 중지되고 해당 임대가 만료되면 다른 대기 복제본이 임대를 획득합니다. 그러다가 원래 리더가 실행을 재개하면 자신이 여전히 리더라고 생각하고 새로 선출된 리더와 함께 계속해서 작업을 수행하게 됩니다. 이런 방식으로 두 명의 리더가 동시에 실행될 수 있습니다.

이 문제를 해결하려면 임대를 참조하는 펜싱 토큰이 서버에 대한 각 요청에 포함되어야 합니다. 펜싱 토큰은 사실상 임대 계약이 바뀔 때마다 1씩 증가하는 정수입니다. 따라서 이전 펜싱 토큰을 사용하는 클라이언트의 요청은 서버에서 거부됩니다. 이 시나리오에서 이전 리더가 절전 모드에서 깨어나고 새 리더가 이미 펜싱 토큰을 증가시킨 경우 이전 리더의 모든 요청은 서버가 본 것보다 오래된(더 작은) 토큰을 보내기 때문에 거부됩니다. 새로운 리더.

Kubernetes에서 펜싱을 구현하는 것은 각 임대에 대한 해당 펜싱 토큰을 설명하기 위해 핵심 API 서버를 수정하지 않으면 어려울 것입니다. 그러나 여러 리더 컨트롤러를 갖는 위험은 k8s API 서버 자체에 의해 다소 완화됩니다. 오래된 개체에 대한 업데이트는 거부되므로 최신 버전의 개체가 있는 컨트롤러만 이를 수정할 수 있습니다. 따라서 여러 컨트롤러 리더를 실행할 수 있지만 컨트롤러가 다른 리더의 변경 사항을 놓치면 리소스 상태가 이전 버전으로 되돌아가지 않습니다. 대신, 두 리더 모두 최신 버전에 따라 작업을 수행할 수 있도록 내부 리소스 상태를 새로 고쳐야 하므로 조정 시간이 늘어납니다.

그래도 이 패키지를 사용하여 다른 데이터 저장소를 사용하여 리더 선택을 구현하는 경우 알아야 할 중요한 주의 사항입니다.

결론

리더 선택과 분산 잠금은 분산 시스템의 중요한 구성 요소입니다. 내결함성 및 고가용성 애플리케이션을 구축하려고 할 때 이와 같은 도구를 마음대로 사용하는 것이 중요합니다. Kubernetes 표준 라이브러리는 애플리케이션 개발자가 자신의 애플리케이션에 리더 선택을 쉽게 구축할 수 있도록 기본 요소에 대한 전투 테스트를 거친 래퍼를 제공합니다.

이 특정 라이브러리를 사용하면 애플리케이션을 Kubernetes에 배포하는 것으로 제한되지만 최근에는 세상이 그렇게 돌아가고 있는 것 같습니다. 실제로 이것이 문제가 되는 경우에는 물론 라이브러리를 포크하고 ACID 호환 및 고가용성 데이터 저장소에 대해 작동하도록 수정할 수 있습니다.

더 많은 k8s 소스 심층 분석을 기대해 주세요!

릴리스 선언문 이 기사는 https://dev.to/sklarsa/how-to-add-kubernetes-powered-leader-election-to-your-go-apps-57jh?1에서 복제됩니다. 침해 사항이 있는 경우, Study_golang에 문의하십시오. @163.com 삭제
최신 튜토리얼 더>

부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.

Copyright© 2022 湘ICP备2022001581号-3