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

K8s Deployment+3Probe【CKAD第12回】

広告
広告

第12回スコープ・学習目標・今ここマップ

動作確認バージョン: kind v0.31.0 / kindest/node:v1.35.0 / kubectl v1.35.0 (Kustomize v5.7.1) / fanclub-backend:0.1.0 / curlimages/curl:8.20.0 / Docker CE 29.4.3 / containerd 2.2.3 / AlmaLinux 10.1(kernel 6.12.0-124.55.3.el10_1)(2026-05-10 時点・k8s-ops 実機検証済・SP_vol1-pre-21 起点)

本回は Kubernetes 実践教科書 第1巻(CKAD 対応・全 19 回)の第12回です。第4部「ワークロード戦略」第1回として、Deployment + 3 種の Probe(startupProbe / livenessProbe / readinessProbe)+ Rolling Update + Probe デバッグ実践の 5 機構をまとめて扱います。

CKAD ドメイン D2「Application Deployment」(出題比率 20 %)+ D3「Application Observability and Maintenance」(出題比率 15 %)の中核 Competency「Deployment とローリングアップデートの実施」「Probe とヘルスチェックの実装」「Kubernetes でのデバッグ」を 1 回で網羅する重要回です。

第11回からの継承状態確認(SP_vol1-pre-21 状態):

項目状態出典
kind クラスタkind-control-plane Ready(v1.35.0・13h old)Lead 実機観察
fanclub-backend Pod(単体)default ns で 1/1 Running(envFrom 注入版・automountServiceAccountToken: false)→ 本回で Deployment 化する対象Lead 実機観察
fanclub-backend ServiceClusterIP 10.96.150.60:80 稼働中Lead 実機観察
fanclub-db StatefulSetfanclub-db-0 Pod Running(PostgreSQL 18・members テーブル 2 行 + score 列)Lead 実機観察
fanclub-db Service / fanclub-db-headless ServiceClusterIP 10.96.131.167:5432 / clusterIP: NoneLead 実機観察
ConfigMapfanclub-config(4 キー: DB_HOST / DB_PORT / DB_NAME / JAVA_OPTS)SP_vol1-pre-21
Secretfanclub-secret(Opaque・2 キー: DB_USER / DB_PASSWORD)SP_vol1-pre-21
ServiceAccountfanclub-backend-sa(ep10 で作成)SP_vol1-pre-21
DaemonSetnode-logger(1 Pod Running・ep11 で作成)Lead 実機観察
CronJobfanclub-member-count(Suspend: True・ep11 で設定)Lead 実機観察

今ここマップ(第1巻 19 回中の現在位置):

第1部 コンテナとDocker
    第1回〜第4回  [完了]

第2部 Kubernetes基礎
    第5回〜第6回  [完了]

第3部 アプリリソース
    第7回〜第11回  [完了]

第4部 ワークロード戦略(第12〜14回)
  ★ 第12回: Deployment + 3 Probe + Rolling Update + Probe デバッグ実践  ← 今ここ
    第13回: Deployment 戦略補完(Blue/Green + Canary + Recreate)
    第14回: ResourceQuota + LimitRange + Multi-tenant Namespace

第5部 セキュリティ基礎(第15〜16回)
第6部 パッケージ管理 + HTTPS公開(第17〜19回)

第12回を終えると、以下を習得した状態になります。

  • Deployment の YAML 構造(replicas / selector / template / strategy)を理解し、fanclub-backend Pod を Deployment に移行できる。spec.selector が一度 apply すると変更不可な不変フィールドであることを説明できる
  • Rolling Update(maxSurge / maxUnavailable)を設定し、kubectl rollout status / history / undo で進行確認・ロールバックができる。新旧 ReplicaSet の共存メカニズムを説明できる
  • startupProbe / livenessProbe / readinessProbe の役割と失敗時の動作の違いを説明し、Payara Micro の起動遅延(約 7.5 秒)を考慮した 3 Probe を設計・実装できる
  • 意図的に失敗する Probe を設定して kubectl describe の Events で CrashLoopBackOff の原因を特定し、修正できる。kubectl logs --previouskubectl debug ephemeral container でランタイム調査ができる
  • Recreate 戦略のダウンタイムと RollingUpdate との違いを説明できる(CKAD D2 デプロイ戦略選択問題対策)

模擬アプリ進捗(第12回):第11回までで Pod 単体の fanclub-backend + StatefulSet の DB + ConfigMap / Secret / SA + Job / CronJob / DaemonSet が揃いました。本回ではアプリ本体の常駐サービスである fanclub-backend を Pod 単体から Deployment に格上げします。

replicas: 2 で冗長化し、3 種の Probe で本番運用に必要なヘルスチェック基盤を整え、Rolling Update でゼロダウンタイム更新を実現します。fanclub-api の構築過程で大きな節目となる回です。

第12回完了後の模擬アプリ状態:fanclub-backend が Deployment(replicas: 2 + 3 Probe + RollingUpdate maxSurge:1 / maxUnavailable:0)で稼働。fanclub-backend Service は ep8 から継続(Pod ラベルが一致するため Service 変更不要)。

fanclub-db StatefulSet・ConfigMap・Secret・SA・DaemonSet・CronJob は ep11 から継続。Rolling Update 履歴がリビジョン 2 以上で残存し、kubectl rollout history で履歴確認ができる状態になります。

Pod 単独・ReplicaSet・Deployment の階層 — なぜ Deployment が必要か

Deployment の YAML を書き始める前に、なぜ Pod 単独では本番運用に不十分なのか、ReplicaSet と Deployment の関係はどうなっているのかを整理します。

CKAD 試験では「Pod / ReplicaSet / Deployment のうち本番常駐サービスに使うべきは何か」「Rolling Update を実現するためのリソースは何か」という階層理解を問う問題が頻出します。

Pod 直接管理の 3 つの限界

ep7 から ep11 まで fanclub-backend は kind: Pod として単体で稼働してきました。学習目的としては最小単位を扱うために必要な構成でしたが、本番運用には以下の 3 つの限界があります。

  • 自己回復がない:Pod が何らかの理由で削除された場合、自動で再作成する主体が存在しない。Node 障害で Pod が消滅すると、誰かが手動で kubectl apply し直さない限りサービスが止まったままになる
  • スケールできない:Pod 単体には replicas の概念がない。台数を 2 つに増やしたければ別ファイルで fanclub-backend-2 Pod を手動で作る必要があり、宣言的管理にならない
  • ローリングアップデートができない:新しいイメージタグに更新するには Pod を kubectl delete してから新版 Pod を kubectl apply する必要があり、ダウンタイムが必ず発生する

これら 3 つの限界を解消するために登場するのが ReplicaSetDeployment です。両者は階層関係にあり、Deployment が ReplicaSet を、ReplicaSet が Pod を管理する 3 層構造になっています。

3 層構造 — Deployment が ReplicaSet を、ReplicaSet が Pod を管理する

Kubernetes のワークロード階層を図示すると以下の通りです。

[Deployment]   ← Rolling Update / ロールバック / リビジョン管理
    │
    │ 管理
    ▼
[ReplicaSet]   ← N 個の Pod を維持(自己回復・スケール)
    │
    │ 管理
    ▼
[Pod] [Pod] [Pod] ...   ← コンテナ実行の最小単位

各層の責務を整理します。

主な責務典型的な操作
Podコンテナの実行単位(1 Pod = 1 つ以上のコンテナ)kubectl run / kubectl exec(デバッグ用途)
ReplicaSet指定数の Pod を常時維持(自己回復・水平スケール)原則として直接操作しない(Deployment 経由)
DeploymentReplicaSet を新旧入れ替えてローリングアップデート・ロールバックを実現kubectl apply / kubectl rollout
Deployment・ReplicaSet・Pod の 3 層構造と Rolling Update の段階遷移を示す図。上部は Deployment が旧 ReplicaSet(rev1)と新 ReplicaSet(rev2)を、各 ReplicaSet が配下の Pod を管理する 3 層関係を表す。下部は maxSurge:1 / maxUnavailable:0 / replicas:2 での Rolling Update が、旧2 → 旧2+新1 → 旧1+新1 → 旧1+新2 → 新2 の 5 ステップで進む様子を示し、更新中も常に 2 Pod が稼働するゼロダウンタイムの仕組みを可視化している

本番常駐サービスには Deployment を使うのが定石で、ReplicaSet を直接 kind: ReplicaSet として作成する場面はほぼありません。理由は単純で、ReplicaSet 単体ではローリングアップデートが扱えず、新旧 ReplicaSet を協調させる Deployment の機能が必須になるためです。

既存クラスタの Deployment / ReplicaSet 階層を観察する

kind クラスタには既に複数の Deployment が動いています。coredns(DNS)・metrics-server(メトリクス収集)・local-path-provisioner(ストレージ)です。これらの Deployment 配下に ReplicaSet が存在する様子を観察すると、3 層構造の理解が一気に進みます。

実行コマンド:

$ kubectl get deployment,replicaset -n kube-system

実行結果:

NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/coredns                    2/2     2            2           13h
deployment.apps/local-path-provisioner     1/1     1            1           13h
deployment.apps/metrics-server             1/1     1            1           13h

NAME                                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/coredns-7c65d6cfc9                   2         2         2       13h
replicaset.apps/local-path-provisioner-57c5987fd4    1         1         1       13h
replicaset.apps/metrics-server-854bf4d4fc            1         1         1       13h

Deployment 1 つに対して、現在動いている Pod を抱える ReplicaSet が 1 つあります。Rolling Update のたびに新しい ReplicaSet が生成され、古い ReplicaSet は replicas: 0 で残ります(ロールバック用)。本回 H2-4 で扱う revisionHistoryLimit でこの保持数を制御します。

ReplicaSet を直接 kubectl get replicaset で確認すると、NAME 列が <deployment-name>-<hash> の形式になっています。hash 部分は Pod テンプレートのハッシュ値で、テンプレートが変わると新しいハッシュ = 新しい ReplicaSet が生成されます。

