本記事には広告(アフィリエイトリンク)が含まれます。

Job・CronJob・DaemonSet|CKAD第11回

広告

新卒インフラエンジニア向け「Kubernetes 実践教科書① CKAD アプリケーション開発編」の第11回です。第10回で設定を ConfigMap / Secret に外部化しました。ここまでのワークロードは Pod / StatefulSet のように常駐するものでした。今回は 一度きりJob定期実行CronJob全ノードに 1 つずつ配置する DaemonSet を学び、fanclub-api に DB マイグレーション Job・サマリーレポート CronJob・ログコレクタ DaemonSet を追加します。

動作確認バージョン:kind v0.31.0(K8s v1.35.0)/ kubectl v1.35.6 / fanclub-backend:0.3.0 / postgres:18 / busybox:1.37(2026-06-27 実機検証時点)

広告

今ここマップ(全 19 回中の現在地)

現在地は第 3 部「アプリリソース」の第 11 回です。第 3 部の最後として、バッチ系のワークロードを押さえます。

  • 第 1 部 コンテナと Docker(第 1〜4 回)
  • 第 2 部 Kubernetes 基礎(第 5〜6 回)
  • 第 3 部 アプリリソース(第 7〜11 回)← 今ここ
  • 第 4 部 ワークロード戦略(第 12〜14 回)
  • 第 5 部 セキュリティ基礎(第 15〜16 回)
  • 第 6 部 パッケージ管理と HTTPS 公開(第 17〜19 回)

この回のゴール

  • Job / CronJob / DaemonSet を用途に合わせて選択し、実装できる。
  • Job の backoffLimit / restartPolicy / ttlSecondsAfterFinished を説明できる。
  • 冪等なマイグレーション Job、定期レポート CronJob、ログコレクタ DaemonSet を fanclub-api に追加できる。

ワークロードリソースの全体像

「Pod をどう動かし続けるか」を担うのがワークロードリソースです。常駐・単発・定期・全ノードで使い分けます。

  • Deployment(常駐・ステートレス):Web/API などをレプリカ維持・ローリングアップデート(第12回)。
  • StatefulSet(常駐・ステートフル):DB など。安定 ID + 専用ストレージ(第9回)。
  • Job(単発):一度きりのバッチ。完了したら終了し、再起動し続けない。
  • CronJob(定期):cron スケジュールで Job を定期生成。
  • DaemonSet(全ノード):対象ノードごとに 1 Pod(ログ/監視エージェント等)。
5 つのワークロードリソースの使い分けを示す早見図。常駐ステートレスは Deployment、常駐ステートフルは StatefulSet、一度きりは Job、定期実行は CronJob、全ノード 1 Pod は DaemonSet。それぞれの用途と生成される Pod の振る舞いを対比する。
図1:ワークロードリソースの使い分け(常駐 / 単発 / 定期 / 全ノード)

Job(一度きりのバッチ)

Job は処理を最後まで実行して完了させるワークロードです。Deployment と違い、完了した Pod を再起動し続けません。主なフィールドは次のとおりです。

  • completions:成功させたい実行回数(既定 1)。parallelism:同時実行数。
  • backoffLimit:失敗時のリトライ上限。超えると Job は失敗扱いになる。
  • restartPolicy:Job では NeverOnFailure のみAlways は指定できません(apply で弾かれます)。
  • ttlSecondsAfterFinished:完了/失敗した Job を一定秒後に自動削除(後片付け)。

CronJob(定期実行)

CronJobschedule の cron 式に従って Job を定期生成します。cron 式は 分 時 日 月 曜日 の 5 フィールドで、例えば 0 9 * * * は「毎日 9:00」です。

  • concurrencyPolicy:前回の実行が終わっていないときの挙動。Allow(既定)/ Forbid(重複させない)/ Replace
  • startingDeadlineSeconds:開始が遅れた場合に実行を諦める猶予。
  • スケジュールを待たずに今すぐ試したいときは、kubectl create job <名前> --from=cronjob/<CronJob名> で手動実行できます。

DaemonSet(全 Workload Node へ 1 Pod)

DaemonSet は対象ノードごとに必ず 1 つ Pod を配置します。ノードが増えれば自動で 1 Pod 追加されます。ログ収集エージェントやノード監視のように「各ノードに 1 つ要る」ものに使います。

  • nodeSelector / affinity:配置するノードを限定する。
  • tolerations:taint の付いたノード(Control Plane 等)にも配置したい場合に付ける。
  • 本回の kind は単一ノードなので DaemonSet の Pod は 1 つです(複数ノードでの本番ログ基盤 Fluent Bit は第2巻で扱います)。

fanclub-api への適用

DB マイグレーション Job

