기본 콘텐츠로 건너뛰기

[Kubernetes - Operator] Kubernetes상의 Operator 나름대로 정리

What is the Operator on Kubernetes

기본 전제 및 용어 정리

  • Kubernetes는 선언적 상태관리 시스템이다.
  • Operator란 Kubernetes 애플리케이션을 패키징, 배포, 관리하는 방법론이다. (운영자 관점)
  • Operator Pattern은 Kuberentes에서 Operator 방법론을 적용해서 확장하는 패턴이다. (확장 개발 관점)
  • Oeprator Framework은 Kubernetes에서 Operator를 실제 구현과 관리를 지원하는 Framework이다. (실 구현 관점)
  • CRD (Custom Resource Definition)은 Operator로 사용할 상태 관리용 객체들의 Spec을 정의한다. (Schema 관점)
  • CR (Custom Resource)은 CRD의 Spec을 지키는 객체들의 실제 상태 데이터 조합이다. (Desired State 관점)
  • CC (Custom Controller)는 CR의 상태를 기준으로 현재의 상태를 규정한 상태로 처리하기 위한 컨트롤 루프다. (Current State 관점)

Let's take a look at the conclusion.

Operator는 운영자 주로 하는 작업들을 묶어서 자동화하는 것이 목표이며 다음과 같이 동작하게 된다.

  • 운영자의 입장에서 관리할 대상에 대한 규정 (Spec)을 정의하고 Kubernetes에 등록한다. (Kubernetes의 CRD로 생성)
  • 관리할 대상이 유지해야 할 상태 정보를 규정에 맞도록 지정하고 Kubernetes에 등록한다. (Kubernetes의 CR 객체로 생성 - 상태 데이터로서 ETCD에 저장관리)
  • 상태 유지를 위한 컨트롤러를 구성해서 Kuberentes에 등록 (Kubernetes의 CC로 생성 - 원하는 상태 유지 작업)

CRD 방식을 사용하면 일반적인 Kubernetes 사용법 (kubectl - API Server)으로 운용이 가능하며 CRD로 등록된 리소스 객체를 그대로 사용할 수 있다. (아래 샘플 참조)

단, Custom Controller 나 API Server 연계 등에 대한 자세한 Spec 등과 가이드가 충분하지 않기 때문에 직접 개발 보다는 지원되는 Framework이나 툴을 이용해서 개발하게 된다.

  • Operator Fraemwork by Redhat (원래는 CoreOS 지만 Redhat에 흡수됨)
  • KUDO : Operator를 작성 배포하기 위한 Kubernetes Addon
  • KubeBuilder : CRD 방식이 아닌 API Server를 확장하는 개념으로 현재는 CRD 방식으로 방향성을 잡고 있으므로 용도가 좀 다르다고 생각된다.
  • Webhook + MetaController : Custom Controller를 쉽게 작성 및 배포하기 위한 Kuberenetes Addon
  • Operator Framework : Operator를 작성 배포하기 위한 Framework

아래의 그림은 Kuberentes의 동작 흐름이다.

Kubernetes Processing flow

아래의 그림은 Operator의 동작 흐름이다.

Kuberentes Operator Processing Flow

두 그림을 비교해 보면 같은 패턴의 프로세싱 흐름을 볼 수 있다. 따라서 향후 방향성은 CRD 기반의 Operator 중심으로 Kubernetes 모듈화 가속이 될 것으로 판단된다.

Operator Framework

참고

  • Operattor Framework은 CoreOS에서 시작되었고, 현재는 CoreOS를 인수한 Redhat에서 리딩하고 있다.
  • Operator Framework은 Cloud에서 하나의 Architecture Pattern으로 분류되며 단순한 작업을 자동화하기 위해 등장한 패턴으로 보면 된다.

Kubernetes 애플리케이션을 패키징, 배포, 운영 자동화를 제공하며 다음과 같은 툴을 제공한다.

