Certified Kubernetes Administrator (CKA) #11 Workloads 2: DaemonSet、StatefulSet、Job、CronJob

#10 Workloads 1 では Deployment と ReplicaSet でステートレスアプリを動かし、rolling update と rollback を手に覚えました。ところがすべてのワークロードが「どのノードでも構わない同じ Pod を複数」で終わるわけではありません。ノードごとに正確に 1 つずつ立つべきエージェントがあり、各 Pod が固有の名前と自分専用のディスクを持つべきデータベースがあり、一度回って終わるべきバッチ作業があり、その作業を毎日明け方に自動で回す必要があるケースがあります。

この記事は Deployment では解けない 4 つのワークロードである DaemonSet、StatefulSet、Job、CronJob を扱います。それぞれがどんな問題を解くために存在するのか、Deployment と何が違うのか、そして試験によく出る YAML と kubectl のパターンを整理します。

Deployment ではなぜ足りないのか #

#10 で見た Deployment は ステートレス (stateless) アプリのためのワークロード です。同じ Pod を replicas の個数だけ立て、どのノードに立っても構わず、Pod 同士を区別する理由がありません。このモデルで解けない 4 つの要求がこの記事のテーマです。

要求解いてくれるワークロード
すべてのノードに正確に 1 つずつ立つべきDaemonSet
各 Pod が固有の名前・順序・自分のディスクを持つべきStatefulSet
一度実行して完了まで責任を持つJob
決まったスケジュールで繰り返し実行するCronJob

この 4 つを Deployment と比べると違いが明確になります。

区分DeploymentDaemonSetStatefulSetJob / CronJob
目的ステートレスアプリノードごとのエージェント状態保存アプリバッチ・一回限り・スケジュール作業
Pod の個数replicas で指定ノード数に応じて自動replicas で指定completions で指定
Pod 名ランダムハッシュノードごとに 1 つ安定した序数 (0、1、2…)ランダムハッシュ
作成・終了の順序保証なし保証なし順序保証completions まで繰り返し
ストレージ通常は共有・外部通常はホストパスPod ごとに専用 PVC通常はなし
restartPolicyAlwaysAlwaysAlwaysOnFailure/Never

それでは各々を見ていきます。

DaemonSet: ノードごとに 1 つずつ #

DaemonSet は クラスターのすべて (または一部) のノードに Pod を正確に 1 つずつ 立てるワークロードです。ノードが新しく join するとその DaemonSet はそのノードにも自動で Pod を追加し、ノードが抜けるとその Pod も消えます。replicas フィールドがない理由はここにあります。個数を人が決めるのではなく、ノード数がそのまま Pod 数になります。

典型的な使いどころは、ノード単位で動作すべきインフラエージェントです。

  • ログ収集器 (fluentd、fluent-bit)
  • ノード監視 (node-exporter)
  • ネットワークプラグイン (CNI)。kube-proxy 自体も DaemonSet で立つケースが多い
  • ストレージデーモン

基本マニフェスト #

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app: node-exporter
  template:
    metadata:
      labels:
        app: node-exporter
    spec:
      containers:
        - name: node-exporter
          image: prom/node-exporter:v1.8.0
          ports:
            - containerPort: 9100

Deployment マニフェストで kind を DaemonSet に変えて replicas だけ消した形とほぼ同じです。selector と template のラベルが一致すべきというルールも同じです。

特定のノードにだけ立てる: nodeSelector #

すべてのノードではなく一部のノードにだけ載せるべきときは、template の Pod spec に nodeSelector を置きます。ラベルが一致するノードにだけ Pod が立ちます。

spec:
  template:
    spec:
      nodeSelector:
        disktype: ssd

control plane ノードにも立てる: tolerations #

デフォルト設定では control plane ノードには taint が掛かっており、一般 Pod が配置されません。ログ収集器のように control plane ノードにも立つべき DaemonSet なら、その taint に耐える toleration を入れる必要があります。taint と toleration の詳しい動作は #14 で扱います。

spec:
  template:
    spec:
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule

更新戦略 #

DaemonSet のデフォルトの更新戦略は RollingUpdate です。template を変えるとノードの Pod を 1 つずつ新しいバージョンに置き換え、maxUnavailable で同時に落とせるノード数を制御します。

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1

もう 1 つの戦略は OnDelete です。template を変えても自動では置き換えず、該当の Pod を自分で削除してはじめてその場所に新しい template で再び立ちます。置き換えのタイミングを人が制御したいときに使います。

# 状態確認 (ノード数と desired/ready を比較)
k get daemonset -n monitoring
k rollout status ds/node-exporter -n monitoring

StatefulSet: 安定 ID と順序 #