Deployment が「Pod テンプレートの変更を検知して ReplicaSet を切り替える」コントローラーであることが命名規則からも読み取れます。

Deployment YAML 構造 — replicas・selector・template・strategy を読み解く

Deployment の YAML を上から下まで分解します。CKAD 試験では「Deployment YAML を一から書く」「既存 YAML の不備を直す」のどちらも頻出します。spec.replicas / spec.selector / spec.template / spec.strategy の 4 大フィールドの役割と制約を押さえることが第一歩です。

spec 配下の主要フィールド一覧

フィールドデフォルト意味
replicas1維持する Pod 数。replicas: 2 なら 2 Pod が常時稼働
selector.matchLabels—(必須)Deployment が管理する Pod を識別するラベル。一度作成すると変更不可(不変フィールド)
template.metadata.labels—(必須)Pod に付与するラベル。selector.matchLabels と一致している必要がある
template.spec—(必須)Pod テンプレート。コンテナ定義・volumes・SA など Pod のすべてを記述
strategy.typeRollingUpdateRollingUpdate または Recreate
strategy.rollingUpdate.maxSurge25%Rolling Update 中に追加で起動できる Pod 数(または比率)
strategy.rollingUpdate.maxUnavailable25%Rolling Update 中に同時に利用不可になってよい Pod 数(または比率)
revisionHistoryLimit10保持する旧 ReplicaSet の数(ロールバック用)
progressDeadlineSeconds600Rolling Update が進行しない場合に Progressing 失敗とみなす秒数

spec.selector の不変フィールド制約

Deployment の spec.selector.matchLabels は一度 kubectl apply で確定すると変更できません。変更しようとすると以下のようなエラーが返ります。

The Deployment "fanclub-backend-deployment" is invalid: spec.selector: Invalid value: ... field is immutable

selector を変更したい場合は kubectl delete deployment で Deployment を削除してから再作成する必要があります。本回の演習①では新規作成のみのため問題になりませんが、ep13 の Blue/Green デプロイで version ラベルを切り替える際にこの制約を正面から扱います。

もう 1 つの注意点として、spec.selector.matchLabelsspec.template.metadata.labels は一致している必要があります。一致していないと apply 時に以下のエラーが返ります。

The Deployment "fanclub-backend-deployment" is invalid: spec.template.metadata.labels: Invalid value: ...: `selector` does not match template `labels`

Pod テンプレート側で app: fanclub-backend を付与し、selector 側でも同じラベルで matchLabels を指定する、という対応関係を必ず守ります。

スケルトン生成テクニック — kubectl create deployment –dry-run

Deployment の YAML を一から書くより、kubectl create deployment でスケルトンを生成して編集する方が高速です。CKAD 試験で時間制限を乗り切るために必須のテクニックです。

実行コマンド:

$ kubectl create deployment fanclub-backend-deployment --image=fanclub-backend:0.1.0 --replicas=2 --dry-run=client -o yaml

実行結果(抜粋):

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: fanclub-backend-deployment
  name: fanclub-backend-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: fanclub-backend-deployment
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: fanclub-backend-deployment
    spec:
      containers:
      - image: fanclub-backend:0.1.0
        name: fanclub-backend
        resources: {}
status: {}

このスケルトンに対して、strategy の中身(maxSurge / maxUnavailable)・envFromresourcesinitContainers・3 種の Probe・serviceAccountNameautomountServiceAccountToken を手動で追加していくのが定石です。

--dry-run=client ではこれらは生成されないため、各フィールドの仕様は kubectl explain deployment.spec.strategykubectl explain pod.spec.containers.livenessProbe で都度確認します。

kubectl explain で fields を確認する

各フィールドの正確な仕様は kubectl explain で参照できます。試験中は暗記に頼らず必ず explain で確認します。

実行コマンド:

$ kubectl explain deployment.spec.strategy

実行結果(抜粋):

GROUP:      apps
KIND:       Deployment
VERSION:    v1

FIELD: strategy <DeploymentStrategy>

DESCRIPTION:
    The deployment strategy to use to replace existing pods with new ones.

FIELDS:
  rollingUpdate	<RollingUpdateDeployment>
    Rolling update config params. Present only if DeploymentStrategyType =
    RollingUpdate.

  type	<string>
    Type of deployment. Can be "Recreate" or "RollingUpdate". Default is
    RollingUpdate.

Probe のパラメータも同様に確認できます。

実行コマンド:

$ kubectl explain pod.spec.containers.livenessProbe

実行結果(抜粋):

GROUP:      core
KIND:       Pod
VERSION:    v1

FIELD: livenessProbe <Probe>

DESCRIPTION:
    Periodic probe of container liveness. Container will be restarted if the
    probe fails.

FIELDS:
  failureThreshold	<integer>
    Minimum consecutive failures for the probe to be considered failed after
    having succeeded. Defaults to 3. Minimum value is 1.

  httpGet	<HTTPGetAction>
    HTTPGet specifies the http request to perform.

  initialDelaySeconds	<integer>
    Number of seconds after the container has started before liveness probes
    are initiated.

  periodSeconds	<integer>
    How often (in seconds) to perform the probe. Default to 10 seconds. Minimum
    value is 1.

  successThreshold	<integer>
    Minimum consecutive successes for the probe to be considered successful
    after having failed. Defaults to 1. Must be 1 for liveness and startup.

  tcpSocket	<TCPSocketAction>
    TCPSocket specifies an action involving a TCP port.

  timeoutSeconds	<integer>
    Number of seconds after which the probe times out. Defaults to 1 second.
    Minimum value is 1.

Rolling Update の仕組み — ReplicaSet の新旧共存と maxSurge / maxUnavailable

Rolling Update の内部動作を ReplicaSet 視点で読み解きます。新旧の ReplicaSet がどのように共存し、maxSurgemaxUnavailable がどう機能するか、本番ではどのパラメータが推奨かを整理します。

Rolling Update の段階的フロー(replicas:2 / maxSurge:1 / maxUnavailable:0 の場合)

本回で採用する maxSurge: 1 / maxUnavailable: 0 設定での Rolling Update の流れは以下の通りです。

初期状態: ReplicaSet v1 (Pod×2 Running)

Step 1: 新 ReplicaSet v2 を作成 → Pod v2 を 1 つ起動
        [v1: Pod×2] + [v2: Pod×1 (起動中)]   合計 3 Pod

Step 2: Pod v2 が Ready になったら、v1 から Pod を 1 つ削除
        [v1: Pod×1] + [v2: Pod×1 Ready]      合計 2 Pod

Step 3: v2 から Pod v2 をもう 1 つ起動
        [v1: Pod×1] + [v2: Pod×2 (1 つ起動中)]   合計 3 Pod

Step 4: Pod v2 (2 つ目) が Ready になったら、v1 から最後の Pod を削除
        [v1: Pod×0] + [v2: Pod×2 Ready]      合計 2 Pod

完了状態: ReplicaSet v1 (replicas:0 で保持) + ReplicaSet v2 (Pod×2)

注目すべきは「常に Ready な Pod が 2 つ以上稼働している」という点です。maxUnavailable: 0 の指定が「同時に Ready 数が replicas を下回ることを許さない」という意味であり、これがゼロダウンタイム更新の本質です。

maxSurge: 1 は「一時的に replicas + 1 = 3 Pod まで増やしてよい」という意味で、一時的なリソース増を許容することでダウンタイムを排除しています。

maxSurge / maxUnavailable の 4 設計パターン

maxSurgemaxUnavailable の組み合わせで複数の更新戦略を表現できます。代表的な 4 パターンを整理します。

パターン名maxSurgemaxUnavailable特徴本番推奨度
ゼロダウンタイム1(または 25%)0追加 Pod を先に起動してからダウン → 常に replicas 数が稼働本番推奨
リソース節約01(または 25%)先にダウンさせてからアップ → 瞬間的に台数が減るリソース制約あり時のみ
高速更新11同時に 1 追加 + 1 削除 → 最速だが瞬間的なダウンあり非本番のみ
K8s デフォルト25%25%明示しない場合の値。本番では意図しないダウンタイムの原因になる明示推奨

本番ガードレール:本番環境では maxSurge: 1(または 25%)+ maxUnavailable: 0 を明示的に設定するのが定石です。デフォルト値(maxUnavailable: 25%)のままだと、replicas: 4 の Deployment では Rolling Update 中に 1 Pod が利用不可になる時間帯が発生し、503 エラーがクライアントに伝わる可能性があります。

Rolling Update を始める前にダッシュボードで設定を確認するのが運用の鉄則です。

revisionHistoryLimit と rollout history の関係

Rolling Update 完了後も旧 ReplicaSet は削除されず replicas: 0 で残ります。kubectl rollout undo でロールバックする際の参照元になるためです。保持される旧 ReplicaSet の数は spec.revisionHistoryLimit で制御します(デフォルト 10)。

本番ガードレール:本番では revisionHistoryLimit: 35 に設定することが多くなっています。デフォルトの 10 では更新を繰り返すたびに旧 ReplicaSet が etcd 上に蓄積し、大規模クラスタではメモリ圧迫の遠因になります。一方で 0 にすると一切ロールバックできなくなるため、3 〜 5 が現実的な妥協点です。

履歴は kubectl rollout history で確認できます。CHANGE-CAUSE 列には kubectl annotate deployment <name> kubernetes.io/change-cause="3 Probe 追加" でアノテーションした内容が表示されるため、運用ではアノテーションを必ず付与する文化を作ると後追いしやすくなります。

Recreate 戦略 — ダウンタイムありの全切り替え

Rolling Update と対をなすのが Recreate 戦略です。RollingUpdate がデフォルトで本番で多用される一方、Recreate も特定のユースケースで必要になる場面があります。本回で概念整理し、ep13 で詳細演習を行う橋渡しの位置付けです。