Operator SDK

SDK를 이용하면 기존의 Kubernetes client-go 라이브러리를 사용하지 않고도 추상화된 Kubernetes API 사용이 가능하다.

Operator SDK : Build, Test, Iterate

Operator Lifecycle Manager (OLM)

OLM은 Operator의 생애주기를 관리하는 매니저로 Operator를 설치하고, 업데이트, 백업, 스캐일링 등을 처리한다. 현재 Operator SDK를 사용하지 않고 OLM을 통해서도 Operator 생성이 가능하다고 되어 있어 애매한 상태이며 향후 통합 또는 변경될 가능성이 있다.

Operator Metering

Operator Framework을 통해서 만들어진 애플리케이션과 Operator 조합을 대상으로 한 미터링 툴로 애플리케이션의 CPU/Memory 등에 대한 시계열 데이터를 활용 (Prometheus와 연동)해서 미터링 리포트 데이터를 생성하고 제공하는 역할을 담당한다.

Operator vs. Helm

Operator는 Helm 기능을 포함하고 있다. Helm이 애플리케이션의 배포 및 업그레이드를 다룬다면, Operator는 배포 및 업그레이드를 포함해 애플리케이션의 운영까지 관리하는 것이다.

단, Operator Framework을 도입하면 Helm Chart를 대체할 수도 있지만, Operator 자체를 Helm Chart로 만들수도 있고 (이때는 OLM 사용 배체), Operator 내부에서 애플리케이션 배포를 Helm을 통해서 할 수도 있다. 따라서 현재까지는 Helm을 대체한다기 보다는 서로 다른 용도/목적이라고 생각하면 된다.

Real Sample (Prometheus Operator)

순수 Prometheus만을 사용하다보면 여러 가지 문제들이 존재한다.

  • 점점 많아지는 Scrape, Rule Condition : 수집해야 하는 Metric 정보와 규칙들이 많아질 수록 Prometheus Configuration이 복잡해져 효율적 관리가 힘들고 CI/CD를 적용하려다 보면 Configuration 의 부분적 재 사용이 필요한 문제 -> Prometheus Configuration 을 CRD로 적용해서 결합도를 낮추고 (decoupled) 운영 효율성 높임
  • 상황에 따른 Logical data sharding 필요 : Operator를 이용하면 N개의 Prometheus를 설치/관리할 수 있다. 위의 decoupling과 연계해서 각 Prometheus른 서로 다른 Metric을 수집할 수 있고 Federation까지 활용해서 복잡한 구조의 Metric 수집도 쉽게 해결이 가능하다.

Prometheus Operator 작동 방식

위의 그림에서 ServiceMonitor는 동작하는 프로세스가 아니라 Configuration인 CRD에 해당한다.

  • 그림의 점선 박스 전체가 CRD
  • 아래의 'Operator'와 'Prometheus Server' 가 실제 동작하는 파드다.

예를 들어 운영자가 Service Monitor를 추가 생성하면 이를 Watching하고 있던 Operator가 새로 생성된 Service Monitor에 현재 동작하고 있는 Prometheus의 Configuration file을 업데이트하고 Prometheus를 재 실행 시킨다.

Kubernetes 확장

참고
Kubernetes의 확장 포인트는 다양하다. 그러나 이 문서에서는 Operator와 관련된 확장에 대해서만 다룬다.

Kubernetes Cluster Diagram

위의 그림은 Kubernetes의 주요 구성 요소들의 관계를 표현한 것으로 Operator와 관련된 부분은 주로 c-m (controller-manager)api server (with Aggregation Layer) 부분과 연관성이 크다.

  • 확장은 Kubernetes Control Plane과 상호 작용하며, 당연히 장애가 발생할 수 있는 위험성이 추가되는 형식이 된다.
    • 클라이언트로 동작하는 프로그램을 작성하는 패턴을 Controller Pattern이라고 한다.
    • Kubernetes가 클라이언트로 원격 서비스를 호출하는 방식을 Webhook이라고 하며 호출을 받아 처리하는 서비스를 Webhook Backend라고 한다.
  • Controller는 객체의 .spec (Wanted) 정보를 읽고 객체의 상태 (current state)와 비교해서 처리한 후에 .status (to ETCD)를 갱신하는 컨트롤 루프다.
  • CRD (Custom Resources Definition)은 API 확장의 범주에 속한다.

