[쿠버네티스 오퍼레이터] 1. 오퍼레이터와 커스텀 리소스

안녕하세요! 펜타시큐리티 클라우드개발팀입니다.

오늘 쿠버네티스 오퍼레이터 시리즈 중 첫번째로 소개해드릴 주제는 “쿠버네티스 오퍼레이터와 커스텀 리소스”입니다.

  1. 오퍼레이터와 커스텀 리소스
  2. 오퍼레이터 구현
  3. 오퍼레이터 개발 모범 사례

쿠버네티스를 사용하다보면 컨트롤러 (Controller), 오퍼레이터 (Operator), 커스텀 리소스 (Custom resource)라는 단어를 자주 만나게 됩니다. 제가 맨 처음 앞선 용어들을 마주했을 때 좀 헷갈려 했었던 기억이 있습니다. 그래서 이번 편에는 오퍼레이터와 커스텀 리소스에 대한 전반적인 설명을 해보려고 합니다.

출처: https://github.com/kubernetes/kubernetes/tree/master/logo

쿠버네티스 컨트롤러

쿠버네티스 “컨트롤러” 공식 문서를 보게 되면 컨트롤러에 대해 잘 설명해주고 있습니다.

쿠버네티스에서 컨트롤러는 클러스터의 상태를 관찰 한 다음, 필요한 경우에 생성 또는 변경을 요청하는 컨트롤 루프이다. 각 컨트롤러는 현재 클러스터 상태를 의도한 상태에 가깝게 이동한다.

컨트롤러는 적어도 하나 이상의 쿠버네티스 리소스 유형을 추적한다. 이 오브젝트 는 의도한 상태를 표현하는 사양 필드를 가지고 있다. 해당 리소스의 컨트롤러(들)은 현재 상태를 의도한 상태에 가깝게 만드는 역할을 한다.

출처: https://subicura.com/2019/05/19/kubernetes-basic-1.html

한번 디플로이먼트 (Deployment)를 생성하는 상황을 생각해보겠습니다. 먼저, 디플로이먼트 스펙을 YAML 파일로 작성하고 kubectl apply 명령어를 실행합니다. 그럼 디플로이먼트 생성 요청이 쿠버네티스 API 서버로 전달되고, ETCD에 새로운 디플로이먼트 오브젝트가 생성될 것입니다.

디플로이먼트 컨트롤러는 디플로이먼트라는 쿠버네티스 리소스 유형을 추적하여 클러스터 상태를 관찰합니다. 만약 특정 디플로이먼트 오브젝트 내 의도한 상태 (Spec)에 적힌 내용과 현재 상태 (Status)가 일치하지 않는다면, 현재 해당 오브젝트의 현재 상태가 의도한 상태가 되도록 필요한 작업을 수행해 줍니다.

의도한 상태와 현재 상태가 일치하지 않는 경우는 어떤 것이 있을까요?

먼저 의도한 상태인 Spec에 무엇이 있는지 생각해보겠습니다. 디플로이먼트 Spec에는 기본적으로 생성할 파드 개수, 생성할 파드 이미지에 대한 정보가 포함되어 있습니다.

kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80

위와 같은 정보를 작성해 디플로이먼트를 생성하는 행위는, 특정 이미지의 파드를 특정 개수만큼 생성하라고 선언 또는 의도하는 것과 같다고 볼 수 있습니다.

만약 디플로이먼트 Spec 내 이미지가 배포된 파드의 이미지 (template.spec.containers[0].image)와 다르거나, Spec 내 파드 개수가 배포된 파드 개수 (replicas)와 다르다면, 컨트롤러는 의도한 상태와 현재 상태가 불일치하는 것으로 판단하게 됩니다.

그럼 디플로이먼트 컨트롤러는 현재 상태가 의도한 상태가 될 수 있도록, 새로운 레플리카셋 (ReplicaSet)을 생성하거나, 기존 레플리카셋 스펙 내 image, replicas 값을 조정하게 될 것입니다.

