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

K8s Blue/Greenデプロイ実装【CKAD第13回】

広告
広告

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

動作確認バージョン: 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-23 起点)

本回は Kubernetes 実践教科書 第1巻(CKAD 対応・全 19 回)の第13回です。第4部「ワークロード戦略」第2回として、第12回の Rolling Update + 3 Probe を土台に、残り 3 つのデプロイ戦略(Recreate / Blue/Green / Canary)を実機演習で習得します。

CKAD ドメイン D2「Application Deployment」(出題比率 20 %)の Competency「共通デプロイ戦略の実装(Blue/Green・Canary)」を本回で網羅し、第12回 + 第13回で D2 全体が完全網羅になります。

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

項目状態出典
kind クラスタkind-control-plane Ready(v1.35.0)Lead 実機観察
fanclub-backend Deploymentreplicas: 2 / 3 Probe 設定済 / RollingUpdate maxSurge:1 maxUnavailable:0 / Rolling Update 履歴ありep12 完了状態
fanclub-backend ServiceClusterIP 10.96.150.60:80 / selector: app: fanclub-backendep8 から継続
fanclub-db StatefulSetfanclub-db-0 Pod Running(PostgreSQL 18・members 2 行 + score 列)ep9 から継続
ConfigMapfanclub-config(4 キー: DB_HOST / DB_PORT / DB_NAME / JAVA_OPTS)ep10 から継続
Secretfanclub-secret(Opaque・2 キー: DB_USER / DB_PASSWORD)ep10 から継続
ServiceAccountfanclub-backend-sa(automountServiceAccountToken: false)ep10 から継続
DaemonSetnode-logger(1 Pod Running)ep11 から継続
CronJobfanclub-member-count(Suspend: True)ep11 から継続

今ここマップ(第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回)

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

  • Recreate 戦略を kubectl patch で設定し、kubectl get pods -w でダウンタイム発生を実機で観察できる。RollingUpdate との挙動差を Pod 遷移レベルで説明できる
  • Blue/Green デプロイを実装し、kubectl patch service で Service の selector を切り替えて即時無停止切り替えを実現できる。ロールバックが selector 戻しのみで完了することを実機で確認できる
  • Canary リリースを Deployment の Pod 数比率で実装し、安定版 4 + Canary 1 = 合計 5 Pod のうち約 20 % のトラフィックが Canary 側へ向かうことを Endpoints から説明できる
  • 4 戦略(RollingUpdate / Recreate / Blue/Green / Canary)のトレードオフ(ダウンタイム有無・リソース消費・ロールバック速度・適用シナリオ)を比較表で説明できる
  • CKAD 試験の D2「共通デプロイ戦略の実装(Blue/Green・Canary)」Competency に対応できる。kubectl patch / kubectl scale / kubectl set image の速攻コマンドを暗記レベルで使える

模擬アプリ進捗(第13回):第12回で fanclub-backend が単体 Pod から Deployment(replicas: 2 + 3 Probe + RollingUpdate)に格上げされ、本番常駐サービスの基盤が完成しました。本回では同じ fanclub-backend に対して 3 つの代替デプロイ戦略を実機で体験します。

Recreate でダウンタイムの実態を確認し、Blue/Green で 2 系統並列稼働の即時切り替えを試し、Canary で Pod 数比率トラフィック分割の粗粒度問題に向き合います。本回の演習は「戦略の体験」が目的で、ep14 への引き継ぎはあくまで「ep12 完了状態の Deployment が稼働している」状態に戻します。

第13回完了後の模擬アプリ状態:fanclub-backend Deployment は ep12 と同じく replicas: 2 + 3 Probe + RollingUpdate で稼働。Service の selector も app: fanclub-backend 単独に戻します。

Blue / Green / Canary の Deployment は演習中に作成しますが、各演習の末尾で削除して ep14(ResourceQuota + LimitRange + Multi-tenant Namespace)へクリーンな状態を引き継ぎます。

4 つのデプロイ戦略の全体像 — どれをいつ使うか

個別の演習に入る前に、本回で扱う 4 つの戦略の全体像を整理します。第12回で扱った RollingUpdate を含めると、CKAD で問われるデプロイ戦略は以下の 4 つです。CKAD 試験では「この状況ならどの戦略が最適か」を問う選択問題が複数回出題されるため、戦略名と適用シナリオの結びつきを早い段階で頭に入れます。

4 戦略の全体マップ

ここで重要なのは、Blue/Green と Canary は K8s に 専用リソースが存在しないという事実です。両者とも Deployment と Service の組み合わせで実現します。一方、RollingUpdate と Recreate は Deployment の spec.strategy.type として K8s に組み込まれた戦略です。

CKAD 試験で「Blue/Green を実装するための Kubernetes リソースは何か」と問われたら、答えは「Deployment + Service」になります。

4 戦略比較表 — 適用シナリオ