Extension Point가 Kubernetes Control Plane과 상호 작용하는 다이어그램

위의 그림은 확장 포인트에 따른 Kubernetes Control Plane과의 상호 작용을 나타내고 있다.

Operator as an extension of Kubernetes

정의

Operator는 사용자 정의 리소스 (CRD : Custom Resource Definition)를 기반으로 애플리케이션 및 컴포넌트를 관리하는 Kubernetes API 확장 개념

  • 서비스 또는 서비스 세트를 관리하는 운영자의 주요 목표가 타겟
  • 운영자가 주로 사용하는 작업들을 Kubernetes가 제공하는 이상으로 자동화를 하기 위한 방법
  • Kubernetes의 워크로드 배포, 실행 등의 자동화 및 수행 방식의 자동화
  • Kubernetes의 컨트롤러 개념을 기반으로 하는 CRD를 위한 컨트롤러 역할을 담당하는 API 클라이언트

What is the Custom Resource

정의

  • Custom Resource는 Custom Object의 모음이며 API 확장을 위한 기본 리소스로 결국은 사용할 객체를 정의하고 추상화한 구조적 데이터와 같다.
  • Custom Resource에서의 객체 정의는 완전히 새로운 객체가 아닌 이미 존재하는 Deployments, Services와 같은 기본 객체를 목적에 맞게 조합하고 추상화해서 새로운 이름으로 명시할 수 있다는 의미다.
  • Custom Resource Definition은 Costom Resource가 데이터로 어떤 항목이 정의되어야 하는지 등을 저장하는 선언적 메타데이터 객체일 뿐이다. (XML Schema 와 XML의 관게를 생각하면 이해하기 쉽다.)

Kubernetes에서는 Resource 개념이 존재하고 Resource는 Kubernetes Object들을 모아놓은 Kubernetes API의 Endpoint라고 정의하고 있다.

참고

  • Kubernetes API의 Endpoint라고 부르는 이유는 예를 들어 파드 리소스가 존재할 떄 이를 사용해서 작업을 하기 위해서는 파드의 CRUD 관련 API 묶음이 Kubernetes API로 등록되어야 하기 때문이라고 생각하면 편할 듯 하다.
  • 현재는 쿠버네티스의 핵심 기능들이 커스텀 리소스를 사용해서 구축되고 있으며 쿠버네티스의 모듈화쪽으로 가고 있다.
  • Custom Resource는 kubectl 을 통해서 사용 가능하다.

Kubernetes의 몇 가지 용어들을 정리하면 다음과 같다.

  • Kubernetes Objects

    "Persistent entities in the Kubernetes system" 로 정의되어 있으며 Kubernetes에 저장된 실체들 이라고 생각하면 된다.

    이 객체들로 다음과 같은 정보를 나타낼 수 있다.

    • 애플리케이션이 배정된 노드들
    • 애플리케이션이 사용할 수 있는 리소스들
    • 애플리케이션 동작에 대한 정책 (어떻게 재 시작할지, 업데이트할지 등)

    Kubernetes 객체들은 생성했을 떄 생성된 실체가 존재하는 것이라기 보다는 상태를 의미 한다. 예를 들어 kubectl을 통해 파드 1개를 생성하는 요청을 보내면 Kubernetes는 1개의 파드가 필요한 상태를 기록한다. 그리고 Kubernetes는 현재의 상태와 기록된 상태를 비교해서 원하는 상태를 맞추도록 동작하게 된다.

  • Object Spec and Status

    모든 Kubernetes 객체들은 공통적으로 두 개의 필드를 가진다.

    • Spec : 객체가 가질 상태에 대한 명세 정보
    • Status : 실체 클러스터에서 객체가 가진 상태 정보 (Kubernetes가 계속 검증하고 반영)
  • Kubernetes API

    Kubernetes의 객체를 이용해서 CRUD 작업을 하기 위해서는 Kubernetes API를 통해야 한다. 즉 사용자가 kubectl을 사용해서 객체 생성 명령을 실행하면 kubectl은 Kubernetes API로 요청을 하고 Kubernetes는 해당 객체를 생성하게 된다. (물론 kubectl이 아닌 Kubernetes API 클라이언트 라이브러리를 통해서 작업도 가능)

