- 第12回スコープ・学習目標・今ここマップ
- Pod 単独・ReplicaSet・Deployment の階層 — なぜ Deployment が必要か
- Deployment YAML 構造 — replicas・selector・template・strategy を読み解く
- Rolling Update の仕組み — ReplicaSet の新旧共存と maxSurge / maxUnavailable
- Recreate 戦略 — ダウンタイムありの全切り替え
- やってみよう①:fanclub-backend を Deployment に移行し replicas: 2 で冗長化する
- 3 種の Probe とは何か — liveness・readiness・startup の役割と設計原則
- Probe パラメータ詳解 — failureThreshold・periodSeconds・initialDelaySeconds・timeoutSeconds
- やってみよう②:Deployment に 3 Probe を追加して Rolling Update を実施する
- Step 1: 3 Probe 追加版の Deployment YAML を作成
- Step 2: apply で Rolling Update をトリガー
- Step 3: rollout status で進行確認
- Step 4: ReplicaSet と Pod の状態を確認
- Step 5: rollout history で履歴を確認
- Step 6: CHANGE-CAUSE アノテーションを付与
- Step 7: kubectl set env で 2 回目の Rolling Update をトリガー
- Step 8: rollout history でリビジョン 3 を確認
- Step 9: rollout undo でロールバック
- Step 10: describe で 3 Probe 設定を確認
- やってみよう③:Probe デバッグ実践 — 意図的に失敗する Probe を設定して原因を特定する
- Step 1: 壊れた Probe の Pod YAML を作成
- Step 2: Pod を apply
- Step 3: RESTARTS の増加を確認
- Step 4: kubectl describe pod で Events を確認
- Step 5: kubectl logs --previous で再起動前のログを取得
- Step 6: kubectl debug ephemeral container でライブ調査
- Step 7: 修正版 Pod YAML を作成
- Step 8: 旧 Pod を削除して修正版を apply
- Step 9: RESTARTS が 0 で安定することを確認
- Step 10: クリーンアップ
- Deployment + 3 Probe の CKAD 試験頻出パターン — kubectl dry-run と explain の活用
- 現場ヒヤリハット — startupProbe 不足と readinessProbe 設定漏れ
- ep12 完了後の模擬アプリ状態と第4部第1回まとめ + ep13 への橋渡し
- 理解度チェック・第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 Service | ClusterIP 10.96.150.60:80 稼働中 | Lead 実機観察 |
| fanclub-db StatefulSet | fanclub-db-0 Pod Running(PostgreSQL 18・members テーブル 2 行 + score 列) | Lead 実機観察 |
| fanclub-db Service / fanclub-db-headless Service | ClusterIP 10.96.131.167:5432 / clusterIP: None | Lead 実機観察 |
| ConfigMap | fanclub-config(4 キー: DB_HOST / DB_PORT / DB_NAME / JAVA_OPTS) | SP_vol1-pre-21 |
| Secret | fanclub-secret(Opaque・2 キー: DB_USER / DB_PASSWORD) | SP_vol1-pre-21 |
| ServiceAccount | fanclub-backend-sa(ep10 で作成) | SP_vol1-pre-21 |
| DaemonSet | node-logger(1 Pod Running・ep11 で作成) | Lead 実機観察 |
| CronJob | fanclub-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 --previousやkubectl debugephemeral 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-2Pod を手動で作る必要があり、宣言的管理にならない - ローリングアップデートができない:新しいイメージタグに更新するには Pod を
kubectl deleteしてから新版 Pod をkubectl applyする必要があり、ダウンタイムが必ず発生する
これら 3 つの限界を解消するために登場するのが ReplicaSet と Deployment です。両者は階層関係にあり、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 経由) |
| Deployment | ReplicaSet を新旧入れ替えてローリングアップデート・ロールバックを実現 | kubectl apply / kubectl rollout |