출처: https://www.linkedin.com/pulse/how-deploy-your-application-microservice-kubernetes-antoine-choula-

새로운 레플리카셋이 생성되거나 기존 레플리카셋이 변경되면 어떻게 될까요?

그럼 레플리카셋 컨트롤러가 레플리카셋 오브젝트의 현재 상태가 의도한 상태가 될 수 있도록 파드 오브젝트를 생성하는 등 여러 작업을 수행하게 될 것입니다.

이와 같이 쿠버네티스는 쿠버네티스 API를 통해 여러 컨트롤러가 상호작용하여 클러스터가 운영되도록 설계되어있습니다.

쿠버네티스 오퍼레이터와 커스텀 리소스

오퍼레이터란?

그럼 오퍼레이터는 무엇일까요? 먼저 CNCF 오퍼레이터 백서에 나와있는 컨트롤러와 오퍼레이터의 차이에 대해 살펴보겠습니다.

Technically, there is no difference between a typical controller and an operator. Often the difference referred to is the operational knowledge that is included in the operator.

기술적으로, 일반적인 컨트롤러와 오퍼레이터에는 큰 차이가 없다. 종종 언급되는 차이점은 오퍼레이터에 포함되어 있는 운영 지식 (operational knowledge)이다.

컨트롤러와 오퍼레이터 모두 클러스터의 특정 리소스의 현재 상태가 의도된 상태가 되도록 작업을 수행하는 컨트롤 루프입니다. 다만 오퍼레이터는 특정 운영 지식을 가지고 있는 점이 다르다고 볼 수 있습니다.

As a result, a controller which spins up a pod when a custom resource is created and the pod gets destroyed afterwards can be described as a simple controller. If the controller has additional operational knowledge like how to upgrade or remediate from errors, it is an operator.

즉 커스텀 리소스가 생성되었을 때 파드를 생성하거나 나중에 그 파드를 삭제하는 컨트롤러는 간단한 컨트롤러라고 볼 수 있다. 만약 컨트롤러가 업그레이드 방법이나 에러로부터 복구하는 방법 등 부수적인 운영 지식을 가지고 있다면, 그것은 오퍼레이터다.

오퍼레이터는 특정 애플리케이션을 업그레이드하거나 에러가 발생했을 때 복구하는 방법 등 부수적인 운영 로직을 가지고 특정 애플리케이션 운영을 자동화하는 역할을 수행합니다.

오퍼레이터 디자인 패턴 및 특징

CNCF 오퍼레이터 백서에서 오퍼레이터 디자인 패턴과 특징에 대해 좀 더 살펴보겠습니다.

오퍼레이터 디자인 패턴은 도메인 특화 (domain-specific) 지식과 선언된 상태를 가지고 어떻게 애플리케이션과 인프라를 관리할 지에 대해 정의합니다. 해당 패턴의 목적은 수동적이고 명시적인 (Imperative) 운영 작업을 최소화하는 것입니다.

애플리케이션을 어떻게 조정하고 유지할지에 대한 지식을 코드로 담고 있는 오퍼레이터를 사용하면, 사용자는 리소스에 대해 원하는 상태를 선언하기만 하면 됩니다. 오퍼레이터는 지속적으로 현재 상태와 선언된 상태를 모니터링하고, 리소스가 선언된 상태를 유지하는 데 필요한 작업들을 수행할 것입니다.

출처: https://github.com/cncf/tag-app-delivery/blob/eece8f7307f2970f46f100f51932db106db46968/operator-wg/whitepaper/Operator-WhitePaper_v1-0.md

오퍼레이터 패턴은 주로 3가지 컴포넌트로 구성됩니다.

  1. 관리할 애플리케이션 또는 인프라
  2. 사용자가 선언적인 방식으로 애플리케이션의 의도한 상태를 설정할 수 있도록 하는 도메인 특화 언어
  3. 지속적으로 동작하는 컨트롤러

