-
Kubernetes Controller 작성의 핵심 개념Kubernetes 2025. 3. 2. 22:43728x90
Kubernetes 컨트롤러는 특정 리소스의 원하는 상태(Desired State) 와 현재 상태(Current State) 를 비교하고,
이를 동기화(Sync)하는 역할을 합니다.1️⃣ Kubernetes 컨트롤러의 기본 개념
컨트롤러는 반복적으로 현재 상태를 확인하고, 원하는 상태로 변경하는 작업을 수행하는 과정입니다.
기본 컨트롤러 루프
for { desired := getDesiredState() // 원하는 상태 가져오기 current := getCurrentState() // 현재 상태 가져오기 makeChanges(desired, current) // 현재 상태를 원하는 상태로 변경 }- Kubernetes 컨트롤러는 실시간으로 이벤트를 감지하여 상태를 조정하는 역할을 합니다.
- Informer를 활용하여 최적화할 수 있습니다.
2️⃣ 컨트롤러 작성 시 주요 원칙
1. 한 번에 하나의 리소스만 처리
- 여러 개의 워커(worker)가 동시에 실행되지만, 동일한 리소스를 중복 처리하지 않도록 보장해야 합니다.
- WorkQueue를 사용하면 한 번에 하나의 리소스만 처리할 수 있습니다.
queue workqueue.RateLimitingInterface2. 여러 리소스를 감지하되, 최적화하여 처리
- 컨트롤러는 여러 리소스(Pod, Deployment 등)의 변경을 감지해야 합니다.
- 하지만 직접적으로 모든 리소스를 감지하는 것이 아니라, 특정 연관 관계에 따라 큐에 추가하는 방식으로 최적화해야 합니다.
- 예: ReplicaSet 컨트롤러는 Pod 삭제 이벤트를 감지하지만, 삭제된 Pod의 ReplicaSet만 큐에 추가하여 처리.
3. 이벤트 순서가 보장되지 않음
- 여러 리소스에서 변경이 발생할 때, 이벤트 순서는 보장되지 않습니다.
- 따라서 컨트롤러는 항상 현재 상태를 조회한 후, 원하는 상태와 비교하여 동작해야 합니다.
4. Edge-driven이 아닌 Level-driven 방식 사용
- 컨트롤러는 특정 이벤트(예: "true → false" 변경)만 감지하는 것이 아니라, 현재 상태를 지속적으로 확인하는 방식(level-driven)으로 동작해야 합니다.
- API 서버가 중간 이벤트를 놓치더라도, 현재 상태만 확인하면 항상 일관된 동작을 수행할 수 있도록 구현해야 합니다.
3️⃣ SharedInformer 사용 권장
- SharedInformer를 사용하여 이벤트(추가, 업데이트, 삭제) 감지
- 캐시를 공유하여 API 서버 부하 감소
- factory.go의 Informer Factory를 사용하면 캐시를 여러 컨트롤러가 공유할 수 있음.
deploymentInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: controller.enqueueFoo, UpdateFunc: func(old, new interface{}) { controller.enqueueFoo(new) }, DeleteFunc: controller.handleDeletedFoo, })✅ SharedInformer를 사용하면:
- API 서버의 부하를 줄일 수 있음 (캐시 공유).
- 여러 컨트롤러가 같은 데이터를 사용할 수 있음.
- 기존 Reflector, DeltaFIFO 방식보다 성능이 개선됨.
4️⃣ 원본 객체 수정 금지
- Kubernetes 캐시는 여러 컨트롤러가 공유합니다.
- 따라서 원본 객체를 직접 수정하면 다른 컨트롤러가 비정상적으로 동작할 수 있음.
- 항상 Deep Copy를 사용하여 수정해야 함.
fooCopy := foo.DeepCopy() fooCopy.Status.AvailableReplicas = deployment.Status.AvailableReplicas _, err := c.sampleclientset.SamplecontrollerV1alpha1().Foos(foo.Namespace).UpdateStatus(ctx, fooCopy, metav1.UpdateOptions{})5️⃣ WaitForCacheSync를 활용하여 캐시 동기화 보장
- 컨트롤러가 시작되기 전에 캐시가 최신 상태인지 확인해야 함.
- WaitForCacheSync()를 사용하여, 모든 필요한 리소스가 동기화될 때까지 기다릴 수 있음.
if !cache.WaitForCacheSync(stopCh, c.podStoreSynced) { return }✅ 이렇게 하면 컨트롤러가 구식 데이터를 사용하여 잘못된 결정을 내리는 것을 방지할 수 있음.
6️⃣ 다른 컨트롤러 및 프로세스와의 상호작용 고려
- Kubernetes는 여러 컨트롤러가 동작하는 분산 시스템이므로, 자신이 수정하지 않은 리소스도 변경될 수 있음을 항상 고려해야 함.
- 예를 들어, Garbage Collector가 리소스를 삭제할 수도 있음.
- Owner References를 설정하여 리소스를 자동 정리하도록 만들 수도 있음.
7️⃣ 컨트롤러에서 에러 처리 및 재시도 (Requeue)
- 실패한 작업을 다시 시도할 수 있도록 Rate-Limited Requeue 메커니즘을 사용해야 합니다.
- WorkQueue를 활용하여 실패한 작업을 자동으로 재시도.
// 실패한 작업을 다시 큐에 추가하여 재시도 c.queue.AddRateLimited(key)- 에러가 발생하면 상위로 에러를 전파해야 하며, 정상 처리된 경우는 큐에서 제거해야 합니다.
err := c.syncHandler(key.(string)) if err == nil { c.queue.Forget(key) // 정상 처리된 경우 큐에서 제거 return true }✅ 즉, 컨트롤러는 작업이 실패하면 백오프(지수적 증가) 방식으로 다시 시도해야 함.
8️⃣ ObservedGeneration을 사용하여 컨트롤러가 처리한 상태 표시
- ObservedGeneration 필드를 사용하여 컨트롤러가 리소스를 처리했음을 API 서버에 기록할 수 있음.
if foo.Status.ObservedGeneration != foo.Generation { foo.Status.ObservedGeneration = foo.Generation }✅ 이렇게 하면 사용자는 컨트롤러가 최신 변경 사항을 반영했는지 쉽게 확인할 수 있음.
9️⃣ Owner References 활용하여 자식 리소스 자동 삭제
- ReplicaSet이 Pod를 관리하듯이, OwnerReferences를 설정하면 부모 리소스 삭제 시 자식 리소스도 자동 삭제됨.
ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(foo, samplev1alpha1.SchemeGroupVersion.WithKind("Foo")), }, }✅ 이렇게 하면 부모 리소스가 삭제될 때 자동으로 관련된 리소스도 삭제됨.
컨트롤러의 전체적인 구조
type Controller struct { podLister cache.StoreToPodLister queue workqueue.RateLimitingInterface } func (c *Controller) Run(threadiness int, stopCh chan struct{}) { defer utilruntime.HandleCrash() defer c.queue.ShutDown() // 캐시 동기화 if !framework.WaitForCacheSync(stopCh, c.podStoreSynced) { return } // 여러 개의 worker 실행 for i := 0; i < threadiness; i++ { go wait.Until(c.runWorker, time.Second, stopCh) } <-stopCh }✅ 즉, 컨트롤러는 WorkQueue를 사용하여 리소스를 감시하고, 변경 사항을 큐에 추가한 후, worker가 이를 하나씩 처리하는 구조로 동작함.
결론
컨트롤러 작성 시 핵심 원칙
- WorkQueue를 사용하여 동일한 리소스를 중복 처리하지 않도록 보장.
- SharedInformer를 사용하여 API 서버 부하를 최소화.
- 원본 객체를 수정하지 않고, 항상 Deep Copy를 사용.
- 캐시 동기화 (WaitForCacheSync) 후 컨트롤러 실행.
- 에러 발생 시 작업을 Requeue하여 재시도.
- OwnerReferences를 설정하여 관련 리소스를 자동 정리.
- ObservedGeneration을 설정하여 컨트롤러가 최신 변경 사항을 반영했는지 확인.
이러한 원칙을 따르면 Kubernetes 컨트롤러가 안정적으로 동작하며, 성능이 최적화된 구조로 운영될 수 있음.
728x90'Kubernetes' 카테고리의 다른 글
istio에서 north-south traffic의 의미 (0) 2025.03.12 서비스 접속 테스트용 curl 이미지 설치 및 사용 (0) 2025.03.10 CRD와 CR만 정의하는 경우 활용 사례 (0) 2025.03.02 client-go under the hood (0) 2025.03.02 client-go의 Reflector와 Watcher의 차이? (0) 2025.03.02