Kubernetes CronJob에서 kubectl로 리소스 제어 자동화하기

김재훈·2023년 8월 13일
0
post-thumbnail

Kubernetes(이하 k8s)에서 어떠한 작업을 수행하기 위해 자동으로 사전에 선언해둔 형태로 pod을 만들고, 내부에서 원하는 작업을 수행할 수 있습니다. 이것을 Job이라고 하는데요. 주기적으로 Job을 생성하는 CronJob의 형태로 생성할 수도 있습니다.

하지만 k8s 공식 문서에서는 단편적인 예제만 제공하고, 각 속성에서 어떤 값을 사용할 수 있는지 파악하기 어려웠습니다. 이에 기록의 필요성을 느껴 글을 작성하게 되었습니다.

원인 파악

서비스 동작 후 3-4시간이 지난 시점에 동작의 문제가 발생하며, 재시작하면 문제가 일시적으로 해결되는 상황입니다.

해당 서비스의 특성상 재시작으로 인한 데이터 유실은 걱정하지 않아도 되었으므로, 재현 환경을 별도로 구성한 뒤 개발 단에서 원인 파악 및 해결이 완료될 때까지 2시간마다 재시작을 반복하기로 했습니다.

CronJob 작성하기

업무 절차 파악하기

이해하기 쉬운 CronJob을 작성하려면, 각 과정에서 해야 하는 작업을 명확히 하는 것이 좋습니다. 우리 서비스에서 '재시작'이 의미하는 바는 다음과 같습니다.

  1. Pod을 제어하는 Deployment에서 Scale을 '0'으로 설정합니다.
  2. Pod이 캐시를 저장하는 데 사용하는 redis key를 모두 제거합니다.
    (flush all 명령으로 제거할 수 있습니다.)
  3. Deployment에서 Scale을 다시 '1'로 설정합니다.

명령어로 테스트하기

특수한 경우가 아니라면 명령어로 목표한 목적을 실행할 수 있어야 자동화가 간편합니다. 이에 각 절차를 한 줄의 명령으로 실행하여 잘 동작하는지 검증합니다.

Pod 스케일 조정하기

kubectl이라고 하는 k8s 설정을 위한 CLI 도구가 존재하는데, 이를 이용하면 Pod의 스케일을 조정하는 작업을 손쉽게 할 수 있습니다.

# 스케일을 0으로 설정
# => 해당 Deployment가 제어하는 Pod이 0개가 되게 함(즉 모두 삭제)
kubectl scale deployment '서비스의 Deployment 이름' -n '네임스페이스 이름' --replicas=0

# 스케일을 1로 설정
# => 해당 Deployment가 제어하는 Pod을 1개 배포
kubectl scale deployment '서비스의 Deployment 이름' -n '네임스페이스 이름' --replicas=0

redis 캐시 모두 지우기

kubectl은 exec 명령을 이용해 특정 pod에 연결하거나, 명령을 실행할 수 있습니다.

이를 확인하기 위해 우선 redis 캐시를 모두 지우는 과정이 어떻게 진행되는지 정리합니다. 저는 Lens라고 부르는 GUI 앱을 사용하고 있으며, 이를 통해 pod shell에 접근할 수 있어 접속하는 과정은 생략합니다.

# pod에서 redis-cli로 접근
redis-cli
# redis-cli가 켜지면 아래 명령어 실행
flushall

이제 실행해야 하는 명령어들을 확인했으므로 이를 kubectl에 그대로 옮겨줍니다.

kubectl exec 'redis pod 이름' -n '네임스페이스 이름' -- redis-cli flushall

yaml 파일로 옮기기

각 행위를 아래와 같이 yaml 파일의 형태로 작성합니다.

# CronJob을 작성하기 위해 종류(kind)를 CronJob으로 선언하고,
# apiVersion은 `batch/v1`을 사용합니다.
kind: CronJob
apiVersion: batch/v1
metadata:
  name: restart-cronjob # 해당 CronJob의 이름을 설정합니다.