項目RollingUpdateRecreateBlue/GreenCanary
ダウンタイムなし(正しく設定時)ありなしなし
リソース消費+maxSurge 分×2 倍(Blue + Green 並列)+α 程度(Canary 数 Pod 分)
ロールバック速度段階的(rollout undo)遅い(再 Recreate でダウンタイム再発)即時(selector 戻し)即時(Canary Deployment 削除)
同時バージョン稼働更新中の一瞬だけなしあり(明示的)あり(明示的)
K8s 組み込みあり(strategy.typeあり(strategy.typeなし(Deployment + Service)なし(Deployment + Service)
適用シナリオ通常の Web サービス更新DB スキーマ破壊的変更・Singleton高リスク更新・即時切替が必要段階的リリース・リスク限定
CKAD 出題頻度高(ep12 で習得済)

戦略選択の判断フロー

本番運用でどの戦略を選ぶかは、以下の 3 つの問いで枝分かれします。

Q1. ダウンタイムを許容できるか?
    No  → Q2 へ
    Yes → Recreate を選択(DB スキーマ破壊的変更等)

Q2. 新旧 2 バージョンが同時稼働して問題ないか?
    No  → Recreate に戻る(同時稼働 NG なら Recreate しか選択肢がない)
    Yes → Q3 へ

Q3. 即時カットオーバー / 段階的リリースのどちらか?
    即時      → Blue/Green
    段階的    → Canary(または通常の RollingUpdate)

この判断フローは CKAD 試験の戦略選択問題に直接対応します。「DB スキーマの破壊的変更で旧版が新スキーマを読むと壊れる」と問題文にあれば Q2 で No に分岐するため Recreate が答えになります。

「現行版を残したまま新版に即時切り替え、問題があれば即時ロールバック」と書かれていれば Blue/Green が答えです。問題文のキーワードと判断フローを対応させて読む練習をしておくと得点が安定します。

4 戦略を支える共通の前提 — readinessProbe と Service Endpoints

4 戦略の挙動を理解するための共通の前提として、readinessProbe と Service Endpoints の関係を再確認します。第12回で扱った内容ですが、本回の Blue/Green と Canary でも前提として効いてくるため、ここで整理し直します。

  • readinessProbe の役割:Pod がリクエストを受け付けられる状態かを判定する。UP を返すまで Pod は Service の Endpoints に登録されない。アプリが起動中・DB プール初期化中・キャッシュロード中の状態でトラフィックが流れることを防ぐ仕組み
  • Service Endpoints と EndpointSlice:Service の selector に合致し、かつ readinessProbe が UP の Pod の IP を集めたリスト。K8s v1.21+ では EndpointSlice として保存され、kube-proxy が監視している。selector または Pod の Ready 状態が変わると即座に更新される
  • kube-proxy のロードバランシング:iptables モードでは statistic mode random の確率テーブルで Endpoint を選択し、ipvs モードでは rr / lc / sed 等のアルゴリズムで分散する。本回の演習③で扱う Canary の Pod 数比率分散もこの仕組みに依存している

本回の演習①の Recreate ダウンタイムは「Service Endpoints が空になる時間帯」として観測されます。演習②の Blue/Green 即時切り替えは「Service の selector 変更で Endpoints が新しい Pod IP リストに書き換わる」現象です。

演習③の Canary 比率分散は「Endpoints に 5 Pod の IP が並んで kube-proxy が均等分散する」結果です。3 つの演習が同じ Endpoints 機構の異なる側面を示していることを念頭に読み進めてください。

Recreate 戦略の詳細 — ダウンタイムを計画的に許容する

Recreate は K8s 組み込みのデプロイ戦略で、Deployment の spec.strategy.type: Recreate として設定します。第12回で概念だけ整理したこの戦略を、本回では実機で挙動を観察します。

Recreate の動作フロー

初期状態:
  ReplicaSet v1 [Pod×2 Running]
       │
       │ Deployment 更新トリガー(image 変更 / env 追加 等)
       ▼
Step 1: 旧 Pod 全削除
  [Pod×0]   ← この瞬間からダウンタイム開始(Service Endpoints が空になる)
       │
       ▼
Step 2: 新 ReplicaSet が新 Pod を全起動
  [Pod×2 ContainerCreating → Running]
       │
       │ startupProbe が成功するまで Endpoints に登録されない
       ▼
完了:
  [Pod×2 Running] ← ダウンタイム終了

RollingUpdate との根本的な違いは「同時稼働する Pod の数」です。RollingUpdate は maxSurge: 1, maxUnavailable: 0 で常時 replicas 数の Ready Pod を維持しますが、Recreate は意図的に「全旧 Pod 削除 → 全新 Pod 起動」の手順を踏みます。

新旧 Pod が同時稼働する瞬間は存在しません。これが「DB スキーマ破壊的変更時の安全網」になります。

ダウンタイムの計算式(教育的補足)

fanclub-backend の場合、Recreate のダウンタイムは以下の和でおおよそ計算できます。

旧 Pod の Terminating 完了:           約 0〜2 秒(preStop なし・gracePeriod 30 秒以内)
+ Init Container wait-for-db 完了:    約 2 秒(fanclub-db 稼働中なので即成功)
+ Payara Micro 起動:                  約 7.5 秒(ep12 ログ実測)
+ startupProbe 最初の評価まで:        最大 10 秒(periodSeconds: 10)
+ readinessProbe 最初の成功まで:      約 5 秒(periodSeconds: 5)
────────────────────────────────────────────
合計:                                 約 20〜25 秒のダウンタイムが発生

20〜25 秒というダウンタイムは Web サービスでは長く、業務時間中に無計画に発生させるとクレームに直結します。Recreate を本番で使う場合はメンテナンスウィンドウを事前に設定し、ステークホルダーに通知する手順が必須になります。

第12回で扱った RollingUpdate(maxSurge: 1 / maxUnavailable: 0)であれば同じ更新でもダウンタイム 0 秒で完了します。「Recreate を使う合理的な理由」が無い限り、本番のデフォルトは RollingUpdate に固定するのが安全策です。

kubectl patch で strategy を変更する構文

既存 Deployment の strategy を Recreate に変更するには、YAML を書き換えて kubectl apply する方法と、kubectl patch でその場で変更する方法の 2 つがあります。CKAD 試験では時間制約が厳しいため、kubectl patch を直接打つ方が高速です。

Recreate へ変更する patch コマンド:

$ kubectl patch deployment fanclub-backend-deployment \
  -p '{"spec":{"strategy":{"type":"Recreate"}}}'

このコマンドは strategic merge patch(kubectl patch のデフォルト)で、JSON で指定したフィールドだけが上書きされます。spec.strategy.type のみを変更し、他のフィールド(replicas / template 等)には影響しません。

RollingUpdate に戻す patch コマンド:

$ kubectl patch deployment fanclub-backend-deployment \
  -p '{"spec":{"strategy":{"type":"RollingUpdate","rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}'

RollingUpdate に戻す際は rollingUpdate サブフィールドを必ず明示します。これを省略するとデフォルト値(25 % / 25 %)に戻ってしまい、ep12 で設定したゼロダウンタイム条件が崩れます。CKAD 試験でも本番でも同じ落とし穴で、「Recreate 演習後に RollingUpdate 値が変わっていた」というインシデントの典型パターンです。

Recreate の適切なユースケース

Recreate を選ぶ合理的なケースは限定されます。代表的な 2 つのシナリオを示します。

  1. 破壊的 DB スキーマ変更:カラム削除・カラム型変更・PK 変更など、旧 Backend が新 Schema を読むと致命的に壊れる変更。新旧 2 バージョンの Backend が同時稼働すると DB アクセスが破綻するため、Recreate で「旧版を全停止 → スキーマ移行 → 新版を全起動」の手順を踏む必要があります。実務では「事前に互換スキーマで RollingUpdate → 旧カラム削除を後追いマイグレーションで実施」する Expand-Contract パターンを優先しますが、それが取れない緊急対応では Recreate が選択肢になります
  2. Singleton リソース・排他制御:分散ロックを取らない設計のレガシーアプリ・ライセンス制約で「同時に 1 バージョンのみ稼働」が必須なシステム・ステートフルなインメモリキャッシュを 1 インスタンスに集約しているアプリ等。RollingUpdate で新旧が同居する一瞬がアプリの不整合を起こす場合、Recreate で同居を完全に避けます

逆に、ステートレスな Web API・REST バックエンド・通常の業務アプリでは Recreate を選ぶ理由はほぼありません。これらは RollingUpdate(maxSurge: 1 / maxUnavailable: 0)でゼロダウンタイム更新ができるためです。

Recreate と RollingUpdate の Pod 入れ替え挙動の比較

Recreate と RollingUpdate の挙動の違いを replicas: 2 のケースで時系列に並べると、以下のように対比できます。本回演習①の kubectl get pods -w の観察と対応させて読むと理解が定着します。

RollingUpdate (maxSurge: 1, maxUnavailable: 0):
  T=0   旧Pod-A Running, 旧Pod-B Running                                 (Ready 2/2)
  T=1   旧Pod-A Running, 旧Pod-B Running, 新Pod-C ContainerCreating      (Ready 2/3)
  T=10  旧Pod-A Running, 旧Pod-B Running, 新Pod-C Running                (Ready 3/3)
  T=11  旧Pod-A Terminating, 旧Pod-B Running, 新Pod-C Running            (Ready 2/3)
  T=12  旧Pod-A 削除完了, 旧Pod-B Running, 新Pod-C Running, 新Pod-D Creating (Ready 2/3)
  T=22  旧Pod-B Running, 新Pod-C Running, 新Pod-D Running                (Ready 3/3)
  T=23  旧Pod-B Terminating, 新Pod-C Running, 新Pod-D Running            (Ready 2/3)
  T=24  新Pod-C Running, 新Pod-D Running                                 (Ready 2/2)
  ──── 全期間で Ready Pod ≥ 2 を維持 → ダウンタイム 0 ────

Recreate:
  T=0   旧Pod-A Running, 旧Pod-B Running                                 (Ready 2/2)
  T=1   旧Pod-A Terminating, 旧Pod-B Terminating                          (Ready 0/2 へ向かう)
  T=2   旧Pod-A 削除完了, 旧Pod-B 削除完了                                 (Ready 0/0)
                  ↓ この時点から Service Endpoints 空 = ダウンタイム
  T=3   新Pod-C Pending, 新Pod-D Pending                                  (Ready 0/2)
  T=4   新Pod-C ContainerCreating, 新Pod-D ContainerCreating              (Ready 0/2)
  T=14  新Pod-C Running 0/1, 新Pod-D Running 0/1 (startupProbe 評価中)     (Ready 0/2)
  T=22  新Pod-C Running 1/1, 新Pod-D Running 1/1 (readinessProbe UP)       (Ready 2/2)
                  ↑ この時点でダウンタイム終了
  ──── 約 T=2〜T=22 の 20 秒間 Ready 0 → ダウンタイム約 20 秒 ────

RollingUpdate は maxSurge: 1 で一時的に Pod を +1 起動して新旧入れ替えを進めるため、常時 Ready Pod ≥ replicas が保たれます。Recreate は意図的にこの保護を外して「全 Pod 削除 → 全 Pod 起動」を実行します。

Recreate を選ぶときは「この約 20 秒のダウンタイムを許容する業務理由」を明文化しておくと、後任メンバーが見たときに判断意図が引き継がれます。

本番ガードレール:Recreate は意図的に選ぶ場合のみ

本番運用では Deployment の strategy を明示的に RollingUpdate + maxSurge: 1 + maxUnavailable: 0 として記述するのが安全のデフォルトです。Recreate を使うときは:

  • メンテナンスウィンドウを事前に設定する(業務時間外・告知済み時間帯)
  • ステークホルダー(業務部門・カスタマーサポート・モニタリング担当)にダウンタイム時間と影響範囲を通知する
  • 事前に DB バックアップを取得する(破壊的スキーマ変更を伴うことが多いため)
  • ロールバック計画を文書化する(Recreate のロールバックは再 Recreate でダウンタイムが再発するため、事前計画が無いと復旧時間が伸びる)

「strategy を一時的に Recreate に変えて演習した」状態のまま放置すると、次の通常更新時に意図せずダウンタイムが発生します。本回の演習①でも末尾で必ず RollingUpdate に戻す手順を踏みます。

Recreate の K8s 公式定義は kubernetes.io/docs/concepts/workloads/controllers/deployment/ の「Strategy」セクションで確認できます。CKAD 試験中はこの URL を Web ブラウザで開いて該当箇所を参照可能です。

やってみよう①: Recreate 戦略でダウンタイムを実機で観察する

所要時間目安:約 25 分。fanclub-backend Deployment の strategy を Recreate に変更し、Pod 全削除 → 全起動の遷移を kubectl get pods -w で観察します。

並行して別ターミナルで curl 連続アクセスを走らせ、ダウンタイム時間帯(約 20〜25 秒)に接続が切れることを実機で確認します。演習末尾で RollingUpdate に戻して ep12 状態を復元します。

Step 1: 前提状態を確認する

演習開始前に Deployment と Pod が ep12 完了状態であることを確認します。

実行コマンド:

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

実行結果:

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
fanclub-backend-deployment    2/2     2            2           3d

NAME                                          READY   STATUS    RESTARTS   AGE
fanclub-backend-deployment-86cf676cf7-b96pz   1/1     Running   0          3d
fanclub-backend-deployment-86cf676cf7-gp58r   1/1     Running   0          3d

READY 2/2 で 2 Pod が Running 状態であれば前提 OK です。続いて現在の strategy を確認します。

実行コマンド:

$ kubectl get deployment fanclub-backend-deployment -n default -o jsonpath='{.spec.strategy}'
$ echo

実行結果:

{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0},"type":"RollingUpdate"}

type: RollingUpdatemaxSurge: 1maxUnavailable: 0 が確認できれば ep12 完了状態です。演習末尾でこの値に戻すため、ここで控えておきます。

Step 2: kubectl patch で strategy を Recreate に変更する

strategic merge patch で strategy.type のみを変更します。既存ファイル ~/fanclub-manifests/fanclub-backend-deployment.yaml は編集せず、kubectl patch で直接変更する手順です(CKAD 速攻パターン)。

実行コマンド:

$ kubectl patch deployment fanclub-backend-deployment -n default \
  -p '{"spec":{"strategy":{"type":"Recreate","rollingUpdate":null}}}'

実行結果:

deployment.apps/fanclub-backend-deployment patched

rollingUpdatenull で削除する指定を入れている理由は、Recreate 戦略では rollingUpdate サブフィールドを保持できないためです。type: Recreate だけ指定すると spec.strategy.rollingUpdate field is forbidden エラーになります。rollingUpdate: null で明示的に削除すると安全です。

変更が反映されたことを確認します。

実行コマンド:

$ kubectl get deployment fanclub-backend-deployment -n default -o jsonpath='{.spec.strategy}'
$ echo

実行結果:

{"type":"Recreate"}

type: Recreate 単独になり、rollingUpdate サブフィールドが消えていれば変更成功です。kubectl describe deployment で確認する場合は StrategyType: Recreate 行を見ます。

Step 3: 別ターミナルで連続 curl を起動する(ダウンタイム観察用)

ここからは作業が並行します。別の SSH セッションを開いて、Service 経由で fanclub-backend に連続 curl をかける一時 Pod を起動します。

Service は selector が app: fanclub-backend のままなので、Recreate 中に旧 Pod が消える瞬間に connection refused または connect timeout が観測できます。

実行コマンド(別ターミナル):

$ kubectl run curl-watch -n default --image=curlimages/curl:8.20.0 --rm -it --restart=Never -- \
  sh -c 'while true; do printf "%s " "$(date +%H:%M:%S)"; curl -sS -o /dev/null -w "HTTP %{http_code} (%{time_total}s)\n" --max-time 3 http://fanclub-backend/health/live || echo "FAILED"; sleep 1; done'

実行結果(最初の数秒・正常時):

If you don't see a command prompt, try pressing enter.
14:32:01 HTTP 200 (0.012s)
14:32:02 HTTP 200 (0.009s)
14:32:03 HTTP 200 (0.010s)
14:32:04 HTTP 200 (0.011s)

HTTP 200 が 1 秒間隔で連続して出力されていれば、Service 経由のアクセスは正常です。このターミナルはそのまま開いたまま、メインターミナルに戻って次の Step に進みます。

Step 4: kubectl set env で更新をトリガーする

Recreate の挙動を発動させるには Deployment の Pod テンプレートに変更を加える必要があります。同じ image タグ(fanclub-backend:0.1.0)を再利用するため、image 変更ではなく環境変数の追加を使います。

kubectl set envRECREATE_TEST という環境変数を追加すれば Pod テンプレートのハッシュが変わり、新しい ReplicaSet が生成されて Recreate のロジックが動きます。

実行コマンド:

$ kubectl set env deployment/fanclub-backend-deployment -n default RECREATE_TEST="v2"

実行結果:

deployment.apps/fanclub-backend-deployment env updated

このコマンドが返った瞬間から Recreate の動作が始まります。すぐに次の Step で Pod 遷移を観察します。

Step 5: kubectl get pods -w で Pod 遷移を観察する

Pod の状態遷移をリアルタイムに観察します。Recreate では「全旧 Pod が Terminating → 全 Pod が消える瞬間 → 新 Pod が ContainerCreating → Running」という遷移が見えます。

実行コマンド:

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

実行結果(時系列・抜粋):

NAME                                          READY   STATUS              RESTARTS   AGE
fanclub-backend-deployment-86cf676cf7-b96pz   1/1     Running             0          3d
fanclub-backend-deployment-86cf676cf7-gp58r   1/1     Running             0          3d
fanclub-backend-deployment-86cf676cf7-b96pz   1/1     Terminating         0          3d
fanclub-backend-deployment-86cf676cf7-gp58r   1/1     Terminating         0          3d
fanclub-backend-deployment-86cf676cf7-b96pz   0/1     Terminating         0          3d
fanclub-backend-deployment-86cf676cf7-gp58r   0/1     Terminating         0          3d
fanclub-backend-deployment-66bd87c865-lzf8s   0/1     Pending             0          0s
fanclub-backend-deployment-66bd87c865-rmrgl   0/1     Pending             0          0s
fanclub-backend-deployment-66bd87c865-lzf8s   0/1     ContainerCreating   0          1s
fanclub-backend-deployment-66bd87c865-rmrgl   0/1     ContainerCreating   0          1s
fanclub-backend-deployment-66bd87c865-lzf8s   0/1     Running             0          3s
fanclub-backend-deployment-66bd87c865-rmrgl   0/1     Running             0          3s
fanclub-backend-deployment-66bd87c865-lzf8s   1/1     Running             0          43s
fanclub-backend-deployment-66bd87c865-rmrgl   1/1     Running             0          43s

注目すべき遷移は以下の 3 点です。Ctrl-C で観察を止めて次の Step に進みます。

  • 旧 ReplicaSet(hash 86cf676cf7)の 2 Pod が同時に Terminating に入る → RollingUpdate なら 1 Pod ずつ Terminating だが、Recreate は全 Pod 同時
  • 旧 Pod が完全に消えてから新 ReplicaSet(hash 66bd87c865)の 2 Pod が起動する → 「全消滅 → 全起動」の境目がここ
  • 新 Pod の 0/1 Running → 1/1 Running の遷移が遅れる(Pod 起動から約 40 秒後)→ startupProbe + readinessProbe が UP を返すまで 0/1 が続く(Payara Micro 起動 ~7 秒 + Probe 評価 ~30 秒)

Step 6: 別ターミナルでダウンタイムを確認する

Step 3 で起動した連続 curl のターミナルに戻ります。Recreate 中の出力にダウンタイム時間帯(FAILED または HTTP 503/000)が並んでいるはずです。

実行結果(連続 curl 出力・抜粋):

14:32:18 HTTP 200 (0.011s)
14:32:19 HTTP 200 (0.010s)
14:32:20 HTTP 200 (0.012s)
14:32:21 FAILED
14:32:22 FAILED
14:32:23 FAILED
14:32:24 FAILED
14:32:25 FAILED
14:32:26 FAILED
14:32:27 FAILED
14:32:28 FAILED
14:32:29 FAILED
14:32:30 FAILED
14:32:31 FAILED
14:32:32 FAILED
14:32:33 FAILED
14:32:34 FAILED
14:32:35 FAILED
14:32:36 FAILED
14:32:37 FAILED
14:32:38 FAILED
14:32:39 FAILED
14:32:40 HTTP 200 (0.014s)
14:32:41 HTTP 200 (0.011s)

FAILED 出力が約 19 秒間連続で並びます。これが Recreate のダウンタイムです(実機計測では 20〜25 秒の範囲・Pod 起動時間のばらつきあり)。

同時刻帯の Step 5 出力と突き合わせると、旧 Pod が Terminating になった瞬間から新 Pod が 1/1 Running(readinessProbe UP)になるまでの時間と一致します。Service の Endpoints が空だった時間帯がそのままダウンタイムとして観測されています。

連続 curl は Ctrl-C で停止します。--rm 指定により curl-watch Pod は自動削除されます。

Step 7: strategy を RollingUpdate に戻す

演習が終わったら必ず ep12 状態の RollingUpdate に戻します。maxSurge: 1 / maxUnavailable: 0 も明示的に指定して、ゼロダウンタイム条件を復元します。

実行コマンド:

$ kubectl patch deployment fanclub-backend-deployment -n default \
  -p '{"spec":{"strategy":{"type":"RollingUpdate","rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}'

実行結果:

deployment.apps/fanclub-backend-deployment patched

復元を確認します。

実行コマンド:

$ kubectl get deployment fanclub-backend-deployment -n default -o jsonpath='{.spec.strategy}'
$ echo

実行結果:

{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0},"type":"RollingUpdate"}