Recreate の動作とダウンタイム

Recreate 戦略では、Deployment の更新が以下の 2 ステップで進みます。

初期状態: ReplicaSet v1 (Pod×2 Running)

Step 1: 旧 Pod を全削除
        [v1: Pod×0]   ← この間ダウンタイム発生

Step 2: 新 ReplicaSet v2 を作成して Pod を起動
        [v1: Pod×0] + [v2: Pod×2 Ready]

完了状態: ReplicaSet v1 (replicas:0) + ReplicaSet v2 (Pod×2)

Step 1 で旧 Pod を全削除してから Step 2 で新 Pod を起動するため、Step 1 と Step 2 の間(新 Pod が Ready になるまで)の時間帯はサービスが停止します。Payara Micro のような起動が遅いアプリでは、起動時間(約 7.5 秒)+ Probe 待機時間でダウンタイムが数十秒に達する場合もあります。

Recreate の YAML

Recreate を指定する YAML は以下の通りです(参考用・本回演習では使用しません)。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-recreate
spec:
  replicas: 2
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: example
  template:
    metadata:
      labels:
        app: example
    spec:
      containers:
        - name: example
          image: nginx:1.27-alpine

strategy.type: Recreate を指定すると rollingUpdate 配下のフィールドは無視されます(maxSurge / maxUnavailable は意味を持たない)。

RollingUpdate と Recreate の比較

項目RollingUpdateRecreate
ダウンタイムなし(正しく設定した場合)あり(旧 Pod 全削除 → 新 Pod 全起動の間)
リソース消費一時的に maxSurge 分多く消費少ない(新旧同時稼働なし)
ロールバック速度kubectl rollout undo で段階的同様に再度 Recreate が走るため遅い
使いどき通常の Web サービス更新DB スキーマ破壊的変更・2 バージョン同時稼働 NG
本回での扱い演習①②で詳細実装概念整理のみ(ep13 で詳細演習)

本番ガードレール:Recreate を選択する場合は計画的なメンテナンスウィンドウが必要です。ダウンタイムが発生することをステークホルダーに事前に伝え、夜間バッチの完了を待ってから実施するなどの段取りが運用の前提になります。クラスタ全体のデフォルトを Recreate にする運用は基本的にありません。

やってみよう①:fanclub-backend を Deployment に移行し replicas: 2 で冗長化する

既存の fanclub-backend Pod(単体)を削除し、Deployment として再作成します。replicas: 2 で 2 Pod 構成にし、fanclub-backend Service が両方の Pod に負荷分散することを実機で確認します。

所要時間目安は約 30 分です。本演習では Probe を含まない最小構成の Deployment を作り、演習②で 3 Probe を追加していく段階的アプローチを採用します。

