# 5. 커스텀 리소스, 컨트롤러, 잡, 데몬셋, 스테이트풀셋

# 커스텀 리소스

  • 쿠버네티스에서 자체적으로 제공하는 리소스 외에 직접 정의해 사용할 수 있는 리소스
  • 커스텀 리소스를 제대로 사용하려면 컨트롤러라는 별도 컴포넌트를 이해하고 구현할 수 있어야 함

# 쿠버네티스 컨트롤러

  • 도커와 쿠버네티스는 리소스를 생성하는 방식이 다름
    • 도커 : docker run처럼 특정 명령을 처리하는 주체와 통신해 그 작업을 수행하고 그 결괏값을 돌려 받는 방식 (명령형)
    • 쿠버네티스 : kubectl apply처럼 최종적으로 도달해야 하는 상태(바람직한 상태, Desired State)를 정의한 뒤, 현재 상태가 바람직한 상태와 다를 경우 이를 일치시키는 방법 (선언형)
  • 선언형 사용 시 최종적으로 완성되어야 하는 상태가 되기 위해 어떠한 동작을 하는지는 쿠버네티스에서 컨트롤러가 내부적으로 결정
  • 이때 상태는 etcd에 저장되어 있으며, 컨트롤러는 쿠버네티스 API 서버의 Watch API를 통해 etcd에 저장된 상태를 받아와 동작 수행
    • etcd : 클러스터 상태와 설정을 저장하는 오픈소스 분산 키-값 저장소
    • Watch API : 리소스의 생성, 수정, 삭제 같은 변경 이벤트를 클라이언트나 다른 컨트롤러에게 실시간으로 알려주는 기능
  • 커스텀 리소스는 직접 정의해 사용할 수 있는 사용자 정의 리소스
  • 디플로이먼트, 서비스 등의 오브젝트의 묶음을 커스텀 리소스로 추상화 함으로써 쿠버네티스 리소스를 묶어 놓은 패키지처럼 사용할 수도 있음
  • 위 그림과 같이 여러 리소스를 한번에 생성할 수 있고, 각 리소스의 생애 주기를 쉽게 관리 가능
  • 많은 관리의 복잡성을 줄일 수 있고, 오브젝트를 원하는 대로 확장해 사용 가능

# Custom Resource Definition

  • crd라 하며 kubectl get crd 명령어를 통해 목록 확인 가능
  • 커스텀 리소스를 정의하는 역할, crd 자체가 커스텀 리소스를 의미하는건 아님
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: managedredises.cloud.com # CRD 이름
spec:
  group: cloud.com
  names: # CR 이름
    kind: ManagedRedis
    plural: managedredises
    shortNames:
    - mr
    singular: managedredis
  scope: Namespaced # 네임스페이스에 속하는지 여부
  versions:
  - name: v1
    schema:
      openAPIV3Schema:
        properties:
          spec:
            properties:
              image:
                description: The image of Redis cluster
                type: string
              replica:
                description: The number of Redis replica
                type: integer
            required: # 반드시 포함되어야 하는 spec 정의 부분
            - replica
            - image
            type: object
          status: # 해당 CRD를 관리하는 컨트롤러에 의해 채워지는 현재 상태 정보
            properties:
              message: # 현재 상태 설명
                description: A descriptive message about the cluster's status.
                type: string
              phase: # 현재 상태
                description: The current phase of the Redis cluster (e.g., Pending,
                  Running, Failed).
                type: string
              primary_endpoint: # 쓰기 주소
                description: The Redis cluster's primary connection endpoint for writing.
                type: string
              secondary_endpoint: # 읽기 주소
                description: The Redis cluster's secondary connection endpoint for
                  reading.
                type: string
            type: object
            x-kubernetes-preserve-unknown-fields: true # CRD 스키마에서 알 수 없는(정의되지 않은) 필드를 허용
        type: object
    subresources:
      status: {}
  • 위와 같이 crd를 정의하는 것 외에 특정 동작을 수행하는 컨트롤러를 정의해줘야 함
  • 컨트롤러는 Watch API를 통해 새로운 CR이 생성되었다는 것을 갑지하고, CR이 원하는 바람직한 상태가 되도록 특정 동작 수행
  • 이러한 바람직한 상태가 되도록 특정 동작을 수행하는 것을 Reconcile이라 부름
  • 이렇게 일련의 동작을 통해 CRD를 사용할 수 있도록 컨트롤러를 구현하는 방법을 오퍼레이터(Operator) 패턴이라 부름

#

  • 잡(Job)은 특정 동작을 수행하고 종료해야 하는 작업을 위한 오브젝트
  • 잡에서 원하는 최종 상태는 포드가 실행되어 정상적으로 종료되는 것
apiVersion: batch/v1
kind: Job
metadata:
  name: job-hello-world
spec:
  template:
    spec:
      restartPolicy: Never # OnFailure 설정 시 spec.backoffLimit으로 재시도 횟수 설정 가능 (기본적으로는 6번)
      containers:
      - image: busybox
        args: ["sh", "-c", "echo Hello, World && exit 0"]
        name: job-hello-world
> kubectl apply -f job-hello-world.yaml
job.batch/job-hello-world created

> kubectl get pods
NAME                    READY   STATUS      RESTARTS   AGE
job-hello-world-cj7lv   0/1     Completed   0          12s