Step 1 の出力と一致していれば ep12 状態への復元成功です。

Step 8: RECREATE_TEST 環境変数を削除する

演習で追加した RECREATE_TEST 環境変数を削除して、Pod テンプレートを ep12 状態に戻します。kubectl set env で末尾に - を付けると環境変数を削除する正式な構文になります。

実行コマンド:

$ kubectl set env deployment/fanclub-backend-deployment -n default RECREATE_TEST-

実行結果:

deployment.apps/fanclub-backend-deployment env updated

環境変数が削除されたことで Pod テンプレートのハッシュが変わり、今度は RollingUpdate 戦略で新 ReplicaSet が生成されます。Step 5 の Pod 観察を再開すると、今度は「1 Pod ずつ入れ替わる(maxSurge: 1 / maxUnavailable: 0)」遷移が見えます。Recreate との対比として有益です。

実行コマンド:

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

実行結果:

Waiting for deployment "fanclub-backend-deployment" rollout to finish: 1 old replicas are pending termination...
deployment "fanclub-backend-deployment" successfully rolled out

RollingUpdate に戻った状態での更新は「1 old replicas are pending termination」というメッセージになり、Recreate のような「全 Pod 一斉 Terminating」は起きません。Recreate と RollingUpdate の挙動差が同じ Deployment 上で確認できます。

Step 9: 演習①の最終状態を確認する

演習①の終了状態は ep12 完了状態と完全一致するはずです。

実行コマンド:

$ kubectl get deployment,pods -l app=fanclub-backend -n default
$ kubectl get deployment fanclub-backend-deployment -n default -o jsonpath='{.spec.template.spec.containers[0].env}'
$ echo

実行結果:

NAME                                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/fanclub-backend-deployment   2/2     2            2           3d

NAME                                              READY   STATUS    RESTARTS   AGE
pod/fanclub-backend-deployment-86cf676cf7-fq49t   1/1     Running   0          54s
pod/fanclub-backend-deployment-86cf676cf7-rgthk   1/1     Running   0          74s

env の出力が空(RECREATE_TEST が消えた状態)・READY 2/2・strategy: RollingUpdate であれば演習①は成功です。Pod のハッシュは演習で 2 回変わっているため ep12 直後とは別 hash になりますが、これは正常です。

Blue/Green デプロイの概念と設計 — Service selector 切り替えで即時無停止移行

Recreate と RollingUpdate は K8s 組み込みの戦略でしたが、Blue/Green は K8s に専用リソースが存在しません。Deployment と Service を組み合わせて自前で実装します。本節では Blue/Green の原理・ラベル設計・Service selector 切り替えのメカニズムを解説します。

Blue/Green デプロイのアーキテクチャ

Blue/Green デプロイのアーキテクチャ図。現行アクティブな Blue Deployment(fanclub-backend-blue・version: blue)を実線、待機中の Green Deployment(fanclub-backend-green・version: green)を破線で描き分け、両者がそれぞれ replicas: 1 の Pod を持つ。下部の fanclub-backend Service が selector を kubectl patch で version: blue から version: green に切り替えることで、Endpoints が瞬時に Green Pod 側へ移行する構造を示している

Blue Deployment(現行版)と Green Deployment(新版)が並列で稼働します。両 Deployment の Pod ラベルには共通の app: fanclub-backend と区別用の version: blue または version: green が付与されます。

Service の selector に version を含めることで、どちらかの Deployment の Pod のみが Endpoints に登録されます。

Service selector 変更が即時反映される仕組み

Service の Endpoints は EndpointSlice Controller が監視・管理しており、Service の selector に合致する Pod のリストを EndpointSlice オブジェクトとして書き出します。

selector が変更されると Controller は新しい selector で Pod を再評価し、合致する Pod の IP を EndpointSlice に書き換えます。

kube-proxy は EndpointSlice の変更を検知して iptables / ipvs ルールを書き換えるため、selector 変更から実トラフィックの切り替えまで通常 1 秒以内で完了します。これが Blue/Green の「即時切り替え」を成り立たせている仕組みです。

Rolling Update のロールバックは kubectl rollout undo で旧 ReplicaSet に maxSurge: 1 / maxUnavailable: 0 の条件で段階的に切り替わるため、replicas が 10 を超えるような大規模 Deployment では数十秒〜数分かかります。

Blue/Green ではロールバックも前進と同じく selector 戻しのみで完了するため、復旧時間が大幅に短くなります。

ラベル設計の注意点 — selector の不変フィールド制約

第12回で確認した通り、Deployment の spec.selector.matchLabels は不変フィールドです。ep12 で作成した fanclub-backend-deployment の selector は {app: fanclub-backend} のみで、version ラベルは含まれていません。

この既存 Deployment の selector を後付けで {app: fanclub-backend, version: blue} に変更しようとすると field is immutable エラーで拒否されます。

このため Blue/Green 演習では 新規の Deployment を別名で作成するアプローチを取ります。

  • fanclub-backend-blue:selector / labels = {app: fanclub-backend, version: blue}
  • fanclub-backend-green:selector / labels = {app: fanclub-backend, version: green}
  • fanclub-backend-deployment(ep12 既存):演習中は scale: 0 で待機させる

既存の fanclub-backend-deploymentversion ラベルを持たないため、Service の selector に version: blue を追加した瞬間に Endpoints から自動的に外れます(selector 不一致のため)。

ただし Pod は依然として動いているため、scale: 0 でリソースを解放しておくのが安全です。演習③(Canary)でこの Deployment を scale: 3 にして再利用する流れも視野に入れた設計です。

Blue/Green の利点とコスト

項目Blue/Green の利点・コスト
利点 1即時切り替え・即時ロールバック:Service selector の変更だけで完了。Rolling Update のような段階的入れ替えがなく復旧時間が一定(通常 1 秒以内)
利点 2事前検証が可能:Green Deployment を起動した後、本物の Pod を内部からテストできる(kubectl port-forward / Pod IP 直アクセス)。selector 切り替え前に動作確認が可能
利点 3新旧同時稼働の制御が明示的:Canary は新旧が確率的に混在するが、Blue/Green は selector 切り替え前後で 100 % どちらか一方
コスト 1リソース 2 倍消費:Blue + Green が並列稼働するため Pod 数・CPU・Memory が 2 倍必要。Replicas が大規模だと無視できないコスト
コスト 2DB スキーマ設計の考慮が必要:Blue と Green は同じ DB を共有するため、新旧 Backend 両方が読み書きしても壊れない互換スキーマが前提。これは Canary も同じだが、Blue/Green では「即時切り替え」を期待されるため事前準備の重要度が増す
コスト 3K8s 標準では HTTPS の重み付けルーティングは不可:Service の selector は「合致する Pod 全体」を対象にするため、Blue 50 % / Green 50 % のような比率制御はできない。本格的な重み付けには Gateway API HTTPRoute weight や Argo Rollouts が必要

本回の Blue/Green 演習では K8s 標準の Deployment + Service で実装します。Argo Rollouts や Gateway API HTTPRoute weight は第1巻のスコープ外で、第2巻以降の GitOps + 高度なリリース戦略の文脈で扱います。

CKAD 試験では K8s 標準の Blue/Green が出題範囲のため、本演習で習得する内容で試験は十分にカバーできます。

Blue/Green が向くケース・向かないケース

Blue/Green は便利ですが何にでも適用できるわけではありません。向くケースと向かないケースを整理して、本番設計時の判断材料にします。

判断軸Blue/Green が向くケースBlue/Green が向かないケース
ロールバック速度SLA 99.9 % 以上の API・金融取引・医療系など即時復旧が必須10 分程度のロールバック時間でも問題ない社内ツール
リソース予算Pod 数が小〜中規模(〜10 Pod)でリソース 2 倍を許容できるPod 数が大規模(数百 Pod)でリソース 2 倍が現実的に取れない
DB スキーマ新旧両 Backend が同じ DB を読んでも壊れない互換スキーマ設計が済んでいる破壊的スキーマ変更を伴う・DB マイグレーション計画が無い
ステートフル性Backend が Stateless(セッションは外部ストア / JWT で管理)Backend が Pod 内メモリにセッションを持ち、Pod 切り替えでセッション喪失
事前検証ニーズGreen を内部 IP で事前検証してから切り替えたい新版が確実に動くと判明済で事前検証が不要

「向かないケース」の典型例として、セッションを Pod 内メモリに保持する古いアプリがあります。Blue から Green への即時切り替えでセッションが消えると、エンドユーザーは「突然ログアウトされた」状態になります。

この場合は Sticky Session(Service の sessionAffinity: ClientIP)を併用するか、セッションを Redis 等の外部ストアに切り出してから Blue/Green を導入する設計判断が必要です。

CKAD 試験では Sticky Session までは深入りしませんが、本番設計の引き出しとして覚えておくと第2巻以降の SRE 文脈で生きます。

やってみよう②: Blue/Green デプロイを実施して Service selector 切り替えを体験する

所要時間目安:約 35 分。Blue(現行版)と Green(新版)の 2 つの Deployment を並列稼働させ、Service の selector を kubectl patch で切り替えてトラフィックを Blue → Green に即時移行します。

続いて Green → Blue へロールバックして、selector 戻しのみで復旧が完了することを確認します。最後にクリーンアップして ep12 完了状態に戻します。所要時間の内訳は YAML 作成 5 分・Blue 起動と疎通確認 10 分・Green 起動と切り替え 10 分・ロールバックとクリーンアップ 10 分です。

kind single-node cluster の CPU 制約に関する注意:本演習で扱う kind クラスタは k8s-ops VM(2 vCPU / 6 GB)上の Docker 内で単一ノードとして稼働しています。

fanclub-backend Pod 1 個あたり CPU 250m を要求するため、kube-system 関連 Pod を含めると 3 Pod までしかスケジュールできません。

outline では Blue 2 + Green 2 = 4 Pod を想定していましたが、本回では Blue: replicas: 1 + Green: replicas: 1 の 2 Pod 構成で実施します。Service selector 切り替えのメカニズムは replicas 1 でも完全に体験できます。

本番クラスタ(Workload Node 数台・各 4 vCPU 以上)では replicas: 2 以上を使うのが一般的です。

Step 1: 既存 fanclub-backend-deployment を scale: 0 にする

目的:Service selector に version ラベルを追加した際に、ep12 既存 Deployment の Pod(version ラベルなし)が Endpoints から確実に除外されることを担保するため、scale: 0 で待機させる。Pod は停止しますが Deployment 定義は残るため、演習末尾で scale: 2 に戻すだけで ep12 状態が復元できます。

実行コマンド:

$ kubectl scale deployment fanclub-backend-deployment -n default --replicas=0

実行結果:

deployment.apps/fanclub-backend-deployment scaled

Pod が消えたことを確認します。

実行コマンド:

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

実行結果:

No resources found in default namespace.

fanclub-backend ラベルを持つ Pod が 0 個になりました。fanclub-backend Service の Endpoints も空になっているはずです。

Step 2: fanclub-backend-blue.yaml を作成する

目的:Blue(現行版)の Deployment 定義を作成する。ep12 の Deployment と同じ image・ConfigMap・Secret・3 Probe 設定を使い、metadata.name と labels の version: blue のみを変える。

ファイル格納先:~/fanclub-manifests/fanclub-backend-blue.yaml

実行コマンド:

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

ファイル内容(全量・省略なし):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fanclub-backend-blue
  namespace: default
  labels:
    app: fanclub-backend
    version: blue
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fanclub-backend
      version: blue
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: fanclub-backend
        version: blue
    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

ep12 で確立した 3 Probe(startupProbe / livenessProbe / readinessProbe)の設定値をそのまま継承しています。

Payara Micro の起動に約 7.5 秒かかるため startupProbe の failureThreshold: 30 × periodSeconds: 10 = 最大 300 秒の起動待機を許容します。Init Container wait-for-db も継承しており、fanclub-db が稼働中なら 2 秒程度で完了します。