기본 제공되는 파드 리소스의 경우도 동일하게 생각하면 파드에 대한 정의와 상태 및 오퍼레이션 으로 생각하면 될 듯 하다. 물론 커스텀 리소스는 이런 기본 제공되는 리소스 이외의 사용자가 정의한 리소스를 의미하고 Kubernetes API를 확장하는 작업에 필요한 것이다.

How to use Custom Resource on Kubernetes

Custom Resource는 CRD (Custom Resource Definition) 또는 API Aggregation 방식으로 사용하는 것이 일반적이다.

using by CRD and Operator

가장 쉽고 일반적인 방법으로 CRD를 사용하는 것으로 CRD는 이미 Kubernetes에서 제공되는 객체의 한 종류로 kubectl get crd 명령으로 목록을 확인할 수 있고 kubectl apply -f ... 명령으로 CRD를 생성할 수도 있다.

그러나 Custom Resource Definition 이라는 이름에서 알 수 있듯이 커스텀 리소스가 어떤 데이터로 구성되어 있는지를 정의하는 객체일 뿐 CRD만으로는 실제 Custom Resource를 생성하지는 않으며, 단지 커스텀 리소스의 데이터에 어떤 항목이 정의되어야 하는지 등을 저장하는 선언적 메타데이터 객체일 뿐이다. 따라서 CRD로 부터 검증되어 실질적 Custom Resource를 생성하려면 CRD에 정의된 객체의 이름, 항목 등을 명시한 YML 파일을 만들고 생성해 줘야 한다. (XML Schema와 XML 파일의 관계로 생각하면 이해하기 쉽다)

