클라우드/DevOps

[K8s deepdive] Controller pattern과 operator

dayeonsheep 2024. 9. 18. 21:44

K8s 딥다이브 스터디 첫 주제로 컨트롤러를 선정한 이유

  • 학교 cloud system 수업에서 이후 실습 커리큘럼 예습 겸 그 순서를 따라가 보려 함
  • 하지만 너무 포괄적인 커리큘럼이라 '딥다이브'에 맞는 주제를 좀 더 찾아보기로...

컨트롤러 패턴 개념

  • 컨트롤러(Controller) 패턴은 쿠버네티스 리소스를 능동적으로 모니터링하고 요청한 상태로 유지 관리
  • 쿠버네티스 중심부는 선언된 오브젝트 상태와 함께 현재 어플리케이션 상태를 정기적으로 모니터링하고 reconcile하는 컨트롤러가 컬렉션으로 구성되어있다.

 

Problem

 

K8s는 오케스트레이션 플랫폼이면서도 모든 어플리케이션을 어떻게 명령해서 돌아가는지는 다루지 않는다.
다만 쿠버네티스는 다행스럽게도 변경이나 중단 없이 쿠버네티스 빌딩 블록 위에서 어플리케이션 확장 기능을 제공한다.

변경이나 중단 없이 어떻게 쿠버네티스를 확장시킬까?
-> 설계상 쿠버네티스는 선언적인 자원 중심 API를 기반으로 한다.

 

*선언적(declarative)이란 말은 명령적(imperative)와는 접근 방식이 다르다.
선언적 접근 방식은 쿠버네티스에게 어떻게 작동해야 하는지를 알려주지 않고 원하는 상태를 선언해놓는다.


예) deployment를 확장하는 경우 쿠버네티스에서 새로운 파드 생성을 명령하지 않는다.
대신 쿠버네티스 api를 통해 deployment 자원의 replicas 속성에 원하는 상태를 기술해놓는다.

 

그렇다면 새로운 파드는 어떻게 만들까?
-> 이것은 컨트롤러에 의해 내부적으로 수행된다.

  • 원하는 자원 상태가 변경될 때마다 쿠버네티스는 이벤트를 생성해 이해관계가 있는 리스너들에게 브로드캐스트 한다.
  • 이러한 리스너는 새로운 자원을 생성 수정 삭제하는데 반응해서 동작하고, 이를 통해 나온 이벤트는 또 다른 이벤트가 생성된다.
  • 그럼 이런 이벤트는 다시 컨트롤러에 의해 선택되어 작업을 수행한다.

=> 이벤트 기반 아키텍처

  • 쿠버네티스는 이벤트 기반 아키텍처를 사용하여 리소스 상태 변경 시 이벤트를 생성하고, 컨트롤러는 이러한 이벤트에 반응하여 작업을 수행합니다.
  • 컨트롤러는 이벤트를 감지한 후 상태를 분석하고, 필요에 따라 리소스를 추가, 삭제, 수정하여 원하는 상태를 유지합니다.

=> 이러한 전체 절차를 State Reconciliation이라고 한다.


대상 상태가 현재 상태와 다르다면, 컨트롤러가 다시 요청해서 원하는 상태로 바꾸는 걸 Reconile 이라는 작업을 한다.

이 관점에서 보면 쿠버네티스는 상태 관리자다.


쿠버네티스 컴포넌트 인스턴스 요청 상태를 선언하고 그 상태로 유지할려고 노력하며 변경사항이 있을 경우 해당 상태로 변경하려고 시도한다.

 

그렇다면 쿠버네티스 코드를 수정하지 않고 어떻게 이 Reconciliation 작업을 수행할 수 있는 걸까?

Solution

쿠버네티스에는 ReplicaSet, DemonSet, StatefulSet, Deployment, Service 같은 표준 쿠버네티스 자원을 관리하는 내부 컨트롤러가 존재한다.
이러한 컨트롤러는 컨트롤러 관리자에 의해 실행되며 마스터 노드에 존재한다.

 

이런 컨트롤러는 서로 독립적으로 존재하며 무한 루프를 톨며 자원을 모니터링하고 변경 사항이 있다면 Reconciliation 작업을 수행한다.

