Search
Duplicate

Custom Resource & Custom Controller

1. Controller Basic

Kubernetes의 리소스는 원하는 상태(desired state)를 지정하는 매니페스트 파일에 의해 정의됩니다. 리소스가 배포되면 적절한 컨트롤러가 클러스터의 기존 상태가 원하는 상태와 일치하도록 업데이트되도록 합니다. 각 컨트롤러는 지속적으로 API 서버를 통해 클러스터의 공유 상태를 감시합니다. (Control-loop) 배포된 리소스에 대한 정보를 기반으로 컨트롤러는 현재 클러스터 상태를 원하는 상태를 만족하도록 동작합니다.
control-loop 의 기본 동작 원리
for { desired := getDesiredState() current := getCurrentState() makeChanges(desired, current) }
JavaScript
복사

2. Custom Controller

Kubernetes controller는 기본적으로 유저가 ‘원하는’ 상태를 읽어서 현재 상태와 비교해 ‘원하는’ 상태로 클러스터의 싱크를 맞춰주는 역할을 합니다.
Custom Controller는 어느 resource와도 같이 사용될 수 있지만 custom resource와 같이 사용될 때 가장 강력합니다. Custom resource와 custom controller를 같이 사용하는 패턴을 Operator Pattern이라고 하는데 Operator pattern은 custom resource에 대해서 사용자가 원하는 상태를 유지하도록 만들어 줍니다.

3. Operator Pattern

현실 세계에서 관리자(operator)는 자신이 담당하고 있는 공정이나 서비스의 자세한 부분들을 속속히 알고 있고 자신이 알고 있는 지식에 따라서 각 공정이나 서비스가 어떻게 운영되어야하고 관리되야하는지를 명확하게 알고 있는 사람입니다.
Kubernetes operator도 이와 비슷합니다. Operator는 자신의 custom resource들을 관리할 때 application domain을 이용해서 resource들 각각이 어떻게 동작해야하고, 어떻게 배포되야하고, 문제에 대해서 어떻게 반응해야하는지 등에 대해서 정의하고 핸들링할 수 있다. 다시 말하면 operator는 application-specific한 controller라고 볼 수 있습니다.
operator 강점은 application-specific하기 때문에 resource에 대해서 application domain을 이용해서 부가적인 동작을 하도록 만들 수도 있다는 점입니다.

4. Kubernetes Resource, Object

Kubernetes에서는 resource라는 개념이 있고, 공식문서에 따르면 resource는 Kubernetes Object를 모아놓은 Kubernetes API의 엔드포인트라고 합니다.
Kubernetes Objects는 공식문서에 persistent entities in the Kubernetes system
으로 표현되는데 Kubernetes는 이 object를 이용해서 다음과 같은 정보를 나타낼 수 있습니다.
application에 현재 붙어 있는 node들
application이 현재 이용할 수 있는 resource들
application이 어떻게 동작할지에 대한 policy: 간단한 예로 application이 어떻게 재시작할지 어떤 방식으로 업데이트할 것인지 fault-tolerance에 대한 정책들이 있을 수 있다.
Object Spec and Status
모든 Kubernetes Object는 공통적으로 spec과 status 라는 두 가지 필드를 가지고 있습니다.
Spec은 해당 object가 어떤 상태를 가질지에 대한 명세
Status는 실제로 클러스터에 떠 있는 object가 어떤 상태를 가지고 있는지에 대한 정보를 담고 있는데 이것은 Kubernetes System에 의해서 바뀝니다.

5. Custom Resource

resource는 API object를 모아놓은 Kubernetes API의 엔드포인트 라고 위에서 정의하였습니다.
이를 pod에 적용하면 pod resource는 pod과 관련된 Kubernetes의 operation + pod에 대한 정의. 라고 볼 수 있습니다.
custom resource는 Kubernetes에서 기본적으로 제공하는 resource들 이외에 사용자가 자신의 필요에 따라서 새로 정의한 resource를 말하는데 다르게 말하면 Kubernetes API를 확장하는 작업이라고 볼 수 있습니다.
이 부분과 관련해서는 Kubernetes에서 aggregation layer라는 개념이 있습니다.
한 번 custom resource가 특정 클러스터에 설치되면 다른 권한이 있는 유저들은 custom resource의 object를 kubectl 을 통해서 생성할 수 있습니다. 이것은 해당 resource에 대해서 REST API가 aggregation layer를 통해서 Kubernetes API가 확장되었기 때문에 가능한 것이라고 볼 수 있습니다.

6. Controller 이벤트 흐름