컨트롤러는 지속적으로 아래 작업들을 수행합니다.

  • 상태를 인지하고 확인하는 작업 수행
  • 자동화된 방식으로 애플리케이션에 대해 작업 수행
  • 선언적인 방식으로 애플리케이션 상태를 알림

커스텀 리소스란?

오퍼레이터 관련 내용을 보다보면, 항상 같이 등장하는 단어가 있습니다. 바로 커스텀 리소스입니다. 위에서 한번 나오긴 했지만 여기서 설명하기 위해 잠깐 넘어갔는데요, 커스텀 리소스란 무엇이고, 왜 사용하는 것일까요?

먼저 쿠버네티스에서 리소스는 쿠버네티스 API에서 특정 종류의 API 오브젝트 모음을 저장하는 엔드포인트입니다. 커스텀 리소스는 쿠버네티스에서 기본으로 제공되지 않는 리소스, 쿠버네티스 API의 익스텐션이라고 볼 수 있습니다. 커스텀 리소스가 설치되면 파드외 같은 빌트인 리소스와 마찬가지로 kubectl을 사용하여 해당 오브젝트를 생성하고 접근할 수 있습니다.

예시로 프로메테우스 오퍼레이터의 Prometheus 커스텀 리소스를 살펴보겠습니다. 아래 예시의 spec에서 serviceAccountName이나 serviceMonitorSelector와 같은 Prometheus 서버 설정에 사용되는 필드들을 확인할 수 있습니다.

apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
name: prometheus
spec:
serviceAccountName: prometheus
serviceMonitorSelector:
matchLabels:
team: frontend
resources:
requests:
memory: 400Mi
enableAdminAPI: false

이와 같이 커스텀 리소스를 사용하게 되면 특정 애플리케이션이나 프로세스에 맞는 구조화된 데이터를 쿠버네티스 API를 통해 저장하고 검색할 수 있게 됩니다.

커스텀 리소스 vs 컨피그맵

쿠버네티스에서는 설정이나 비밀 값을 저장하는 리소스로 컨피그맵 (ConfigMap)과 시크릿 (Secret)을 제공합니다. 애플리케이션 동작에 필요한 데이터를 저장한다는 점에서 커스텀 리소스와 컨피그맵은 유사하게 보입니다. 어떤 경우에 컨피그맵 대신 커스텀 리소스를 사용하는 게 나을까요?

컨피그맵은 저장할 수 있는 데이터의 형식이 정해져 있지 않습니다. 따라서 커스텀 리소스와 달리 좀 더 포괄적인 데이터를 저장하는 데 사용이 될 수 있습니다.

반면에 커스텀 리소스는 특정 애플리케이션에 대한 설정 데이터를 저장하기 위해 사용이 됩니다. 특정 애플리케이션의 설정을 더 잘 표현할 수 있는 데이터 형식을 갖도록 할 수 있고, 따라서 각 필드를 검증하는 것도 더 편할 수 있습니다. 따라서 설정 에러 발생의 가능성을 낮출 수 있고, 사용자가 특정 애플리케이션의 세세한 지식 없이도 손쉽게 설정하도록 할 수 있습니다.

쿠버네티스 “커스텀 리소스” 공식 문서를 보면, 컨피그맵을 선택하는 것이 권장되는 상황과 커스텀 리소스를 선택하는 것이 권장되는 상황에 대해 서술한 내용이 있습니다. 아래에 그대로 가져와 붙여 넣었으니 한번 읽어보시기를 추천드립니다.

다음 중 하나에 해당하면 컨피그맵을 사용하자.

  • mysql.cnf 또는 pom.xml과 같이 잘 문서화된 기존 구성 파일 형식이 있다.
  • 전체 구성 파일을 컨피그맵의 하나의 키에 넣고 싶다.
  • 구성 파일의 주요 용도는 클러스터의 파드에서 실행 중인 프로그램이 파일을 사용하여 자체 구성하는 것이다.
  • 파일 사용자는 쿠버네티스 API가 아닌 파드의 환경 변수 또는 파드의 파일을 통해 사용하는 것을 선호한다.
  • 파일이 업데이트될 때 디플로이먼트 등을 통해 롤링 업데이트를 수행하려고 한다.