StatefulSet は 状態を保存すべきアプリ のためのワークロードです。データベース、メッセージブローカー、分散 KV ストアのように、各インスタンスが固有のアイデンティティと自分専用のデータを持つべきケースで使います。Deployment が与えられない 3 つを保証します。

  1. 安定したネットワーク ID。Pod 名が 名前-0名前-1名前-2 のように序数で固定されます。Pod が死んで再び立っても、同じ名前と同じ DNS 名で戻ってきます。
  2. 順序保証。作成は 0 から順に (0 が Ready であってはじめて 1 を作成)、削除とスケールダウンは高い番号から逆順で進みます。
  3. Pod ごとの専用ストレージvolumeClaimTemplates で各 Pod ごとに独立した PVC が作られ、Pod が再作成されても同じ番号の PVC に再び接続されます。

headless Service がまず必要だ #

StatefulSet の安定した DNS 名は headless Service があってはじめて動作します。headless Service は clusterIP: None で作る Service で、クラスター IP を割り当てる代わりに各 Pod の DNS レコードを直接公開します。その結果、各 Pod が Pod 名.サービス名.ネームスペース.svc.cluster.local の形の固有アドレスを持ちます。Service の種類と動作全般は #18 で扱います。

StatefulSet + headless Service の例 #

# headless Service: clusterIP が None
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  clusterIP: None
  selector:
    app: nginx
  ports:
    - name: web
      port: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: nginx       # 上の headless Service の名前と一致すべき
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.27
          ports:
            - name: web
              containerPort: 80
          volumeMounts:
            - name: data
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates:     # Pod ごとに PVC を自動生成
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Gi

このマニフェストが作るものを整理します。

  • Pod は web-0web-1web-2 の順に作成されます。
  • 各 Pod には data-web-0data-web-1data-web-2 という PVC が自動で付きます。
  • DNS 名は web-0.nginx.<namespace>.svc.cluster.local のように安定して保たれます。
# Pod 名が序数で立ったか確認
k get pods -l app=nginx

# 自動生成された PVC を確認
k get pvc

# headless Service のエンドポイント (各 Pod IP) を確認
k get endpoints nginx

スケールと削除時の注意 #

スケールダウンすると Pod は高い番号から消えますが、volumeClaimTemplates で作った PVC は自動では削除されません。 データ損失を防ぐための意図された動作です。StatefulSet を消しても PVC は残るので、整理が必要なら PVC を自分で削除する必要があります。

# スケールダウン (web-2 が先に消え、PVC は残る)
k scale statefulset web --replicas=2

# StatefulSet だけ削除して Pod は残す (まれに使用)
k delete statefulset web --cascade=orphan

更新戦略はデフォルトが RollingUpdate で、高い番号の Pod から逆順で置き換えます。partition の値を置くと、その番号以上だけを更新する段階的ロールアウト (canary) も可能です。

Job: 完了を目標に回る作業 #

Deployment と DaemonSet は Pod を 生かし続ける ワークロードです。一方 Job は 決まった回数だけ成功裏に完了したら終わる ワークロードです。データマイグレーション、バッチ計算、バックアップスクリプトのように、一度 (または決まった回数) 回って終わるべき作業に使います。

主要なフィールドは 4 つです。

フィールド意味
completions成功すべき総 Pod 数。デフォルト 1
parallelism同時に回せる Pod 数。デフォルト 1
backoffLimit失敗時の再試行上限。超過すると Job 失敗
restartPolicyOnFailure または Never のみ許可 (Always 不可)

Deployment と違い、Job の Pod には restartPolicy: Always を使えません。完了を目標とする作業に無限再起動は意味をなさないからです。

基本 Job の例 #

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  completions: 4        # 計 4 回成功すべき完了
  parallelism: 2        # 一度に 2 個ずつ並列実行
  backoffLimit: 4       # 失敗時に最大 4 回まで再試行
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: pi
          image: perl:5.34
          command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
# Job の状態 (COMPLETIONS が 4/4 になれば完了)
k get jobs

# Job が作った Pod とログを確認
k get pods --selector=job-name=pi
k logs job/pi

# 完了した Job を整理
k delete job pi

activeDeadlineSeconds を置くとその時間を超えた Job を強制終了でき、ttlSecondsAfterFinished を置くと完了した Job とその Pod を一定時間後に自動で整理します。

CronJob: スケジュールに合わせて Job を打ち出す #

CronJob は cron 式に従って周期的に Job を作成する ワークロードです。CronJob 自体は直接 Pod を作らず、スケジュールごとに Job オブジェクトを 1 つずつ作り、その Job が Pod を作って作業を実行します。夜間バックアップ、定期レポート、キャッシュ整理のような繰り返し作業に使います。

主要なフィールドは次のとおりです。