> kubectl get jobs
NAME              STATUS     COMPLETIONS   DURATION   AGE
job-hello-world   Complete   1/1           8s         9s
  • 한 번 수행하고 종료되는 배치 작업에 사용됨
  • 잡은 동시성을 엄격하게 보장해야 하는 병렬 처리를 위해 사용하는 것이 아님
  • restartPolicy에 의해 재시작될 수도 있어서 잡이 처리하는 작업은 멱등성을 가져야 함
    • 멱등성 : 첫 번째 수행을 한 뒤 여러 차례 적용해도 결과를 변경시키지 않는 작업 또는 기능의 속성

# 잡의 세부옵션

  • spec.completions : 잡이 성공했다고 여겨지려면 몇 개의 포드가 성공해야 하는지
  • spec.parallelism : 동시에 생성될 포드의 개수 (기본값은 1)
  • spec.activeDeadlineSeconds : 포드가 실행될 수 있는 최대 시간. 더 오래 실행될 경우 강제로 종료되며, 잡은 실패함

# 크론잡으로 잡을 주기적으로 실행하기

  • 잡을 주기적으로 실행하는 쿠버네티스 오브젝트
apiVersion: batch/v1
kind: CronJob
metadata:
  name: cronjob-ex
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      restartPolicy: Never
      containers:
      - image: busybox
        args: ["sh", "-c", "echo Hello, World && exit 0"]
        name: job-hello-world
  • 리눅스의 크론 스케줄 방법을 사용하여 정의 가능

# 데몬셋

  • 데몬셋(DaemonSet)은 쿠버네티스의 모든 노드에 동일한 포드를 하나씩 생성하는 오브젝트
  • 로깅, 모니터링, 네트워킹 등을 위한 에이전트를 각 노드에 생성해야 할 때 사용
> kubectl get daemonsets -n kube-system
NAME         DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
kube-proxy   1         1         1       1            1           kubernetes.io/os=linux   69d
  • 쿠버네티스 네트워킹을 위한 kube-proxy 컴포넌트 등이 데몬셋으로 실행되고 있음
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: daemonset-example
spec:
  selector:
    matchLabels:
      name: my-daemonset-example # 포드를 생성하기 위한 셀렉터
  template:
    metadata:
      labels:
        name: my-daemonset-example # 포드 라벨
    spec:
      containers:
      - name: daemonset-example
        image: busybox
        args: ["tail", "-f", "/dev/null"]
  • 특정 노드에만 데몬셋의 포드를 생성하고 싶다면 nodeSelector나 Node Affinity를 포드에 적용할 수도 있음

# 스테이트풀셋

  • 쿠버네티스에서 MSA 구조로 동작하는 애플리케이션은 대부분 Stateless인 경우가 많음
  • 데이터베이스처럼 Stateful한 애플리케이션은 스테이트풀셋(StatefulSet)으로 관리 가능
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: statefulset-example
spec:
  serviceName: statefulset-service
  selector:
    matchLabels:
      name: statefulset-example
  replicas: 3
  template:
    metadata:
      labels:
        name: statefulset-example
    spec:
      containers:
      - name: statefulset-example
        image: alicek106/rr-test:echo-hostname
        ports:
        - containerPort: 80
          name: web

---

apiVersion: v1
kind: Service
metadata:
  name: statefulset-service
spec:
  ports:
   - port: 80
     name: web
  clusterIP: None
  selector:
    name: statefulset-example
> kubectl apply -f statefulset-example.yaml
statefulset.apps/statefulset-example created

> kubectl get sts
NAME                  READY   AGE
statefulset-example   3/3     18s

> kubectl get pods
NAME                    READY   STATUS      RESTARTS   AGE
job-hello-world-cj7lv   0/1     Completed   0          54m
statefulset-example-0   1/1     Running     0          25s
statefulset-example-1   1/1     Running     0          13s
statefulset-example-2   1/1     Running     0          12s
  • 포드에 랜덤한 이름이 아닌 고유한 이름이 붙혀짐
  • spec.ServiceName을 정의하는 이유는 랜덤 포드가 아닌 개별 포드에 요청을 전달하기 위함
  • 각 포드는 고유하게 식별되어야 하며, 랜덤한 포드로 전달되는 동작은 스테이트풀셋이 원하는 동작이 아님
  • 이때 일반적인 서비스가 아닌 헤드리스 서비스 사용 가능

# 헤드리스 서비스

  • clusterIP의 항목이 None으로 되어 있음 → 헤드리스 서비스 의미
  • 로드 밸런싱을 수행하지 않고, 개별 파드에 직접 접근할 수 있는 경로를 제공하는 방식
> kubectl run -i --tty --image busybox:1.28 debug --restart=Never --rm \
> nslookup statefulset-service
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      statefulset-service
Address 1: 10.1.1.186 statefulset-example-0.statefulset-service.default.svc.cluster.local
Address 2: 10.1.1.188 statefulset-example-2.statefulset-service.default.svc.cluster.local
Address 3: 10.1.1.187 statefulset-example-1.statefulset-service.default.svc.cluster.local
  • 위와 같이 <포드의 이름>.<서비스 이름>을 통해서도 포드에 접근 가능
  • 포드의 데이터는 일반적으로 휘발성이기 때문에 스테이트풀셋에 PV를 마운트해서 데이터를 보존하는 것이 일반적임
  • PVC를 정의해두고 다이나믹 프로비저닝으로 PV 자동 생성 방식도 사용 가능