시작하며...
Kubernetes는 기본로 제공하는 Built-in Object(ex: Deployment, Service) 외에 따로 정의한 Custom Resource를 통해 클러스터의 기능을 확장시킬 수 있다. 예를 들어 Istio의 VirtualService나 DestinationRule, ArgoCD의 Project, Application 등을 예로 들 수 있다. 물론 Custom Resource만을 정의한다고 해서 새로운 기능을 추가할 수는 없으며, 'Custom Controller' 또는 'Operator'를 통해 built-in object와 custom resource에 대한 동작을 구현해야 한다. 참고로 k8s는 Custom Controller의 쉬운 구현을 위해 client-go 라이브러리를 제공하고 있다.
Reconcilliation
Kubernetes에서 Resource를 생성할 때, 일반적으로 관련 Manifest 파일을 작성하여 이를 API 서버로 전달하는 선언적(Declarative) 방식으로 동작한다. 이때 Manifest 파일에 우리가 정의한 Resource의 Spec(Desired State)을 명시하며, 클러스터 안에서 해당 Resource가 존재하는 실제 상태(Actual State)를 Status라고 한다.
Controller는 자신이 담당하는 Resource의 Actual State와 Desired State가 비교하여 불일치하는 경우, 추가하거나 삭제하는 동기화 작업을 수행하는데 이를 'Reconcilliation'이라고 한다. 예를 들어 ReplicaSet의 replca 갯수가 부족한 경우 새로운 Pod을 생성하고, 반대의 경우 기존 Pod을 삭제한다. 이를 아래와 같이 간단한 그림으로 나타낼 수 있다.
아래는 k8s controller의 Reconcilliation을 수행하는 기본 코드(실제는 예외처리, 로그 등의 부수적인 코드가 추가됨)로, Item을 queue에서 꺼내 sync 작업을 무한 반복한다.
func (c *Controller) runWorker() {
for c.processNextWorkItem() {
}
}
func (c *Controller) processNextWorkItem() bool {
item := c.workqueue.Get()
err := c.syncHandler(item)
c.handleErr(err, item)
}
Custom Controller의 구성
Custom Controller는 client-go에서 제공하는 컴포넌트와 우리가 Custom Controller에서 구현해야하는 컴포넌트 및 Function들로 구성된다. 각 Component에 대한 설명은 다음과 같다.
client-go에서 제공하는 component
Reflector
지정한 리소스 형식(kind)에 대해 API Server에 List/Watch를 수행한다. watch API를 통해 새로운 리소스 인스턴스가 발생한 것을 알게되면, list API를 호출하여 해당 object를 가져와 Delta FIFO 큐에 추가한다. 참고로 Delta FIFO 큐는 Object의 전체 정보가 아닌 Delta만을 저장하는 자료구조이다.
Informer
Informer는 Delta FIFO 큐로부터 Object를 꺼내와 Indexer를 통해 저장하고, 해당 Object의 이벤트 타입(create, update, delete)에 맞는 핸들러를 호출한다. 각 이벤트 핸들러는 Custom Controller에서 직접 구현한다.
Indexer
Indexer는 object에 대해 인덱싱 작업 수행 후, Thread Safe한 저장소에 해당 Index값(key)과 함께 저장한다. key는 일반적으로 해당 Object의 namespace와 name을 조합하여 생성한다.
custom controller에서 구현해야하는 component
Informer reference
Custom Controller에서 작업할 리소스 Object를 취급하는 Informer 인스턴스의 레퍼런스. 기본 리소스 타입에 대한 Informer는 client-go에서 제공하지만 Custom Resource의 경우, 아래와 같이 별도로 정의해야 한다.
type FooInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.FooLister
}
type fooInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
Indexer reference
작업할 리소스 Ojbect를 취급하는 Indexer 인스턴스의 레퍼런스, 이를 통해 원하는 object를 가져올 수 있다. client-go는 informer, indexer를 생성할 수 있는 function을 제공한다.
indexer, informer := cache.NewIndexerInformer(podListWatcher, &v1.Pod{}, 0, cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err == nil {
queue.Add(key)
}
},
UpdateFunc: func(old interface{}, new interface{}) {
key, err := cache.MetaNamespaceKeyFunc(new)
if err == nil {
queue.Add(key)
}
},
DeleteFunc: func(obj interface{}) {
// IndexerInformer uses a delta queue, therefore for deletes we have to use this
// key function.
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
if err == nil {
queue.Add(key)
}
},
}, cache.Indexers{})
Resource Event Handlers
Informer로부터 호출되는 callback function들로 informer로부터 object를 전달하는 기능을 수행한다. 보통은 object의 key를 work queue에 넣는 패턴으로 구현한다.
Work queue
object의 전달과 처리 과정을 분리하기 위한 큐로 Resource Event Handler가 object의 key를 추가(enqueue)하여 전달이 이루어진다.
Process Item
Custom Controller에서 대상 리소스에 대한 실제 Reconcilliation 작업을 수행하는 Function을 정의한다. Work Queue로부터 Object의 Key를 전달받아 Indexer를 통해 매칭되는 Object를 가져온다. 그리고 해당 Object를 Deep Copy(원본 Object에 바로 작업할 경우, 다른 Controller에 영향을 줄 수 있다)한 뒤, Acutal State와 Desired State를 비교하여 우리가 원하는 로직을 추가한다.
마치며...
client-go에서 필요한 컴포넌트를 제공받더라도, 직접 작성해야 할 Code의 양이 상당하다. 보다 쉬운 operator 구현을 원한다면 kubebuilder 등의 k8s operator SDK 사용을 권장한다. 이 포스트는 controller가 어떤 구조로 이루어져 있는지 이해하는 목적으로만 참고하자.
참고
sample-controller - kubernetes github
Writing Kube Controllers for Everyone - Maciej Szulik, Red Hat (Beginner Skill Level)
Writing Kubernetes Custom Controllers
'Kubernetes > Architecture' 카테고리의 다른 글
Kubernetes와 TLS (0) | 2021.04.15 |
---|---|
Pod Lifecycle (0) | 2021.03.07 |
Calico Components (0) | 2021.02.11 |
Linux Namespaces (0) | 2021.02.02 |
CNI - Spec (0) | 2021.01.24 |