本文へスキップ

CKAD 概念ノート: ワークロードリソースの選び方

  • kubernetes
  • ckad
  • deployment
  • statefulset
  • daemonset
  • job
  • cronjob

基準: Kubernetes v1.35 (kind kindest/node:v1.35.8 でコマンド・YAML を確認)

目次

選ぶ基準

問い選択
状態を持たない replica を必要な数だけ、ローリングアップデート・ロールバックが必要Deployment
Pod ごとに固定の名前・DNS、Pod ごとの専用 PersistentVolume、順序のあるデプロイ・スケールStatefulSet
すべてのノード (または条件に合うノード) に Pod を一つずつ。ログ収集、ノード監視、ストレージデーモンDaemonSet
最後まで実行し、成功回数を数える必要がある処理Job
バックアップやレポート生成のように、決まったスケジュールで繰り返す処理CronJob

StatefulSet のドキュメントは、固定の identifier や順序の保証が要らないなら Deployment か ReplicaSet を使え、と書いている。DaemonSet のドキュメントも、replica 数の調整とロールアウトが大事なら Deployment、どのノードに立つかが大事なら DaemonSet と区別している。

Deployment と ReplicaSet

Deployment は Pod と ReplicaSet の宣言的な更新を提供する。望む状態を書けば、Deployment controller が実際の状態をそちらに合わせていく。

  • 構造: Deployment → ReplicaSet → Pod。ReplicaSet の名前と Pod ラベルの pod-template-hash は Deployment controller が付ける
  • Deployment が所有する ReplicaSet は直接いじらない
  • ロールアウトは .spec.template が変わったときだけ起きる。イメージや template のラベルの変更はロールアウト、replicas の変更 (スケール) はロールアウトではない
  • ロールアウトのたびに新しい ReplicaSet ができて revision が上がる。古い ReplicaSet は .spec.revisionHistoryLimit (デフォルト 10) だけ残る。0 だとロールバック不可
  • apps/v1 では .spec.selector は必須で、作成後は変更できない。.spec.template.metadata.labels と一致していないと API が受け付けない
  • template の restartPolicy は Always のみ

更新戦略

.spec.strategy.type動作
RollingUpdate (デフォルト)新しい ReplicaSet を増やし、古いものを減らしながら少しずつ入れ替える
Recreate既存の Pod をすべて終了してから新しい Pod を作る

RollingUpdate の細かい設定:

  • maxUnavailable: 更新中に使えない Pod の最大数。デフォルト 25%、パーセントは切り捨て
  • maxSurge: 望む数を超えて作れる Pod の最大数。デフォルト 25%、パーセントは切り上げ
  • 両方 0 にはできない

例: replicas: 3、デフォルト 25%

maxSurge       = ceil(3 * 0.25)  = ceil(0.75)  = 1  -> Pod は全体で最大 4 個
maxUnavailable = floor(3 * 0.25) = floor(0.75) = 0  -> 利用可能な Pod は最低 3 個
  • 既存の Pod を先に落とす余裕がないので、新しい Pod を 1 個立ち上げて ready になってから既存の Pod を 1 個落とす。これを 3 回繰り返す
  • maxUnavailable は切り捨てなので利用可能な Pod を減らしにくい側、maxSurge は切り上げなので replicas が少なくても最低 1 個は追加で立ち上げられる側。デフォルト値なら更新が止まらない
  • 両方 0 だと、Pod を追加で作ることも既存の Pod を落とすこともできず、更新が進まない。だから API が拒否する

Deployment のロールアウト

kubectl set image deployment/web nginx=nginx:1.16.1
kubectl rollout status deployment/web
kubectl rollout history deployment/web
kubectl rollout undo deployment/web
kubectl rollout undo deployment/web --to-revision=2
kubectl rollout pause deployment/web
kubectl rollout resume deployment/web
kubectl scale deployment/web --replicas=5
kubectl annotate deployment/web kubernetes.io/change-cause="image updated to 1.16.1"

CHANGE-CAUSE は kubernetes.io/change-cause annotation から来る。--record フラグは deprecated。

StatefulSet

StatefulSet は Pod ごとに固定の identity を保つ。次のどれか一つ以上が必要なときに使う。

  • 固定で一意なネットワーク identifier
  • 固定の永続ストレージ
  • 順序のあるデプロイとスケール
  • 順序のある自動ローリングアップデート

ここでの「固定」は、Pod が再スケジュールされても維持されるという意味。

