: O. Yuanying

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をコピーしてもう一度実行する

一度実行された CronJobJob リソースを作成します。その 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 という便利な機能が増えていてリソースの任意のフィールドを別のフィールドからコピーして使うことができるのです。

例えば以下のように CronJobJob を用意します。

# 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 してみると Jobspec がちゃんと埋められているのが確認できます。

# 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 用にもディレクトリを切っておけば CronJobJob が別々にビルドすることができるという寸法です。

まとめ

CronJob から Job を作る方法を5種紹介しました。他にもこんな方法があるよ!という方がいらっしゃいましたら是非教えてください。

追記

早速 @superbrothers から以下のコマンドを教えてもらいました。

kubectl create job test-job --from=cronjob/a-cronjob

jq いらんかったのか。。。

追記2

どちらかというと kustomize replacements で object も扱えることに感動したのだけど、例が悪かった気がしてきた。