kubernetes の cronjob のマニフェストファイルを書く時、普通に手作業で YAML ファイルを書こうとすると Cronjob ごとに同じ項目が多くなるため、DRYでなくなり管理がしにくくなります。 Pod内で複数のコンテナを扱ったり、volumeMount が多いワークロードを動かす時はより困難になります。
そのため、cronjob のマニフェストは手で書かず、半分動的に作る方法に落ち着きましたので紹介します。
テンプレートの YAMLファイルと差分を用意し、それを Python スクリプトで組み合わせて Cronjob の YAML を組み立てます。
cronjob のテンプレートファイルを作る
cronjob-template.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: UNTITLED # override
namespace: torico
labels:
app: my-awesome-app
spec:
concurrencyPolicy: Replace
schedule: "30 12 * * *" # override
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- command: [] # override
args: [] # override
name: my-awesome-app
image: torico/my-awesome-app-image:latest
imagePullPolicy: Always
env:
- name: DJANGO_SETTINGS_MODULE
value: my_awesome_app.settings.production
volumeMounts: []
volumes: []
正しい YAML ファイルとして、cronjob のテンプレートを作ります。 volumeMounts, volumes は空リストにしていますが、これは deployment.yaml にある定義からコピーします。
image, env 等も deployment からコピーしてもいいと思いますし、containers をまるごと deployment からコピーして来ても良いと思います。
deployments のファイル
deployments は既に運用しているものがあります。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-awesome-app-deployment
namespace: torico
spec:
replicas: 2
selector:
matchLabels:
app: my-awesome-app
template:
metadata:
labels:
app: my-awesome-app
spec:
containers:
- name: my-awesome-app
image: torico/my-awesome-app-image:latest
imagePullPolicy: Always
env:
- name: DJANGO_SETTINGS_MODULE
value: my_awesome_app.settings.production
- containerPort: 8002
volumeMounts:
- name: storage-1
mountPath: /mnt/storage-1
- name: storage-2
mountPath: /mnt/storage-2
readinessProbe:
httpGet:
port: 8002
path: /
livenessProbe:
httpGet:
port: 8002
path: /
volumes:
- name: storage-1
persistentVolumeClaim:
claimName: pvc-storage-1
- name: storage-2
persistentVolumeClaim:
claimName: pvc-storage-2
実際は、volumeのマウントが10個ぐらいあるワークロードだったため、コピペで cronjob マニフェストを作るのは避けました。
cronjob の差分ファイルを作る
cronjob-specs.py
LOG_DIR = '/var/log/cronlog'
cronjob_specs = [
{
'metadata.name': 'my-awesome-app-cronjob-1',
# JST 2:10, 19:10
'spec.schedule': '10 10,17 * * *',
'spec.jobTemplate.spec.template.spec.containers.0.command': [
'/bin/bash',
],
'spec.jobTemplate.spec.template.spec.containers.0.args': [
'-c',
f'/app/sh/app-job-1.sh >> {LOG_DIR}/app-job-1.log 2>&1',
],
},
{
'metadata.name': 'my-awesome-app-cronjob-1',
# JST 4:10, 14:10
'spec.schedule': '10 5,19 * * *',
'spec.jobTemplate.spec.template.spec.containers.0.command': [
'/bin/bash',
],
'spec.jobTemplate.spec.template.spec.containers.0.args': [
'-c',
f'/app/sh/app-job-2.sh >> {LOG_DIR}/app-job-2.log 2>&1',
],
},
...
]
ジョブの差分のみ、列挙します。
なお、この例ではワークロードの都合上シェルスクリプトの結果をログファイルにリダイレクトしていますが、 実際はファイルに書き出さずに標準出力に出したほうが都合が良いと思います。
cronjob ファイルを生成するスクリプトを書く
Python で書きました。
#!/usr/bin/env python3
import copy
import os
import yaml
from cronjob_specs import cronjob_specs
def set_value(y: dict, k: str, v: any) -> None:
keys = k.split('.')
for key in keys[:-1]:
if isinstance(y, list):
y = y[int(key)]
else:
y = y[key]
y[keys[-1]] = v
def main():
os.chdir(os.path.dirname(__file__))
cj = yaml.load(open('cronjob-template.yml', 'r'), Loader=yaml.SafeLoader)
dep = yaml.load(open('deployment.yml', 'r'), Loader=yaml.SafeLoader)
cronjob_yamls = []
for spec in cronjob_specs:
y = copy.deepcopy(cj)
print('{} を生成中'.format(spec['metadata.name']))
for k, v in spec.items():
set_value(y, k, v)
y['spec']['jobTemplate']['spec']['template']['spec']['containers'][0][
'volumeMounts'
] = dep['spec']['template']['spec']['containers'][0]['volumeMounts']
y['spec']['jobTemplate']['spec']['template']['spec']['volumes'] = dep[
'spec'
]['template']['spec']['volumes']
cronjob_yamls.append(y)
with (open('_generated_cronjob.yml', 'w')) as f:
f.write('# このファイルは、generate_cronjob.py により生成されています。\n')
f.write('# 直接編集してはいけません。\n')
yaml.dump_all(cronjob_yamls, f)
if __name__ == "__main__":
main()
Kubernetes に Apply する
apply-cronjob.sh
#!/usr/bin/env zsh
cd $(dirname $0) || exit
export KUBECONFIG=${HOME}/.kube/config-my-cluster
./generate_cronjob.py
kubectl apply -f _generated_cronjob.yml