identity

  • Pod 名: $(statefulset name)-$(ordinal)。replica 3 なら web-0、web-1、web-2
  • DNS: headless Service (clusterIP: None) がドメインを管理する。Pod ごとの名前は $(podname).$(service name).$(namespace).svc.cluster.local
  • headless Service は StatefulSet が作ってくれない。自分で作って .spec.serviceName に名前を書く
  • ストレージ: volumeClaimTemplates の項目ごとに、Pod ごとの PersistentVolumeClaim が一つずつできる。Pod が別のノードに再スケジュールされても同じ PVC をマウント

順序の保証

  • 作成は 0 から N-1 まで順番に、削除は N-1 から 0 まで逆順
  • 次の Pod を作る前に、それより前の番号の Pod がすべて Running かつ Ready でなければならない
  • この動作が .spec.podManagementPolicy: OrderedReady (デフォルト)。Parallel なら待たずに同時に作成・削除する。一意な identity の保証はそのまま

更新と削除

  • .spec.updateStrategy.type: RollingUpdate (デフォルト) は最も大きい ordinal から小さい方へ一つずつ削除して作り直す。OnDelete は自動更新をせず、ユーザーが Pod を削除して初めて新しい template で作り直す
  • rollingUpdate.partition を指定すると、その値以上の ordinal の Pod だけ更新
  • StatefulSet を削除・スケールダウンしてもボリュームは消さない。.spec.persistentVolumeClaimRetentionPolicy の whenDeleted、whenScaled のデフォルトが Retain
  • StatefulSet を削除するとき、Pod の終了順序は保証されない。順番に落としたいなら先に replicas を 0 にスケール

StatefulSet と headless Service

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  clusterIP: None # headless Service。Pod ごとに DNS レコードができる
  selector:
    app: nginx
  ports:
    - port: 80
      name: web
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: nginx # Pod の DNS (web-0.nginx) に使う headless Service
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: registry.k8s.io/nginx-slim:0.24
          ports:
            - containerPort: 80
              name: web
          volumeMounts:
            - name: www
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates: # Pod ごとに PVC を 1 つずつ作成 (www-web-0, www-web-1, …)
    - metadata:
        name: www
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Gi

storageClassName を空にすると default StorageClass が使われる。

DaemonSet

DaemonSet は、すべての (または一部の) ノードで Pod のコピーが一つずつ動くことを保証する。

  • ノードが追加されれば Pod も追加、ノードが外れればその Pod は garbage collection。DaemonSet を削除すると作った Pod も片付く
  • 一部のノードだけ: .spec.template.spec.nodeSelector か affinity を指定。どちらもなければ全ノード
  • node.kubernetes.io/unschedulable:NoSchedule のような toleration が自動で付くので、unschedulable 扱いのノードにも立てる
  • template の restartPolicy は Always か空 (デフォルト Always) でなければならない
  • .spec.selector は作成後に変更不可、template のラベルと一致が必要
  • .spec.updateStrategy.type: RollingUpdate (デフォルト) または OnDelete
  • replica 数のフィールドがない。数は条件に合うノードの数で決まる

YAML の例

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-agent
spec:
  selector:
    matchLabels:
      app: node-agent
  template:
    metadata:
      labels:
        app: node-agent
    spec:
      nodeSelector:
        kubernetes.io/os: linux
      containers:
        - name: agent
          image: busybox:1.28
          command: ["sh", "-c", "sleep 3600"]

Job

Job は Pod を一つ以上作り、指定した数だけ正常終了するまで再試行する。成功回数が満たされたら Job は完了。

  • template の restartPolicy は Never か OnFailure のみ
  • .spec.template が .spec の唯一の必須フィールド。.spec.selector はほぼ指定しない

completions と parallelism

種類設定完了条件
非並列両方空 (どちらもデフォルト 1)Pod が一つ正常終了
固定の完了回数completions を正の数に。parallelism は空なら 1成功した Pod が completions 個
work queuecompletions を空にして parallelism だけ指定Pod が一つでも成功し、すべて終了
  • parallelism: 0 なら事実上の一時停止
  • 固定の完了回数の Job では、実際の同時実行数は残りの completions を超えない
  • .spec.completionMode: NonIndexed (デフォルト) か Indexed。Indexed なら Pod ごとに 0 から completions-1 までのインデックスが付き、コンテナ内では JOB_COMPLETION_INDEX 環境変数で読める。インデックスごとに成功した Pod が一つずつあれば完了