演習の全体フローは以下のとおりです。

  1. 前提状態の確認(kubectl get pods,svc,deploy -n default
  2. 旧 fanclub-backend Pod を削除(kubectl delete pod fanclub-backend
  3. fanclub-backend-deployment.yaml を作成(Probe なし版・H2-3 で確認した YAML 構造)
  4. kubectl apply -f ~/fanclub-manifests/fanclub-backend-deployment.yaml で Deployment を作成
  5. kubectl get deployment で READY 2/2 を確認
  6. kubectl get pods で 2 Pod が Running を確認
  7. kubectl get endpoints fanclub-backend で 2 エンドポイント登録を確認
  8. curlimages/curl 一時 Pod で Service 経由疎通確認
  9. kubectl describe deployment fanclub-backend-deployment で全設定を確認

Step 1: 前提状態の確認

k8s-ops の作業端末で ~/fanclub-manifests/ に移動し、現在のクラスタ状態を確認します。

実行コマンド:

$ cd ~/fanclub-manifests/
$ kubectl get pods,svc,deploy -n default

実行結果(fanclub-backend Pod が単体・Deployment はゼロ件):

NAME                   READY   STATUS    RESTARTS   AGE
pod/fanclub-backend    1/1     Running   0          13h
pod/fanclub-db-0       1/1     Running   0          13h
pod/node-logger-xxxx   1/1     Running   0          13h

NAME                       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/fanclub-backend    ClusterIP   10.96.150.60    <none>        80/TCP     13h
service/fanclub-db         ClusterIP   10.96.131.167   <none>        5432/TCP   13h
service/fanclub-db-headless ClusterIP  None            <none>        5432/TCP   13h
service/kubernetes         ClusterIP   10.96.0.1       <none>        443/TCP    13h

No resources found in default namespace. (deployment)

Step 2: 旧 fanclub-backend Pod を削除

Deployment は spec.selector で管理対象 Pod を識別します。既存の Pod 単体(app: fanclub-backend ラベル付き)が残ったまま Deployment を作ると、Deployment が既存 Pod も自分の管理下に取り込もうとして混乱が起きます。先に旧 Pod を削除します。

実行コマンド:

$ kubectl delete pod fanclub-backend -n default

実行結果:

pod "fanclub-backend" deleted

削除直後は fanclub-backend Service の Endpoints が空になり、一時的にサービスが停止します。本演習は学習環境のため許容しますが、本番では「先に Deployment を別名で作成してから旧 Pod を削除する」段取りが定石です。

Step 3: Deployment YAML を作成(Probe なし版)

fanclub-backend-deployment.yaml を作成します。本演習では 3 Probe を含めない最小構成にし、演習②で 3 Probe を追加して Rolling Update のデモに使う流れにします。

実行コマンド:

$ vi ~/fanclub-manifests/fanclub-backend-deployment.yaml

ファイル内容:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fanclub-backend-deployment
  namespace: default
  labels:
    app: fanclub-backend
spec:
  replicas: 2
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      app: fanclub-backend
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: fanclub-backend
    spec:
      serviceAccountName: fanclub-backend-sa
      automountServiceAccountToken: false
      initContainers:
        - name: wait-for-db
          image: busybox:1.36
          imagePullPolicy: IfNotPresent
          command:
            - sh
            - -c
            - "until nc -z fanclub-db 5432; do echo waiting for db; sleep 2; done"
      containers:
        - name: fanclub-backend
          image: fanclub-backend:0.1.0
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
          envFrom:
            - configMapRef:
                name: fanclub-config
            - secretRef:
                name: fanclub-secret
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "1000m"

各フィールドの設計意図を整理します。

  • apiVersion: apps/v1:Deployment の正式 API バージョン。extensions/v1beta1 は廃止済
  • spec.replicas: 2:2 Pod 構成。Rolling Update のフローを観察する最小単位
  • spec.revisionHistoryLimit: 5:旧 ReplicaSet の保持数を 5 に明示(本番想定)
  • spec.selector.matchLabels.app: fanclub-backend:ep8 で作成した fanclub-backend Service の selector と一致させる(Service 変更なしで Endpoints が紐づく)
  • spec.strategy.type: RollingUpdate + maxSurge: 1 + maxUnavailable: 0:ゼロダウンタイム更新の本番パターン
  • spec.template.spec.serviceAccountName: fanclub-backend-sa:ep10 で作成した最小権限 SA を継承
  • automountServiceAccountToken: false:ep10 で確立した「Pod から API Server を呼ばない設計」を継承
  • initContainers:ep7 で導入した wait-for-db。Pod 起動のたびに DB 接続を確認してから本体コンテナを起動
  • envFrom:ep10 で確立した fanclub-config + fanclub-secret の一括注入
  • resources:ep10 で設定した requests / limits を継承

Step 4: Deployment を apply

実行コマンド:

$ kubectl apply -f ~/fanclub-manifests/fanclub-backend-deployment.yaml

実行結果:

deployment.apps/fanclub-backend-deployment created

Step 5: Deployment の READY 状態を確認

Deployment が READY 2/2 になるまで待機します。Init Container wait-for-db の DB 接続確認 + Payara Micro 本体の起動(約 7.5 秒)+ コンテナ起動時間で、合計 10〜20 秒程度の待機が想定されます。

実行コマンド:

$ kubectl get deployment fanclub-backend-deployment -n default

実行結果(READY 2/2 になれば完了):

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
fanclub-backend-deployment   2/2     2            2           10s

Step 6: Pod 一覧と ReplicaSet を確認

実行コマンド:

$ kubectl get pods,replicaset -l app=fanclub-backend -n default

実行結果(Pod 2 個 + ReplicaSet 1 個):

NAME                                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/fanclub-backend-deployment-7df85c6644   2         2         2       10s

NAME                                              READY   STATUS    RESTARTS   AGE   IP            NODE                 NOMINATED NODE   READINESS GATES
pod/fanclub-backend-deployment-7df85c6644-2hmjf   1/1     Running   0          10s   10.244.0.44   kind-control-plane   <none>           <none>
pod/fanclub-backend-deployment-7df85c6644-znkcq   1/1     Running   0          10s   10.244.0.45   kind-control-plane   <none>           <none>

Pod 名が fanclub-backend-deployment-<hash>-<suffix> の形式になっています。<hash> は ReplicaSet の Pod テンプレートハッシュ、<suffix> は ReplicaSet が Pod を生成するときに付与するランダム文字列です。

Pod 名から所属 ReplicaSet を逆引きできる命名規則になっています。

Step 7: Service の Endpoints を確認

fanclub-backend Service が新しい 2 Pod に対して Endpoints を更新したかを確認します。

実行コマンド:

$ kubectl get endpoints fanclub-backend -n default

実行結果(2 つのエンドポイント IP:8080 が表示される):

NAME              ENDPOINTS                         AGE
fanclub-backend   10.244.0.44:8080,10.244.0.45:8080   10s

2 つの Pod IP がカンマ区切りで Endpoints に登録されていれば成功です。fanclub-backend Service(ClusterIP 10.96.150.60:80)にリクエストすると、kube-proxy がランダムにいずれかの Pod にルーティングします。

Step 8: 一時 Pod から Service 経由で疎通確認

curlimages/curl 一時 Pod を起動し、fanclub-backend Service 経由で Liveness エンドポイントを叩きます。

実行コマンド:

$ kubectl run curl-once --image=curlimages/curl:8.20.0 --rm -it --restart=Never -- curl -s http://fanclub-backend/health/live

実行結果:

{"status":"UP","checks":[{"name":"fanclub-api-live","status":"UP","data":{}}]}

同様に /health/ready/health/started も確認できます。MicroProfile Health のレスポンスは {"status":"UP",...} の JSON 形式で、本演習②で 3 Probe にこれらのパスを設定する根拠になります。

Step 9: Deployment の全設定を describe で確認

実行コマンド:

$ kubectl describe deployment fanclub-backend-deployment -n default

実行結果(抜粋):

Name:                   fanclub-backend-deployment
Namespace:              default
Selector:               app=fanclub-backend
Replicas:               2 desired | 2 updated | 2 total | 2 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  0 max unavailable, 1 max surge
Pod Template:
  Labels:  app=fanclub-backend
  Service Account: fanclub-backend-sa
  Init Containers:
   wait-for-db:
    Image:  busybox:1.36
  Containers:
   fanclub-backend:
    Image:  fanclub-backend:0.1.0
    Port:   8080/TCP
NewReplicaSet:  fanclub-backend-deployment-7df85c6644 (2/2 replicas created)
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
Events:
  Normal  ScalingReplicaSet  10s  deployment-controller  Scaled up replica set fanclub-backend-deployment-7df85c6644 to 2

StrategyType 行に RollingUpdate、RollingUpdateStrategy 行に 0 max unavailable, 1 max surge が表示されていれば設定通りです。

Pod Template セクションには Init Container と本体コンテナの定義が、Conditions セクションには Available: True / Progressing: True が表示されます。

3 種の Probe とは何か — liveness・readiness・startup の役割と設計原則

Deployment が完成したら次は Probe を設計します。Kubernetes には 3 種類の Probe(livenessProbe / readinessProbe / startupProbe)があり、それぞれ役割と失敗時の動作が異なります。3 つを正しく組み合わせないと、本番運用で「再起動ループに陥る」「起動中の Pod にリクエストが流れる」といった事故が発生します。

3 Probe の役割と失敗時の動作比較

Probe役割失敗時の動作コンテナ再起動Service Endpoints への影響
startupProbe起動完了の判定failureThreshold 超過でコンテナ再起動あり成功するまで Endpoints に追加されない
livenessProbe生存(プロセスがハングしていないか)の判定failureThreshold 超過でコンテナ再起動あり再起動中は Endpoints から除外
readinessProbeリクエスト処理可能かの判定失敗中は Endpoints から除外なしEndpoints から除外(重要)

3 つの Probe を一言でまとめると以下の通りです。

  • startupProbe:起動が遅いアプリを保護する Probe。成功するまで liveness / readiness は評価されない。一度成功すれば以降は実行されない(一回限り)
  • livenessProbe:「壊れた Pod を再起動する」Probe。プロセスが動いているがハング(デッドロック等)している状態を検知する
  • readinessProbe:「起動中・一時障害中の Pod を Service から外す」Probe。失敗してもコンテナは再起動されないため、一時的な負荷増や DB 切断時に「自分は今リクエスト受けたくない」と申告する用途

3 Probe の実行順序フロー

3 Probe の実行順序を時系列で図示します。

コンテナ起動
    │
    ▼
[startupProbe 開始 (periodSeconds: 10 で繰り返し)]
    │   失敗が failureThreshold (30) を超えると → コンテナ再起動
    │
    ▼ 成功 (UP)
    │
[livenessProbe と readinessProbe が並行評価開始]
    │
    ├─ [livenessProbe 失敗 (failureThreshold 連続)] → コンテナ再起動 (Pod IP 変わらない)
    │
    └─ [readinessProbe 失敗] → Endpoints から除外 (コンテナ再起動なし)
        readinessProbe 成功 → Endpoints に追加
3 種の Probe の実行順序フロー図。コンテナ起動後にまず startupProbe が評価され、成功するまで livenessProbe と readinessProbe は保留される。startupProbe 成功後に liveness と readiness が並行して開始し、livenessProbe 失敗時はコンテナ再起動(Pod IP は変わらない)、readinessProbe 失敗時は Service の Endpoints から除外(コンテナ再起動なし)になる流れを縦フローと分岐で示している

注目点は「startupProbe が成功するまで liveness と readiness は評価されない」という点です。Payara Micro は起動に約 7.5 秒かかるため、もし startupProbe なしで livenessProbe を initialDelaySeconds: 5 で設定すると、起動 5 秒時点で livenessProbe が走り始め、まだアプリが立ち上がっていないために連続失敗 → コンテナ再起動 → また起動中に失敗、というループに陥ります。

startupProbe はこのループを物理的に発生させない仕組みです。

MicroProfile Health と K8s Probe の対応

Payara Micro 7.2026.4(fanclub-backend:0.1.0 で採用)は MicroProfile Health 4.0 仕様を実装しており、3 つのエンドポイントを提供します。これらが K8s の 3 Probe と 1:1 で対応します。

K8s ProbeMicroProfile Health エンドポイントレスポンス例設計意図
startupProbe/health/started{"status":"UP",...}Payara Micro 起動 + DB 接続初期化完了で UP
livenessProbe/health/live{"status":"UP",...}アプリプロセス正常動作中(DB 接続を確認しない)
readinessProbe/health/ready{"status":"UP",...}DB 接続確認・DOWN なら Endpoints から除外

重要な設計判断:fanclub-backend の livenessProbe を /health/live(DB 接続を確認しない)にしている理由は、DB が一時的に切断された場合に liveness 失敗 → Pod 再起動 → DB 未接続でまた liveness 失敗 → 再起動ループ という事故を防ぐためです。

DB 接続の有無は readinessProbe(/health/ready)が担当します。DB 切断時には readinessProbe が DOWN を返して該当 Pod だけが Endpoints から外れ、DB が復旧すれば自動で Endpoints に戻る、という挙動になります。

「livenessProbe で DB 接続を確認しない」という設計は MicroProfile Health 公式ガイドラインでも推奨されており、本シリーズ全体の方針として採用します。

Probe パラメータ詳解 — failureThreshold・periodSeconds・initialDelaySeconds・timeoutSeconds

各 Probe には複数のパラメータがあり、組み合わせによって挙動が大きく変わります。本セクションで定量的に整理し、Payara Micro の起動時間(約 7,471 ms = 7.5 秒)を根拠とした設計値を確定します。

Probe 共通パラメータ一覧

パラメータデフォルト意味
initialDelaySeconds0コンテナ起動後、最初の Probe 実行までの待機秒数
periodSeconds10Probe の実行間隔(秒)
timeoutSeconds1Probe の応答タイムアウト秒数
failureThreshold3連続失敗で「失敗」とみなす試行回数
successThreshold1失敗後に「成功」と判定するために必要な連続成功回数(liveness/startup は 1 のみ有効)

startupProbe の設計計算(Payara Micro 実測値ベース)

本シリーズでは startupProbe を failureThreshold: 30 + periodSeconds: 10 で設計します。計算根拠を示します。

Payara Micro 起動時間実測値: 約 7,471 ms (= 7.5 秒・ep3 PoC 実機確認値)

startupProbe 設定:
  failureThreshold: 30
  periodSeconds: 10
  最大待機時間: 30 × 10 = 300 秒 (= 5 分)

設計余裕: 300 秒 ÷ 7.5 秒 ≒ 40 倍
余裕の意図: 高負荷ノード・コールドスタート・JVM ウォームアップの分散・Init Container の DB 接続待ちを含めた最悪ケース

40 倍は過剰に見えますが、本番では JVM の GC やクラウド環境のディスク I/O 揺らぎで起動が 30 秒以上かかる場面もあるため、5 分の上限は現実的な値です。設計時に「最大待機時間 = failureThreshold × periodSeconds」の式を必ず計算し、ピアレビューで根拠を明示するのが運用の定石です。

livenessProbe / readinessProbe のパラメータ設計

本シリーズでは livenessProbe と readinessProbe を以下の値で設計します。

パラメータlivenessProbereadinessProbe設計意図
path/health/live/health/readyMicroProfile Health 標準
port80808080fanclub-backend のコンテナポート
initialDelaySeconds305startupProbe があるためバッファ程度
periodSeconds105readiness は短めで Service 投入を早める
failureThreshold33連続 3 回失敗で発動
timeoutSeconds55Payara Micro の初期クエリ処理を考慮

readinessProbe の periodSeconds: 5 は livenessProbe より短く設定しています。理由は「Service への投入判定(追加 / 除外)はリアルタイム性が求められる」ためです。本番でロードバランサ前段の Pod が一時的に DB 接続を失った場合、5 秒以内に Endpoints から除外して影響を最小化したい、という運用要件です。

パラメータ設計のアンチパターン

アンチパターン症状解決策
livenessProbe.failureThreshold: 1起動直後に 1 回失敗しただけでコンテナが即再起動failureThreshold: 3 以上を設定
readinessProbe.periodSeconds: 30障害検知が 30 秒遅れ、長時間壊れた Pod に流量periodSeconds: 510
timeoutSeconds: 1 (デフォルト)Payara Micro の初期クエリで頻発する Probe timeouttimeoutSeconds: 5 に設定
startupProbe なしで livenessProbe.initialDelaySeconds: 10起動時間ばらつきで断続的な CrashLoopBackOffstartupProbe を必ず追加(H2-12 ヒヤリハット 1 で詳述)

やってみよう②:Deployment に 3 Probe を追加して Rolling Update を実施する

演習①で作成した Deployment に 3 Probe を追加し、kubectl apply で Rolling Update がトリガーされる様子を観察します。

さらに kubectl set env で環境変数を追加して 2 回目の Rolling Update を実施し、kubectl rollout history で履歴確認、kubectl rollout undo でロールバックを体験します。所要時間目安は約 35 分です。

演習の全体フローは以下のとおりです。

  1. fanclub-backend-deployment.yaml に 3 Probe を追加して保存
  2. kubectl apply で更新(リビジョン 2 が生成される)
  3. kubectl rollout status で進行確認
  4. kubectl get pods で 2 Pod が Running/Ready を確認
  5. kubectl rollout history でリビジョン 1→2 の履歴確認
  6. kubectl annotate で CHANGE-CAUSE を付与
  7. kubectl set env で環境変数を追加 → リビジョン 3 を生成
  8. kubectl rollout history でリビジョン 3 を確認
  9. kubectl rollout undo でロールバック → リビジョン 4(= リビジョン 2 内容)が生成
  10. kubectl describe deployment で 3 Probe 設定を確認

Step 1: 3 Probe 追加版の Deployment YAML を作成

演習①の fanclub-backend-deployment.yaml を編集し、spec.template.spec.containers[0]startupProbe / livenessProbe / readinessProbe の 3 つを追加します。

実行コマンド:

$ vi ~/fanclub-manifests/fanclub-backend-deployment.yaml

ファイル内容(3 Probe 追加版・全量):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fanclub-backend-deployment
  namespace: default
  labels:
    app: fanclub-backend
spec:
  replicas: 2
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      app: fanclub-backend
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: fanclub-backend
    spec:
      serviceAccountName: fanclub-backend-sa
      automountServiceAccountToken: false
      initContainers:
        - name: wait-for-db
          image: busybox:1.36
          imagePullPolicy: IfNotPresent
          command:
            - sh
            - -c
            - "until nc -z fanclub-db 5432; do echo waiting for db; sleep 2; done"
      containers:
        - name: fanclub-backend
          image: fanclub-backend:0.1.0
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
          envFrom:
            - configMapRef:
                name: fanclub-config
            - secretRef:
                name: fanclub-secret
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "1000m"
          startupProbe:
            httpGet:
              path: /health/started
              port: 8080
            failureThreshold: 30
            periodSeconds: 10
            timeoutSeconds: 5
          livenessProbe:
            httpGet:
              path: /health/live
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10
            failureThreshold: 3
            timeoutSeconds: 5
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
            failureThreshold: 3
            timeoutSeconds: 5

3 Probe の port: 8080 はコンテナの containerPort: 8080 と一致させます。Probe は Pod の中から localhost に対して実行されるため、Service の port ではなくコンテナポートを指定します。

Step 2: apply で Rolling Update をトリガー

実行コマンド:

$ kubectl apply -f ~/fanclub-manifests/fanclub-backend-deployment.yaml

実行結果:

deployment.apps/fanclub-backend-deployment configured

configured が返れば Pod テンプレートに変更があったため Rolling Update がトリガーされます。Pod テンプレートのハッシュ値が変わるため、新 ReplicaSet(リビジョン 2)が生成されます。

Step 3: rollout status で進行確認

Rolling Update の進行状況をリアルタイム確認します。rollout status は更新完了までブロックする同期コマンドです。

実行コマンド:

$ kubectl rollout status deployment/fanclub-backend-deployment -n default

実行結果:

Waiting for deployment "fanclub-backend-deployment" rollout to finish: 0 out of 2 new replicas have been updated...
Waiting for deployment "fanclub-backend-deployment" rollout to finish: 1 out of 2 new replicas have been updated...
Waiting for deployment "fanclub-backend-deployment" rollout to finish: 1 out of 2 new replicas have been updated...
Waiting for deployment "fanclub-backend-deployment" rollout to finish: 1 out of 2 new replicas have been updated...
Waiting for deployment "fanclub-backend-deployment" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "fanclub-backend-deployment" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "fanclub-backend-deployment" rollout to finish: 1 old replicas are pending termination...
deployment "fanclub-backend-deployment" successfully rolled out

新 Pod が Ready になるまで startupProbe(最大 300 秒)の余裕分の待機が発生する可能性があります。実際は Payara Micro が約 7.5 秒で起動するため、おおむね 30〜60 秒で完了します。

Step 4: ReplicaSet と Pod の状態を確認

実行コマンド:

$ kubectl get replicaset,pods -l app=fanclub-backend -n default

実行結果:

NAME                                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/fanclub-backend-deployment-7df85c6644   0         0         0       2m36s
replicaset.apps/fanclub-backend-deployment-86cf676cf7   2         2         2       71s

NAME                                              READY   STATUS    RESTARTS   AGE
pod/fanclub-backend-deployment-86cf676cf7-m9z4f   1/1     Running   0          71s
pod/fanclub-backend-deployment-86cf676cf7-gp58r   1/1     Running   0          55s

旧 ReplicaSet が DESIRED 0 / READY 0 で残存している点が重要です。kubectl rollout undo でロールバックする際、この旧 ReplicaSet の Pod テンプレートを元に新しい ReplicaSet が再生成されます。完全削除されているわけではない、というのが「リビジョン保持」の実体です。

Step 5: rollout history で履歴を確認

実行コマンド:

$ kubectl rollout history deployment/fanclub-backend-deployment -n default

実行結果(リビジョン 1 と 2 が表示される):

deployment.apps/fanclub-backend-deployment
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

リビジョン 1 が初版(Probe なし)、リビジョン 2 が現在の 3 Probe 追加版です。CHANGE-CAUSE 列が <none> なのは、まだアノテーションを付与していないためです。次のステップで付与します。

Step 6: CHANGE-CAUSE アノテーションを付与

運用では「いつ何のために更新したか」を後追いできるように kubernetes.io/change-cause アノテーションを必ず付ける文化を作ります。

実行コマンド:

$ kubectl annotate deployment fanclub-backend-deployment kubernetes.io/change-cause="3 Probe 追加 (startup/liveness/readiness)" --overwrite -n default

実行結果:

deployment.apps/fanclub-backend-deployment annotated

注意点として、annotate だけでは Pod テンプレートが変わらないため新たな Rolling Update はトリガーされません。CHANGE-CAUSE は「現リビジョン」の情報として記録されます。次の更新を行うと、annotate した内容が「直前リビジョン」のメッセージとして history に残ります。

Step 7: kubectl set env で 2 回目の Rolling Update をトリガー

同じイメージタグでは Rolling Update がトリガーされません。本演習では kubectl set env で環境変数を追加することで Pod テンプレートを変更し、Rolling Update を意図的に走らせます。

実行コマンド:

$ kubectl set env deployment/fanclub-backend-deployment DEMO_VERSION=v3 -n default

実行結果:

deployment.apps/fanclub-backend-deployment env updated

続けて完了まで待機します。

実行コマンド:

$ kubectl rollout status deployment/fanclub-backend-deployment -n default

実行結果:

deployment "fanclub-backend-deployment" successfully rolled out

Step 8: rollout history でリビジョン 3 を確認

実行コマンド:

$ kubectl rollout history deployment/fanclub-backend-deployment -n default

実行結果:

deployment.apps/fanclub-backend-deployment
REVISION  CHANGE-CAUSE
1         <none>
2         3 Probe 追加 (startup/liveness/readiness)
3         <none>

Step 6 で annotate したメッセージがリビジョン 2 の CHANGE-CAUSE として記録されている点を確認します。「annotate した時点の現リビジョン」が後の history に「過去リビジョン」として残る、という挙動です。

Step 9: rollout undo でロールバック

1 つ前のリビジョン(リビジョン 2)の状態にロールバックします。rollout undo はリビジョンを「巻き戻す」のではなく、過去リビジョンの設定で新しいリビジョンを再生成する挙動です。リビジョン 2 の設定がリビジョン 4 として再生成されます。

実行コマンド:

$ kubectl rollout undo deployment/fanclub-backend-deployment -n default

実行結果:

deployment.apps/fanclub-backend-deployment rolled back

続けて履歴を確認します。

実行コマンド:

$ kubectl rollout history deployment/fanclub-backend-deployment -n default

実行結果:

deployment.apps/fanclub-backend-deployment
REVISION  CHANGE-CAUSE
1         <none>
3         <none>
4         <none>

注意:rollout undo するとリビジョン 2 自体は履歴から消え、リビジョン 4 として再登場します。これは「同じ Pod テンプレートが二重に履歴に残らない」設計のためで、history 上は一見リビジョンが飛んだように見えます。慣れるまで戸惑いやすい挙動なので、CKAD 試験でも頻出の理解度確認ポイントです。

特定のリビジョンに戻したい場合は kubectl rollout undo deployment/<name> --to-revision=N を使います。--to-revision=1 なら初版(Probe なし)に戻ります。本演習では undo(リビジョン 2 への暗黙ロールバック)のみ実行します。

Step 10: describe で 3 Probe 設定を確認

実行コマンド:

$ kubectl describe deployment fanclub-backend-deployment -n default

実行結果(Pod Template > Containers > Liveness / Readiness / Startup の 3 行):

Pod Template:
  Labels:           app=fanclub-backend
  Service Account:  fanclub-backend-sa
  Init Containers:
   wait-for-db:
    Image:  busybox:1.36
  Containers:
   fanclub-backend:
    Image:  fanclub-backend:0.1.0
    Port:   8080/TCP
    Liveness:   http-get http://:8080/health/live delay=30s timeout=5s period=10s #success=1 #failure=3
    Readiness:  http-get http://:8080/health/ready delay=5s timeout=5s period=5s #success=1 #failure=3
    Startup:    http-get http://:8080/health/started delay=0s timeout=5s period=10s #success=1 #failure=30

3 行が出力されていれば 3 Probe が正しく設定されています。#failure=30(startupProbe)/ #failure=3(liveness/readiness)の値が設計通りです。

実機観察ポイント:Rolling Update 直後に kubectl describe pod の Events で以下のような一過性の Warning が出ることがあります。

Warning  Unhealthy  9s    kubelet    spec.containers{fanclub-backend}: Startup probe failed: HTTP probe failed with statuscode: 503

これは Payara Micro が起動中(JVM 初期化中)に startupProbe が最初のチェックを行い、まだ /health/started が HTTP 503 を返している瞬間に記録された一過性の失敗です。failureThreshold: 30 の余裕があるため、数秒後に Payara Micro の起動が完了して UP(HTTP 200)を返すと startupProbe が成功し、以降は liveness / readiness が正常に評価されます。

この Warning は設計通りの動作であり、Pod 再起動には至りません。

やってみよう③:Probe デバッグ実践 — 意図的に失敗する Probe を設定して原因を特定する

livenessProbe に存在しないポート(9999)への TCP socket 接続を設定した Pod を意図的に作成し、再起動が繰り返される過程を観察します。kubectl describe の Events で Unhealthy: connection refusedKillingCreatedStarted の一連を読み取り、kubectl logs --previous で再起動前のログを取得します。

さらに kubectl debug で ephemeral container を起動して同 Pod 内をライブ調査する手順も体験します。所要時間目安は約 30 分です。

演習の全体フローは以下のとおりです。

  1. 意図的に壊れた Probe の Pod YAML を作成(broken-probe-pod.yaml・tcpSocket port: 9999)
  2. kubectl apply で Pod を起動
  3. 約 60 秒待機して RESTARTS 増加を確認
  4. kubectl describe pod の Events を確認
  5. kubectl logs --previous で再起動前のログを確認
  6. kubectl debug ephemeral container でライブ調査
  7. 修正版 Pod YAML(tcpSocket → httpGet /health/live)を作成
  8. 旧 Pod を削除して修正版を apply
  9. RESTARTS が 0 のままで安定することを確認
  10. kubectl delete pod でクリーンアップ

Step 1: 壊れた Probe の Pod YAML を作成

livenessProbe に存在しないポート 9999 への TCP socket 接続を設定します。fanclub-backend は 9999 番ポートを Listen していないため、必ず connection refused になり Probe が連続失敗します。

実行コマンド:

$ vi ~/fanclub-manifests/broken-probe-pod.yaml

ファイル内容:

apiVersion: v1
kind: Pod
metadata:
  name: broken-probe
  labels:
    app: broken-probe
spec:
  containers:
    - name: app
      image: fanclub-backend:0.1.0
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 8080
      envFrom:
        - configMapRef:
            name: fanclub-config
        - secretRef:
            name: fanclub-secret
      livenessProbe:
        tcpSocket:
          port: 9999
        initialDelaySeconds: 10
        periodSeconds: 5
        failureThreshold: 3
        timeoutSeconds: 3

TCP socket probe は指定ポートへの接続試行で成否を判定します。HTTP probe と異なりアプリの応答内容に依存しないため、「ポートが開いているか」という単純な生死確認に使います。今回はデバッグ演習の目的で、意図的に 存在しないポート 9999 を指定して確実に connection refused を発生させます。

Step 2: Pod を apply

実行コマンド:

$ kubectl apply -f ~/fanclub-manifests/broken-probe-pod.yaml

実行結果:

pod/broken-probe created

Step 3: RESTARTS の増加を確認

初回 livenessProbe は initialDelaySeconds: 10 秒後に始まり、periodSeconds: 5 × failureThreshold: 3 = 15 秒後に再起動が発動します。合計で起動から約 60 秒後に RESTARTS が 1 になり始めます。

実行コマンド:

$ sleep 90; kubectl get pod broken-probe -n default

実行結果(RESTARTS が 1 以上 + STATUS が Running):

NAME           READY   STATUS    RESTARTS      AGE
broken-probe   1/1     Running   1 (29s ago)   90s

STATUS が Running + RESTARTS 1 は「liveness probe 3 回連続失敗でコンテナが再起動された直後の状態」です。再起動が繰り返されると kubelet の指数バックオフが働き、再起動間隔が広がって CrashLoopBackOff に遷移することもあります。

Step 4: kubectl describe pod で Events を確認

Probe デバッグの最重要コマンドが kubectl describe pod です。Events セクションに kubelet が何を判定したかが時系列で記録されています。

実行コマンド:

$ kubectl describe pod broken-probe -n default

実行結果(Events セクション抜粋):

Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  91s                default-scheduler  Successfully assigned default/broken-probe to kind-control-plane
  Normal   Pulled     40s (x2 over 90s)  kubelet            spec.containers{app}: Container image "fanclub-backend:0.1.0" already present on machine and can be accessed by the pod
  Normal   Created    40s (x2 over 90s)  kubelet            spec.containers{app}: Container created
  Normal   Started    40s (x2 over 90s)  kubelet            spec.containers{app}: Container started
  Warning  Unhealthy  20s (x6 over 80s)  kubelet            spec.containers{app}: Liveness probe failed: dial tcp 10.244.0.53:9999: connect: connection refused
  Normal   Killing    20s (x2 over 70s)  kubelet            spec.containers{app}: Container app failed liveness probe, will be restarted

注目すべきメッセージは以下の 2 つです。

  • Unhealthy: Liveness probe failed: dial tcp 10.244.0.53:9999: connect: connection refused ← 原因がここに書いてある(9999 番ポートは存在しない)
  • Killing: Container app failed liveness probe, will be restarted ← 結果として再起動が発動

TCP socket probe の失敗メッセージは dial tcp <pod-ip>:<port>: connect: connection refused の形式になります。HTTP probe の HTTP probe failed with statuscode: NNN とは異なり、TCP レベルで接続拒否された状態を示します。

ポート番号誤り・対象プロセス未起動・アプリのクラッシュを疑います。

Step 5: kubectl logs --previous で再起動前のログを取得

RESTARTS が 1 以上のとき、現在の Pod インスタンスのログ(kubectl logs broken-probe)は新しいコンテナのものになります。再起動前のログを見るには --previous(または短縮形 -p)を付けます。

実行コマンド:

$ kubectl logs broken-probe --previous -n default

実行結果(Payara Micro 起動ログが表示される):

[2026-05-10T21:45:12.305+0000] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1746917112305] [levelValue: 800] Payara Micro 7.2026.4 #badassfish started in 7,471 (ms)
[2026-05-10T21:45:19.820+0000] [] [WARNING] [] [KernelLogger] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1746917119820] [levelValue: 900] Container app failed liveness probe