하지만 이렇게 바로 사용 가능한 컨트롤러 외에도 쿠버네티스 이벤트 중심 아키텍처를 통해 기본적으로 여러 사용자 정의 컨트롤러를 플러그인(Plug in)으로 사용할 수 있다.

 

사용자 정의 컨트롤러는 내부 컨트롤러와 동일한 방식으로 동작 상태 변경 이벤트에 별도의 기능을 추가할 수 있다.
-> 즉, 컨트롤러의 특징은 이벤트에 반응해 빠르게 처리한다는 특징이다.

Reconciliation의 절차

  • 관측(observe) : 관측하고 있는 자원이 변경될 때 쿠버네티스가 배포하는 이벤트를 주시해서 실제 상태를 찾는다.
  • 분석(analyze) : 실제 상태와 요청한 상태의 차이를 알아낸다.
  • 실행(act) : 실제 상태가 원하는 요청한 상태로 되도록 작업을 수행한다.

 

예를 들어 레플리카세트 컨트롤러는 레플리카 세트 자원을 모니터링 하고 있다.

이때 원하는 요청이 들어오면 현재 실행 상태와 원하는 상태의 차이를 알아내고 쿠버네티스 API 서버에 해당하는 작업을 제출한다.

그럼 쿠버네티스 API 서버는 노드 컴포넌트(kubelet, kubeproxy)에게 요청해서 그 작업을 수행하도록 한다.

 

정리하면,

  • 컨트롤러는 원하는 요청을 받기 위해서 이벤트 리스너로 자신을 등록시켜 놓는다.
  • 그리고 요청이 들어오면 해당 작업을 분석해서 쿠버네티스 API에게 작업을 제출한다.
  • 컨트롤러는 Control Plane에 포함되어 있고 복잡한 애플리케이션 수명주기를 관리하게 가능하도록 하는 표준 매커니즘이 되었다.

그 결과 Operator 라는 더욱 정교한 차세대 컨트롤러가 탄생!


- 기본적으로 제공되는 Controller의 기능 이외에 추로 개발 해야하는 어플리케이션에 따라 추가 해야 하는 새로운 로직이 필요한 경우를 커버하기 위해 등장한 것이 Operator


- Operator 는 Custom Resource 와 상호 작용하는 정교한 Reconcile 과정을 나타내며, 이를 통해 운영자는 복잡한 어플리케이션 도메인 로직을 캡슐화하여 관리할 수 있게 된다.

 

  • 컨트롤러 : 표준 쿠버네티스 자원을 모니터링 하고 작동하는 간단한 Reconciliation 프로세스로 컨트롤러는 플랫폼 동작을 향상시키고 새로운 플랫폼 기능을 추가할 수 있다.
  • 오퍼레이터 : 오퍼레이터 패턴의 핵심인 CustomResourceDefinition(CRD)와 연동하는 정교한 Reconciliation 프로세스로 일반적으로 복잡한 애플리케이션 도메인 로직을 캡슐화 하고 전체 애플리케이션 수명주기를 관리한다.

Controller == Works on vanilla K8s resources
Operator == a Controller that adds custom resources (CRDs) required for it's operation

 

오퍼레이터 : 컨트롤러 패턴을 애플리케이션의 자동화에 사용하면 오퍼레이터


여러개의 컨트롤러가 동일한 자원에 동시에 접근하는 걸 막기위해 컨트롤러는 싱글톤 패턴을 사용한다.

 

컨트롤러는 deployment로 배포되지만 쿠버네티스는 자원 객체가 변경될 때마다 동시성 문제를 막기위해서 Optimistic locking을 사용하기 때문에 하나의 레플리카만 사용한다.

-> 이 말은 컨트롤러는 백그라운드에서 영구적으로 실행되는 애플리케이션이란 말

 

컨트롤러는 자원을 정의하는 모든 필드를 모니터링하고 처리할 수 있지만 metadata와 configmap이 이러한 컨트롤러의 목적에는 적합하다.

 