Step 3: Blue Deployment を apply する

目的:Blue Deployment を実際にクラスタに作成し、1 Pod が Ready になるまで待機する。

実行コマンド:

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

実行結果:

deployment.apps/fanclub-backend-blue created

Rolling Update の進行を待ちます。

実行コマンド:

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

実行結果:

Waiting for deployment "fanclub-backend-blue" rollout to finish: 0 of 1 updated replicas are available...
deployment "fanclub-backend-blue" successfully rolled out

1 Pod が Ready になるまで約 20〜30 秒かかります(startupProbe 評価 + readinessProbe 評価)。「successfully rolled out」が出力されれば次の Step に進みます。

Step 4: Service selector を version=blue に切り替える

目的:fanclub-backend Service の selector に version: blue を追加し、Blue Pod のみが Endpoints に登録されるようにする。

切り替え前の selector を確認します。

実行コマンド:

$ kubectl get service fanclub-backend -n default -o jsonpath='{.spec.selector}'
$ echo

実行結果:

{"app":"fanclub-backend"}

selector に version: blue を追加します。

実行コマンド:

$ kubectl patch service fanclub-backend -n default \
  -p '{"spec":{"selector":{"app":"fanclub-backend","version":"blue"}}}'

実行結果:

service/fanclub-backend patched

Step 5: Endpoints が Blue Pod の IP になっていることを確認する

目的:Service の selector 変更後、Endpoints に Blue Pod の IP が登録されていることを実機で確認する。Blue/Green の「selector 変更で Endpoints が即座に切り替わる」メカニズムを確認する重要 Step。

実行コマンド:

$ kubectl get endpoints fanclub-backend -n default
$ kubectl get pods -l app=fanclub-backend,version=blue -n default -o wide

実行結果:

Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME              ENDPOINTS          AGE
fanclub-backend   10.244.0.59:8080   3d12h

NAME                                    READY   STATUS    RESTARTS   AGE   IP            NODE                 NOMINATED NODE   READINESS GATES
fanclub-backend-blue-86676986d7-6jcc8   1/1     Running   0          2m    10.244.0.59   kind-control-plane   <none>           <none>

Endpoints の IP(10.244.0.59)と Blue Pod の IP が完全一致していれば成功です。version ラベルなしの旧 fanclub-backend-deployment Pod(scale: 0 のため存在しない)は Endpoints に含まれず、Blue Pod のみが Service の対象になっています。

冒頭の Warning は K8s v1.33+ の v1 Endpoints API 廃止予告で、後続の EndpointSlice(discovery.k8s.io/v1)へ移行が推奨される旨を kubectl が出力しています。

本文中の Endpoints 表示は読みやすさのため kubectl get endpoints を使い続け、Warning 行は本演習以降の出力例では省略します(実機では毎回表示されます)。

Step 6: Service 経由で Blue への疎通を確認する

目的:curl で Service の ClusterIP に HTTP リクエストを投げ、Blue の /health/live が UP を返すことを確認する。

実行コマンド:

$ kubectl run curl-bg -n default --image=curlimages/curl:8.20.0 --rm -it --restart=Never -- \
  curl -sS http://fanclub-backend/health/live

実行結果:

{"status":"UP","checks":[{"name":"fanclub-api-live","status":"UP","data":{}}]}
pod "curl-bg" deleted from default namespace

HTTP 200 + JSON で "status":"UP" が返れば、Service → Blue Pod の経路が確立しています。

Step 7: fanclub-backend-green.yaml を作成する

目的:Green(新版)の Deployment 定義を作成する。Blue YAML をコピーして metadata.name と labels の versiongreen に変えるだけで完成する。

ファイル格納先:~/fanclub-manifests/fanclub-backend-green.yaml

実行コマンド:

$ cp ~/fanclub-manifests/fanclub-backend-blue.yaml ~/fanclub-manifests/fanclub-backend-green.yaml
$ sed -i 's/fanclub-backend-blue/fanclub-backend-green/g; s/version: blue/version: green/g' ~/fanclub-manifests/fanclub-backend-green.yaml

変更箇所を確認します。

実行コマンド:

$ grep -E '(name:|version:)' ~/fanclub-manifests/fanclub-backend-green.yaml | head -10

実行結果:

  name: fanclub-backend-green
    version: green
      version: green
      version: green
  - name: wait-for-db
    - name: fanclub-backend

name: fanclub-backend-green + version: green が 3 箇所(metadata.labels / selector.matchLabels / template.metadata.labels)で置換されていれば OK です。

Step 8: Green Deployment を apply する

目的:Green Deployment を起動し、Blue と並列稼働状態にする。この時点で Service の selector は version: blue のままなので、Green Pod は起動しても Endpoints には登録されない。

実行コマンド:

$ kubectl apply -f ~/fanclub-manifests/fanclub-backend-green.yaml
$ kubectl rollout status deployment/fanclub-backend-green -n default

実行結果:

deployment.apps/fanclub-backend-green created
Waiting for deployment "fanclub-backend-green" rollout to finish: 0 of 1 updated replicas are available...
deployment "fanclub-backend-green" successfully rolled out

Blue と Green が並列稼働している状態を確認します。

実行コマンド:

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

実行結果:

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
fanclub-backend-blue          1/1     1            1           5m
fanclub-backend-deployment    0/0     0            0           3d
fanclub-backend-green         1/1     1            1           45s

NAME                                     READY   STATUS    RESTARTS   AGE
fanclub-backend-blue-86676986d7-6jcc8    1/1     Running   0          5m
fanclub-backend-green-54f5f7bf4b-spndk   1/1     Running   0          45s

Blue 1 Pod + Green 1 Pod = 合計 2 Pod が動いている状態です。fanclub-backend-deployment は scale: 0(READY 0/0)で待機中です。

本演習では kind 2 vCPU 制約のため Blue/Green ともに replicas: 1 ですが、それでも「Blue + Green = 通常運用の 2 倍」というリソース倍増の関係は本演習で確認できます。

本番では Blue/Green ともに replicas: N のため 2N Pod 分のリソースが必要になり、Replicas を増やすほど無視できないコストになります。

Step 9: Endpoints を別ターミナルで監視しながら Service selector を Green に切り替える

目的:Service selector の変更が Endpoints に反映される瞬間をリアルタイムで観察する。Blue/Green の「即時切り替え」を実機で確認する核心 Step。

別ターミナルで Endpoints を監視します。

実行コマンド(別ターミナル):

$ kubectl get endpoints fanclub-backend -n default -w

初期状態の出力:

NAME              ENDPOINTS          AGE
fanclub-backend   10.244.0.59:8080   3d12h

メインターミナルで Service selector を Green に切り替えます。

実行コマンド(メインターミナル):

$ kubectl patch service fanclub-backend -n default \
  -p '{"spec":{"selector":{"app":"fanclub-backend","version":"green"}}}'

実行結果:

service/fanclub-backend patched

別ターミナルの監視出力に追記が表示されます。

監視ターミナルの実行結果(追記行):

fanclub-backend   10.244.0.62:8080   3d12h

Endpoints の IP が 10.244.0.59(Blue Pod)から 10.244.0.62(Green Pod)に切り替わりました。patch コマンドが返った直後(通常 1 秒以内)に Endpoints が更新されます。これが Blue/Green の即時切り替えです。Pod の起動を待つ必要は無く、selector を書き換えるだけで完了します。

Green Pod の IP と一致するか確認します。

実行コマンド:

$ kubectl get pods -l app=fanclub-backend,version=green -n default -o wide

実行結果:

NAME                                     READY   STATUS    RESTARTS   AGE   IP            NODE
fanclub-backend-green-54f5f7bf4b-spndk   1/1     Running   0          3m    10.244.0.62   kind-control-plane

Green Pod の IP(10.244.0.62)が Endpoints と完全一致しています。切り替え成功です。

Step 10: Service 経由で Green への疎通を確認する

目的:Service の宛先が Green に切り替わったことを HTTP リクエストレベルで確認する。

実行コマンド:

$ kubectl run curl-bg -n default --image=curlimages/curl:8.20.0 --rm -it --restart=Never -- \
  curl -sS http://fanclub-backend/health/live

実行結果:

{"status":"UP","checks":[{"name":"fanclub-api-live","status":"UP","data":{}}]}
pod "curl-bg" deleted from default namespace

同じく UP が返ります。本演習では Blue と Green が同じ image(fanclub-backend:0.1.0)のため応答ボディは変わりませんが、本番では Green に新版 image を載せて応答内容で切り替えを判別できます。

例えば /api/version のような自前エンドポイントで Pod 内のバージョン文字列を返す設計にしておくと、curl 結果から Blue/Green を判別できます。

Step 11: ロールバック — Service selector を Blue に戻す

目的:Green に問題が見つかった想定でロールバックを実施する。Blue/Green のロールバックは前進と同じ手順(selector 戻し)で完了することを実機で確認する。

実行コマンド:

$ kubectl patch service fanclub-backend -n default \
  -p '{"spec":{"selector":{"app":"fanclub-backend","version":"blue"}}}'

実行結果:

service/fanclub-backend patched

Endpoints が Blue に戻ったことを確認します。

実行コマンド:

$ kubectl get endpoints fanclub-backend -n default

実行結果:

NAME              ENDPOINTS          AGE
fanclub-backend   10.244.0.59:8080   3d12h

Blue Pod の IP に戻りました。kubectl rollout undo のような段階的なロールバックではなく、selector 戻しだけで瞬時に復旧しています。これが Blue/Green のロールバックの速さです。本番環境で「新版がリリース直後にエラー率急上昇 → 30 秒以内にロールバック」を実現する手段として有効です。

Step 12: クリーンアップ — Blue/Green Deployment 削除と Service selector 復元

目的:演習②で作成したリソースを削除し、Service selector を ep12 状態(app: fanclub-backend のみ)に戻す。fanclub-backend-deployment は次の演習③で再利用するため scale: 0 のまま残しておく(演習③冒頭で scale: 3 にする)。

Blue / Green Deployment を削除します。

実行コマンド:

$ kubectl delete deployment fanclub-backend-blue fanclub-backend-green -n default

実行結果:

deployment.apps "fanclub-backend-blue" deleted from default namespace
deployment.apps "fanclub-backend-green" deleted from default namespace

Service selector から version キーを削除します。strategic merge patch では null 値で明示的にキーを削除するのが正式な構文です。

実行コマンド:

$ kubectl patch service fanclub-backend -n default \
  -p '{"spec":{"selector":{"app":"fanclub-backend","version":null}}}'

実行結果:

service/fanclub-backend patched

selector が ep12 状態に戻ったことを確認します。

実行コマンド:

$ kubectl get service fanclub-backend -n default -o jsonpath='{.spec.selector}'
$ echo

実行結果:

{"app":"fanclub-backend"}

app: fanclub-backend 単独に戻りました。fanclub-backend-deployment は scale: 0 のままで、Pod は存在しません。次の演習③で scale: 4 に戻して Canary 演習を始めます。

Canary リリースの概念と設計 — Pod 数比率でリスクを限定したリリース

Canary リリースは Blue/Green と同じく K8s に専用リソースが存在しません。Deployment と Service を組み合わせて Pod 数の比率でトラフィックを分割する手法です。本節では Canary の原理・ラベル設計・K8s 標準実装の限界を解説します。

Canary の語源と狙い

Canary は炭鉱で坑道のガス漏れを検知するためにカナリア鳥を連れて入った歴史的慣行に由来します。鳥が先に毒ガスを察知して鳴き止むことで、人間に危険を知らせる仕組みでした。ソフトウェアの Canary リリースはこの比喩で、「新版をごく一部のトラフィックに限定して投入し、問題があれば全量適用前に検知する」ことを狙います。

Blue/Green が「100 % Blue → 100 % Green」の即時切り替えなのに対し、Canary は「100 % 安定版 → 80 % 安定版 + 20 % 新版 → 50 % + 50 % → 100 % 新版」のように段階的にトラフィックを移行します。

新版に潜在的な問題(メモリリーク・特定リクエストでクラッシュ・性能劣化等)があっても、影響範囲を一部に限定できるのが利点です。

K8s 標準 Canary の仕組み — Pod 数比率での分散

Canary リリースの比率分散図。安定版 Deployment(fanclub-backend-deployment)が 3 Pod、Canary Deployment(fanclub-backend-canary・track: canary)が 1 Pod を稼働させ、両 Deployment の Pod が共通ラベル app: fanclub-backend で fanclub-backend Service の Endpoints に 4 Pod としてフラットに並ぶ。kube-proxy が 4 Pod に均等分散することで安定版 75 % / Canary 25 % のトラフィック比率が Pod 数比率で達成される仕組みを示す。CKAD 試験頻出の Stable 4 + Canary 1 = 80 % / 20 % パターンも脚注で併記している