members テーブル自体は第9回の初期化(initdb)で作成済みです。ここではその後のスキーマ/データ移行を Job で実演します。移行は冪等(何度実行しても同じ結果)にするのが鉄則です。今回は「列の追加(ADD COLUMN IF NOT EXISTS)」と「シードデータ投入(ON CONFLICT DO NOTHING)」を行います。DB 接続情報は第10回で作った ConfigMap / Secret を envFrom で再利用します(設定外部化の恩恵)。

次のマニフェストを developer のホームに fanclub-db-migrate.yaml として作成します(一般ユーザー所有のファイルで、kubectl apply はクラスタへ送るだけなので root 権限は不要です)。マイグレーションは postgres:18 イメージの psql で実行します。

apiVersion: batch/v1
kind: Job
metadata:
  name: fanclub-db-migrate
spec:
  backoffLimit: 3
  ttlSecondsAfterFinished: 300
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: migrate
        image: postgres:18
        envFrom:
        - configMapRef:
            name: fanclub-config
        - secretRef:
            name: fanclub-db-secret
        command:
        - sh
        - -c
        - |
          set -e
          echo "migration start"
          PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 \
            -c "ALTER TABLE members ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP" \
            -c "INSERT INTO members (name,email,plan) VALUES ('鈴木花子','hanako@example.com','standard'),('佐藤一郎','ichiro@example.com','free') ON CONFLICT (email) DO NOTHING"
          echo "migration done"
  • restartPolicy: Never:失敗時は新しい Pod でリトライ(backoffLimit: 3 まで)。
  • ttlSecondsAfterFinished: 300:完了 5 分後に Job を自動削除。
  • envFrom で第10回の fanclub-config / fanclub-db-secret を再利用し、psql$DB_HOST 等を渡します。

実行コマンド:

$ kubectl apply -f fanclub-db-migrate.yaml
$ kubectl get job fanclub-db-migrate
$ kubectl logs job/fanclub-db-migrate

実行結果(例):Job が Complete になり、ログに INSERT 0 2(2 件追加)が出ます。

NAME                 STATUS     COMPLETIONS   DURATION   AGE
fanclub-db-migrate   Complete   1/1           3s         4s

migration start
ALTER TABLE
INSERT 0 2
migration done

冪等性を確認します。Job をいったん削除して同じものを再適用すると、ADD COLUMN IF NOT EXISTSON CONFLICT DO NOTHING により、追加は INSERT 0 0(0 件)になり、会員数は増えません。完了 Job は ttlSecondsAfterFinished: 300 で 5 分後に自動削除されるため、すでに消えている場合に備えて削除には --ignore-not-found を付けます。

実行コマンド:

$ kubectl delete job fanclub-db-migrate --ignore-not-found
$ kubectl apply -f fanclub-db-migrate.yaml
$ kubectl logs job/fanclub-db-migrate | grep INSERT

実行結果(例):

INSERT 0 0

シードされた会員はブラウザ / API からも見えます。/api/members に 3 件(山田太郎・鈴木花子・佐藤一郎)が並びます。

実行コマンド:

$ kubectl run c1 --rm -i --restart=Never --image=curlimages/curl:latest -- \
    curl -s http://fanclub-backend:8080/api/members

実行結果(例・実際は 1 行で返りますが読みやすく改行しています):

[{"createdAt":"2026-06-27T00:04:07.560548","email":"taro@example.com","id":1,"name":"山田太郎","plan":"premium"},
 {"createdAt":"2026-06-27T09:47:02.547117","email":"hanako@example.com","id":2,"name":"鈴木花子","plan":"standard"},
 {"createdAt":"2026-06-27T09:47:02.547117","email":"ichiro@example.com","id":3,"name":"佐藤一郎","plan":"free"}]
Job と CronJob が Pod を生成する関係を示す図。Job は 1 回の実行で Pod を生成し完了する。CronJob は schedule ごとに Job を生成し、その Job が Pod を生成する。kubectl create job --from=cronjob で手動実行もできることを示す。
図2:Job / CronJob と生成される Pod の関係(CronJob → Job → Pod)

会員サマリーレポート CronJob

毎朝 9:00 にプラン別の会員数を集計する CronJob を作ります。次を developer のホームに fanclub-report.yaml として作成します(一般ユーザー所有・root 不要)。

apiVersion: batch/v1
kind: CronJob
metadata:
  name: fanclub-report
spec:
  schedule: "0 9 * * *"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      backoffLimit: 2
      ttlSecondsAfterFinished: 300
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: report
            image: postgres:18
            envFrom:
            - configMapRef:
                name: fanclub-config
            - secretRef:
                name: fanclub-db-secret
            command:
            - sh
            - -c
            - |
              echo "=== 会員サマリーレポート ==="
              PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" \
                -c "SELECT plan, count(*) AS members FROM members GROUP BY plan ORDER BY plan"