다음 중 대부분이 적용되는 경우 커스텀 리소스(CRD 또는 애그리게이트 API(aggregated API))를 사용하자.

  • 쿠버네티스 클라이언트 라이브러리 및 CLI를 사용하여 새 리소스를 만들고 업데이트하려고 한다.
  • kubectl 의 최상위 지원을 원한다. 예: kubectl get my-object object-name.
  • 새 오브젝트에 대한 업데이트를 감시한 다음 다른 오브젝트를 CRUD하거나 그 반대로 하는 새로운 자동화를 구축하려고 한다.
  • 오브젝트의 업데이트를 처리하는 자동화를 작성하려고 한다.
  • .spec, .status.metadata와 같은 쿠버네티스 API 규칙을 사용하려고 한다.
  • 제어된 리소스의 콜렉션 또는 다른 리소스의 요약에 대한 오브젝트가 되기를 원한다.

오퍼레이터 특징

위에서 살펴봤듯이, 오퍼레이터의 주요 목적은 쿠버네티스 API에 새로운 도메인 지식이 반영되도록 확장하는 것으로 크게 아래 3가지 카테고리에 대해 오퍼레이터를 활용할 수 있습니다.

동적 설정 (Dynamic Configuration)

동적 설정이란, 애플리케이션 재시작 없이 동작 중인 시스템의 설정을 변경하는 기능을 의미합니다.

오퍼레이터가 커스텀 리소스와 함께 사용이 되면, 선언적 API를 제공해줄 수 있게 됩니다. 사용자가 리소스의 의도한 상태를 선언하면, 오퍼레이터가 쿠버네티스 오브젝트의 현재 상태가 선언한 의도한 상태로 동기화되도록 할 것입니다. 이 때 상황에 맞는 적합한 설정을 애플리케이션에게 제공해줄 수 있습니다.

대표적으로 쿠버네티스의 컨피그맵을 통해 애플리케이션에 설정을 전달할 수 있습니다. 컨피그맵을 생성해 애플리케이션의 볼륨으로 마운트하고, 애플리케이션이 해당 볼륨에 있는 설정을 읽도록 할 수 있습니다. 오퍼레이터에서 컨피그맵 내 값을 동적으로 변경하게 되면, 애플리케이션 볼륨 내 설정 파일 내용도 동기화되어 변경되게 됩니다.

애플리케이션이 설정 파일 수정을 감시하고, 변경되었을 경우 애플리케이션 재시작 없이 다시 로드하는 Hot reload(또는 Hot restart) 기능을 가지고 있다면 완전한 동적 설정이 가능하게 됩니다.

운영 자동화 (Operational Automation)

대부분 오퍼레이터들은 커스텀 리소스와 함께 최소 하나의 커스텀 컨트롤러를 포함하고 있습니다. 이런 컨트롤러들은 쿠버네티스 안에서 동작하는 데몬으로 볼 수 있으며, 쿠버네티스 내 리소스를 통해 다른 컴포넌트나 컨트롤러와 상호작용하여, 공통적이거나 반복적인 작업에 대한 자동화를 제공합니다.

예를 들어 오퍼레이터들은 클러스터로 동작하는 소프트웨어 배포, 자동화된 백업 및 복구 (restores), 부하 기반 동적 스케일링 등과 같은 하이레벨 자동화를 쿠버네티스 클러스터에 제공해 줄 수 있습니다.

공통적이고 반복되는 운영 작업들을 오퍼레이터에 코드로 작성함으로써, 해당 작업들이 표준화된 방법으로 반복 가능하고, 테스트 가능하며, 업그레이드가 가능해집니다. 따라서 작업 과정들이 누락되지 않고 다른 작업들과 동기화되지 않아 문제가 발생하는 것을 막을 수 있습니다. 또한 유지 보수 작업에 소요되는 불필요한 시간을 줄여 팀의 자율성을 향상시킬 수 있습니다.