本演習ではアプリ自身は正常に起動しています(Payara Micro started in 7,471 (ms) が確認できる)。アプリに ERROR や Exception は発生しておらず、「アプリは正常 → Probe 設定が原因」という切り分けが --previous ログから読み取れます。

TCP socket probe の場合、アプリ側には一切ログが残らないため、Events の connection refused メッセージが唯一の手がかりになります。

Step 6: kubectl debug ephemeral container でライブ調査

Pod が動いている状態で同じ namespace に ephemeral container を割り込ませて調査する kubectl debug コマンドを使います。CrashLoopBackOff 中の Pod に対して、コンテナ内のネットワーク・プロセスを生で確認できる強力なデバッグ手段で、CKAD D3「Kubernetes でのデバッグ」の Competency に対応します。

実行コマンド:

$ TARGET=broken-probe
$ kubectl debug -it $TARGET --image=curlimages/curl:8.20.0 --target=app --profile=general -n default -- sh -c 'echo "=== nc port 9999 ===" ; nc -zv localhost 9999 2>&1; echo "=== curl /health/live ==="; curl -s http://localhost:8080/health/live'

ephemeral container に入った後、Probe と同じ TCP socket 接続を手動で試みてから、正常な HTTP エンドポイントとの対比を確認します。

実行結果:

