원래 내 블로그에 게시됨
기본적으로 Kubebuilder 및 컨트롤러 런타임을 사용하여 구축된 연산자는 한 번에 하나의 조정 요청을 처리합니다. 운영자 개발자가 애플리케이션의 논리를 추론하고 디버깅하는 것이 더 쉽기 때문에 이는 합리적인 설정입니다. 또한 컨트롤러에서 ectd 및 API 서버와 같은 핵심 Kubernetes 리소스까지의 처리량을 제한합니다.
그러나 작업 대기열이 백업되기 시작하고 대기열에 처리 대기 중인 요청으로 인해 평균 조정 시간이 증가한다면 어떻게 될까요? 다행스럽게도 컨트롤러 런타임 컨트롤러 구조체에는 MaxConcurrentReconciles 필드가 포함되어 있습니다(이전에 Kubebuilder 팁 기사에서 언급한 대로). 이 옵션을 사용하면 단일 컨트롤러에서 실행되는 동시 조정 루프 수를 설정할 수 있습니다. 따라서 1보다 큰 값을 사용하면 여러 Kubernetes 리소스를 동시에 조정할 수 있습니다.
운영 초기에 제가 가졌던 한 가지 질문은 동일한 리소스가 2개 이상의 고루틴에 의해 동시에 조정되지 않는다는 것을 어떻게 보장할 수 있느냐는 것이었습니다. MaxConcurrentReconciles를 1보다 높게 설정하면 조정 루프 내부의 개체 상태가 외부 소스의 부작용(다른 스레드에서 실행되는 조정 루프)을 통해 변경될 수 있으므로 모든 종류의 경쟁 조건과 바람직하지 않은 동작이 발생할 수 있습니다. .
저는 이 문제에 대해 잠시 생각해 보았고 고루틴이 지정된 리소스(네임스페이스/이름을 기반으로)에 대한 잠금을 획득할 수 있도록 하는 sync.Map 기반 접근 방식을 구현하기도 했습니다.
최근에 (k8s 슬랙 채널에서) 컨트롤러 작업 대기열에 이 기능이 이미 포함되어 있다는 사실을 알게 되었기 때문에 이 모든 노력은 헛된 것으로 밝혀졌습니다! 구현이 더 간단하긴 하지만
이것은 k8s 컨트롤러의 작업 대기열이 고유 리소스가 순차적으로 조정되도록 보장하는 방법에 대한 간단한 이야기입니다. 따라서 MaxConcurrentReconciles가 1보다 높게 설정되더라도 특정 리소스에 대해 한 번에 하나의 조정 기능만 작동한다고 확신할 수 있습니다.
Controller-runtime은 client-go/util/workqueue 라이브러리를 사용하여 기본 조정 대기열을 구현합니다. 패키지의 doc.go 파일에는 작업 대기열이 다음 속성을 지원한다는 설명이 있습니다.
잠깐만요... 제 답은 바로 여기 두 번째 글머리 기호인 "인색함" 속성에 있습니다! 이 문서에 따르면 대기열은 코드 한 줄도 작성하지 않고도 자동으로 이 동시성 문제를 처리합니다. 구현을 살펴보겠습니다.
workqueue 구조체에는 Add, Get, Done의 3가지 주요 메소드가 있습니다. 컨트롤러 내부에서 정보 제공자는 조정 요청(일반 k8s 리소스의 네임스페이스 이름)을 작업 대기열에 추가합니다. 별도의 고루틴에서 실행되는 조정 루프는 대기열에서 다음 요청을 가져옵니다(비어 있는 경우 차단). 루프는 컨트롤러에 작성된 모든 사용자 지정 논리를 수행한 다음 컨트롤러는 대기열에서 Done을 호출하여 조정 요청을 인수로 전달합니다. 그러면 프로세스가 다시 시작되고 조정 루프가 Get을 호출하여 다음 작업 항목을 검색합니다.
이는 작업자가 대기열에서 항목을 꺼내어 처리한 다음 처리가 완료되었으며 대기열에서 항목을 제거해도 안전함을 나타내는 "확인"을 메시지 브로커로 다시 보내는 RabbitMQ의 메시지 처리와 유사합니다. 대기열.
그래도 QuestDB Cloud의 인프라를 구동하는 프로덕션 환경의 운영자가 있고 작업 대기열이 광고된 대로 작동하는지 확인하고 싶었습니다. 그래서 A는 동작을 검증하기 위해 빠른 테스트를 작성했습니다.
다음은 "인색함" 속성을 검증하는 간단한 테스트입니다.
package main_test import ( "testing" "github.com/stretchr/testify/assert" "k8s.io/client-go/util/workqueue" ) func TestWorkqueueStingyProperty(t *testing.T) { type Request int // Create a new workqueue and add a request wq := workqueue.New() wq.Add(Request(1)) assert.Equal(t, wq.Len(), 1) // Subsequent adds of an identical object // should still result in a single queued one wq.Add(Request(1)) wq.Add(Request(1)) assert.Equal(t, wq.Len(), 1) // Getting the object should remove it from the queue // At this point, the controller is processing the request obj, _ := wq.Get() req := obj.(Request) assert.Equal(t, wq.Len(), 0) // But re-adding an identical request before it is marked as "Done" // should be a no-op, since we don't want to process it simultaneously // with the first one wq.Add(Request(1)) assert.Equal(t, wq.Len(), 0) // Once the original request is marked as Done, the second // instance of the object will be now available for processing wq.Done(req) assert.Equal(t, wq.Len(), 1) // And since it is available for processing, it will be // returned by a Get call wq.Get() assert.Equal(t, wq.Len(), 0) }
작업 대기열은 내부적으로 뮤텍스를 사용하므로 이 동작은 스레드로부터 안전합니다. 따라서 대기열을 깨기 위해 여러 고루틴을 동시에 고속으로 읽고 쓰는 테스트를 더 작성하더라도 작업 대기열의 실제 동작은 단일 스레드 테스트의 동작과 동일할 것입니다.
Kubernetes 표준 라이브러리에는 이와 같은 작은 보석이 많이 숨겨져 있으며 그 중 일부는 그다지 눈에 띄지 않는 위치(예: go 클라이언트 패키지에 있는 컨트롤러 런타임 작업 대기열)에 있습니다. 이 발견과 내가 과거에 했던 다른 유사한 발견에도 불구하고, 나는 여전히 이러한 문제를 해결하려는 이전의 시도가 완전한 시간 낭비가 아니라고 생각합니다. 이는 분산 시스템 컴퓨팅의 근본적인 문제에 대해 비판적으로 생각하도록 강요하고 내부에서 무슨 일이 일어나고 있는지 더 많이 이해하도록 도와줍니다. 그래서 "Kubernetes가 해냈다"는 사실을 발견했을 때쯤에는 코드베이스를 단순화하고 불필요한 단위 테스트를 일부 제거할 수 있다는 사실에 안도감을 느꼈습니다.
위 내용은 Kubernetes Operator는 동시성을 어떻게 처리합니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!