spec:
  # schedule에 반복할 주기를 설정합니다.
  schedule: "0 */2 * * *"  # 2시간마다 실행
  jobTemplate:
    spec:
      # 실패할 때마다 다시 시도할 횟수를 설정합니다.
      # 적은 숫자만큼 Pod을 계속 생성하기 때문에, 0으로 설정했습니다.
      backoffLimit: 0
      # CronJob이 성공해도 기본적으로 CronJob이 동작하면서 생성한 Job과 Pod들은 제거되지 않습니다.
      # ttl 값을 설정하면 입력한 숫자(단위: 초)만큼 시간이 지난 후 작업을 위해 생성됐던 Job과 Pod이 제거됩니다.
      ttlSecondsAfterFinished: 30
      template:
        spec:
          # pod이 kubectl 명령어를 통해 자신이 속한 클러스터에서 작업을 실행하려면 권한이 필요합니다.
          # 필요한 권한이 설정된 서비스 계정 이름을 입력합니다.
          serviceAccountName: default
          # Job이 실패했을 때 반복할지 여부를 입력합니다. Never 또는 OnFailure만 설정할 수 있습니다.
          restartPolicy: Never
          # 작업을 실행할 컨테이너들을 순서대로 입력합니다.
          containers:
          - name: scale-down-service
            # kubectl 명령어가 기본으로 제공되는 도커 이미지를 이용합니다.
            image: bitnami/kubectl:latest
            command:
            - /bin/sh
            - -c
            - kubectl scale deployment example-deployment -n example-namespace --replicas=0
          - name: redis-flush-all
            image: bitnami/kubectl:latest
            command: 
            - /bin/sh
            - -c
            - kubectl exec example-redis -n redis -- redis-cli flushall
          - name: scale-up-service
            image: bitnami/kubectl:latest
            command: 
            - /bin/sh
            - -c
            - kubectl scale deployment aggregator-worker -n orakl --replicas=1

계정 생성하기

위에 최종적으로 작성한 yaml 파일을 유심히 보면, kubectl 명령을 실행하기 위한 권한들이 있음을 알 수 있습니다. 저는 네임스페이스 생성 시 기본적으로 생성되는 서비스 계정인 default 사용자에게 필요한 권한들을 부여하여 해결했습니다.

k8s에서는 권한과 권한 할당, 사용자가 모두 분리되어 있는데, 이를 통해 중복되는 권한을 하나로 묶어 통일하고 이를 여러 사용자에게 할당할 수 있습니다.

이렇게 하나로 묶인 것을 역할(Role)이라 부르고, RoleBinding을 통해 사용자에게 Role을 할당할 수 있습니다. 하지만 하나의 Role은 자신이 속한 네임스페이스에서만 권한을 행사할 수 있으므로 예시 CronJob처럼 조작해야 하는 리소스들이 여러 네임스페이스에 걸쳐 있는 경우 ClusterRole을 이용할 수 있습니다.

ClusterRole은 모든 네임스페이스에 대해 동일한 권한을 갖습니다.

# (Cluster 포함)Role, RoleBinding 종류를 처리하기 위한 apiVersion은 `rbac.authorization.k8s.io/v1`을 사용합니다.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: restart-role
rules:
  # deployment를 확인할 수 있어야 그와 연계되는 명령을 실행할 수 있습니다.
  # 따라서 deployment에 대한 get, list 권한을 할당합니다.
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list"]
  # scale 명령은 별도의 리소스로 취급됩니다.
  # scale의 값을 올리고 내리는 작업은 patch 권한이 필요하므로 이를 할당합니다.
  - apiGroups: ["apps"]
    resources: ["deployments/scale"]
    verbs: ["patch"]
  # pod은 별도 apiGroup이 없으므로 빈 문자열을 사용합니다.
  # exec 명령을 사용하려면 pod을 조회할 수 있어야 하므로 get, list 권한도 함께 할당합니다.
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "exec"]
  # exec 권한이 있어도 그 뒤에 따라오는 작업은 exec 리소스의 create 권한을 필요로 합니다.
  # 따라서 해당 권한을 할당합니다.
  - apiGroups: [""]
    resources: ["pods/exec"]
    verbs: ["create"]

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: restart-binding
# subjects에서 역할이 할당될 대상을 지정할 수 있습니다.
subjects:
- kind: ServiceAccount
  name: default
  namespace: exampleNamespace
# roleRef를 통해 할당할 역할을 지정할 수 있습니다.
roleRef:
  kind: ClusterRole
  name: restart-role
  apiGroup: rbac.authorization.k8s.io
profile
개발하면서 새롭게 배운 내용, 시행착오한 내용들을 잊지 않기 위해 기록합니다.

0개의 댓글