쿠버네티스에서 서비스를 개발하거나 운영하다 보면 자주 접하게 될 개념 중 하나가, 바로 이 Controller 와 Operator 일 것이다. 어떤 경우에는, 동일한 서비스를 두고 ‘이건 Controller 야’ 라고 말 할 때도 있고 ‘이 Operator 는…’ 이라고 불릴 때도 있어서 헷갈릴 수 있다. 심지어 공식 문서에서도 두 개념에 대한 비교는 하지 않고 있어서 명확한 구분이 필요한 것 같다.
와중에 이 문서를 발견했는데, 요약하자면 이렇다.
Controller
So in the Kubernetes world, a controller will basically monitor and measure the cluster resources state to adjust those resources that diverge from the desired state.
더 줄여보면, Controller 는 (1) K8s 리소스의 상태를 체크하고 (2) 이 리소스들의 상태를 ‘목표 상태 (desired state)’ 로 조정하는 역할을 한다.
ReplicaSet
, StatefulSet
, DaemonSet
이 대표적인 Controller 들이다. 이 리소스에 속해 있는 Pod 들의 상태를 ‘목표 상태’ 로 만들기 위해, Pod 을 추가하거나 삭제한다.
Operator
CoreOS 에서 정의하는 Operator 는 다음과 같다.
An Operator is a method of packaging, deploying and managing a Kubernetes application.
그런데 이 개념을 구현하기 위해서는 필연적으로 Controller 가 들어가게 된다.
그렇다면 Controller 와 비교했을 때 Operator 의 차이점은 무엇일까?
Operator vs. Controller
여기에서 말하는 ‘Kubernetes application’ 란, 사용자가 직접 지정할 수 있는 CRD (Custom Resource Definition) 과 거기서 파생된 CR (Custom Resource) 까지 포함하고 있는 것이다.
예를 들면, Orange 라는 CRD 를 하나 만들어 보자.
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: oranges.example.com
spec:
group: example.com
version: v1
scope: Namespaced
names:
plural: oranges
singular: orange
kind: Orange
subresources:
status: {}
이 정의 (definition)를 가지고, orange1
이란 CR 을 하나 만들 수 있을 것이다.
특정 Orange CR 이 만들어지거나 수정될 때, 삭제될 때엔 뭘 해야 하는지 정의하는 코드를 만들 수 있을 것이다. 예를 들면, Orange CR 에는 1개의 Service
와 1개의 Pod
이 만들어져야 한다고 해보자. 그걸 Golang 으로 아주 간단히 표현하면,
func (h *orangeHandler) Handle(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
// Get the Orange instance
orange := &examplev1.Orange{}
if err := sdk.Get(ctx, req.NamespacedName, orange); err != nil {
if errors.IsNotFound(err) {
return reconcile.Result{}, nil
}
return reconcile.Result{}, errors.Wrap(err, "failed to get Orange instance")
}
// Create the Pod for the Orange instance
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: orange.Name + "-pod",
Namespace: orange.Namespace,
Labels: map[string]string{
"app": orange.Name,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{...},
},
}
if err := sdk.Create(ctx, pod); err != nil {
return reconcile.Result{}, errors.Wrap(err, "failed to create Pod")
}
// Create the Service for the Orange instance
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: orange.Name + "-service",
Namespace: orange.Namespace,
Labels: map[string]string{
"app": orange.Name,
},
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{
"app": orange.Name,
},
Ports: []corev1.ServicePort{...},
Type: corev1.ServiceTypeClusterIP,
},
}
if err := sdk.Create(ctx, service); err != nil {
// Error creating the Service
return reconcile.Result{}, errors.Wrap(err, "failed to create Service")
}
return reconcile.Result{}, nil
}
이처럼 내부에 구현된 Reconcile Loop Code (특정 CR이 수정되는 이벤트를 관찰하고, 이벤트에 맞춰 코드를 실행하는 루프) 는 일종의 Orange Controller 라고도 부를 수 있다. (또는 이 코드가 실행되는 pod 도 controller 라고 볼 수 있다)
하지만 이렇게 Orange CR 을 관리하는 application 자체를 가리킬 때, 또는 이렇게 관리하는 전체 패턴을 Orange CR 의 Operator 라고 부를 수 있다. 바꿔 말해서, 실재하는 K8s 리소스인 Service 와 Pod 을 만드는 역할은 Controller 에서 이뤄지지만, Orange CR 의 명세를 관리하는 전체 과정은 Operator 라고 부를 수 있다.
정리하면
개인적으로 Controller 는 기술적인 개념이고, Operator 는 좀 더 포괄적이면서 논리적인 개념으로 받아들여진다. 문서에서도 ‘모든 Operator 는 CR 을 관리하기 위해 쓰이는 Controller 라고 부를 수 있다.’ 라고 하고 있으니까 말이다.
그리고 Controller 나 Operator 모두 어떤 프레임워크일 뿐이지, 프로그래밍 언어에 국한된 것은 아니라고 언급한다. 나 역시 Go 언어와 Python 으로 모두 구현해 봤기 때문이다. 다만 용어를 사용할 때 좀 더 확실히 이해하고 쓴다면 헷갈릴 여지가 없기를 바란다.