Kubernetes(이하 k8s)에서 어떠한 작업을 수행하기 위해 자동으로 사전에 선언해둔 형태로 pod을 만들고, 내부에서 원하는 작업을 수행할 수 있습니다. 이것을 Job이라고 하는데요. 주기적으로 Job을 생성하는 CronJob의 형태로 생성할 수도 있습니다.
하지만 k8s 공식 문서에서는 단편적인 예제만 제공하고, 각 속성에서 어떤 값을 사용할 수 있는지 파악하기 어려웠습니다. 이에 기록의 필요성을 느껴 글을 작성하게 되었습니다.
서비스 동작 후 3-4시간이 지난 시점에 동작의 문제가 발생하며, 재시작하면 문제가 일시적으로 해결되는 상황입니다.
해당 서비스의 특성상 재시작으로 인한 데이터 유실은 걱정하지 않아도 되었으므로, 재현 환경을 별도로 구성한 뒤 개발 단에서 원인 파악 및 해결이 완료될 때까지 2시간마다 재시작을 반복하기로 했습니다.
이해하기 쉬운 CronJob을 작성하려면, 각 과정에서 해야 하는 작업을 명확히 하는 것이 좋습니다. 우리 서비스에서 '재시작'이 의미하는 바는 다음과 같습니다.
flush all
명령으로 제거할 수 있습니다.)특수한 경우가 아니라면 명령어로 목표한 목적을 실행할 수 있어야 자동화가 간편합니다. 이에 각 절차를 한 줄의 명령으로 실행하여 잘 동작하는지 검증합니다.
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
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 파일의 형태로 작성합니다.
# 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