도메인 지식 (Domain Knowledge)

위에 살펴본 운영 자동화와 연결되어, 특정 소프트웨어나 프로세스에 대한 전문적인 도메인 지식을 오퍼레이터에 코드로 작성할 수 있습니다.

대표적 예시로 애플리케이션 업그레이드가 있습니다. Stateless한 애플리케이션은 디플로이먼트가 기본 제공하는 롤링 업데이트 방식을 이용해 배포할 수 있지만, 데이터베이스와 같은 Stateful 애플리케이션은 업그레이드를 안전하게 진행하기 위해 매우 상세한 과정들이 순차적으로 수행되는 것이 요구될 수 있습니다.

오퍼레이터 코드에 대상 애플리케이션의 업그레이드에 대해 요구되는 특정 방식이나 절차를 작성함으로써 해당 애플리케이션에 딱 맞는 운영 자동화를 실현할 수 있습니다.

또 다른 예시로는 에러 해결 (Error Remediation)이 있습니다. 쿠버네티스 기본 복구 동작은 컨테이너가 정상 동작할 때까지 컨테이너를 재시작하는 것입니다. 대체로 좋은 방법이긴하지만 애플리케이션에 따라 더 빠르고 적합한 방법이 있을 수 있습니다. 오퍼레이터가 애플리케이션을 모니터링하고 에러가 발생했을 때, 애플리케이션에 맞는 적절한 복구 작업을 수행하도록 할 수 있습니다. 만약 오퍼레이터가 해결할 수 없는 에러라면 이슈를 발생시켜 운영자에게 알림을 전달할 수 있습니다. 이를 통해 MTTR (Mean Time To Recovery)을 줄이는 이점을 얻을 수 있습니다.

오퍼레이터 활용 예시

쿠버네티스 “오퍼레이터(operator) 패턴” 공식 문서를 보게 되면 오퍼레이터 활용 목적인 운영 자동화의 몇 가지 예시를 소개하고 있습니다.

  • 주문형 애플리케이션 배포
  • 해당 애플리케이션의 상태를 백업하고 복원
  • 데이터베이스 스키마 또는 추가 구성 설정과 같은 관련 변경 사항에 따른 애플리케이션 코드 업그레이드 처리
  • 쿠버네티스 API를 지원하지 않는 애플리케이션에 서비스를 게시하여 검색을 지원
  • 클러스터의 전체 또는 일부에서 장애를 시뮬레이션하여 가용성 테스트
  • 내부 멤버 선출 절차없이 분산 애플리케이션의 리더를 선택

마침

쿠버네티스 오퍼레이터 문서 하단에 위치한 “자신만의 오퍼레이터 작성” 섹션을 보면 오퍼레이터 구현에 많이 사용되는 몇 가지 도구들을 소개해주고 있습니다.

2편에서는 소개된 도구 중 하나인 오퍼레이터 프레임워크 (Operator SDK)를 활용한 오퍼레이터 구현에 대해 말씀드리도록 하겠습니다!

오퍼레이터 시리즈 바로가기

  1. 오퍼레이터와 커스텀 리소스
  2. 오퍼레이터 구현
  3. 오퍼레이터 개발 모범 사례

참고 자료

[1]https://kubernetes.io/ko/docs/concepts/architecture/controller/

[2]https://github.com/cncf/tag-app-delivery/blob/eece8f7307f2970f46f100f51932db106db46968/operator-wg/whitepaper/Operator-WhitePaper_v1-0.md

[3]https://kubernetes.io/ko/docs/concepts/extend-kubernetes/operator/

[4]https://kubernetes.io/ko/docs/concepts/extend-kubernetes/api-extension/custom-resources/

[5]https://blog.twitter.com/engineering/en_us/topics/infrastructure/2018/dynamic-configuration-at-twitter

--

--

펜타시큐리티 보안기술연구소
PentaSecurity Labs

펜타시큐리티 보안 기술 연구소 사람들의 생활과 기술 연구 및 각종 활동에 관한 이야기를 담은 블로그