이제 이해를 위한 간략한 샘플을 보도록 한다.

  • STEP 1 - 리소스 스키마인 Definition 정의와 데이터에 대한 선언적 Resource 정의

    "my-crd.yml" 파일로 CRD를 정의한다.

    apiVersion: "apiextensions.k8s.io/v1"
    kind: "CustomResourceDefinition"      # <- 객체 유형은 CRD
    metadata:
      name: "ccambo.morris.com"
    spec:                                 # <- 리소스 객체 정의 Spec
      group: "morris.com"
      version: "v1alpha1"
      scope: "Namespaced"
      names:
        plural: "ccambo"
        singular: "ccambo"
        kind: "Ccambo"                    # <- 객체 유형 정의
      validation:
        openAPIV3Schema:
          required: ["spec"]
          properties:
            spec:
              required: ["Pet"]           # <- 필수 정보 정의
              properties:
                Pet:                      # <- 데이터 형식 정의
                  type: "string"
                  minimum: 1

    위의 내용은 ccambo라는 리소스에 Pet 이라는 데이터 항목이 string 형식으로 반드시 존재해야 하는 것을 명시한 것이다. 따라서 정의한 규칙을 따르는 YML 파일만 리소스를 생성할 수 있다.

    위와 같이 CRD를 정의하고 CRD를 생성한다. (Definition 생성)

    $ kubectl apply -f my-crd.yml
    
    customresourcedefinition.apiextensions.k8s.io/ccambo.morris.com created
    
    $ kubectl get crd
    
    NAME                                                  CREATED AT
    ...
    ccambo.morris.com                                     2020-12-03T09:42:29Z
    ...

    규정에 맞는 실제 사용할 커스텀 리소스는 아래와 같이 "ccambo-resource.yml" 파일로 정의한다.

    apiVersion: "morris.com/v1alpha1"
    kind: "Ccambo"                  # <- 정의된 객체 유형
    metadata:
      name: "my-custom-resource"
    spec:
      Pet: "I like Hedgehog"        # <- 필수 데이터 항목 정의

    위의 내용은 검증용 샘플로 CRD로 정의된 형식에 맞춰서 작성한 것으로 아래의 명령으로 생성한다. (Resource 생성)

    $ kubectl apply -f ccambo-resource.yml
    
    ccambo.morris.com/my-custom-resource created
    
    $ kubectl get ccambo
    
    NAME                 AGE
    my-custom-resource   2m54s

    간단하게 커스텀 리소스를 생성해 보았다. 그러나 이런 리소스도 결국은 ETCD에 저장되는 단순한 데이터일 뿐이고 실제 동작하는 파드나 서비스는 아니라는 것을 명심해야 한다.

    커스텀 리소스는 리소스가 어떤 목적인지를 나타내는 데이터라면 이제 리소스가 실제 어떻게 동작할 것인지를 정의할 선언적 컨트롤러를 구현해야 한다.

  • STEP 2 - 동작을 규정하는 선언적 컨트롤러 구현

    쿠버네티스의 내장 객체를 예시로 생각해 보면 Deployment 객체의 목적은 "ReplicaSet"을 만드는 것이고, ReplicaSet 객체의 목적은 Matchlabel에 맞는 "파드"를 생성하는 것이다. 따라서 커스텀 리소스가 어떻게, 무엇을 위해서 동작할 것인지를 정의하는 부분이다.

    이런 선언적인 동작을 수행하도록 구현한 것이 컨트롤러 (Controller) 이며 커스텀 리소스가 가져야할 최종적인 상태를 유지하는 역할이다. 예를 들어 커스텀 리소스의 목적이 "CAT", "HEDGEHOG" 라는 두개의 파드가 실행 상태에 있어야 한다는 것이라면 컨틀롤러는 두 개의 파드를 생성하려고 시도하고 최종적으로 두 개의 파드가 실행 중인 상태를 유지한다.

    이 부분부터는 Kubernetes에서 제공하는 저수준의 API를 알아야 하고 어떻게 구현해야 하는지에 대한 가이드도 별로 없기 때문에 Operator SDK를 이용해서 처리해야 한다.

지금까지 언급한 내용을 정리하면 다음과 같다.

  1. 쿠버네티스의 객체 중에 하나인 CRD를 통해서 커스텀 리소스에 대한 정의를 생성한다.
  2. CRD로 정의된 포맷대로 커스텀 리소스를 생성한다.
  3. 커스텀 컨트롤러를 구현한다.
  4. 커스텀 컨트롤러는 쿠버네티스의 Watch API를 통해서 커스텀 리소스가 생성된 것을 식별한다.
  5. 커스텀 컨트롤러는 커스텀 리소스의 설정에 맞는 동작을 수행한다. (파드 또는 서비스 생성들...)

참고
Clound Native Infrastructure에서 자주 언급되는 단어들 중에 선언적 (Declarative) 와 명령적 (Imperative) 동작이라는 것이 있다. 쿠버네티스는 선언적 및 명령적 동작을 모두 지원하지만 가능하면 선언적 동작을 권장하고 있다.

  • 선언적 동작 : 원하는 상태를 미리 정의하고, 현재 상태가 원하는 상태가 되도록 하는 것을 의미한다.
  • 명령적 동작 : 지정한 상태로 만들어 버리는 것을 의미한다.