核心は「両 Deployment の Pod に共通ラベル app: fanclub-backend を付ける」ことです。Service の selector は {app: fanclub-backend} のままで、両 Deployment の Pod が同じ Service に組み込まれます。

Endpoints には 4 Pod の IP がフラットに並び、kube-proxy(iptables / ipvs)は均等にトラフィックを分散します。「比率制御」は Pod 数の比率(3:1)で達成しており、Service や Endpoint Slice に重み(weight)の概念はありません。

トラフィック比率の計算式

Canary 比率 = Canary replicas / (Stable replicas + Canary replicas)

例:
  Stable 3 + Canary 1 → Canary 1/4 = 25 %  ← 本演習はこのパターン
  Stable 4 + Canary 1 → Canary 1/5 = 20 %  ← CKAD 試験本番想定の頻出パターン
  Stable 9 + Canary 1 → Canary 1/10 = 10 %
  Stable 19 + Canary 1 → Canary 1/20 = 5 %
  Stable 99 + Canary 1 → Canary 1/100 = 1 %  ← 1 % のために 100 Pod が必要

25 % まではリーズナブルですが、5 % や 1 % のような細かい比率を実現するには Stable 側を大量に持つ必要があり、リソース効率が悪化します。これが K8s 標準 Canary の「粗粒度」の問題で、H2-12 のヒヤリハット②で取り上げます。

CKAD 試験では伝統的に Stable 4 + Canary 1 = 20% のパターンで出題されるため、H2-10「CKAD 試験頻出パターン」で改めて整理します。

Canary の昇格・ロールバックフロー

Canary が一定期間(数分〜数時間〜数日)安定稼働し、メトリクス・ログでエラーが見つからなければ昇格します。逆に問題が見つかれば即座にロールバックします。

  • 昇格(Canary が安定):① 安定版 Deployment の image を新版タグに更新(kubectl set image)→ Rolling Update で順次入れ替え → ② Canary Deployment を削除 → ③ 安定版 replicas を元の数(本演習では 2)に戻す。新版が全量稼働する状態に到達
  • ロールバック(Canary に問題):Canary Deployment を kubectl delete するだけで完了。Endpoints から Canary Pod が即座に外れ、全トラフィックが安定版に戻る。安定版は変更していないため副作用なし

本演習③では昇格シナリオを擬似的に実施します。実際の image 更新は伴いませんが、「Canary 削除 + 安定版 replicas を 2 に戻す」手順で ep12 完了状態への復元を実現します。

K8s 標準 Canary の限界 — Pod 数比率では足りないケース

限界具体例解決ツール(第1巻スコープ外)
細粒度の重み付け不可1 % だけ Canary に流したい → Stable 99 Pod 必要でリソースが破綻Gateway API HTTPRoute weight(Traefik v3)
Argo Rollouts の trafficRouting.weight
HTTP ヘッダ条件分岐不可X-Beta-User: true を持つリクエストだけ Canary に流したいGateway API HTTPRoute の HeaderMatch
Istio VirtualService の match.headers
ユーザー属性ベースのルーティング不可特定ユーザー ID(社内テスター等)だけ Canary に流したいFlagger + サービスメッシュ
Argo Rollouts + 専用 Gateway
自動メトリクス分析・自動ロールバック不可Canary のエラー率が閾値を超えたら自動的に Canary を削除Argo Rollouts の analysis ステップ
Flagger の metric template

K8s 標準の Canary は「Pod 数比率での粗い分散」と「人手による昇格・ロールバック判断」が組み合わさった素朴な仕組みです。本格的な Canary(5 % トラフィック + メトリクス監視 + 自動昇格)を実現したい場合は Argo Rollouts や Flagger を導入します。

第2巻以降で GitOps + Argo Rollouts の組み合わせを扱う予定です(リンクなし・言及のみ)。CKAD 試験では K8s 標準 Canary が出題範囲のため、本演習で習得する内容で試験対応は十分です。

Canary と Blue/Green の使い分け

Canary と Blue/Green はどちらも「新旧 2 バージョンを同時稼働させる」点で似ていますが、目的と挙動が異なります。試験対策・本番設計の両面で使い分けを正確に押さえます。

観点Blue/GreenCanary
主目的即時カットオーバー + 即時ロールバック段階的リリース + リスク限定
新版へのトラフィック0 % → 100 %(瞬時に全切替)0 % → 20 % → 50 % → 100 %(段階的)
切り替え操作Service selector を変更(1 操作)Canary Deployment の replicas 増減 + 安定版 image 更新(複数操作)
新旧同居期間切り替え瞬間のみ(事前検証期は除く)昇格完了まで継続的に同居
DB スキーマ要求互換スキーマ必須(同居期間ゼロでも切替直後に旧 Pod の終了を待つ)互換スキーマ必須(同居期間が長い分シビア)
適する更新機能変更・UI 大幅変更などのカットオーバー必要案件パフォーマンス改善・アルゴリズム変更などの段階検証案件

Blue/Green と Canary を組み合わせる「Canary 内蔵 Blue/Green」のような複合戦略もあります。

例えば「Green を 1 Pod だけ起動して Service selector に version in (blue, green) を設定すると Canary 風に動く」というハックがありますが、K8s の Service selector は equality-based match のみで set-based match はサポートしないため、実際には EndpointSlice を手動で操作するか Gateway API HTTPRoute weight に移行する必要があります。CKAD 試験では出題されない応用範囲です。

Canary の K8s 公式定義は kubernetes.io/docs/concepts/workloads/controllers/deployment/ の「Canary deployments」セクションで確認できます。CKAD 試験中はこの URL を Web ブラウザで開いて Pod ラベル設計の例を参照できます。

やってみよう③: Canary リリースを実施して約 25% トラフィック分散を確認する

所要時間目安:約 30 分。

fanclub-backend-deployment(安定版)を replicas: 3 にスケールし、fanclub-backend-canary Deployment(replicas: 1)を新規作成して、合計 4 Pod が fanclub-backend Service の Endpoints に登録されることを確認します。

Canary が安定していたと仮定して昇格シナリオも実施し、最終的に ep12 完了状態(replicas: 2)に戻します。

kind single-node cluster の CPU 制約に関する注意:本演習でも kind クラスタの 2 vCPU 制約のため、安定版を replicas: 3 + Canary 1 = 合計 4 Pod の構成に調整しています。

本番想定(CKAD 試験頻出パターン)の Stable 4 + Canary 1 = 5 Pod では 4:1 = 80:20 のトラフィック比率になりますが、本演習では Stable 3 + Canary 1 = 3:1 = 75:25 で「Pod 数比率での分散」の本質的なメカニズムを体験します。

また Canary Deployment のみ CPU 要求を 100m(安定版の 250m より少なく)に設定して制約に収めますが、これは本演習限定の調整であり、本番では Canary も安定版と同じ要求値で「同じワークロードを安定的に処理できるか」を検証するのが本来の Canary の意義です。

Step 1: fanclub-backend-deployment を replicas: 3 にスケールする

目的:演習②終了時点で fanclub-backend-deployment は scale: 0 のため、Canary 演習用に scale: 3 に増やす。安定版を 3 Pod にすることで、Canary 1 Pod を追加した際に「3:1 = 75:25」のトラフィック比率になる。

実行コマンド:

$ kubectl scale deployment fanclub-backend-deployment -n default --replicas=3
$ kubectl rollout status deployment/fanclub-backend-deployment -n default

実行結果:

deployment.apps/fanclub-backend-deployment scaled
deployment "fanclub-backend-deployment" successfully rolled out

3 Pod が Ready になるまで約 30〜40 秒かかります。fanclub-db が稼働中なので Init Container は即時完了し、Payara Micro の起動が律速になります。

Step 2: 安定版の Pod IP と Endpoints を確認する

目的:Canary 投入前の Endpoints が安定版 3 Pod のみであることを確認する。後の Step で Canary 追加後の Endpoints と比較するための基準値。

実行コマンド:

$ kubectl get pods -l app=fanclub-backend -n default -o wide
$ kubectl get endpoints fanclub-backend -n default

実行結果:

NAME                                          READY   STATUS    RESTARTS   AGE   IP            NODE
fanclub-backend-deployment-86cf676cf7-g7h2z   1/1     Running   0          1m    10.244.0.64   kind-control-plane
fanclub-backend-deployment-86cf676cf7-gqt2j   1/1     Running   0          1m    10.244.0.65   kind-control-plane
fanclub-backend-deployment-86cf676cf7-t4nw9   1/1     Running   0          1m    10.244.0.66   kind-control-plane

NAME              ENDPOINTS                                            AGE
fanclub-backend   10.244.0.64:8080,10.244.0.65:8080,10.244.0.66:8080   3d12h

Endpoints に 3 Pod の IP が登録されています。次に Canary を投入します。

Step 3: fanclub-backend-canary.yaml を作成する

目的:Canary(新版)の Deployment 定義を作成する。安定版と同じ image を使い、metadata.name と labels の track: canary のみが違う。Blue/Green では version ラベルで識別したが、Canary では track ラベルを使うのが Kubernetes 公式ドキュメントの慣行に沿った命名。

ファイル格納先:~/fanclub-manifests/fanclub-backend-canary.yaml

実行コマンド:

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

ファイル内容(全量・省略なし):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fanclub-backend-canary
  namespace: default
  labels:
    app: fanclub-backend
    track: canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fanclub-backend
      track: canary
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: fanclub-backend
        track: canary
    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: "100m"
            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

重要なポイントは Pod テンプレートの labels に app: fanclub-backend(安定版と共通)+ track: canary(識別用)の 2 つを含めることです。

Service の selector が {app: fanclub-backend} 単独のため、Canary Pod も自動的に Service の Endpoints に組み込まれます。track: canary は Service の selector には含めないため、トラフィック分散の対象から外れません。

resources.requests.cpu は本演習限定で 100m(kind 2 vCPU 制約に対応・冒頭の注意ボックス参照)で、安定版(250m)とは値が異なります。本番では Canary も安定版と同じ要求値が原則です。

Step 4: Canary Deployment を apply する

目的:Canary Deployment を作成し、合計 4 Pod(安定版 3 + Canary 1)が Service の Endpoints に登録されることを確認する。

実行コマンド:

$ kubectl apply -f ~/fanclub-manifests/fanclub-backend-canary.yaml
$ kubectl rollout status deployment/fanclub-backend-canary -n default

実行結果:

deployment.apps/fanclub-backend-canary created
Waiting for deployment "fanclub-backend-canary" rollout to finish: 0 of 1 updated replicas are available...
deployment "fanclub-backend-canary" successfully rolled out

Step 5: 4 Pod の一覧と Endpoints を確認する

目的:Service の selector に合致する Pod が 4 個(安定版 3 + Canary 1)になり、Endpoints も 4 IP に増えていることを実機で確認する。Canary の核心であるトラフィック分散の前提が成立していることをここで担保する。

実行コマンド:

$ kubectl get pods -l app=fanclub-backend -n default -o wide
$ kubectl describe endpoints fanclub-backend -n default | head -20

実行結果:

NAME                                          READY   STATUS    RESTARTS   AGE    IP            NODE
fanclub-backend-canary-6999cdcddd-kcm5q       1/1     Running   0          60s    10.244.0.67   kind-control-plane
fanclub-backend-deployment-86cf676cf7-g7h2z   1/1     Running   0          106s   10.244.0.64   kind-control-plane
fanclub-backend-deployment-86cf676cf7-gqt2j   1/1     Running   0          106s   10.244.0.65   kind-control-plane
fanclub-backend-deployment-86cf676cf7-t4nw9   1/1     Running   0          106s   10.244.0.66   kind-control-plane

Name:         fanclub-backend
Namespace:    default
Labels:       endpoints.kubernetes.io/managed-by=endpoint-controller
Annotations:  endpoints.kubernetes.io/last-change-trigger-time: 2026-05-13T14:24:45Z
Subsets:
  Addresses:          10.244.0.64,10.244.0.65,10.244.0.66,10.244.0.67
  NotReadyAddresses:  <none>
  Ports:
    Name  Port  Protocol
    ----  ----  --------
    http  8080  TCP

Events:  <none>

Subsets.Addresses に 4 つの IP(10.244.0.6466 = 安定版 + 10.244.0.67 = Canary)が並んでいれば成功です。kube-proxy はこの 4 IP に均等にトラフィックを分散します。Canary Pod は 1/4 = 25 % の確率で選ばれることになります。

Step 6: トラフィック分散の仕組みを iptables ルールから理解する

目的:「25 % が Canary に向かう」という理論値の根拠となる kube-proxy の挙動を理解する。fanclub-api には Pod 識別のためのエンドポイント(/api/hostname 等)が無いため curl 連続アクセスでの分散実測は割愛し、kube-proxy が iptables ルールでどのように 25 % の分散を実現するかを確認する。