フィールド意味
schedulecron 式 (分 時 日 月 曜日)
concurrencyPolicy前の実行が終わっていないときのポリシー
successfulJobsHistoryLimit保管する成功 Job 数。デフォルト 3
failedJobsHistoryLimit保管する失敗 Job 数。デフォルト 1
startingDeadlineSeconds予定時刻を逃したときに許容する遅延上限

concurrencyPolicy の値は 3 つです。

  • Allow (デフォルト)。前の実行が終わっていなくても新しい Job を同時に開始します。
  • Forbid。前の実行が終わっていなければ今回の実行をスキップします。
  • Replace。前の実行をキャンセルして新しい Job に置き換えます。

CronJob の例 #

apiVersion: batch/v1
kind: CronJob
metadata:
  name: db-backup
spec:
  schedule: "0 3 * * *"           # 毎日明け方 3 時
  concurrencyPolicy: Forbid       # 前のバックアップが終わっていなければスキップ
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  startingDeadlineSeconds: 120
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: backup
              image: bitnami/postgresql:16
              command: ["/bin/sh", "-c", "pg_dump ... > /backup/dump.sql"]

schedule分 時 日 月 曜日 の 5 桁です。0 3 * * * は毎日 03:00、*/15 * * * * は 15 分ごと、0 0 * * 0 は毎週日曜日の深夜を意味します。

# CronJob の一覧と最後の実行時刻
k get cronjob

# CronJob が打ち出した Job を確認
k get jobs --watch

# 今すぐ一度手動実行 (テストするとき)
k create job manual-backup --from=cronjob/db-backup

CronJob を一時的に止めるには spec.suspend: true を設定すると、新しい Job の作成が中断されます。点検中にバックアップが実行されるのを防ぐときに便利です。

試験ポイント #

CKA 実技でこれらのワークロードが出るとき、点数を分ける箇所を集めました。

  • 命令型作成で時間節約。Job と CronJob は kubectl create で骨組みを作ったあと do (--dry-run=client -o yaml) でマニフェストを抜き出して編集する方が速いです。

    k create job test --image=busybox $do -- /bin/sh -c "echo hi" > job.yaml
    k create cronjob hello --image=busybox --schedule="*/1 * * * *" $do \
      -- /bin/sh -c "date" > cron.yaml
  • DaemonSet は kubectl create で作れない。DaemonSet 専用の作成コマンドがないので、Deployment マニフェストを do で抜き出したあと kind を DaemonSet に変えて replicas を消すパターンを覚えておくと速いです。

  • restartPolicy の罠。Job と CronJob の Pod template には OnFailure または Never のみ許可されます。デフォルト値 (Always) をそのまま置くとマニフェストが拒否されるので、必ず明示します。

  • StatefulSet は serviceName と headless Service がセットserviceName が指す headless Service (clusterIP: None) がないと安定した DNS が動作しません。両方を 1 つのマニフェストで一緒に提出する習慣が安全です。

  • PVC は残る。StatefulSet をスケールダウンしたり削除したりしても volumeClaimTemplates が作った PVC は自動削除されません。問題が整理を求めるなら PVC を自分で消す必要があります。

  • selector と template のラベル一致。4 つのワークロードはすべて #10 で見たように、selector.matchLabelstemplate.metadata.labels が一致しないと作成が拒否されます。

これらのワークロードを実際のクラスターで自分で作って壊してみる練習が、試験会場での手の速さを作ります。より広い背景が必要なら、Kubernetes 中級シリーズ で同じリソースを運用の観点から改めて整理してあります。

まとめ #

この記事で押さえたこと:

  • DaemonSet。ノードごとに Pod 1 つ。replicas なし。nodeSelector で対象ノードを制限、tolerations で control plane ノードまで拡張、RollingUpdate/OnDelete の更新戦略
  • StatefulSet。安定した序数 ID・作成/削除の順序保証・Pod ごとの専用 PVC。serviceName が指す headless Service (clusterIP: None) と volumeClaimTemplates が核心。PVC は自動削除されない
  • Job。完了を目標とする作業。completions/parallelism/backoffLimit、restartPolicy は OnFailure/Never のみ許可
  • CronJob。スケジュールごとに Job を作成。schedule (cron の 5 桁)、concurrencyPolicy (Allow/Forbid/Replace)、history limit、suspend で一時停止
  • 試験ポイント。命令型作成で骨組みを確保、DaemonSet は変換で作成、restartPolicy の罠、StatefulSet は headless Service とセット、PVC 残存の処理

次へ: ConfigMap と Secret の深掘り #

ここまででワークロードの形をすべて押さえました。ところが Pod に設定値と秘密情報をどう注入するかはまだ扱っていません。

#12 ConfigMap と Secret の深掘り では、設定をコードから分離する ConfigMap、機微な情報を入れる Secret のタイプと base64 エンコーディング、そしてこの 2 つを環境変数とボリュームで Pod に注入する方法、値が変わったときの反映動作まで整理します。

X