Chap 16. 사용자 정의 리소스

ka8main.png

사용자 정의 리소스란?

Custom Resource

사용자 정의 리소스는 쿠버네티스 API를 사용자가 원하는대로 확장한 리소스를 말한다. 예를 들어, 쿠버네티스 코어 API에 Pod라는 리소스가 있지만 사용자가 원하는 특별한 기능을 더한 MyPod라는 리소스를 새롭게 정의할 수 있다. 쿠버네티스에는 사용자가 쉽게 API를 확장할 수 있도록 CustomResourceDefinition(CRD)라는 리소스를 제공한다.

CRD는 바로 새로운 리소스를 정의하는 리소스이다.

# mypod-crd.yaml
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: mypods.crd.example.com
spec:
  group: crd.example.com
  version: v1
  scope: Namespaced
  names:
    plural: mypods    # 복수 이름
    singular: mypod   # 단수 이름
    kind: MyPod       # kind 이름
    shortNames:       # 축약 이름
    - mp
  • kind: CustomResourceDefinition — 이 리소스가 CRD임을 나타냄. 쿠버네티스에서 새로운 리소스 타입을 정의할 때 사용함.
  • name: mypods.crd.example.com — CRD의 전체 이름. . 형식을 따르며, 이 이름으로 API 서버에 등록됨.
  • group: crd.example.com — 해당 CRD가 속한 API 그룹. 커스텀 리소스는 코어 그룹이 아닌 사용자 정의 그룹에 속함.
  • version: v1 — 리소스의 버전. 동일 그룹 내 여러 버전을 둘 수 있으며, 이 버전은 사용하는 스키마에 영향을 줌.
  • scope: Namespaced — 리소스가 네임스페이스 범위인지(Namespaced) 클러스터 범위인지(Cluster) 지정.
  • names: 복수, 단수, kind, 축약 이름 등 이 리소스를 참조할 때 사용하는 명명 규칙 정의.
  • plural: mypods — REST API에서 URL 경로로 사용됨 (/apis/crd.example.com/v1/namespaces/{namespace}/mypods)
  • singular: mypod — 단수형 이름, 일반적으로 kubectl 명령어 또는 코드에서 사용
  • kind: MyPod — 생성되는 리소스 오브젝트의 종류. Go 코드에서 타입으로 사용
  • shortNames: mp — kubectl에서 축약 이름으로 사용 가능 (kubectl get mp)
kubectl apply -f mypod-crd.yaml

kubectl get crd | grep mypods
apiVersion: crd.example.cop/v1
kind: MyPod
metadata:
  name: mypod-test
spec:
  uri: "any uri"
  customCommand: "custom command"
  image: nginx
kubectl get mypod

kubectl get mp

새로운 리소스를 만들었지만, 어떤 동작을 수행해야 하는지에 대해 아무런 정보가 없다.

실제 동작을 수행하기 위해서는 Custom Controller의 도움이 필요하다.

Custom Controller

쿠버네티스에서는 적절한 권한만 가지고 있다면 쿠버네티스 API를 통해 특정 리소스의 상태를 조회할 수 있다. Controller는 쿠버네티스 클러스터 내에 Pod 형태로 존재하면서 주기적으로 특정 리소스의 이벤트를 모니터링하다가 해당 리소스의 상태 변화에 따라 사전에 정의된 동작을 수행하는 주체이다. 예를 들어, Deployment Controller는 쿠버네티스 내부 리소스인 Deployment의 생성, 삭제 이벤트와 같은 상태 변화를 지속적으로 관찰(watching)하여 새로운 Deployment 리소스 생성 시 Deployment에 정의된 정보에 맞게 Pod들을 배포하는 역할을 담당한다.

GO
아래는 Go 언어 기반의 MyPod 컨트롤러 예시 코드이다:

package main

import (
    "context"
    "fmt"
    "time"

    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/client-go/dynamic"
    "k8s.io/client-go/tools/clientcmd"
)

func main() {
    config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile)
    if err != nil {
        panic(err)
    }

    dynClient, err := dynamic.NewForConfig(config)
    if err != nil {
        panic(err)
    }

    gvr := schema.GroupVersionResource{
        Group:    "crd.example.com",
        Version:  "v1",
        Resource: "mypods",
    }

    for {
        mypods, err := dynClient.Resource(gvr).Namespace("default").List(context.TODO(), metav1.ListOptions{})
        if err != nil {
            fmt.Println("Error listing MyPods:", err)
        } else {
            for _, pod := range mypods.Items {
                fmt.Printf("Found MyPod: %s\n", pod.GetName())
            }
        }

        time.Sleep(10 * time.Second)
    }
}