Informer
Current state와 desired state를 확인하는 과정에는 컨트롤러가 API 서버와 통신하여 객체 정보를 가져오는 과정이 포함됩니다. 컨트롤러에서 API 서버에 대한 호출 수는 배포된 Kubernetes 개체 수가 증가함에 따라 기하급수적으로 증가합니다. 
이것은 비용이 많이 들고 시스템에 부담이 됩니다. Informer는 객체 데이터를 검색하고 컨트롤러의 로컬 캐시에 저장하여 이 문제를 해결합니다. 그런 다음 Informer는 이후에 발생하는 모든 생성, 수정 및 삭제 이벤트를 감시합니다.
>> Kubernets 와 컨트롤러 사이의 프록시
그러나 이 모델에는 단점이 있습니다. 여러 컨트롤러가 단일 개체를 감시할 수 있는 경우가 있으며 이 경우 각 컨트롤러는 자체 로컬 캐시를 업데이트합니다. 이로 인해 메모리 오버헤드와 개체 데이터 측면에서 서로 동기화되지 않을 수 있는 여러 캐시 데이터 저장소가 발생할 수 있습니다. 
Shared Informer
SharedInformer는 캐시 데이터 저장소가 모든 컨트롤러에서 공유되도록 사용됩니다. 이렇게 하면 클러스터에서 여러 컨트롤러에서 하나의 리소스를 관찰하고 업데이트하는 과정에서 발생하는 문제가 해결됩니다.
Queue
SharedInformer는 공유되기 때문에 각 컨트롤러의 활동을 추적할 수 없으므로 외부 대기열 시스템에 의존합니다. 지원되는 다양한 유형의 대기열이 있습니다. 예로는 rate-limiting queue, the delayed queue, timed queue 등이 있습니다. 개체 또는 리소스가 변경되면 리소스 이벤트 핸들러가 작업 대기열에 키를 넣습니다. key는 다음과 같은 형태로 존재합니다. resource_namespace/<resource_name>
Custom Controller와 Custome Resource를 정의해서 사용하는 경우도 기존의 컨트롤러와 동일한 방식으로 동작합니다. Informer, queue, controller가 존재하지만 새로운 custom resource를 정의하고 그 정보를 informer에게 제공하는 식으로 사용할 수 있습니다.

7. Custom Controller & Custom Resource 정의

시나리오
시스템 장애 상황에 대비해서 주기적으로 유저 활동 로그를 찍어서 파일로 저장하는 Logger.
Kubernetes 클러스터를 호스팅하고 있는 클라우드 서비스가 죽지않는 이상 적어도 하나 이상의 Logger는 살아남아서 계속 유저들의 활동 로그들을 찍어서 저장해야함.
7.1 Custom Resource 정의하기
Custom resource 정의 및 컨트롤러 구현을 하기 위해 directory를 준비하고 Group name을 설정합니다. Group Name은 register.go 파일에 정의됩니다.
// directory 구조 만들기 $ mkdir -p pkg/apis/logger/v1 ----------------------------------- // 새 파일에 그룹 이름 const 생성 $ vim pkg/apis/logger/register.go ----------------------------------- package logger const ( GroupName = "example.com" )
Go
복사
다음으로는 Custom Resource의 구조에 대해 정의합니다.
먼저 요구사항을 만족시킬 수 있는 Logger Resource를 생성하기 위해 필요한 자료구조를 만듭니다. Logger struct와 같이 struct 최상위에서는 Kubernetes resource로서 역할을 하기 위한 기본적인 메타데이터 필드를 넣고 커스텀하게 만들 구체적인 필드는 Spec 아래에 정의합니다.
첫 번째 버전에서는 우선 활동 기록 로거의 이름 Name과 몇 초 주기로 활동기록을 가져올지를 나타내는  TimeInterval에 대해서 정의합니다.
$ vim pkg/apis/logger/v1/types.go ------------------------------ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type Logger struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` Status LoggerStatus `json:"status,omitempty"` Spec LoggerSpec `json:"spec,omitempty"` } type LoggerStatus struct { Value StatusValue `json:"state"` } type StatusValue string const ( Available StatusValue = "Available" Unavailable StatusValue = "Unavailable" ) type LoggerSpec struct { Name string `json:"name"` TimeInterval int `json:"timeInterval"` Replicas *int32 `json:"replicas"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type LoggerList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `son:"metadata,omitempty"` Items []*Logger `json:"loggers"` }
Go
복사
아래에 나와있는 부분이 코드 생성기에서 코드 생성을 위한 특정 동작을 지정하는 indicator 입니다. // +<tag_name>[=value] 형식으로 되어있으며 동작 방식은 아래와 같습니다.
+genclient
— 현재 패키지에 정의된 자료 구조에 대해서 Kubernetes Client를 생성하라. 사용자는 해당 client를 통해 Kubernetes와 인터렉션할 수 있음.
+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
— 다음 코드에 대해서 deep copy 로직을 생성하라.
다음으로는 패키지에 대한 문서 소스 파일을 생성합니다. types.go 파일과 마찬가지로 코드 생성기를 위한 몇 가지 주석 태그가 있습니다. 이 파일에 명시된 주석들은 해당 패키지에 존재하는 모든 코드들에 대해 영향을 줍니다.
$ vim pkg/apis/logger/v1/doc.go --------------- // +k8s:deepcopy-gen=package // +groupName=example.com package v1
Go
복사
마지막으로 CRD client가 우리가 새롭게 정의한 타입에 대해서 알기 위해서는 위의 코드에서와 같이 AddToScheme 과 Resource에 대해서 정의해줘야 합니다. 이를 이용하여 code generator가 CRD client 코드를 생성하게 됩니다.
$ vim pkg/apis/logger/v1/register.go --------------------------------- package v1 import ( "github.com/zeroFruit/operator-demo/pkg/worker/apis/logger" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) var SchemeGroupVersion = schema.GroupVersion{ Group: logger.GroupName, Version: "v1", } var ( SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) AddToScheme = SchemeBuilder.AddToScheme ) func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes( SchemeGroupVersion, &Logger{}, &LoggerList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil }
Go
복사
7.2 Code Generator 실행
Code Generator를 실행하기 위해서 git repository를 clone하고 환경을 세팅합니다.
$ mkdir $GOPATH/k8s.io $ git clone https://github.com/kubernetes/code-generator.git $ cd $GOPATH/src/github.com/kkj/operator-demo $ $GOPATH/src/k8s.io/code-generator/generate-groups.sh all \ github.com/kkj/operator-demo/pkg/client \ github.com/kkj/operator-demo/pkg/apis \ "logger:v1"
Go
복사
참고