失敗の扱い

  • .spec.backoffLimit: Job を失敗とみなす前の再試行回数。デフォルト 6 (backoffLimitPerIndex を使うとデフォルトが変わる)
  • 失敗した Pod は 10s、20s、40s … 最大 6 分の間隔で作り直される
  • restartPolicy: OnFailure なら Pod はノードに残り、コンテナだけ再実行。Never なら Pod が失敗し、Job controller が新しい Pod を作る
  • OnFailure は backoffLimit に達すると Pod が終了してログを見にくくなる。ドキュメントはデバッグ時は Never を勧めている
  • .spec.activeDeadlineSeconds: Job 全体の実行時間の上限。backoffLimit より優先。超えると実行中の Pod をすべて終了し、reason: DeadlineExceeded で失敗
  • activeDeadlineSeconds は Job spec と Pod spec の両方にあるフィールドなので、場所を間違えやすい
  • 失敗で終わった Job は自動では再開しない

後片付け

  • 完了した Job と Pod はデフォルトで残る。ログや状態を見られるようにするため
  • kubectl delete job <name> で消すと Pod も一緒に消える
  • .spec.ttlSecondsAfterFinished を指定すると、終わってからその時間が経つと自動削除

YAML の例

apiVersion: batch/v1
kind: Job
metadata:
  name: batch-demo
spec:
  completions: 5
  parallelism: 2
  backoffLimit: 4 # 失敗時のリトライ上限。超えると Job は失敗
  activeDeadlineSeconds: 120 # Job 全体の制限時間 (秒)。超えると Pod を終了し Job は失敗
  ttlSecondsAfterFinished: 100 # 終了後この秒数が経つと Job と Pod を自動削除
  template:
    spec:
      containers:
        - name: worker
          image: busybox:1.28
          command: ["sh", "-c", "echo done"]
      restartPolicy: Never

CronJob

CronJob は繰り返しのスケジュールに従って Job を作る。crontab の一行に相当。

  • .spec.schedule (必須): Cron 形式 分 時 日 月 曜日。0 3 * * 1 は毎週月曜 03 時。*/2 のような step、@monthly のようなマクロも使える
  • .spec.jobTemplate (必須): Job spec と同じスキーマで、apiVersion・kind だけがない
  • .spec.timeZone: 指定しなければ kube-controller-manager のローカルタイムゾーン基準。schedule の中に CRON_TZ や TZ を書くと validation エラー
  • 名前は 52 文字以下。controller が Job 名に 11 文字を付け足し、Job 名の上限が 63 文字だから

schedule の step

/ は step (間隔)。範囲/間隔 と読み、* はそのフィールドの全範囲。分フィールドの */2 は 0-59 を 0 から 2 ずつ飛ばして 0, 2, 4, …, 58 分。

schedule意味
*/2 * * * *2 分ごと (0, 2, 4…分)
*/15 * * * *15 分ごと (0, 15, 30, 45 分)
0 */6 * * *6 時間ごとの正時 (0, 6, 12, 18 時)
0 9-18/3 * * 1-5平日 9, 12, 15, 18 時の正時
  • 「実行から 2 分後」ではなく時計基準。1 分 40 秒に作っても最初の実行は 2 分
  • 割り切れないと境界で間隔が崩れる。分フィールドの */7 は 56 分の次が次の時間の 0 分なので、その間だけ 4 分
  • * */2 * * * は「2 時間ごと」ではなく、偶数時の間に毎分実行。2 時間ごとに 1 回なら 0 */2 * * *

同時実行と遅延

.spec.concurrencyPolicy前の Job がまだ実行中のとき
Allow (デフォルト)同時に実行
Forbid今回の実行をスキップ
Replace実行中の Job を新しい Job に置き換え
  • 同じ CronJob が作った Job 同士にだけ適用。別の CronJob の Job は常に同時実行できる
  • .spec.startingDeadlineSeconds: 予定時刻を逃したとき、遅れてでも開始できる上限 (秒)。超えるとその回はスキップされ失敗扱い。10 秒未満だと controller が 10 秒ごとに確認するため、まったくスケジュールされないことがある
  • 逃したスケジュールが 100 回を超えると Job を開始せずにエラーを残す
  • .spec.suspend: true: 以降の実行を止める。すでに始まった Job には影響なし
  • .spec.successfulJobsHistoryLimit のデフォルトは 3、.spec.failedJobsHistoryLimit は 1
  • CronJob を修正しても、すでに始まった Job はそのまま。変更は次の新しい Job から
  • 一回のスケジュールで Job が二つできたり一つもできなかったりすることがあるので、Job は idempotent に作る