9:00 を待たずに動作確認するため、CronJob から手動で Job を作って実行します。実行コマンド:

$ kubectl apply -f fanclub-report.yaml
$ kubectl get cronjob fanclub-report
$ kubectl create job report-manual --from=cronjob/fanclub-report
$ kubectl logs job/report-manual

実行結果(例):プラン別の集計が出力されます。

=== 会員サマリーレポート ===
   plan   | members
----------+---------
 free     |       1
 premium  |       1
 standard |       1
(3 rows)

ログコレクタ DaemonSet(模擬)

各ノードに 1 つ配置するログコレクタを busybox で模擬します。次を developer のホームに fanclub-logcollector.yaml として作成します(一般ユーザー所有・root 不要)。

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fanclub-logcollector
spec:
  selector:
    matchLabels:
      app: fanclub-logcollector
  template:
    metadata:
      labels:
        app: fanclub-logcollector
    spec:
      containers:
      - name: logcollector
        image: busybox:1.37
        command:
        - sh
        - -c
        - 'echo "log collector started on $(hostname)"; while true; do sleep 3600; done'
        resources:
          requests:
            memory: "16Mi"
            cpu: "10m"
          limits:
            memory: "64Mi"
            cpu: "50m"

実行コマンド:

$ kubectl apply -f fanclub-logcollector.yaml
$ kubectl get daemonset fanclub-logcollector
$ kubectl get pods -l app=fanclub-logcollector -o wide

実行結果(例):単一ノードの kind では DESIRED=1 で 1 Pod が配置されます。

NAME                   DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
fanclub-logcollector   1         1         1       1            1           <none>          60s

NAME                         READY   STATUS    RESTARTS   AGE   IP            NODE
fanclub-logcollector-vqstz   1/1     Running   0          60s   10.244.82.3   kind-control-plane

やってみよう

  1. fanclub-db-migrate.yaml(Job)を作成・適用し、Complete とログの INSERT 0 2 を確認する。
  2. Job を削除して再適用し、INSERT 0 0(冪等)と会員数が増えないことを確認する。
  3. fanclub-report.yaml(CronJob)を作成し、kubectl create job --from=cronjob/fanclub-report で手動実行してプラン別集計を確認する。
  4. fanclub-logcollector.yaml(DaemonSet)を作成し、ノードごとに 1 Pod 配置されることを確認する。
  5. kubectl get job,cronjob,daemonset で 3 種のワークロードが揃ったことを確認する。

理解度チェック

次の各文が正しいか(○)誤りか(×)を判断してください。解答は下にまとめています。

  1. Job は処理を完了させるワークロードで、完了後に Pod を再起動し続けない。
  2. Job の restartPolicy には Always を指定できる。
  3. ttlSecondsAfterFinished で完了した Job を自動削除できる。
  4. CronJob の schedule は cron 式で定期実行のタイミングを指定する。
  5. CronJob はスケジュールの時刻が来るまで手動では実行できない。
  6. DaemonSet は対象ノードごとに 1 つの Pod を配置する。
  7. 第10回で作った ConfigMap / Secret は、Job / CronJob からも envFrom で再利用できる。

解答

  • 1. ○:Job は完了したら終了する(Deployment のように再起動し続けない)。
  • 2. ×:Job は NeverOnFailure のみ。Always は不可。
  • 3. ○:完了/失敗 Job を一定秒後に自動削除する。
  • 4. ○:分 時 日 月 曜日 の 5 フィールドで指定する。
  • 5. ×:kubectl create job --from=cronjob/... で手動実行できる。
  • 6. ○:ノードごとに 1 Pod。kind 単一ノードでは 1 つ。
  • 7. ○:configMapRef / secretRef で同じ設定を再利用できる。

まとめ

本記事では、常駐とは異なるワークロードとして Job(単発)・CronJob(定期)・DaemonSet(全ノード 1 Pod)を学びました。fanclub-api には、冪等な DB マイグレーション Job(backoffLimit / ttlSecondsAfterFinished / restartPolicy: Never)、プラン別集計の CronJob(手動トリガで確認)、ログコレクタ DaemonSet を追加しました。DB 接続情報は第10回の ConfigMap / Secret を envFrom でそのまま再利用でき、設定外部化の効果も確認できました。これで第 3 部「アプリリソース」は完了です。

次回予告

次回(第12回)から第 4 部「ワークロード戦略」に入ります。Backend を Deployment 化し、3 種の Probe(liveness / readiness / startup)とローリングアップデート、Probe のデバッグを学びます。

シリーズ一覧

第1部:コンテナと Docker

第2部:Kubernetes 基礎

第3部:アプリリソース

第4部:ワークロード戦略

第5部:セキュリティ基礎

第6部:パッケージ管理 + HTTPS 公開

広告
kubernetes
スポンサーリンク