Controller, 넌 도대체 뭐냐?
컨트롤러가 뭐냐고 물어보면 뭐라고 할까요? Pod를 관리하는 컴포넌트? 의도한 상태를 유지할 수 있도록 도와주는 컨트롤 루프? 막상 제대로 답하려니 너무 막연하게 표현하게 되는 컨트롤러... 이제는 제대로 답할 수 있도록 제대로 한번 파보도록 하겠습니다.
Controller, 넌 도대체 뭐냐?
Background
쿠버네티스 설계 원칙 : 선언적 API
쿠버네티스의 동작을 이해하기 위해서는 선언적 API의 의미를 이해해야합니다. 선언적 API라는 개념과 대비되는 개념으로 명령적 API이 있는데, 명령적 API는 말 그대로 어떠한 행위를 수행하도록 명령을 하는 것입니다. 그리고 선언적 API는 특정 상태이길 원하는 상태를 정의함으로써, 원하는 상태가 되도록 지속적으로 맞춰가게 유도합니다. 쿠버네티스는 이러한 선언적 API를 사용함으로써 관리자가 직접 모니터링하며 원하는 상태를 맞춰주기 위해 명령을 내릴 필요 없다는 장점을 얻을 수 있습니다.
출처 : https://chacha95.github.io/2020-08-29-Docker_Kubernetes5/
쿠버네티스 오브젝트 vs 쿠버네티스 컨트롤러
쿠버네티스는 크게 오브젝트와 오브젝트를 관리하는 컨트롤러로 구분이 됩니다. 그러니 컨트롤러를 제대로 알려면 오브젝트를 제대로 알아야겠죠? 문서에 따르면 쿠버네티스는 클러스터의 상태를 나타내기 위해서 오브젝트를 이용한다고 서술하고 있습니다.
- 어떤 컨테이너화된 애플리케이션이 동작 중인지 (그리고 어느 노드에서 동작 중인지) ⇒ status
- 그 애플리케이션이 이용할 수 있는 리소스
💡 그 애플리케이션이 이용할 수 있는 리소스... 도대체 무슨 말일까?
- 그 애플리케이션이 어떻게 재구동 정책, 업그레이드, 그리고 내고장성과 같은 것에 동작해야 하는지에 대한 정책 ⇒ spec
오브젝트는 spec 이라는 필드에 desired status를 담게 됩니다. 즉, 쿠버네티스는 오브젝트의 생성을 보장하기 위해 작동하고, 오브젝트가 생성되면 spec 필드를 참고하여 desired status와 status(현재 상태)를 비교하여 status가 desired status가 되도록 지속적으로 관리합니다.
그런데 desired status가 되기 위한 관리를 누가 하고 있을까요!? 누군가가 주기적으로 desired status와 status를 비교하며 적절한 작업을 수행해주고 있는 것일까? 맞습니다. 바로 그 역할을 수행하는 것이 컨트롤러입니다.
쿠버네티스 오브젝트 필드
- apiVersion : 쿠버네티스 API 버전 명세
- kind : 오브젝트 종류
- metadata : name, UID, namespace
- spec : 원하는 상태
# application/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2 # tells deployment to run 2 pods matching the template
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
kube-apiserver
kube-apiserver는 다음과 같은 특징을 가집니다.
- 오브젝트에 대한 CRUD(Create, Read, Update, Delete) 요청을 받고 수행하는 역할을 수행 (정확히는 kube-apiserver를 통해서만 etcd에 접근하여 오브젝트에 대한 CRUD를 수행)
- 쿠버네티스의 컴포넌트 간의 통신도 kube-apiserver를 거처 이루어짐
- 마스터 노드에 위치한 컨트롤 플레인에 속하는 컴포넌트
쿠버네티스 클러스터 아키텍처
그림을 보면 controller-manager와 kube-apiserver가 통신을 하는 것을 확인할 수 있습니다. controller가 kube-apiserver에 1) 어떤 경우에, 2) 어떤 방식으로 요청을 보내는지 궁금해진다면 당신은 이미 쿠버네티스 덕후입니다.
Controller란?
역할
- current status가 desired status로 될 수 있도록 관리
- kube-apiserver를 통해 클러스터 status를 감시하는 컨트롤 루프
- 공통적인 구조로 Informer, SharedInformer, Workqueue를 가짐
출처 : https://www.reddit.com/r/kubernetes/comments/darsgg/inside_kubernetes_controller_deep_dive/
💡 Deployment Type에 대한 yaml 파일을 apply하게 되면, Deployment Controller와 ReplicaSet Controller가 관리해야하는 리소스별로 분리되어 생성될까 하나로 생성될까...? 예상은 recursive한 구조로...?
종류
- replication controller
- replicaset
- deployment
- daemonset
- statefulset
- horizontal pod autoscaling
- 쿠버네티스 내장 컨트롤러들이 어디서 관리되고 있을까?
func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc { controllers := map[string]InitFunc{} controllers["endpoint"] = startEndpointController controllers["endpointslice"] = startEndpointSliceController controllers["endpointslicemirroring"] = startEndpointSliceMirroringController controllers["replicationcontroller"] = startReplicationController controllers["podgc"] = startPodGCController controllers["resourcequota"] = startResourceQuotaController controllers["namespace"] = startNamespaceController controllers["serviceaccount"] = startServiceAccountController controllers["garbagecollector"] = startGarbageCollectorController controllers["daemonset"] = startDaemonSetController controllers["job"] = startJobController controllers["deployment"] = startDeploymentController controllers["replicaset"] = startReplicaSetController controllers["horizontalpodautoscaling"] = startHPAController controllers["disruption"] = startDisruptionController controllers["statefulset"] = startStatefulSetController controllers["cronjob"] = startCronJobController controllers["csrsigning"] = startCSRSigningController controllers["csrapproving"] = startCSRApprovingController controllers["csrcleaner"] = startCSRCleanerController controllers["ttl"] = startTTLController controllers["bootstrapsigner"] = startBootstrapSignerController controllers["tokencleaner"] = startTokenCleanerController controllers["nodeipam"] = startNodeIpamController controllers["nodelifecycle"] = startNodeLifecycleController if loopMode == IncludeCloudLoops { controllers["service"] = startServiceController controllers["route"] = startRouteController controllers["cloud-node-lifecycle"] = startCloudNodeLifecycleController // TODO: volume controller into the IncludeCloudLoops only set. } controllers["persistentvolume-binder"] = startPersistentVolumeBinderController controllers["attachdetach"] = startAttachDetachController controllers["persistentvolume-expander"] = startVolumeExpandController controllers["clusterrole-aggregation"] = startClusterRoleAggregrationController controllers["pvc-protection"] = startPVCProtectionController controllers["pv-protection"] = startPVProtectionController controllers["ttl-after-finished"] = startTTLAfterFinishedController controllers["root-ca-cert-publisher"] = startRootCACertPublisher controllers["ephemeral-volume"] = startEphemeralVolumeController if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerIdentity) && utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StorageVersionAPI) { controllers["storage-version-gc"] = startStorageVersionGCController } return controllers }
- cmd/kube-controller-manager/app/controllermanager.go
Informer/SharedInformer, Workqueue
Informer
: Object Event(Added, Updated, Deleted...)를 모니터링
매번 컨트롤러가 api-server에 요청해서 Object의 변화를 모니터링하려고 하면 로드가 너무 크다는 문제가 발생합니다.
⇒ 그래서 Informer가 object data를 인-메모리 캐시 형태로 저장
- Listwatcher : 특정 네임 스페이스의 특정 리소스에 대한 list function과 watch function의 결합
⇒ 이를 통해 컨트롤러가 특정 리소스에만 집중할 수 있도록 한다.
- Listwatcher source code
func NewFilteredListWatchFromClient(c Getter, resource string, namespace string, optionsModifier func(options *metav1.ListOptions)) *ListWatch { listFunc := func(options metav1.ListOptions) (runtime.Object, error) { optionsModifier(&options) return c.Get(). Namespace(namespace). Resource(resource). VersionedParams(&options, metav1.ParameterCodec). Do(context.TODO()). Get() } watchFunc := func(options metav1.ListOptions) (watch.Interface, error) { options.Watch = true optionsModifier(&options) return c.Get(). Namespace(namespace). Resource(resource). VersionedParams(&options, metav1.ParameterCodec). Watch(context.TODO()) } return &ListWatch{ListFunc: listFunc, WatchFunc: watchFunc} }
- client-go/tools/cache/listwatch.go
list function은 initial list를 만들기 위해 사용되고, watch function은 리소스에 대한 watch를 수행하기 위해 사용됩니다.
- Resource Event Handler :
- ResyncPeriod
SharedInformer
: 컨트롤러 간에 단일 공유 캐시를 위한 Informer
실제로는 Informer 보다는 Shared Informer를 사용한다고 합니다. 하나의 리소스에 대해 여러 컨트롤러가 모니터링을 수행하는 경우 Shared Informer를 사용해서 부하를 줄일 수 있습니다.
Workqueue
: SharedInformer에 대한 대기 및 재시도 메카니즘을 위한 큐
정리
Read from In-memory-cache. Write to api-server.
Controller Manager
쿠버네티스의 컨트롤러들은 Controller Manager가 관리하고 있습니다. 각각의 쿠버네티스 내장 컨트롤러는 논리적으로 분리되어 있지만, 실제 구현 상에서는 컨트롤러 매니저에 통합되어 하나의 바이너리로 컴파일되어 실행됩니다.
참고자료
'DevOps > Kubernetes' 카테고리의 다른 글
kubernetes - kube-proxy proxy-mode (0) | 2023.05.14 |
---|---|
kops - 설치 방법 (kubernetes in aws) (0) | 2023.05.14 |