위 코드는 간단한 Dynamic 클라이언트를 활용해 “mypods” 리소스를 10초마다 조회하며, 감지된 리소스를 출력한다.

Python
아래는 Python 언어 기반의 MyPod 컨트롤러 예시 코드이다:

from kubernetes import client, config
import time

def main():
    config.load_kube_config()
    custom_api = client.CustomObjectsApi()

    group = "crd.example.com"
    version = "v1"
    namespace = "default"
    plural = "mypods"

    while True:
        try:
            response = custom_api.list_namespaced_custom_object(
                group=group,
                version=version,
                namespace=namespace,
                plural=plural
            )
            for item in response.get('items', []):
                print(f"Found MyPod: {item['metadata']['name']}")
        except Exception as e:
            print(f"Error retrieving MyPods: {e}")
        time.sleep(10)

if __name__ == "__main__":
    main()

위 코드는 Kubernetes Python 클라이언트를 활용하여 “mypods” 리소스를 주기적으로 조회하고, 이름을 출력한다.

Operator 패턴

Operator 패턴이란 Custom Resource와 그에 대응하는 컨트롤러의 조합을 이용하여 특정 애플리케이션이나 서비스의 생성과 삭제를 관리하는 패턴을 말한다.

Operator패턴을 이용해서 쿠버네티스 코어 API에 포함되지 않은 애플리케이션을 마치 쿠버네티스 native리소스처럼 동작하게끔 만들 수 있다.

💡 신규 프로젝트를 시작할때마다 CI/CD를 위한 Jenkins 애플리케이션을 매번 새로 생성하고 프로젝트가 완료된 이후 jenkins를 삭제하는 시나리오

#!/bin/bash

# 1. Jenkins Custom Resource 배포
echo "📦 Jenkins Custom Resource 배포 중..."

cat <<EOF | kubectl apply -f -
apiVersion: jenkins.io/v1alpha1
kind: Jenkins
metadata:
  name: jenkins-ci
  namespace: ci-namespace
spec:
  master:
    image: jenkins/jenkins:lts
    resources:
      limits:
        memory: "1Gi"
        cpu: "500m"
  service:
    type: ClusterIP
EOF

echo "✅ Jenkins 배포 완료"

# 2. 프로젝트 완료 후 Jenkins 삭제
read -p "❓ 프로젝트가 완료되었습니까? [y/N]: " confirm
if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
  echo "🗑 Jenkins 삭제 중..."
  kubectl delete jenkins jenkins-ci -n ci-namespace
  echo "✅ Jenkins 삭제 완료"
else
  echo "⏸ Jenkins 삭제를 취소했습니다."
fi

이 스크립트는 다음과 같은 목적에 맞춰 구성되어 있다:

  • Operator에 의해 관리되는 Jenkins 리소스를 생성
  • 사용자의 입력에 따라 삭제 작업을 자동화

Operator tools

Operator를 쿠버네티스 API를 이용하여 처음부터 개발하는 것도 가능하지만, Operator를 편리하게 만들 수 있게 제공하는 툴들이 이미 있다.

  • kubebuilder: Go 기반의 Operator 개발 프레임워크. controller-runtime 라이브러리를 활용하며, CLI를 통해 CRD, 컨트롤러, 웹훅 등을 자동 생성할 수 있어 빠르게 프로젝트를 시작할 수 있다.
  • Operator Framework: Go, Helm, Ansible을 이용한 다양한 방식의 Operator 생성을 지원하는 생태계. Operator SDK, OLM, OperatorHub 등을 포함하여 전체 수명주기 관리가 가능하다.
  • Metacontroller: 컨트롤러 로직을 외부 웹훅으로 위임하여 다양한 언어로 구현 가능하게 하는 프레임워크. 빠른 프로토타이핑이나 가벼운 컨트롤러 작성에 적합하다.
  • KUDO: 선언형 YAML만으로 Operator를 작성할 수 있게 해주는 프레임워크. Helm보다 복잡한 상태 관리가 필요한 경우 유용하며, 반복 가능한 배포를 쉽게 구성할 수 있다.