kubectl apply -f ... 는 선언적이고, kubectl create ... 는 명령적인 것을 의미한다.

using by API Aggregation

API Aggregation은 여러 개의 API 서버를 하나의 API 서버인 것처럼 사용할 수 있도록 하는 쿠버네티스의 기능을 말하는 것으로 공식 문서들이 너무 어렵게 쓰여 있지만 정리하면 그렇게 어려운 것은 아니다.

쿠버네티스는 API 서버의 기능을 직접 확장해 사용할 수 있도록 API Registration이라는 것을 제공한다. 별도로 필요한 기능이 있다면 이를 직접 별도의 서버에 구현한 뒤에 쿠버네티스에 등록하는 방식이다. 그냥 보기에는 기존 쿠버네티스 API 서버 엔드포인트와 별도로 구현한 API 서버의 엔드포인트가 각각 존재하기 때문에 각각 접속해야 할 것 같지만, 쿠버네티스는 API Aggreation이라는 기능을 통해서 여러 개의 API 서버에 접근할 수 있도록 추상화된 엔드포인트를 제공한다.

추가적인 기능을 구현한 별도의 API 서버는 apiserver-builderkubebuilder를 통해 구현할 수 있다. (실제 구현할 때는 sample-apiserver가 제공되고 있으므로 직접 테스트가 가능하다) 이를 통해서 구현된 API 서버를 APIService 객체를 통해 API 서버에 등록하면 직접 구현한 API 기능을 기존의 API 서버를 통해서 사용할 수 있게 된다.

정리하면 아래의 그림과 같이 기존 쿠버네티스 API 서버가 일종의 프록시 역할을 대신하는 것이라고 생각하면 된다.

API Aggregation을 통한 API 서버 연계

확장된 API 서버 내부에서 커스텀 리소스에 대한 API를 정의한 뒤 이를 APIService 를 통해서 등록하고 사용하면 된다. 아래의 내용은 sample-apiserver 예제에서 제공하는 APIService 내용이다.

apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1alpha1.wardle.k8s.io
spec:
  insecureSkipTLSVerify: true
  group: wardle.k8s.io
  groupPriorityMinimum: 1000
  versionPriority: 15
  service:
    namespace: wardle             # 1. wardle 이라는 네임스페이스에 존재하는
    name: api                     # 2. api 라는 서비스를 통해 확장된 API 서버에 접근할 수 있다.
  version: v1alpha1

개념적으로는 간단해도 별도의 API 서버를 구축하는 작업은 만만치 않아 보인다. apiserver-builderkubebuilder를 이용해서 서버를 빌드해야하고 쿠버네티스 API 서버와의 인증 작업도 신경써야 하며, API Aggregation을 통해서 커스텀 리소스를 정의헀다고 해도 커스텀 컨틀로러의 구현도 직접해야 하고 별도의 API 서버를 위한 스토리지 구성등도 따로 해줘야 하는 상황이 발생할 수 밖에는 없다.

mysql-operator의 mysql-apiserver 아키텍쳐 를 만든 사람이 구성한 아래의 그림을 살펴보는 것이 좋다.

mysql-apiserver Architecture

작성자는 CRD의 기능이 점차 API Aggregation의 기능을 따라잡고 있으니 이런 구조는 필요없을 것이라고 말하고 있다.

결론적으로 커스텀 리소스만을 사용할 것이라면 API Aggregation보다는 Operator를 통한 CRD를 사용하는 추세로 가는 것으로 보이고, 지금까지의 설명으로도 Operator가 더 쉽게 느껴진다. 물론 API Aggregation 기능이 커스텀 리소스에만 사용되는 것이 아니기 때문에 API Server의 확장을 위해서도 필요하므로 검토해 봐야 한다. 현재라도 CRD에서 지원하지 않는 기능은 API Aggregation을 통해서 확장하는 방법외에는 없다.

참고 자료

댓글