kube-proxy の iptables モードでは、Service の各 Endpoint への遷移確率が 1/N, 1/(N-1), ..., 1/1 の累積形式で iptables ルールに設定されます。4 Endpoint であれば最初のルールが 25 % で 1 つ目を選び、外れた 75 % のうち 33 % で 2 つ目を選び、と続いて結果として均等分散になります。

実行コマンド(iptables ルール確認・参考):

$ docker exec kind-control-plane iptables -t nat -L KUBE-SVC-FANCLUB-BACKEND -n 2>/dev/null | grep -c "statistic mode random"

期待値(Service 名から KUBE-SVC-XXX のチェーン名が決まるため、上記コマンドは参考表記です。実際には kubectl get service fanclub-backend -o yaml から ClusterIP を取得し、対応するチェーンを探す):

4

iptables ルールには Endpoint 数(4 件)の statistic mode random エントリが並びます。kube-proxy のロードバランシングは厳密な均等分散ではなく統計的な均等分散ですが、十分な試行回数があれば 25 % に収束します。

本演習では理論説明にとどめ、25 % の正確な実測は割愛します(curl 1000 回で 250 ± 30 回程度の Canary ヒットが期待される)。

Step 7: Canary 昇格シナリオ — Canary 削除 + 安定版 replicas を 2 に戻す

目的:Canary が想定通り安定していたと仮定して昇格シナリオを実施する。実際の本番では「安定版の image を新版タグに更新」が必要だが、本演習は概念演習のため image 更新は割愛し、Canary 削除 + 安定版 scale: 2 への復元のみ行う。

Canary Deployment を削除します。

実行コマンド:

$ kubectl delete deployment fanclub-backend-canary -n default

実行結果:

deployment.apps "fanclub-backend-canary" deleted from default namespace

Endpoints から Canary Pod の IP が外れたことを確認します。

実行コマンド:

$ kubectl get endpoints fanclub-backend -n default

実行結果:

NAME              ENDPOINTS                                            AGE
fanclub-backend   10.244.0.64:8080,10.244.0.65:8080,10.244.0.66:8080   3d12h

Endpoints が 3 IP(安定版のみ)に戻りました。Canary 削除のみで全トラフィックが安定版に戻るのが Canary の特徴です。安定版は何も変更していないため、Canary 由来の問題が発生していたとしても影響範囲は Canary Pod 内に閉じていました。

続いて安定版 Deployment を ep12 の replicas: 2 に戻します。

実行コマンド:

$ kubectl scale deployment fanclub-backend-deployment -n default --replicas=2
$ kubectl rollout status deployment/fanclub-backend-deployment -n default

実行結果:

deployment.apps/fanclub-backend-deployment scaled
deployment "fanclub-backend-deployment" successfully rolled out

scale を 3 → 2 に下げる場合は新規 Pod の起動を待つ必要が無いため即時完了します。

Step 8: 演習③の最終状態を確認する

目的:演習③終了時点で ep12 完了状態(fanclub-backend-deployment が replicas: 2 + 3 Probe + RollingUpdate)に復元できていることを確認する。

実行コマンド:

$ kubectl get deployment -l app=fanclub-backend -n default
$ kubectl get endpoints fanclub-backend -n default

実行結果:

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
fanclub-backend-deployment    2/2     2            2           3d

NAME              ENDPOINTS                           AGE
fanclub-backend   10.244.0.64:8080,10.244.0.65:8080   3d12h

Deployment が fanclub-backend-deployment のみ(Blue/Green/Canary は削除済)・READY 2/2・Endpoints も 2 IP になっていれば演習③成功です。次の節(H2-9 クリーンアップ)で最終チェックを行います。

ep13 完了後のクリーンアップ — クラスタを通常 Deployment 状態に戻す

3 つの演習を経て、クラスタが ep12 完了状態に戻っているはずです。本節では最終チェックを行い、ep14(ResourceQuota + LimitRange + Multi-tenant Namespace)への引き継ぎ状態を確定します。

不要リソースが残っていないかを確認する

演習で作成した Blue / Green / Canary Deployment が残っていないか念のため確認します。

実行コマンド:

$ kubectl get deployment -n default
$ kubectl get pods -n default

実行結果:

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
fanclub-backend-deployment    2/2     2            2           3d

NAME                                          READY   STATUS    RESTARTS   AGE
fanclub-backend-deployment-86cf676cf7-g7h2z   1/1     Running   0          12m
fanclub-backend-deployment-86cf676cf7-gqt2j   1/1     Running   0          12m
fanclub-db-0                                  1/1     Running   0          3d8h

Deployment は fanclub-backend-deployment のみ(Blue / Green / Canary は削除済)。Pod は安定版 2 + fanclub-db-0 のみ。クリーンアップ成功です。

Service / ConfigMap / Secret / SA / DaemonSet / CronJob の継承確認

ep12 から継承しているリソースが全て無事であることを確認します。

実行コマンド:

$ kubectl get all,cm,secret,sa,pvc,daemonset,cronjob -n default

実行結果(抜粋):

NAME                                              READY   STATUS    RESTARTS   AGE
pod/fanclub-backend-deployment-86cf676cf7-g7h2z   1/1     Running   0          15m
pod/fanclub-backend-deployment-86cf676cf7-gqt2j   1/1     Running   0          15m
pod/fanclub-db-0                                  1/1     Running   0          3d8h

NAME                            TYPE        CLUSTER-IP       PORT(S)    AGE
service/fanclub-backend         ClusterIP   10.96.150.60     80/TCP     3d12h
service/fanclub-db              ClusterIP   10.96.131.167    5432/TCP   3d8h
service/fanclub-db-headless     ClusterIP   None             5432/TCP   3d8h

NAME                                         READY   AGE
deployment.apps/fanclub-backend-deployment   2/2     3d

NAME                                                  DESIRED   CURRENT   READY   AGE
statefulset.apps/fanclub-db                           1         1         1       3d8h

NAME                                        DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   AGE
daemonset.apps/node-logger                  1         1         1       1            1           3d3h

NAME                                  SCHEDULE       SUSPEND   ACTIVE   AGE
cronjob.batch/fanclub-member-count   */5 * * * *    True      0        3d3h

NAME                              DATA   AGE
configmap/fanclub-config          4      3d
configmap/fanclub-db-init         1      3d8h

NAME                       TYPE     DATA   AGE
secret/fanclub-secret      Opaque   2      3d

NAME                                    SECRETS   AGE
serviceaccount/fanclub-backend-sa       0         3d

NAME                                              STATUS   VOLUME                                     CAPACITY
persistentvolumeclaim/postgres-data-fanclub-db-0  Bound    pvc-...                                    1Gi

fanclub-backend Service の selector が {app: fanclub-backend}(version なし)に戻っていることも改めて確認します。

実行コマンド:

$ kubectl get service fanclub-backend -n default -o jsonpath='{.spec.selector}'
$ echo

実行結果:

{"app":"fanclub-backend"}

すべて ep12 完了状態と一致しました。ep14(ResourceQuota + LimitRange + Multi-tenant Namespace)へのクリーンな引き継ぎが完了です。

なお、演習②③で作成した ~/fanclub-manifests/fanclub-backend-blue.yaml~/fanclub-manifests/fanclub-backend-green.yaml~/fanclub-manifests/fanclub-backend-canary.yaml は教材リソースとして残してあります(再演習・参考用)。不要なら rm で削除してください。

4 戦略の CKAD 試験頻出パターン — 判断根拠と kubectl 速攻テクニック

CKAD 試験は 120 分の制限時間で 15〜20 題程度の実機操作問題を解く Performance-based 形式です。Deployment 戦略関連は D2「Application Deployment」(出題比率 20 %)で複数題出題される頻出領域です。本節では試験本番で時間を稼ぐための判断パターンと kubectl コマンドの速攻手順を整理します。

CKAD 試験頻出パターン 4 選

問題文のキーワードと戦略選択の対応表です。試験本番ではこの対応を秒で判断できるようにしておくと、コマンド作成に時間を割けます。

#問題文のキーワード選ぶ戦略核心コマンド
1「ダウンタイムなしでイメージを更新」「無停止デプロイ」RollingUpdate(maxSurge: 1 / maxUnavailable: 0)kubectl set image deployment/<name> <container>=<image>:<tag>
2「DB スキーマの破壊的変更」「新旧 2 バージョン同時稼働 NG」「Singleton リソース」Recreatekubectl patch deployment <name> -p '{"spec":{"strategy":{"type":"Recreate","rollingUpdate":null}}}'
3「即時カットオーバー」「即時ロールバック」「現行版を残したまま新版に切り替え」Blue/Greenkubectl patch service <name> -p '{"spec":{"selector":{...,"version":"green"}}}'
4「新機能を段階的にリリース」「20 % だけ新版に流す」「リスク限定リリース」Canarykubectl scale deployment <stable> --replicas=4
kubectl apply -f <canary>.yaml(replicas: 1)

※ 本表 4 番目「Canary」の核心コマンドは CKAD 試験本番想定の Stable 4 + Canary 1 = 20% のパターンを示しています。

本回の演習③では kind 2 vCPU 制約のため Stable 3 + Canary 1 = 25% で実施しましたが、試験対応としては本表通り --replicas=4 で 4:1 = 20% の比率を覚えておきます。

比率の計算式(Canary / (Stable + Canary))が理解できていれば、replicas の数値に依らず正解にたどり着けます。

kubectl 速攻コマンド早見表(試験中の即引き参照用)

操作コマンド
strategy を Recreate に変更kubectl patch deployment <name> -p '{"spec":{"strategy":{"type":"Recreate","rollingUpdate":null}}}'
strategy を RollingUpdate に戻すkubectl patch deployment <name> -p '{"spec":{"strategy":{"type":"RollingUpdate","rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}'
replicas を変更kubectl scale deployment <name> --replicas=N
image を更新(Rolling Update トリガー)kubectl set image deployment/<name> <container>=<image>:<tag>
Pod テンプレート変更をトリガー(env 追加)kubectl set env deployment/<name> KEY=VALUE
Pod テンプレート変更をトリガー(env 削除)kubectl set env deployment/<name> KEY-(末尾ハイフン)
Service selector を変更kubectl patch service <name> -p '{"spec":{"selector":{"key":"value"}}}'
Service selector からキーを削除kubectl patch service <name> -p '{"spec":{"selector":{"key":null}}}'
Endpoints を確認kubectl get endpoints <service>
Endpoints をリアルタイム監視kubectl get endpoints <service> -w
rollout status 確認kubectl rollout status deployment/<name>
rollout 履歴確認kubectl rollout history deployment/<name>
rollout ロールバックkubectl rollout undo deployment/<name>

試験中の落とし穴 3 選

  1. Recreate に変更したまま戻し忘れる:演習①の Step 7 で扱った通り、Recreate 演習後は必ず RollingUpdate に戻し、rollingUpdate サブフィールドも明示的に指定する。試験で「ダウンタイムなし更新」が次の問題で求められたとき、strategy が Recreate のままだとアウト
  2. Blue/Green の selector 変更で Endpoints を確認しないkubectl patch service の構文は正しくても、Pod 側の labels が一致していなければ Endpoints は空になる。selector 変更直後の kubectl get endpoints を必ずワンセットで実行する。これは H2-12 のヒヤリハット①で詳述
  3. Canary の Pod 数比率を勘違いする:「20 % を Canary に」と問題文にあったときに「Canary 2 + Stable 8」と書くと 20 % にはなる(2/10)が、よくある誤りは「Canary 2 + Stable 10 = 16.7 %」のように合計と区別しない計算ミス。比率 = Canary / (Stable + Canary) であることを正確に押さえる

kubectl explain で field を即引きする習慣

CKAD 試験では暗記に頼らず kubectl explain で field 仕様を都度確認するのが定石です。Deployment strategy 関連で頻出する explain ターゲットを示します。

実行コマンド:

$ kubectl explain deployment.spec.strategy
$ kubectl explain deployment.spec.strategy.rollingUpdate
$ kubectl explain service.spec.selector

strategy.type の取りうる値(RollingUpdate / Recreate)・rollingUpdate.maxSurge と maxUnavailable のデフォルト(25 % / 25 %)・Service selector の構造(map of string to string)を kubectl explain 出力から即引きできます。

試験会場の Web ブラウザで kubernetes.io を開かなくても explain で十分な場合が多いため、explain を起点にする習慣を作ります。

試験対策の時間配分の目安

CKAD 試験は 120 分で 15〜20 題程度を解くため、1 題あたり約 6〜8 分が標準的な配分になります。Deployment 戦略関連の問題で時間を稼ぐための内訳の目安を示します。