YAML の例

apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/5 * * * *"
  timeZone: "Etc/UTC" # schedule を解釈するタイムゾーン
  concurrencyPolicy: Forbid # 前の Job がまだ動いていれば今回の実行はスキップ (Allow/Forbid/Replace)
  startingDeadlineSeconds: 200 # 予定時刻を逃しても、この秒数以内なら遅れて実行
  successfulJobsHistoryLimit: 3 # 残しておく成功 Job の数
  failedJobsHistoryLimit: 1 # 残しておく失敗 Job の数
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: hello
              image: busybox:1.28
              command: ["/bin/sh", "-c", "date; echo Hello"]
          restartPolicy: OnFailure

例

imperative で骨組みを作る

kubectl create deployment web --image=nginx --replicas=3
kubectl create job pi --image=perl:5.34.0 -- perl -Mbignum=bpi -wle 'print bpi(2000)'
kubectl create cronjob hello --image=busybox:1.28 --schedule="*/1 * * * *" -- date
kubectl create job hello-manual --from=cronjob/hello

# YAML に書き出して修正
kubectl create job pi --image=perl:5.34.0 --dry-run=client -o yaml > job.yaml

kubectl create には statefulset、daemonset のサブコマンドがない。YAML を直接書くか、Deployment の YAML を書き出して直す。DaemonSet にするときは kind を変え、replicas と strategy を消す (DaemonSet のフィールド名は updateStrategy)。

紛らわしいものの比較

DeploymentStatefulSetDaemonSetJobCronJob
apiVersionapps/v1apps/v1apps/v1batch/v1batch/v1
数の決まり方replicasreplicas条件に合うノードの数completions、parallelismスケジュールごとに Job 1 つ
Pod 名名前-hash-ランダム名前-ordinal で固定ランダムランダム (Indexed なら hostname が 名前-インデックス)CronJob 名ベース
ストレージ共有またはなしPod ごとの PVC普通はノードローカル--
更新戦略のフィールドstrategy: RollingUpdate/RecreateupdateStrategy: RollingUpdate/OnDeleteupdateStrategy: RollingUpdate/OnDelete-修正は次の Job から適用
許可される restartPolicyAlwaysAlwaysAlwaysOnFailure、NeverOnFailure、Never
imperative な作成可能不可不可可能可能
  • Deployment は strategy、StatefulSet・DaemonSet は updateStrategy。フィールド名からして違う
  • Recreate が保証するのは、アップグレード時に既存の Pod の終了を先にすることだけ。Pod を手で消すと ReplicaSet がすぐに代わりの Pod を作る。「最大一つ」の保証が必要なら StatefulSet を見よ、というのがドキュメントの説明
  • Job の restartPolicy は Pod に適用されるもの。Job 自体が失敗したら再実行はされない

試験のポイント

  • まず imperative で作れるもの (Deployment、Job、CronJob) は kubectl create ... --dry-run=client -o yaml で骨組みを作り、必要なフィールドだけ足す
  • StatefulSet、DaemonSet には imperative なコマンドがない → ドキュメントの例をコピーして、フィールドは下の explain で確認
  • CronJob は jobTemplate.spec.template.spec までインデントが三段なので、restartPolicy の位置をよく間違える
  • Job の template で restartPolicy を書き忘れたり Always を入れたりすると拒否される
  • completions と parallelism を取り違えない。成功の総数が completions、同時実行数が parallelism
  • activeDeadlineSeconds は Job の spec の直下に置く。Pod template の spec にも同じフィールドがある
  • kubectl rollout undo は一つ前の revision へ。特定の revision は --to-revision
  • CronJob を手動で動かして確認するなら kubectl create job <name> --from=cronjob/<cronjob>
  • selector は Deployment・DaemonSet とも作成後に変えられない。間違えたら削除して作り直す

kubectl explain

kubectl explain job.spec                                                   # completions、parallelism、backoffLimit、activeDeadlineSeconds
kubectl explain cronjob.spec                                               # schedule、concurrencyPolicy、history limit
kubectl explain cronjob.spec.jobTemplate.spec.template.spec.restartPolicy  # 深い位置を一度に確認
kubectl explain statefulset.spec                                           # serviceName、volumeClaimTemplates
kubectl explain daemonset.spec.updateStrategy

参考ドキュメント