CronJobからJobを作る
CronJob
から Job
を作る。何を今更当たり前のことをブログのエントリにしてるんだ、と思われるかもしれないが聞いてください。
ユースケース
例えば以下のようなCronJob
でデータベースをバックアップしていたとしましょう。
apiVersion: batch/v1
kind: CronJob
metadata:
name: mail-backup
spec:
schedule: "33 8 * * *"
jobTemplate:
metadata:
labels:
job: mail
spec:
template:
spec:
containers:
- name: backup
image: ghcr.io/yuanying/docker-sshd:0.2.2
imagePullPolicy: IfNotPresent
command: [sudo, rsync, -avz, --delete, /original/, /backup/]
restartPolicy: OnFailure
ふと、ディスクの交換を思い立ち、このバックアップを日次処理ではなく、「今」実行したくなった場合どうすればいいでしょうか?ベタな方法を含めて5つほど方法が考えつきます。
CronJob と同じ Job を実行する方法
1. 同じ内容のJobを作成しapplyし、実行する
まずは一番ベタな方法です。CronJobの内容をそのままコピーして新しくJobを作ってそれをapplyします。
# cat <<EOF | k apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: mail-backup
spec:
template:
spec:
containers:
- name: backup
image: ghcr.io/yuanying/docker-sshd:0.2.2
imagePullPolicy: IfNotPresent
command: [sudo, rsync, -avz, --delete, /original/, /backup/]
restartPolicy: OnFailure
EOF
ベタですねー。同じ内容のコマンドやイメージを使って内容をコピーして作ってます。どっちかを修正した場合、もう一方も修正しなくちゃなりません。Single source of Truthの原則から言ってもこんな管理はしたくありません。
2. すでに実行されたJobをコピーしてもう一度実行する
一度実行された CronJob
は Job
リソースを作成します。その Job
を再利用する方法です。jq
を使えば意外と簡単に再利用できますね!
注意点としては、一度作られた Job
にはcontrollerによっていくつか値が設定されているのでそれをリセットしておく必要があるくらいです。
$ k get job -l job=mail -o json \
| jq '.items[-1]
| .metadata.name = "oneshot-job"
| del(.spec.template.metadata.labels)
| del(.spec.selector)' \
| k apply -f -
3. CronJob の jobTemplate から直接 Job を作る
上記のの方法は一つ問題点があって、それは最低でも一回 CronJob
によって Job
が実行されていなくてはいけないという点です。しかしあなたは気づくはずです、そもそも Job
の元となるデータは CronJob
が全て持っていたじゃないかと。それを再利用するのは jq
マスターなあなたなら容易なはずです。
$ k get cronjob mail-backup -o json \
| jq -r '.spec.jobTemplate
| .+{"apiVersion": "batch/v1", "kind": "Job"}
| .metadata |= .+ {"name": "oneshot-job"}' \
| k apply -f -
4. kustomize replacements
上記の方法を使えばうまく CronJob
から Job
を作ることができました。しかしあなたはきっとモヤモヤしてるはずです。なぜ Kubernetes ネイティブな俺様が jq
などという外部バイナリを使わなければならないんだ。
そんなあなたに kustomize
です。kustomize
には何時ごろからかは知りませんが replacements
という便利な機能が増えていてリソースの任意のフィールドを別のフィールドからコピーして使うことができるのです。
例えば以下のように CronJob
と Job
を用意します。
# cat <<EOF > cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: mail-backup
spec:
schedule: "33 8 * * *"
jobTemplate:
metadata:
labels:
job: mail
spec:
template:
spec:
containers:
- name: backup
image: ghcr.io/yuanying/docker-sshd:0.2.2
imagePullPolicy: IfNotPresent
command: [sudo, rsync, -avz, --delete, /original/, /backup/]
restartPolicy: OnFailure
EOF
# cat <<EOF > job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: oneshot-job
labels:
type: oneshot
spec:
EOF
あれ、Job
の spec が空じゃないか?と思ったかもしれませんが大丈夫です。この src.yqml
を読み込むように kustomization.yaml
を作ります。
# cat <<EOF > kustomization.yaml
resources:
- cronjob.yaml
- job.yaml
replacements:
- source:
kind: CronJob
name: mail-backup
fieldPath: spec.jobTemplate.spec
targets:
- select:
kind: Job
name: oneshot-job
fieldPaths:
- spec
EOF
肝は replacements
のところで、この設定で CronJob
の jobTemplate を参照して Job
の spec を埋めてくれます。build してみると Job
のspec
がちゃんと埋められているのが確認できます。
# k kustomize . | tail -n 21
apiVersion: batch/v1
kind: Job
metadata:
labels:
type: oneshot
name: oneshot-job
spec:
template:
spec:
containers:
- command:
- sudo
- rsync
- -avz
- --delete
- /original/
- /backup/
image: ghcr.io/yuanying/docker-sshd:0.2.2
imagePullPolicy: IfNotPresent
name: backup
restartPolicy: OnFailure
k kustomize
すると CronJob も build されてしまうので apply
するときはラベルを指定してあげましょう。
$ k apply -k ./ -l type=oneshot
job.batch/oneshot-job created
5. kustomize replacements (2)
apply
するときに毎回ラベルを指定するのは嫌だな。もしかしたらそう思う方もいるかもしれません。私は思いました。Job
を実行する時は CI からではなく手作業で行うことが多いですので(そこが問題だ、とか言わないでください。)ラベルを指定し忘れることは十分考えられます。
そんな時はこれです。「PodTemplate
」リソースの出番です。
なんだそのリソースは、と思うかもしれませんが公式も思ったらしく一時期削除されそうになったという曰く付きの不遇な core/v1
リソースです。これを有効活用してあげましょう。
# mkdir base
# cat <<EOF > base/src.yaml
apiVersion: v1
kind: PodTemplate
metadata:
name: mail-backup
template:
spec:
containers:
- command: [sudo, rsync, -avz, --delete, /original/, /backup/]
image: ghcr.io/yuanying/docker-sshd:0.2.2
imagePullPolicy: IfNotPresent
name: backup
restartPolicy: OnFailure
EOF
# cat <<EOF > base/kustomization.yaml
resources:
- src.yaml
EOF
削除が検討されただけあって、このリソースを実際に作っても何も起きません。
$ k apply -k base
podtemplate/mail-backup created
この PodTemplate
リソースを参照するように Job
を作ってあげれば CronJob
を build してしまうこともありません。PodTemplate
リソースが build されてしまうことが若干気持ち悪いですが、、何もしないとい噂ですので無視しましょう。
# mkdir job
# cat <<EOF > job/src.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: oneshot-job
labels:
type: oneshot
spec:
template:
EOF
# cat <<EOF > job/kustomization.yaml
resources:
- ../base
- src.yaml
replacements:
- source:
kind: PodTemplate
name: mail-backup
fieldPath: template
targets:
- select:
kind: Job
name: oneshot-job
fieldPaths:
- spec.template
EOF
こんな感じで job
ディレクトリを切って Job
ビルド用の kustomization.yaml
を用意します。
$ k kustomize job | head -n 21
apiVersion: batch/v1
kind: Job
metadata:
labels:
type: oneshot
name: oneshot-job
spec:
template:
spec:
containers:
- command:
- sudo
- rsync
- -avz
- --delete
- /original/
- /backup/
image: ghcr.io/yuanying/docker-sshd:0.2.2
imagePullPolicy: IfNotPresent
name: backup
restartPolicy: OnFailure
ビルドしてみるとちゃんと oneshot Job
が作られていることが確認できますね!同じように CronJob
用にもディレクトリを切っておけば CronJob
と Job
が別々にビルドすることができるという寸法です。
まとめ
CronJob
から Job
を作る方法を5種紹介しました。他にもこんな方法があるよ!という方がいらっしゃいましたら是非教えてください。
追記
早速 @superbrothers から以下のコマンドを教えてもらいました。
kubectl create job test-job --from=cronjob/a-cronjob
jq
いらんかったのか。。。
追記2
どちらかというと kustomize replacements で object も扱えることに感動したのだけど、例が悪かった気がしてきた。