フェーズ目安時間やること
問題文の読解30 秒「ダウンタイム」「即時切替」「段階的」などのキーワードを拾って戦略を即決
kubectl context / namespace の切替15 秒kubectl config use-context <ctx> + kubectl config set-context --current --namespace=<ns>
YAML 生成 or 既存リソース確認1〜2 分kubectl create deployment ... --dry-run=client -o yaml または kubectl get -o yaml
戦略部分の編集 / kubectl patch1〜2 分strategy.type の指定・selector 変更・replicas 変更
適用と動作確認1〜2 分kubectl apply + kubectl rollout status + kubectl get endpoints
合計4〜7 分標準配分に収まる

問題文の読解で 30 秒以上かかる場合は「複雑な要件が紛れ込んでいる可能性」を疑います。例えば「ダウンタイムなしで」+「新版を一部のユーザーだけに」のように 2 つの要件が同居しているケースは、片方を満たすと片方が外れる罠です。優先要件を見極め、CKAD の出題範囲内(K8s 標準 4 戦略)でどれが最も近いかを選び、選んだ理由を心の中で言語化してからコマンドに進むと精度が上がります。

本番での Deployment 戦略設計 — Argo Rollouts / Gateway API weight への発展

本回で扱った K8s 標準の 4 戦略は CKAD 試験範囲を完全にカバーしますが、本番環境ではより高度な制御が必要になる場面があります。本節では K8s 標準の限界と、第1巻のスコープを超えた発展ツール(Argo Rollouts・Gateway API HTTPRoute weight・Flagger)の概念を紹介します。第2巻以降の予告編としての位置付けです。

K8s 標準 4 戦略の限界まとめ

戦略K8s 標準での限界解決ツール(第1巻スコープ外)
RollingUpdatereadinessProbe の精度に依存。Probe が UP を返した直後でも実際にはアプリ未準備のことがあるArgo Rollouts の analysis 機能(メトリクスベースの自動判定)
Recreateダウンタイムは原理的に避けられないBlue/Green に切り替えて DB スキーマ移行を事前完了する設計(Expand-Contract パターン)
Blue/GreenDB スキーマ管理が複雑。Service の selector では HTTPS の重み付けは不可Argo Rollouts BlueGreen + analysis
Gateway API HTTPRoute weight(Traefik v3)
CanaryPod 数比率の粗粒度(1 % のために 99 Pod 必要)。HTTP 条件分岐不可Flagger + サービスメッシュ
Argo Rollouts Canary with weight
Gateway API HTTPRoute weight

本番での戦略選択指針

  1. 通常の機能追加・バグ修正 → RollingUpdate(maxSurge: 1 / maxUnavailable: 0)+ readinessProbe。99 % のリリースはここで完了させる
  2. DB スキーマ破壊的変更を含む更新 → 事前 DB 移行(新旧 Schema 互換設計)+ RollingUpdate、または Blue/Green + 事前 DB 移行。Recreate は最終手段で、業務影響が小さい時間帯に限定する
  3. 高リスク機能の段階的リリース → Canary(K8s 標準 = 20 % 比率まで)。1 % トラフィックや HTTP ヘッダ条件分岐が必要なら Argo Rollouts / Flagger を導入
  4. 即時カットオーバーが必要な更新 → Blue/Green。リソース 2 倍消費を許容できる規模で、ロールバック時間が重要なケース(金融・医療など SLA が厳しい領域)に向く

Argo Rollouts の概念(第2巻以降で扱う発展トピック)

Argo Rollouts は Kubernetes の CRD として高度なリリース戦略を提供する OSS です。第2巻以降の GitOps + 高度なリリース戦略の文脈で扱う予定で、本回ではリンクは張らず概念のみ紹介します。

  • Canary with weight:Service の selector ではなく、Gateway API HTTPRoute の weight(または Istio VirtualService)を使って 1 % トラフィックを Canary に流す。Pod 数比率の制約を回避できる
  • analysis ステップ:Prometheus メトリクス・New Relic・Datadog などの外部指標を参照し、Canary のエラー率が閾値を超えたら自動ロールバックする
  • 段階的昇格:Canary を 1 % → 5 % → 25 % → 50 % → 100 % のように段階的に増やし、各段階で analysis を実行する
  • BlueGreenUpdate:K8s 標準の Blue/Green と同じ概念だが、Service の selector 切り替えを Rollouts コントローラーが自動化し、事前検証期間も組み込める

Gateway API HTTPRoute weight への発展

第1巻 ep18 で扱う Gateway API + Traefik では、HTTPRoute の backendRefs.weight フィールドでバックエンド Service への重み付けが可能です。これを Canary に応用すると、Service 単位ではなく HTTP リクエスト単位での比率制御(5 % → 50 % など細粒度)が実現できます。

第1巻では HTTPRoute の基本構成までを扱い、weight を使った Canary は第2巻以降の発展トピックです。

CKAD 試験では K8s 標準の 4 戦略が範囲です。Argo Rollouts や Gateway API HTTPRoute weight は CKAD には出題されないため、本回の演習①②③で習得した内容で試験対応は十分です。本節は「次のステップを把握しておく」目的の補足として読んでください。

現場ヒヤリハット — Blue/Green の Endpoints 混在と Canary の粗粒度問題

本節では現場で発生した 2 件のヒヤリハット事例を扱います。どちらも本回の演習②③で扱った内容に直結する典型的な失敗パターンです。根本原因と再発防止のための本番ガードレールまでまとめて学びます。

ヒヤリハット ①: Blue/Green 移行時の Endpoints 混在

状況:あるチームが商用 Web API の Blue/Green デプロイを実施した。

既存 Deployment(selector: app: api のみ)はそのまま残したまま、新しく api-blue(selector: app: api, version: blue)を追加し、Service の selector を kubectl patch で {app: api, version: blue} に変更した。直後に「Blue Pod だけがトラフィックを受けている」と判断してリリース完了を宣言した。

発生事象:実際には kubectl patch の JSON 表記に誤りがあり、selector が {app: api, version: ""}(空文字列)になっていた。

空文字列値は「version ラベルが空文字列の Pod に一致」を意味する独自条件として解釈され、Pod 側にラベル version: "" を持つ Pod は存在しなかったため Endpoints は空になった。

さらに直前のテスト用 patch で {app: api}(既存通り)に一瞬戻していたため、その時点で旧 Deployment の Pod が Endpoints に加わっており、移行が完了したと誤認したオペレーターはこの状態を放置した。

結果として旧 Pod 経由のトラフィックが継続し、Blue は実際には起動済みなのにトラフィックを受けていない異常な状態が数時間続いた。

根本原因

  • Service selector 変更後に kubectl get endpoints で実際の Endpoints を確認しなかった
  • kubectl patch の JSON 構文の検証を実機で行わず、ローカルの想定と実際の Service spec が乖離していた
  • 「patch コマンドが成功した = 期待通り変更された」と思い込んでいた(コマンドは成功するが内容が誤っているケースが見落とされた)

解決策

  • Service selector 変更直後に kubectl get endpoints <service> を必ず実行し、Endpoints が期待する Pod IP リストになっていることを目視確認する
  • kubectl get pods -l <期待ラベル> -o wide で Pod IP を取得し、Endpoints と完全一致するかをセットで確認する
  • kubectl patch の JSON は kubectl get svc <name> -o yaml で適用後の spec を再読み込みして検証する(dry-run でも可)

本番ガードレール

  • Blue/Green 切り替え手順書に「selector 変更直後の kubectl get endpoints 確認」を必須項目として明記する
  • 切り替え完了をステークホルダーに連絡する前に、Endpoints の Pod IP 一覧と kubectl get pods -l version=blue -o wide の IP 一覧が完全一致することをスクリーンショットで残す
  • 本回演習②の Step 5・Step 9 と同じく「selector 変更 → Endpoints 確認 → Pod IP との一致確認」の 3 ステップを必ずセットで実行する
  • kubectl patch の代わりに kubectl apply -f <svc.yaml>(Service YAML を Git 管理)の方が誤りを発見しやすい運用もある。GitOps 移行後は selector 変更も PR レビュー対象にできる(第2巻以降で扱う ArgoCD の文脈に繋がる)

ヒヤリハット ②: Canary の Pod 数比率制御の粗粒度問題

状況:あるチームが新機能の高リスク変更を「最初は 1 % のユーザーだけに試験展開してから全量リリースする」という要件で Canary リリースを設計した。チームは本回演習③と同じ K8s 標準 Canary(Pod 数比率)の知識で実装に取り掛かった。

発生事象:1 % トラフィックを Canary に流すためには Canary 1 Pod に対して安定版が 99 Pod 必要だと気付いた。安定版アプリ本来の必要 Pod 数は 6 Pod(業務要件に基づく)だったため、99 Pod は本来必要のないリソースを 16 倍消費することになる。

クラスタの空きリソースに収まらず、ノード追加コストを試算したところ月額 50 万円超になることが判明した。結局 Canary 比率を 20 %(Canary 1 + 安定 4 = 5 Pod 合計)まで粗くして妥協するか、Canary そのものを諦めて Blue/Green に切り替えるかの判断を迫られた。

根本原因

  • K8s 標準 Canary は Pod 数比率でトラフィック分散するため、細粒度(1 % 等)の比率を実現するには大量の Pod が必要になる原理的な制約があった
  • 設計段階で「1 % という比率が K8s 標準 Canary で実現可能か」を検証していなかった
  • 「Canary = どんな比率でも自由に設定可能」という Argo Rollouts や Istio のイメージを K8s 標準 Canary に持ち込んでしまっていた

解決策

  • 細粒度の Canary(1 %〜10 % 程度)が必要なら Gateway API HTTPRoute の weight(Traefik v3 対応)または Argo Rollouts の trafficRouting.weight を導入する
  • HTTP ヘッダ条件(特定ユーザーだけ Canary)が必要なら Gateway API HTTPRoute の HeaderMatch や Flagger を導入する
  • K8s 標準 Canary を使う場合は「20 %〜50 % 程度の粗い比率で十分な要件か」を設計初期に確認する

本番ガードレール

  • Canary 設計の意思決定フローに「必要な比率は何 %?」「K8s 標準で実現可能か?」「必要なら専用ツール(Argo Rollouts / Flagger / Gateway API weight)を選定するか?」の判断ゲートを設ける
  • K8s 標準 Canary は 10 〜 50 % の粗い比率分割が現実的な範囲。1 % 以下の精密制御が必要な場合は専用ツールを導入する判断を、要件定義時点で確定する
  • HTTP 条件分岐(X-Beta-User ヘッダ・特定ユーザー ID 等)が必要な場合は K8s 標準 Canary では実現不可能。Gateway API HeaderMatch や Flagger の選定を検討する
  • 第2巻以降で扱う ArgoCD + Argo Rollouts のセットを早期に評価し、本番リリースパイプラインの選択肢として整備しておくと、要件変更時に選択肢を取りやすくなる

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

本回で 3 つのデプロイ戦略(Recreate / Blue/Green / Canary)を実機で体験し、第12回の RollingUpdate と合わせて K8s 標準 4 戦略を網羅しました。第4部「ワークロード戦略」第2回として ep14 への橋渡しを行います。

ep13 完了後のクラスタ状態

リソース状態
fanclub-backend Deployment(ep12 状態に復元)replicas: 2 / 3 Probe 設定済 / RollingUpdate maxSurge:1 maxUnavailable:0
fanclub-backend ServiceClusterIP 10.96.150.60:80 / selector: app: fanclub-backend(version なし)
fanclub-db StatefulSet(fanclub-db-0)継続稼働
fanclub-db / fanclub-db-headless Service継続
postgres-data-fanclub-db-0 PVCBound(継続)
ConfigMap fanclub-config4 キー継続
Secret fanclub-secret2 キー継続
ServiceAccount fanclub-backend-sa継続
DaemonSet node-logger1 Pod Running 継続
CronJob fanclub-member-countSuspend: True 継続
members テーブル2 行 + score カラム継続
fanclub-backend-blue Deployment削除済(演習②末尾でクリーンアップ)
fanclub-backend-green Deployment削除済(演習②末尾でクリーンアップ)
fanclub-backend-canary Deployment削除済(演習③末尾でクリーンアップ)

CKAD D2 完全網羅の達成

第12回で「Deployment とローリングアップデートの実施」を、本回(第13回)で「共通デプロイ戦略の実装(Blue/Green・Canary)」と Recreate 詳細を習得しました。これにより CKAD ドメイン D2「Application Deployment」(出題比率 20 %)の Competency が完全網羅されます。

CKAD D2 Competency習得した回
Understand Deployments and how to perform rolling updates第12回
Understand Recreate / RollingUpdate deployment strategies第12回(概念)+ 第13回(詳細演習)
Use Kubernetes primitives to implement common deployment strategies (e.g. blue/green or canary)第13回(演習②③)
Perform and configure canary deployments第13回(演習③)

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