컨트롤러가 모니터링하는 자원을 정의하는 위치를 선택할 때 고려해야할 사항

  1. Label
  • 자원 메타데이터의 일부인 레이블은 컨트롤러에서 watch할 수 있으며 백엔드 데이터베이스에 인덱싱되어 쿼리로 효율적으로 검색할 수 있다. 셀렉터 등의 기능이 필요할 때 레이블을 사용하면 좋다.
  1. Annotation
  • 레이블 대신으로 쓸 수 있는 대안으로 만약 레이블 값이 구문 제한으로 벗어난다면 에노테이션이 적합할 수 있다. annotation은 인덱싱되지 않으므로 컨트롤러 쿼리에서 키로 사용되지않는 비식별정보에 annotation을 사용한다. 이를 사용해도 쿠버네티스 성능에 부정적인 영향은 없다.
  1. Configmap
  • 컨트롤러에 label이나 annotation에 잘 맞지 않는 추가 정보가 필요한 경우 컨피그맵을 사용해 대상 상태를 저장하는게 가능하다. 이러한 컨피그맵은 컨트롤러에서 watch하고 읽어들인다.
  • CRD를 사용해서 대상 상태 명세를 설계하는게 훨씬 더 적합하고 쉽지만 클러스터 권한이 높아야한다. 그러한 권한이 없다면 컨피그맵이 더 적합하다.

Conclusion

  • 컨트롤러는 관심 객체의 요청한 상태에 실제 상태를 알기 위해 관심 객체를 모니터링하는 능동적인 Reconiliation 프로세스다.
  • 그런 다음 현재 상태를 요청한 상태와 비슷하게 변경하기 위해 지시를 보낸다.
  • 쿠버네티스는 이 매커니즘을 내부 컨트롤러에서 사용하며 사용자 정의 컨트롤러에서 재사용할 수 있다.

 

custom controller와 operator의 차이 정리

 

1. Custom Controller

쿠버네티스의 표준 리소스나 사용자 정의 리소스(Custom Resources, CRs)를 감시하고, 이를 관리하는 컨트롤러입니다.
간단한 리소스 상태의 변경을 감지하고, 이를 자동으로 처리하는 역할을 수행합니다.

특징:

  • 리소스 감시 및 상태 관리: 주로 Deployment, Service, Pod 등의 표준 Kubernetes 리소스를 감시하거나, Custom Resource를 추가하여 그 상태를 관리하는 역할
  • 구성 요소: Custom Controller는 일반적으로 Reconciliation 절차를 따른다.
  • 구현: Custom Controller는 Go, Python, Shell 등 여러 프로그래밍 언어로 작성할 수 있으며, 기본적으로 쿠버네티스 API를 통해 동작한다.

=> Deployment의 Pod 개수를 유지하거나, 특정 리소스가 충족하는 조건에 따라 다른 리소스를 생성/삭제하거나, 표준 리소스 및 간단한 사용자 정의 리소스를 관리하는 간단한 작업에 주로 사용된다.

 

2. Operator

Custom Controller에서 발전된 개념으로, 더 복잡한 애플리케이션의 라이프사이클을 자동화하는 데 중점을 둔다.
CRD를 사용하여 애플리케이션의 고유한 리소스 및 상태를 모델링하며, 해당 리소스의 전체 라이프사이클을 관리한다.

특징:

  • Operator는 단순히 리소스를 감시하고 수정하는 것 이상으로, 특정 애플리케이션의 도메인 지식을 활용해 애플리케이션의 설치, 백업, 복구, 업그레이드 등 복잡한 작업을 자동화한다.
  • CRD를 통해 애플리케이션 도메인에 맞는 리소스를 정의
    • 예) 데이터베이스 클러스터의 관리 작업을 수행하는 Operator는 "MySQLCluster" 같은 CR을 정의하여, 이를 통해 데이터베이스 클러스터의 상태를 관리하고 조정
  • 애플리케이션의 업그레이드, 스케일링, 백업 및 복구 같은 복잡한 작업을 처리하고, 애플리케이션 도메인에 대한 깊은 이해를 코드에 반영하여, 복잡한 비즈니스 로직과 규칙을 반영할 수 있다.

사용 사례:

  • DB를 배포하고 관리하는 Operator는 자동 백업, 업그레이드, 스케일링 등의 작업을 수행
  • 복잡한 상태 관리가 필요한 애플리케이션(예: 카산드라 클러스터, 엘라스틱서치 클러스터 등)의 라이프사이클을 자동화할 때 적합

=> CRD 이용해서 더 복잡한 거 관리 해준다는 얘기

 

차이점 요약:

항목 Custom Controller Operator
목적 표준 리소스 또는 간단한 사용자 정의 리소스 관리 복잡한 애플리케이션의 도메인 로직 및 라이프사이클 관리
CRD 사용 여부 CRD를 사용하지 않거나 간단한 리소스에 사용 CRD를 사용하여 애플리케이션 도메인에 맞는 리소스 모델링
복잡성 상대적으로 단순한 로직과 리소스 상태 유지 애플리케이션 배포, 관리, 업그레이드 등 복잡한 로직 관리
주요 기능 리소스 상태 감시 및 단순한 수정 작업 애플리케이션의 설치, 백업, 복구, 업그레이드, 스케일링 등의 작업 자동화
언어 여러 언어로 작성 가능 (Go, Python, Shell 등) 주로 Go로 작성, 더 복잡한 도메인 로직 포함

 

operator == helm ?

  • Helm
    • stateless 앱 배포에 적합 (복잡도가 낮은)
    • 비즈니스 로직이 모두 컨테이너 내부에 포함
  • operator
    • 배포 + 운영 + 원하는 모든 자동화 를 이룰 수 있음
    • stateful 앱에 적합함 (복잡도가 높은)
    • operator 자체의 배포는 helm

 

유명한 operator들

  • prometheus operator, Mysql operator, elsticsearch operator, postgres operator, JVM operator

 

custom controller용 add-on, framework

  1. metacontroller[https://metacontroller.github.io/metacontroller/intro.html]
  • custom controllers를 쉽게 배포하고 작성할 수 있게 해주는 k8s용 add-on (with webhook)
  • Metacontroller로 Operators를 빌드하면 개발자는 Kubernetes 컨트롤러와 API를 구현하는 내부 메커니즘을 배우는데서 벗어나 애플리케이션 도메인의 문제 해결에 집중할 수 있습니다.
  • Go로 Operators를 작성하지 않고도 공유 캐시와 같은 기존 API 메커니즘을 활용할 수 있다.
  • Metacontroller의 웹훅 API는 마치 JSON을 생성하는 단발성(one-shot) 클라이언트 측 generator를 작성하여 그 결과를 kubectl apply에 전달하는 느낌을 주도록 설계되었다.

(Go 를 이용해서 개발한다면)
2. Kubebuilder
3. Operator SDK

 

Operator는 만능이 아니다

 

많은 경우, 표준 리소스를 사용하는 일반적인 컨트롤러가 충분하다.


컨트롤러만 이용할 경우 CRD를 등록하기 위해 클러스터 관리자 권한이 필요하지 않다는 점이지만, 보안이나 검증 측면에서 한계가 있을 수 있다.

 

Operator는 선언형 쿠버네티스 방식과 반응형 컨트롤러로 리소스를 처리하는 방법에 잘 맞는 맞춤형 도메인 로직을 모델링할 때 적합한 선택이다.

 

구체적으로 CRD를 사용하는 Operator를 고려하기 좋은 상황 예시:

  • 이미 존재하는 쿠버네티스 도구(kubectl 등)와의 긴밀한 통합이 필요한 경우
  • 애플리케이션을 처음부터 설계할 수 있는 그린필드 프로젝트에 있는 경우
  • 리소스 경로, API 그룹, API 버전 관리, 특히 네임스페이스 같은 쿠버네티스 개념을 활용할 수 있는 경우
  • API 접근에 대한 클라이언트 지원(watch, 인증, 역할 기반 권한 관리, 메타데이터에 대한 selector)을 필요로 하는 경우

이러한 기준에 맞지만, 커스텀 리소스를 구현하고 유지하는 데 더 큰 유연성이 필요하다면 커스텀 API 서버를 사용하는 것도 고려할 수 있다.


그러나 쿠버네티스 확장 포인트가 모든 문제의 만능 해법이라고 생각해서는 안된다!

 

만약 사용 사례가 선언형이 아니거나 관리하려는 데이터가 쿠버네티스 리소스 모델에 적합하지 않거나 플랫폼과의 긴밀한 통합이 필요하지 않다면,
독립 실행형 API를 작성하고 클래식한 ServiceIngress 객체를 통해 이를 노출하는 것이 더 나을 수 있다.

 


Reference

- 개념 관련

- custom controller 관련

- 따라해 볼 operator 구현 예제