kubernetes の cronjob のマニフェストファイルが長すぎ問題に対応するために動的に作る


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
現在の評価: 5

コメント

コメントを投稿
コメントするには TORICO-ID にログインしてください。
ログイン コメント利用規約