第7回:  Pod (Backend) 起動
第8回:  Service で外部接続性
第9回:  StatefulSet (DB) で 3 層構成完成
第10回: ConfigMap / Secret / SA で設定外部化
第11回: Job / CronJob / DaemonSet でバッチ系・デーモン系を追加
第12回: Deployment + 3 Probe で本番常駐サービス化
第13回: ★ Blue/Green + Canary + Recreate の戦略体験  ← 今ここ
        - 演習のみ・ep12 完了状態に戻して ep14 へ引き継ぐ
        - K8s 標準 4 戦略のトレードオフを実機で確認
        - CKAD D2 完全網羅達成

第3部「アプリリソース」(ep7〜ep11)でリソース基盤を整え、第4部第1回(ep12)で常駐サービスの本番運用準備を完了し、第4部第2回(ep13)でデプロイ戦略の引き出しを増やしました。次の ep14 では運用面のもう 1 つの柱である「リソース制限」を扱います。

ep13 で習得したコマンドの振り返り

本回の演習で使った kubectl コマンドを振り返ります。CKAD 試験本番でこれらを暗記レベルで打てるようにしておくと、戦略選択問題で時間を稼げます。

  • kubectl patch deployment <name> -p '{"spec":{"strategy":{"type":"Recreate","rollingUpdate":null}}}' — strategy を Recreate に変更(rollingUpdate サブフィールドを null で削除する点が重要)
  • kubectl patch deployment <name> -p '{"spec":{"strategy":{"type":"RollingUpdate","rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}' — RollingUpdate に戻す(rollingUpdate のサブフィールドを明示)
  • kubectl set env deployment/<name> KEY=VALUE / KEY- — Pod テンプレートを変更して更新をトリガー(末尾ハイフンで削除)
  • kubectl scale deployment <name> --replicas=N — replicas 変更(Canary の Stable 側スケール調整に使用)
  • kubectl patch service <name> -p '{"spec":{"selector":{"key":"value"}}}' — Service selector 変更(Blue/Green の切り替え)
  • kubectl patch service <name> -p '{"spec":{"selector":{"key":null}}}' — Service selector からキー削除(クリーンアップで使用)
  • kubectl get endpoints <service> -w — Endpoints をリアルタイム監視(Blue/Green 切り替えの瞬間を観察)
  • kubectl describe endpoints <service> — Endpoints の完全リストを表示(Canary の 5 Pod 確認)
  • kubectl rollout status deployment/<name> — ロールアウト完了を待機
  • kubectl get pods -l <label> -o wide — Pod IP を確認(Endpoints との突合)

これらに加えて第12回で扱った kubectl rollout history / undo / restartkubectl set imagekubectl explain を組み合わせると、CKAD D2「Application Deployment」の操作系問題はほぼ網羅できます。日常の kind クラスタでの素振りで打鍵速度を上げておくと、試験本番で時間に余裕が生まれます。

ep14 への橋渡し

第14回では Namespace でワークロードを分離し、ResourceQuota と LimitRange でリソース制限を設定します。CKAD ドメイン D4「Application Environment, Configuration and Security」(出題比率 25 %)の Competency「リソース要件・制限・クォータの定義と管理」を扱います。

  • Multi-tenant Namespacedev / prod Namespace を作成し、それぞれに fanclub-api を分離してデプロイする
  • ResourceQuota:Namespace 単位での CPU / Memory / Pod 数の上限設定。Quota 超過時のリソース作成エラーを実機で確認する
  • LimitRange:Pod / Container 単位のデフォルトリソース要求と上限の自動付与。Pod spec に resources を明示しないケースの安全網になる

ep13 まで default Namespace 内で完結していた fanclub-api を、ep14 で Namespace 分離するステップに入ります。第5部「セキュリティ基礎」(ep15・ep16)で扱う RBAC と NetworkPolicy も Namespace 単位の制御が前提のため、ep14 の Namespace 分離は後続回の土台になります。

第4部全体(ep12〜ep14)の位置付け

第4部「ワークロード戦略」の 3 回(ep12・ep13・ep14)は、第3部までで揃えたリソース基盤を「本番運用品質」に引き上げるパートです。ep12 で常駐サービスの基盤を、ep13 でリリース戦略の引き出しを、ep14 でリソース制限の枠組みを整えます。第5部以降のセキュリティ・パッケージ管理・HTTPS 公開の前提が、第4部完了時点で全て揃う設計です。

  • ep12:Deployment + 3 Probe + Rolling Update — 自己回復・ヘルスチェック・ゼロダウンタイム更新の基盤
  • ep13(本回):Recreate + Blue/Green + Canary — リリース戦略の引き出しと CKAD D2 完全網羅
  • ep14:ResourceQuota + LimitRange + Multi-tenant Namespace — リソース制限とテナント分離

第4部を終えた時点で、読者は「本番のワークロードを K8s 上で安全に稼働させる基本動作」を一通り押さえた状態になります。第5部以降では「これらをセキュアにし、運用しやすくし、外部から HTTPS で公開する」という応用に進みます。

本回で習得したデプロイ戦略の引き出しは、第6部 ep18 の Gateway API + cert-manager で HTTPS 公開した本番アプリの更新フローでも使うことになります。

第2巻(CKA)以降では本回の延長線上にある GitOps(ArgoCD)と高度なリリース戦略(Argo Rollouts)を本格的に扱います。本回で「K8s 標準の 4 戦略では実現できないこと」を実機で確認できた経験が、後続シリーズでツール導入の必然性を理解する助けになります。

本回の演習②で「Service selector 変更直後の Endpoints 確認」を体に染み込ませた読者は、第2巻以降の GitOps ワークフローで PR が自動的に Service spec を書き換えるシーンでも、変更後の確認を当たり前に組み込めるはずです。

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

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

問 1strategy.type: Recreate を設定した Deployment を更新すると、新旧 Pod が一時的に同時稼働する時間帯がある。

問 2:Blue/Green デプロイでは kubectl rollout undo でロールバックするより kubectl patch service で selector を変更する方が高速にロールバックできる。

問 3:K8s 標準の Canary リリースでは、Canary Pod 1 個 / 全体 5 Pod の構成で約 20 % のトラフィックが Canary に向かう。

問 4:Blue/Green デプロイではリソース消費が RollingUpdate と比べて少なくなる。

問 5:Recreate 戦略は、DB スキーマの破壊的変更時(旧バージョンと新バージョンのアプリが同時稼働できない場合)に検討される選択肢の一つである。

問 6kubectl patch service <name> -p '{"spec":{"selector":{"app":"myapp","version":"green"}}}' で Service の selector を変更すると、Endpoints は新しい selector に合致する Pod に切り替わる。

問 7:K8s 標準の Canary リリースで HTTP ヘッダの値に基づいて Canary Pod にルーティングすることができる。

問 8:Blue/Green デプロイで Service の selector に version: blue を指定した場合、version: blue ラベルを持たない Pod は Endpoints から除外される。

問 9:Kubernetes には Blue/Green デプロイ専用のリソース(kind: BlueGreenDeployment 等)が組み込まれている。

解答

解答解説
問 1×Recreate は「全旧 Pod 削除 → 全新 Pod 起動」の順で進む。新旧 Pod が同時稼働する時間帯は存在しない。これが Recreate を「同時 2 バージョン稼働 NG のシステム」に向くと判断する根拠になる
問 2Rolling Update の rollout undo は旧 ReplicaSet への段階的入れ替えで時間がかかる。Blue/Green は Service selector の変更だけで Endpoints が瞬時に切り替わるため、ロールバックも前進と同じ速さで完了する
問 3Canary 比率 = Canary replicas / (Stable + Canary)。1/(4+1) = 1/5 = 20 %。kube-proxy が 5 Endpoint に統計的に均等分散するため期待値として 20 % が Canary に向かう
問 4×Blue/Green は Blue Deployment + Green Deployment の 2 系統を並列稼働させるため、ピーク時に Pod 数が 2 倍になる。RollingUpdate(maxSurge: 1 / maxUnavailable: 0)が一時的に +1 Pod 増える程度なのと対照的
問 5Recreate は新旧同時稼働しないため、旧版が新スキーマを読むことで壊れる破壊的変更時の選択肢になる。ただしダウンタイムが発生するため、可能なら Expand-Contract パターン(互換スキーマ + RollingUpdate + 後追いカラム削除)の方が推奨される
問 6Service の selector 変更は EndpointSlice Controller によって即座に再評価され、kube-proxy が iptables / ipvs ルールを書き換える。実機では通常 1 秒以内に Endpoints が新しい Pod に切り替わる
問 7×K8s 標準 Canary は Pod 数比率のみで分散する。HTTP ヘッダ条件・ユーザー属性等の条件付きルーティングには Gateway API HTTPRoute の HeaderMatch や Argo Rollouts・Flagger 等の専用ツールが必要
問 8Service の selector は AND 条件のラベルマッチで Pod を選ぶ。selector に version: blue を指定すれば、その条件を満たさない Pod(version ラベルなし、または version: green 等)は Endpoints から除外される
問 9×Kubernetes には Blue/Green 専用のリソースは組み込まれていない。Deployment と Service の組み合わせで実装する。専用 CRD としては Argo Rollouts の kind: Rollout(BlueGreen / Canary 両対応)等の OSS が存在する

第13回まとめ

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

  • 4 つのデプロイ戦略(RollingUpdate / Recreate / Blue/Green / Canary)のトレードオフを比較表で整理し、戦略選択の判断フロー(Q1 ダウンタイム許容 → Q2 同時稼働許容 → Q3 即時/段階的)を確立した。Blue/Green と Canary は K8s に専用リソースが無く Deployment + Service で実装することを確認した
  • Recreate 戦略を kubectl patch で適用し、kubectl set env で更新をトリガーして全 Pod 一斉 Terminating → 全 Pod 一斉 ContainerCreating の遷移を kubectl get pods -w で観察した。連続 curl で約 20 秒のダウンタイムを実機計測し、Payara Micro の起動時間(約 7.5 秒)+ 3 Probe 評価時間の合計と一致することを確認した。演習末尾で RollingUpdate に戻し、rollingUpdate サブフィールドの明示指定を忘れない手順を定着させた
  • Blue/Green デプロイを実装し、fanclub-backend-blue + fanclub-backend-green の 2 Deployment を並列稼働させた。Service の selector を kubectl patch で Blue → Green に切り替え、kubectl get endpoints -w で 1 秒以内の Endpoints 切り替えを観察した。Green → Blue へのロールバックも selector 戻しで瞬時に完了することを確認した。Deployment の spec.selector 不変フィールド制約を回避するため、別名 Deployment を新規作成するアプローチを取った
  • Canary リリースを安定版 3 Pod + Canary 1 Pod = 合計 4 Pod の構成で実装し(kind 2 vCPU 制約に対応した本演習限定の比率)、Service の Endpoints に 4 Pod の IP が並ぶことを kubectl describe endpoints で確認した。トラフィック比率 = Canary / (Stable + Canary) = 1/4 = 25 % の理論を kube-proxy の iptables ルール(statistic mode random)と対応付けた。本番想定の CKAD 試験頻出パターンは 4:1 = 80:20 のため、Canary 比率計算式の理解が試験対応の核心であることを押さえた。Canary 昇格シナリオで Canary 削除 + 安定版 scale: 2 への復元を実機で行った
  • CKAD 試験頻出パターン 4 選(ダウンタイムなし更新 → RollingUpdate / DB スキーマ変更 → Recreate / 即時切替 → Blue/Green / 段階的リリース → Canary)と kubectl 速攻コマンド早見表を整理した。試験中の落とし穴 3 選(Recreate 戻し忘れ・Endpoints 確認漏れ・Canary 比率の計算ミス)も併せて押さえた
  • 現場ヒヤリハットを 2 件扱った。Blue/Green 移行時に Service selector 変更後の Endpoints 確認を怠って旧 Pod が混在した事例、K8s 標準 Canary の Pod 数比率制御で 1 % Canary に 99 Pod 必要になりリソース破綻した事例を、根本原因・解決策・本番ガードレールまで整理した。本シリーズ全体で「selector 変更直後の kubectl get endpoints 確認は必須」「Canary の必要比率を要件定義時点で確認する」を方針として確立した

次回予告

第14回 ResourceQuota + LimitRange + Multi-tenant Namespaceでは、本回まで default Namespace 内で完結していた fanclub-api を dev / prod の 2 つの Namespace に分離します。

Namespace 単位の ResourceQuota(CPU / Memory / Pod 数の上限)と LimitRange(Pod / Container 単位のデフォルトリソース要求と上限)を設定し、Quota 超過時のリソース作成エラーを実機で確認します。

CKAD ドメイン D4「Application Environment, Configuration and Security」(出題比率 25 %)の Competency「リソース要件・制限・クォータの定義と管理」を扱い、第5部「セキュリティ基礎」(RBAC・NetworkPolicy)の土台となる Namespace 分離を完成させます。

シリーズ一覧

第1部:コンテナと Docker

第2部:Kubernetes 基礎

第3部:アプリリソース

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

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

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

広告
kubernetes
スポンサーリンク