本番常駐サービスには 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 配下の主要フィールド一覧
| フィールド | デフォルト | 意味 |
|---|---|---|
replicas | 1 | 維持する Pod 数。replicas: 2 なら 2 Pod が常時稼働 |
selector.matchLabels | —(必須) | Deployment が管理する Pod を識別するラベル。一度作成すると変更不可(不変フィールド) |
template.metadata.labels | —(必須) | Pod に付与するラベル。selector.matchLabels と一致している必要がある |
template.spec | —(必須) | Pod テンプレート。コンテナ定義・volumes・SA など Pod のすべてを記述 |
strategy.type | RollingUpdate | RollingUpdate または Recreate |
strategy.rollingUpdate.maxSurge | 25% | Rolling Update 中に追加で起動できる Pod 数(または比率) |
strategy.rollingUpdate.maxUnavailable | 25% | Rolling Update 中に同時に利用不可になってよい Pod 数(または比率) |
revisionHistoryLimit | 10 | 保持する旧 ReplicaSet の数(ロールバック用) |
progressDeadlineSeconds | 600 | Rolling 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.matchLabels と spec.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)・envFrom・resources・initContainers・3 種の Probe・serviceAccountName・automountServiceAccountToken を手動で追加していくのが定石です。
--dry-run=client ではこれらは生成されないため、各フィールドの仕様は kubectl explain deployment.spec.strategy や kubectl 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 がどのように共存し、maxSurge と maxUnavailable がどう機能するか、本番ではどのパラメータが推奨かを整理します。
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 設計パターン
maxSurge と maxUnavailable の組み合わせで複数の更新戦略を表現できます。代表的な 4 パターンを整理します。
| パターン名 | maxSurge | maxUnavailable | 特徴 | 本番推奨度 |
|---|---|---|---|---|
| ゼロダウンタイム | 1(または 25%) | 0 | 追加 Pod を先に起動してからダウン → 常に replicas 数が稼働 | 本番推奨 |
| リソース節約 | 0 | 1(または 25%) | 先にダウンさせてからアップ → 瞬間的に台数が減る | リソース制約あり時のみ |
| 高速更新 | 1 | 1 | 同時に 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: 3 〜 5 に設定することが多くなっています。デフォルトの 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 の比較
| 項目 | RollingUpdate | Recreate |
|---|---|---|
| ダウンタイム | なし(正しく設定した場合) | あり(旧 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 を追加していく段階的アプローチを採用します。
演習の全体フローは以下のとおりです。
- 前提状態の確認(
kubectl get pods,svc,deploy -n default) - 旧 fanclub-backend Pod を削除(
kubectl delete pod fanclub-backend) fanclub-backend-deployment.yamlを作成(Probe なし版・H2-3 で確認した YAML 構造)kubectl apply -f ~/fanclub-manifests/fanclub-backend-deployment.yamlで Deployment を作成kubectl get deploymentで READY 2/2 を確認kubectl get podsで 2 Pod が Running を確認kubectl get endpoints fanclub-backendで 2 エンドポイント登録を確認curlimages/curl一時 Pod で Service 経由疎通確認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 に追加

注目点は「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 Probe | MicroProfile 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 共通パラメータ一覧
| パラメータ | デフォルト | 意味 |
|---|---|---|
initialDelaySeconds | 0 | コンテナ起動後、最初の Probe 実行までの待機秒数 |
periodSeconds | 10 | Probe の実行間隔(秒) |
timeoutSeconds | 1 | Probe の応答タイムアウト秒数 |
failureThreshold | 3 | 連続失敗で「失敗」とみなす試行回数 |
successThreshold | 1 | 失敗後に「成功」と判定するために必要な連続成功回数(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 を以下の値で設計します。
| パラメータ | livenessProbe | readinessProbe | 設計意図 |
|---|---|---|---|
| path | /health/live | /health/ready | MicroProfile Health 標準 |
| port | 8080 | 8080 | fanclub-backend のコンテナポート |
| initialDelaySeconds | 30 | 5 | startupProbe があるためバッファ程度 |
| periodSeconds | 10 | 5 | readiness は短めで Service 投入を早める |
| failureThreshold | 3 | 3 | 連続 3 回失敗で発動 |
| timeoutSeconds | 5 | 5 | Payara Micro の初期クエリ処理を考慮 |
readinessProbe の periodSeconds: 5 は livenessProbe より短く設定しています。理由は「Service への投入判定(追加 / 除外)はリアルタイム性が求められる」ためです。本番でロードバランサ前段の Pod が一時的に DB 接続を失った場合、5 秒以内に Endpoints から除外して影響を最小化したい、という運用要件です。
パラメータ設計のアンチパターン
| アンチパターン | 症状 | 解決策 |
|---|---|---|
livenessProbe.failureThreshold: 1 | 起動直後に 1 回失敗しただけでコンテナが即再起動 | failureThreshold: 3 以上を設定 |
readinessProbe.periodSeconds: 30 | 障害検知が 30 秒遅れ、長時間壊れた Pod に流量 | periodSeconds: 5〜10 秒 |
timeoutSeconds: 1 (デフォルト) | Payara Micro の初期クエリで頻発する Probe timeout | timeoutSeconds: 5 に設定 |
startupProbe なしで livenessProbe.initialDelaySeconds: 10 | 起動時間ばらつきで断続的な CrashLoopBackOff | startupProbe を必ず追加(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 分です。
演習の全体フローは以下のとおりです。
fanclub-backend-deployment.yamlに 3 Probe を追加して保存kubectl applyで更新(リビジョン 2 が生成される)kubectl rollout statusで進行確認kubectl get podsで 2 Pod が Running/Ready を確認kubectl rollout historyでリビジョン 1→2 の履歴確認kubectl annotateで CHANGE-CAUSE を付与kubectl set envで環境変数を追加 → リビジョン 3 を生成kubectl rollout historyでリビジョン 3 を確認kubectl rollout undoでロールバック → リビジョン 4(= リビジョン 2 内容)が生成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 refused → Killing → Created → Started の一連を読み取り、kubectl logs --previous で再起動前のログを取得します。
さらに kubectl debug で ephemeral container を起動して同 Pod 内をライブ調査する手順も体験します。所要時間目安は約 30 分です。
演習の全体フローは以下のとおりです。
- 意図的に壊れた Probe の Pod YAML を作成(
broken-probe-pod.yaml・tcpSocket port: 9999) kubectl applyで Pod を起動- 約 60 秒待機して
RESTARTS増加を確認 kubectl describe podの Events を確認kubectl logs --previousで再起動前のログを確認kubectl debugephemeral container でライブ調査- 修正版 Pod YAML(tcpSocket → httpGet
/health/live)を作成 - 旧 Pod を削除して修正版を apply
- RESTARTS が 0 のままで安定することを確認
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 選
- 「起動に N 秒かかるアプリに startupProbe を設定して liveness の誤射を防ぐ」→
failureThreshold×periodSeconds≥ N の式で算出。例:60 秒ならfailureThreshold: 12, periodSeconds: 5 - 「Rolling Update でダウンタイムをゼロにする Deployment を作成する」→
maxSurge: 1, maxUnavailable: 0(または比率で25%/0) - 「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 PVC | Bound(ep9 から) |
ConfigMap fanclub-config | 4 キー継続 |
Secret fanclub-secret | 2 キー継続 |
ServiceAccount fanclub-backend-sa | 継続(ep10 から) |
DaemonSet node-logger | 1 Pod Running 継続(ep11 から) |
CronJob fanclub-member-count | Suspend: 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 で値を変更できる。
問 2:maxSurge: 1 / maxUnavailable: 0 に設定した Rolling Update では、更新中に常に replicas 数の Pod が稼働している状態を維持できる。
問 3:strategy.type: Recreate を設定した Deployment を更新すると、旧 Pod がすべて削除されてから新 Pod が起動するためダウンタイムが発生する。
問 4:startupProbe が成功するまでの間、livenessProbe と readinessProbe は評価されない。
問 5:readinessProbe が failureThreshold 回連続で失敗すると、コンテナが再起動される。
問 6:livenessProbe が failureThreshold 回連続で失敗すると、Pod が削除されて新しい Pod に置き換えられる。
問 7:Payara Micro のような起動が遅いアプリに対して livenessProbe.initialDelaySeconds のみで対応するより、startupProbe を使う方が起動時間のばらつきに強い。
問 8:kubectl rollout undo deployment/<name> を実行すると、Deployment 自体が削除される。
問 9:Deployment の Rolling Update 完了後、旧バージョンの ReplicaSet は完全に削除される。
解答:
| 問 | 解答 | 解説 |
|---|---|---|
| 問 1 | × | spec.selector は不変フィールド。変更しようとすると spec.selector: Invalid value: ... field is immutable エラーが返る。selector を変えたい場合は Deployment を kubectl delete してから再作成する |
| 問 2 | ○ | maxUnavailable: 0 は「同時に Ready 数が replicas を下回ることを許さない」という意味。maxSurge: 1 で一時的に + 1 Pod を許容することで常時 replicas 数を維持する。これがゼロダウンタイム更新の本質 |
| 問 3 | ○ | Recreate 戦略では旧 Pod を全削除 → 新 Pod を全起動の順で進む。新 Pod が Ready になるまでの時間帯はサービス停止になる。Payara Micro のような起動が遅いアプリでは数十秒のダウンタイムになることもある |
| 問 4 | ○ | startupProbe は「起動完了判定」専用。成功するまで liveness と readiness は評価されない仕様で、起動が遅いアプリの誤射を防ぐ役割を持つ。一度成功すれば以降は実行されない(一回限り) |
| 問 5 | × | readinessProbe 失敗時はコンテナ再起動しない。Service の Endpoints から該当 Pod を除外するのみ。一時的な高負荷や DB 切断のような「治る可能性がある状態」を扱うのが readiness の役割。コンテナ再起動を発動するのは liveness と startup |
| 問 6 | × | livenessProbe 失敗時はコンテナのみ再起動される。Pod は削除されず、Pod IP も変わらない。ReplicaSet が同じ Pod を再生成するわけでもない。kubelet が同じ Pod 内のコンテナを停止 → 再起動するという挙動 |
| 問 7 | ○ | initialDelaySeconds は固定値で、起動時間のばらつき(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 debugephemeral 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
- 第1回 コンテナ技術概念 + Docker 環境準備
- 第2回 Docker 基本操作
- 第3回 Dockerfile + マルチステージビルド + JDK 25 / Payara Micro イメージビルド
- 第4回 コンテナレジストリ + イメージタグ戦略 + Trivy スキャン
第2部:Kubernetes 基礎
第3部:アプリリソース
- 第7回 Pod + Multi-container パターン
- 第8回 Service とネットワーキング
- 第9回 ストレージ(PVC + StatefulSet)+ PostgreSQL DB 追加
- 第10回 ConfigMap + Secret + ServiceAccount 基礎
- 第11回 Job + CronJob + DaemonSet
第4部:ワークロード戦略
- 第12回 Deployment + 3 Probe + Rolling Update + Probe デバッグ実践 ← 今ここ
- 第13回 Deployment 戦略補完(Blue/Green + Canary + Recreate)
- 第14回 ResourceQuota + LimitRange + Multi-tenant Namespace