Targeting container "app". If you don't see processes from this container it may be because the container runtime doesn't support this feature.
Defaulting debug container name to debugger-9l2sm.
=== nc port 9999 ===
nc: connect to localhost port 9999 (tcp) failed: Connection refused
=== curl /health/live ===
{"status":"UP","checks":[{"name":"fanclub-api-live","status":"UP","data":{}}]}

--target=app で対象コンテナの PID 名前空間を共有しているため、nc / curl は対象コンテナの localhost に届きます。ポート 9999 が Connection refused であること(TCP socket probe が失敗する理由)と、ポート 8080 の /health/live が HTTP 200 を返すこと(アプリ自体は正常)の対比が実機で確認できます。

「probe 設定誤りでポートが間違っていた」と原因が確定します。

ephemeral container は Pod を終了させずに調査を行えるため、本番でも安全に使えます。kubectl exec がコンテナイメージにシェルが含まれていない場合に使えないのに対して、kubectl debug は別イメージを差し込めるため curl/jq/tcpdump 等の調査ツールを後から持ち込めるのが利点です。CKAD 試験でも頻出のテクニックです。

補足: Payara MicroProfile Health の path 設計の罠

本演習で tcpSocket: port 9999 を採用した理由は実機検証で判明した Payara MicroProfile Health の挙動に起因します。実機で確認した結果:

/health/nonexistent  → HTTP 200  {"status":"UP","checks":[]}
/totally-bogus-path  → HTTP 404

Payara MicroProfile Health は /health/* 配下のどんなサブパスに対してもデフォルトで HTTP 200 を返します(registered health checks が 0 件でも UP を返す仕様)。
そのため livenessProbe.httpGet.path: /health/nonexistent を設定しても Probe は常に成功し、再起動ループが発生しません。

/health/ 配下のパスを livenessProbe のデバッグ失敗例に使うと意図通りに壊れない、という現場で陥りやすい設計の罠です。本演習では接続自体が失敗する TCP socket probe(存在しないポート 9999)を使って確実に再起動を発生させています。

Step 7: 修正版 Pod YAML を作成

原因が「livenessProbe の tcpSocket ポート誤り(存在しない 9999 番)」と確定したので、正しい HTTP probe(/health/live ポート 8080)に修正します。

実行コマンド:

$ vi ~/fanclub-manifests/broken-probe-pod.yaml

変更箇所(livenessProbe を tcpSocket から httpGet に変更):

livenessProbe:
  httpGet:
    path: /health/live
    port: 8080
  initialDelaySeconds: 30
  periodSeconds: 10
  failureThreshold: 3
  timeoutSeconds: 5

Step 8: 旧 Pod を削除して修正版を apply

Pod は spec の大半が不変フィールドのため、Probe のパス変更には Pod 削除 + 再 apply が必要です。Deployment ならこの作業は不要で、kubectl apply で自動 Rolling Update がトリガーされます(だからこそ本番では Pod 単体ではなく Deployment を使う動機になります)。

実行コマンド:

$ kubectl delete pod broken-probe -n default
$ kubectl apply -f ~/fanclub-manifests/broken-probe-pod.yaml

実行結果:

pod "broken-probe" deleted
pod/broken-probe created

Step 9: RESTARTS が 0 で安定することを確認

実行コマンド:

$ sleep 90; kubectl get pod broken-probe -n default

実行結果(RESTARTS が 0 のまま Running):

NAME           READY   STATUS    RESTARTS   AGE
broken-probe   1/1     Running   0          90s

RESTARTS が 0 のまま 90 秒経過すれば、Probe 修正が正しく効いています。

Step 10: クリーンアップ

演習③で作成したデモ用 Pod を削除します。fanclub-backend Deployment は残します。

実行コマンド:

$ kubectl delete pod broken-probe -n default

実行結果:

pod "broken-probe" deleted

Deployment + 3 Probe の CKAD 試験頻出パターン — kubectl dry-run と explain の活用

CKAD 試験は Performance-based(実機操作)形式で、120 分の制限時間の中で複数の課題を解く必要があります。Deployment + 3 Probe 関連の頻出パターンと、それを高速に解くためのテクニックを整理します。

CKAD 試験頻出パターン 3 選

  1. 「起動に N 秒かかるアプリに startupProbe を設定して liveness の誤射を防ぐ」failureThreshold × periodSeconds ≥ N の式で算出。例:60 秒なら failureThreshold: 12, periodSeconds: 5
  2. 「Rolling Update でダウンタイムをゼロにする Deployment を作成する」maxSurge: 1, maxUnavailable: 0(または比率で 25% / 0
  3. 「readinessProbe が失敗したときに Service への影響を確認する」kubectl get endpoints <service-name> -w で Endpoints の Pod IP が外れる様子を観察

速攻テクニック:dry-run スケルトン → 手書き追加

試験では「Deployment を nginx:1.27-alpine イメージで replicas: 3 で作成し、livenessProbe を設定せよ」のような複合課題が出ます。dry-run でスケルトンを生成してから Probe を手書き追加する流れが定石です。

実行コマンド:

$ kubectl create deployment myapp --image=nginx:1.27-alpine --replicas=3 --dry-run=client -o yaml > myapp.yaml
$ vi myapp.yaml

spec.template.spec.containers[0] 配下に livenessProbe: ブロックを手書き追加して保存し、kubectl apply -f myapp.yaml で適用します。Probe のフィールド名や値が思い出せない場合は kubectl explain pod.spec.containers.livenessProbe.httpGet で確認します。

速攻テクニック:kubectl debug で実機確認

試験では「Probe 失敗の原因を特定せよ」という課題も出ます。describe で Events を読む基本フローに加えて、kubectl debug ephemeral container で対象 Pod に curl/jq/tcpdump を持ち込めると CrashLoopBackOff 中の Pod でも調査が進みます。

実行コマンド(典型形):

$ kubectl debug -it <pod-name> --image=curlimages/curl:8.20.0 --target=<container-name> -- sh

--target は対象コンテナの PID 名前空間を共有するためのオプションです。これにより ephemeral container 内から localhost:8080 で対象コンテナのアプリにアクセスできます。指定しないと、ephemeral container は Pod の他コンテナとは独立した PID 名前空間で動きます。

速攻テクニック:rollout コマンド一覧

コマンド用途
kubectl rollout status deployment/<name>更新完了まで待機(同期)
kubectl rollout history deployment/<name>リビジョン一覧確認
kubectl rollout history deployment/<name> --revision=N特定リビジョンの詳細確認
kubectl rollout undo deployment/<name>1 つ前のリビジョンにロールバック
kubectl rollout undo deployment/<name> --to-revision=N特定リビジョンにロールバック
kubectl rollout pause deployment/<name>進行中の Rolling Update を一時停止
kubectl rollout resume deployment/<name>一時停止した Rolling Update を再開
kubectl rollout restart deployment/<name>同じイメージで強制 Rolling Update(環境変数や Secret 更新時に使う)

kubectl rollout restart は本演習②で使った kubectl set env の代替手段です。Pod テンプレートに kubectl.kubernetes.io/restartedAt アノテーションを自動付与することで Rolling Update をトリガーします。

Secret や ConfigMap を更新したけれど Pod に反映されない、というときに rollout restart で全 Pod を順次再起動するのが定石の運用テクニックです。

現場ヒヤリハット — startupProbe 不足と readinessProbe 設定漏れ

Probe の設計ミスは本番で発覚すると深刻な障害につながります。本セクションでは典型的な 2 件のヒヤリハットを根本原因と本番ガードレールまで含めて整理します。

ヒヤリハット 1: startupProbe なし・initialDelaySeconds 不足での再起動ループ

シナリオ:JVM 系アプリ(Spring Boot + Payara Micro 等)の Deployment に livenessProbe.initialDelaySeconds: 15 のみを設定した。

ローカル開発環境では起動 5〜6 秒で問題なかったが、本番の高負荷ノードでは JVM の初期化が 15〜20 秒かかることがあり、livenessProbe の最初の評価(initialDelaySeconds 経過後)が起動途中に行われて失敗 → コンテナ再起動 → また初期化 → また失敗 → CrashLoopBackOff に陥った。

確認コマンド(事故時の Events 確認):

$ kubectl describe pod <pod-name>

実行結果(Events 抜粋):

Events:
  Warning  Unhealthy  ...  Liveness probe failed: Get "http://10.244.0.42:8080/health/live": dial tcp 10.244.0.42:8080: connect: connection refused
  Normal   Killing    ...  Container app failed liveness probe, will be restarted
  Warning  Unhealthy  ...  Liveness probe failed: Get "http://10.244.0.42:8080/health/live": dial tcp 10.244.0.42:8080: connect: connection refused
  Normal   Killing    ...  Container app failed liveness probe, will be restarted
  ...

HTTP statuscode が connection refused(実質ステータス 0)になっているため、「アプリプロセスがまだポートを Listen していない」という状態です。アプリが起動完了する前に Probe が走っているのが原因と判定できます。

根本原因initialDelaySeconds のみで起動遅延に対応しようとすると、起動時間のばらつきに脆弱になります。特に JVM はノードの CPU 負荷・GC 設定・JIT コンパイル状況で起動時間が大きく変動し、固定値の initialDelaySeconds ではカバーしきれません。

解決策:startupProbe を追加して liveness / readiness の評価を「起動完了後」に遅延させます。本シリーズの設計では startupProbe.failureThreshold: 30 × periodSeconds: 10 = 300 秒 の余裕を持たせています。

本番ガードレール:JVM 系・Ruby on Rails・機械学習モデルロード等、起動が遅いアプリには必ず startupProbe を設定する。failureThreshold × periodSeconds で最大起動待機時間を設計し、ピアレビューで根拠を文書化する。

CI/CD では PR レビュー時に「livenessProbe があるが startupProbe がない」を検出する Linter を導入する運用が一般的です。

ヒヤリハット 2: readinessProbe 設定漏れで起動中 Pod に Service トラフィック

シナリオ:Deployment に livenessProbe と startupProbe は設定したが readinessProbe を設定し忘れた。

Rolling Update で新 Pod が起動する際、Payara Micro の起動(約 7.5 秒)中でも Pod が Running 状態(startupProbe が成功すると Ready 判定される)になった瞬間に Service の Endpoints に追加されてしまい、本来はリクエストを受けられない瞬間にトラフィックが流れてクライアントが 503 Service Unavailable を受け取った。

確認コマンド:

$ kubectl get endpoints fanclub-backend -w

readinessProbe がない場合、Pod の Ready 条件は startupProbe 成功または「コンテナプロセスが起動した時点」で判定されます。アプリが完全にトラフィック処理可能になる前に Endpoints に追加されるタイムラグが発生します。

根本原因:readinessProbe が定義されていない場合、kubelet は startupProbe 成功(または startupProbe もない場合はプロセス起動)の時点で Pod を Ready と判定します。

アプリが内部的にまだ「DB プールの初期化中」「キャッシュロード中」等の前処理を実行している瞬間でも Service 投入されるため、503 や 500 系エラーが断続的に発生します。

解決策:readinessProbe を追加し、アプリ側で「リクエスト受付準備完了」を返すエンドポイント(例:MicroProfile Health の /health/ready)を実装する。readinessProbe が UP を返すまで Endpoints に追加されないため、Rolling Update 中のトラフィック損失を防げます。

本番ガードレール:Rolling Update 中のゼロダウンタイムには readinessProbe が必須。livenessProbe のみでは新 Pod が起動途中でリクエストを受ける時間帯が必ず発生する。3 Probe はセットで設定する文化を作る。

kubectl get endpoints <service> -w で Rolling Update 中の Endpoints 変化を観察し、Pod IP が遅れて追加されることを確認する習慣を持つと事故が減ります。

ep12 完了後の模擬アプリ状態と第4部第1回まとめ + ep13 への橋渡し

本回で fanclub-backend が単体 Pod から Deployment + 3 Probe に格上げされ、本番に近い構成になりました。第4部「ワークロード戦略」第1回として ep13・ep14 への橋渡しを行います。

ep12 完了後のクラスタ状態

リソース状態
fanclub-backend Deployment(新規)replicas: 2 / 2 Pod Running / 3 Probe 設定済 / RollingUpdate maxSurge:1 maxUnavailable:0 / Rolling Update 履歴 3 リビジョン以上
fanclub-backend Service(ClusterIP)変更なし(ep8 から継続)
fanclub-db StatefulSet(fanclub-db-0)継続稼働(ep9 から)
fanclub-db / fanclub-db-headless Service継続(ep9 から)
postgres-data-fanclub-db-0 PVCBound(ep9 から)
ConfigMap fanclub-config4 キー継続
Secret fanclub-secret2 キー継続
ServiceAccount fanclub-backend-sa継続(ep10 から)
DaemonSet node-logger1 Pod Running 継続(ep11 から)
CronJob fanclub-member-countSuspend: True 継続(ep11 から)
members テーブル2 行 + score カラム継続(ep11 から)
旧 fanclub-backend Pod(単体)削除済
broken-probe Pod削除済(演習③後クリーンアップ)

fanclub-api をどう作ってきたか

第7回:  Pod (Backend) 起動
第8回:  Service で外部接続性
第9回:  StatefulSet (DB) で 3 層構成完成
第10回: ConfigMap / Secret / SA で設定外部化
第11回: Job / CronJob / DaemonSet でバッチ系・デーモン系を追加
第12回: ★ Deployment + 3 Probe で本番常駐サービス化  ← 今ここ
        - 自己回復 (replicas: 2)
        - ゼロダウンタイム更新 (RollingUpdate maxSurge:1 maxUnavailable:0)
        - ヘルスチェック完備 (startup / liveness / readiness)
        - リビジョン履歴管理 (rollout history / undo)

第3部「アプリリソース」(ep7〜ep11)でリソース基盤が揃い、第4部第1回(ep12)で「常駐サービスの本番運用準備」が完了しました。

CKAD ドメイン D2「Application Deployment」の中核 Competency「Deployment とローリングアップデートの実施」と D3「Application Observability and Maintenance」の中核 Competency「Probe とヘルスチェックの実装」「Kubernetes でのデバッグ」を本回で網羅しました。

ep13 への橋渡し

ep13 では本回で作成した fanclub-backend Deployment をベースに、複数のデプロイ戦略を実機で比較します。

  • Blue/Green デプロイ:2 つの Deployment(blue / green)を別 selector で稼働させ、Service の selector を切り替えて瞬時にトラフィックを切り替える
  • Canary リリース:本番 Deployment と Canary Deployment のレプリカ比率でトラフィック分割を実現する
  • Recreate 戦略の詳細演習:本回で概念整理した Recreate を実際に実行してダウンタイムを実機で確認する

ep13 では本回で確認した「spec.selector は不変フィールド」という制約と正面から向き合い、Blue/Green の selector 切り替えが Deployment の再作成を伴うことを実機で扱います。CKAD D2 の残り Competency「共通デプロイ戦略の実装」を完全網羅して第4部第2回が完結します。

理解度チェック・第12回まとめ・次回予告・シリーズ一覧

理解度チェック(○×形式・9 問)

問 1:Deployment の spec.selector は一度 apply した後に kubectl apply で値を変更できる。

問 2maxSurge: 1 / maxUnavailable: 0 に設定した Rolling Update では、更新中に常に replicas 数の Pod が稼働している状態を維持できる。

問 3strategy.type: Recreate を設定した Deployment を更新すると、旧 Pod がすべて削除されてから新 Pod が起動するためダウンタイムが発生する。

問 4startupProbe が成功するまでの間、livenessProbereadinessProbe は評価されない。

問 5readinessProbefailureThreshold 回連続で失敗すると、コンテナが再起動される。

問 6livenessProbefailureThreshold 回連続で失敗すると、Pod が削除されて新しい Pod に置き換えられる。

問 7:Payara Micro のような起動が遅いアプリに対して livenessProbe.initialDelaySeconds のみで対応するより、startupProbe を使う方が起動時間のばらつきに強い。

問 8kubectl rollout undo deployment/<name> を実行すると、Deployment 自体が削除される。

問 9:Deployment の Rolling Update 完了後、旧バージョンの ReplicaSet は完全に削除される。

解答

解答解説
問 1×spec.selector は不変フィールド。変更しようとすると spec.selector: Invalid value: ... field is immutable エラーが返る。selector を変えたい場合は Deployment を kubectl delete してから再作成する
問 2maxUnavailable: 0 は「同時に Ready 数が replicas を下回ることを許さない」という意味。maxSurge: 1 で一時的に + 1 Pod を許容することで常時 replicas 数を維持する。これがゼロダウンタイム更新の本質
問 3Recreate 戦略では旧 Pod を全削除 → 新 Pod を全起動の順で進む。新 Pod が Ready になるまでの時間帯はサービス停止になる。Payara Micro のような起動が遅いアプリでは数十秒のダウンタイムになることもある
問 4startupProbe は「起動完了判定」専用。成功するまで liveness と readiness は評価されない仕様で、起動が遅いアプリの誤射を防ぐ役割を持つ。一度成功すれば以降は実行されない(一回限り)
問 5×readinessProbe 失敗時はコンテナ再起動しない。Service の Endpoints から該当 Pod を除外するのみ。一時的な高負荷や DB 切断のような「治る可能性がある状態」を扱うのが readiness の役割。コンテナ再起動を発動するのは liveness と startup
問 6×livenessProbe 失敗時はコンテナのみ再起動される。Pod は削除されず、Pod IP も変わらない。ReplicaSet が同じ Pod を再生成するわけでもない。kubelet が同じ Pod 内のコンテナを停止 → 再起動するという挙動
問 7initialDelaySeconds は固定値で、起動時間のばらつき(JVM の GC や JIT、ノード負荷の影響)に弱い。startupProbe は failureThreshold × periodSeconds の上限内で柔軟に成功を待つため、ばらつきに強い
問 8×kubectl rollout undo は Deployment を削除しない。1 つ前のリビジョンの設定で新しいリビジョンを再生成する。リビジョン 3 から undo するとリビジョン 4(= 旧リビジョン 2 内容)が生成される
問 9×Rolling Update 完了後も旧 ReplicaSet は replicas: 0 で保持される。kubectl rollout undo でロールバックする際の参照元になる。spec.revisionHistoryLimit(デフォルト 10、本番推奨 3〜5)で保持数を制御

第12回まとめ

第12回では以下を実施しました。

  • Pod / ReplicaSet / Deployment の 3 層構造を整理した。Deployment が ReplicaSet を、ReplicaSet が Pod を管理する階層関係を kube-system の coredns / metrics-server で実機観察した。本番常駐サービスには Deployment を使うのが定石で、ReplicaSet を直接作成する場面はほぼないことを確認した
  • Deployment YAML の主要フィールド(replicas / selector / template / strategy)を網羅した。spec.selector は一度 apply すると変更不可な不変フィールドで、変更には Deployment 削除 + 再作成が必要であることを確認した。kubectl create deployment --dry-run=client -o yaml でスケルトン生成 → 手書きで Probe や envFrom を追加する CKAD 試験テクニックを習得した
  • Rolling Update を maxSurge: 1 / maxUnavailable: 0 のゼロダウンタイム設定で実装した。新旧 ReplicaSet の共存メカニズム、revisionHistoryLimit(本番推奨 3〜5)、kubectl rollout status / history / undo / restart の使い分けを実機で確認した。kubectl set env で Rolling Update をトリガーし、rollout undo 後のリビジョン番号が増える仕組み(リビジョン 2 → リビジョン 4 として再生成)を体験した
  • 3 種の Probe(startupProbe / livenessProbe / readinessProbe)を体系的に学んだ。startupProbe は起動完了判定専用で成功するまで liveness/readiness を保留する。livenessProbe はコンテナ再起動を発動する。readinessProbe は Service Endpoints からの除外のみでコンテナは再起動されない。MicroProfile Health の /health/started / /health/live / /health/ready エンドポイントを 3 Probe に対応させ、Payara Micro 7.5 秒起動を根拠に startupProbe.failureThreshold: 30 × periodSeconds: 10 = 300 秒 を設計した
  • Probe デバッグ実践として、意図的に壊れた livenessProbe(tcpSocket: port 9999・存在しないポート)の Pod で再起動ループを発生させ、kubectl describe pod の Events で Unhealthy: dial tcp 10.244.0.53:9999: connect: connection refused を読み取った。kubectl logs --previous で再起動前のログを取得し、kubectl debug ephemeral container で対象 Pod に nc / curl を持ち込んでライブ調査するテクニックを習得した。なお実機検証で判明した Payara MicroProfile Health の挙動として /health/nonexistent は HTTP 200 を返す(/health/* 配下はすべて UP)ため、HTTP probe を意図的に失敗させるには /totally-bogus-path(HTTP 404)を使う必要があることも確認した
  • 現場ヒヤリハットを 2 件扱った。startupProbe なし + livenessProbe.initialDelaySeconds 不足での再起動ループ、readinessProbe 設定漏れで起動中 Pod に Service トラフィックが流れて 503 が出た事例を、根本原因と本番ガードレールまで整理した。本シリーズ全体で「JVM 系アプリには startupProbe 必須」「3 Probe はセットで設定」を方針として確立した

次回予告

第13回 Deployment 戦略補完(Blue/Green + Canary + Recreate)では、本回で作成した fanclub-backend Deployment をベースに、複数のデプロイ戦略を実機で比較します。Blue/Green デプロイ(2 つの Deployment を Service の selector 切り替えで切り替える)・Canary リリース(レプリカ比率でトラフィック分割)・Recreate 戦略の詳細演習を行い、CKAD D2 の残り Competency「共通デプロイ戦略の実装」を完全網羅します。

本回で確認した「spec.selector は不変フィールド」という制約と正面から向き合い、Blue/Green の selector 切り替えが Deployment の再作成を伴うことを実機で扱います。

シリーズ一覧

第1部:コンテナと Docker

第2部:Kubernetes 基礎

第3部:アプリリソース

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

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

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

広告
kubernetes
スポンサーリンク