Kubernetes入門 第6回:ローリングアップデート(無停止メンテナンス)

Kubernetes入門
第6回:ローリングアップデート(無停止メンテナンス)


「来週のリリース、手順書できた?」

VM時代、アプリケーションの更新作業には必ず「手順書」がありました。ロードバランサーからサーバーを切り離す順番、更新コマンドの実行、ヘルスチェックの確認、そしてロードバランサーへの再登録——これらを1台ずつ、手作業で繰り返す。深夜のメンテナンス窓で、眠い目をこすりながら手順書とにらめっこした経験、ありませんか?

第6回となる今回は、その手作業を過去のものにするローリングアップデートを体験します。Kubernetesでは、マニフェストのイメージタグを書き換えて kubectl apply するだけ。新しいバージョンのコンテナが1つ起動したら、古いコンテナが1つ退場する——この「バトンタッチ」が自動で行われるのです。

前回、3つのPodが協力してリクエストを捌く「レプリケーション」を学びました。今回はその3つのPodを、サービスを止めずに新しいバージョンへ入れ替える方法を実践します。


6.1 バージョン管理:深夜メンテをゼロにする戦略

6.1.1 VM時代の「手動ローリング更新」がなぜ神経をすり減らしたのか

誤解のないように言っておくと、VM時代でも「無停止更新」は可能でした。むしろ、本番環境のWebサーバー更新で「全台停止して一括入れ替え」を選ぶエンジニアはほとんどいません。

一般的だったのは、以下の2つの手法です。

手法A:ブルーグリーン方式

  1. 新バージョンのVMを新規に構築(グリーン環境)
  2. 動作検証後、ロードバランサーの向き先をグリーンに切り替え
  3. 問題なければ旧VM(ブルー環境)を破棄

手法B:ローリング方式

  1. VMの半数をロードバランサーから切り離す
  2. 切り離したVMをバージョンアップ
  3. ロードバランサーに再登録
  4. 残り半数に対して同じ作業を繰り返す

どちらも「サービスを止めない」という目的は達成できます。しかし、問題は別のところにありました。

課題具体的なシナリオ
手順書の複雑さLB切り離し→更新→ヘルスチェック→LB再登録を台数分繰り返す手順書が10ページ超え
人的オペレーションの負荷深夜2時、疲れた目で「次はweb03を切り離して…」と手順を追う緊張感
判断の属人化「ヘルスチェックが通らない、続行していいのか?」を現場で判断
ロールバックの重さ問題発生時、更新済みの台を1台ずつ戻す逆手順を実行
自動化の作り込みAnsibleやスクリプトで自動化するには、LB APIの統合やエラーハンドリングの実装が必要

つまり、VM時代の課題は「無停止更新ができない」ことではなく、「無停止更新を実現するための手順が複雑で、人間の注意力に依存していた」ことなのです。

6.1.2 ローリングアップデートの仕組み:新旧が「バトンタッチ」するプロセス

Kubernetesのローリングアップデートは、VM時代に手動で行っていた「1台ずつ入れ替える」作業を完全に自動化します。その仕組みを、3台のPodを持つDeploymentで図解してみましょう。

【更新前】リクエストは3つのPodに分散
┌─────────────────────────────┐
│  Service (ロードバランサー)                              │
│      ↓         ↓         ↓                            │
│  [Pod v1.27] [Pod v1.27] [Pod v1.27]                     │
│   (Ready)     (Ready)     (Ready)                        │
└─────────────────────────────┘

【更新中 Step 1】新Podを1つ追加起動
┌─────────────────────────────┐
│  Service (ロードバランサー)                              │
│      ↓         ↓         ↓         ↓                 │
│  [Pod v1.27] [Pod v1.27] [Pod v1.27] [Pod v1.28]         │
│   (Ready)     (Ready)     (Ready)    (Starting)          │
└─────────────────────────────┘

【更新中 Step 2】新Podが Ready になったら、旧Podを1つ終了
┌─────────────────────────────┐
│  Service (ロードバランサー)                              │
│      ↓         ↓                     ↓                │
│  [Pod v1.27] [Pod v1.27] [終了中...]  [Pod v1.28]        │
│   (Ready)     (Ready)   (Terminating) (Ready)            │
└─────────────────────────────┘

【更新中 Step 3〜4】これを繰り返す
┌─────────────────────────────┐
│  Service (ロードバランサー)                              │
│                ↓         ↓         ↓                  │
│  [終了中...]  [Pod v1.27] [Pod v1.28] [Pod v1.28]        │
└─────────────────────────────┘

【更新完了】全Podが新バージョンに
┌─────────────────────────────┐
│  Service (ロードバランサー)                              │
│      ↓         ↓         ↓                            │
│  [Pod v1.28] [Pod v1.28] [Pod v1.28]                     │
│   (Ready)     (Ready)     (Ready)                        │
└─────────────────────────────┘

ポイントは「常に最低限のPodがReadyな状態を維持する」こと。新しいPodが「準備完了」と報告するまで、古いPodは退場しません。リレー競技のバトンタッチのように、確実に次の走者が走り出してから前の走者が止まるのです。

VM時代との決定的な違いは、この一連の流れに人間の判断や操作が一切介在しないこと。「次のサーバーを切り離していいか?」「ヘルスチェックは通ったか?」という確認を、Kubernetesが自動で行います。

6.1.3 2026年のデプロイ標準:ダウンタイムゼロは「当たり前」の機能

2026年現在、Kubernetes v1.32におけるローリングアップデートは特別な設定なしで利用できます。Deploymentを作成した時点で、すでにこの機能は有効になっています。

# Deploymentのデフォルト更新戦略(明示しなくても適用される)
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%

maxSurge: 25% は一時的に増やせるPod数、maxUnavailable: 25% は同時に停止できるPod数を表します。

VM時代に手順書を書き、Ansibleを整備し、深夜に手動オペレーションしていた「無停止更新」が、Kubernetesではマニフェストを書き換えて apply するだけで実現します。

これは「便利になった」というレベルの話ではありません。運用の質が根本から変わるのです。

VM時代Kubernetes
手順書を作成し、レビューを受けるマニフェストの差分をGitでレビュー
深夜のメンテナンス窓を確保平日日中にいつでもデプロイ可能
1台ずつ手動で切り替えkubectl apply 1回で完了
ロールバックは逆手順を実行kubectl rollout undo で数秒

6.2 実践:Webサイトの無停止更新と自動切り替え

それでは実際に、Nginxのバージョンを 1.27 から 1.28 にアップデートし、その過程を観察しましょう。

6.2.1 マニフェストの image タグを書き換えて kubectl apply

まず、前回作成したDeploymentのマニフェストを確認します。

[Execution User: developer]

# 作業ディレクトリに移動
cd ~/k8s-practice/04-declarative

# 現在のマニフェストを確認
cat web-deployment.yaml

前回のマニフェストでは nginx:1.27 を指定していました。今回はこれを nginx:1.28(stable版)に書き換えます。

[Execution User: developer]

# sedコマンドでイメージタグを書き換え
sed -i 's|nginx:1.27|nginx:1.28|g' web-deployment.yaml

# 変更後のマニフェストを確認
cat web-deployment.yaml

変更後のマニフェスト(該当部分):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: nginx
        image: nginx:1.28
        ports:
        - containerPort: 80

準備ができたら、kubectl apply で変更を適用します。

[Execution User: developer]

# マニフェストを適用(ローリングアップデート開始)
kubectl apply -f web-deployment.yaml

出力例:

deployment.apps/web-server configured

configured と表示されれば、アップデートが開始されています。

6.2.2 更新の観察:kubectl rollout status で進捗を追いかける

アップデートの進行状況をリアルタイムで確認しましょう。

[Execution User: developer]

# ローリングアップデートの進捗を監視
kubectl rollout status deployment/web-server

出力例(リアルタイムで更新される):

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

別のターミナルを開いて、Podの入れ替わりをリアルタイムで観察することもできます。

[Execution User: developer]

# 別ターミナルで実行:Podの状態を1秒ごとに更新表示
watch -n 1 kubectl get pods -l app=web -o wide

出力例(刻々と変化する):

NAME                          READY   STATUS        RESTARTS   AGE   IP           NODE
web-server-5d6b77d8c4-abc12   1/1     Running       0          5m    10.244.0.5   kind-control-plane
web-server-5d6b77d8c4-def34   1/1     Running       0          5m    10.244.0.6   kind-control-plane
web-server-5d6b77d8c4-ghi56   1/1     Terminating   0          5m    10.244.0.7   kind-control-plane
web-server-7f9b8c6d5-jkl78    1/1     Running       0          10s   10.244.0.8   kind-control-plane

Terminating(終了中)と Running(実行中)のPodが混在している様子が見えます。これがまさに「バトンタッチの瞬間」です。

更新完了後、イメージバージョンを確認しましょう。

[Execution User: developer]

# 全Podのイメージバージョンを確認
kubectl get pods -l app=web -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].image}{"\n"}{end}'

出力例:

web-server-7f9b8c6d5-jkl78	nginx:1.28
web-server-7f9b8c6d5-mno90	nginx:1.28
web-server-7f9b8c6d5-pqr12	nginx:1.28

3つのPodすべてが nginx:1.28 に更新されました。

6.2.3 検証:アップデート中に curl を連打し、一度もエラーが出ないことを確認する

本当にダウンタイムゼロなのか?実際に検証してみましょう。

まず、もう一度バージョンを戻してから、アップデート中にリクエストを連打するテストを行います。

[Execution User: developer]

# バージョンを1.27に戻す(次のテストの準備)
sed -i 's|nginx:1.28|nginx:1.27|g' web-deployment.yaml
kubectl apply -f web-deployment.yaml
kubectl rollout status deployment/web-server

ServiceのClusterIPを確認します。

[Execution User: developer]

# ServiceのClusterIPを確認
kubectl get svc web-service -o jsonpath='{.spec.clusterIP}'

それでは、連続リクエストを送りながらアップデートを実行します。2つのターミナルを使います。

ターミナル1:リクエストを連打

[Execution User: developer]

# Serviceの ClusterIP を変数に格納
SERVICE_IP=$(kubectl get svc web-service -o jsonpath='{.spec.clusterIP}')

# クラスタ内からリクエストを送り続けるPodを起動
kubectl run curl-loop --image=curlimages/curl:8.5.0 --rm -it --restart=Never -- \
  sh -c "while true; do
    RESPONSE=\$(curl -s -o /dev/null -w '%{http_code}' --max-time 2 http://${SERVICE_IP}/)
    TIMESTAMP=\$(date '+%H:%M:%S')
    if [ \"\$RESPONSE\" = '200' ]; then
      echo \"\${TIMESTAMP} - HTTP \${RESPONSE} OK\"
    else
      echo \"\${TIMESTAMP} - HTTP \${RESPONSE} ERROR <<<<<<<<<<<\"
    fi
    sleep 0.1
  done"

ターミナル2:アップデートを実行

[Execution User: developer]

# 作業ディレクトリに移動
cd ~/k8s-practice/04-declarative

# マニフェストを1.28に変更
sed -i 's|nginx:1.27|nginx:1.28|g' web-deployment.yaml

# アップデート適用
kubectl apply -f web-deployment.yaml

ターミナル1の出力例:

14:23:45 - HTTP 200 OK
14:23:45 - HTTP 200 OK
14:23:45 - HTTP 200 OK
14:23:46 - HTTP 200 OK
14:23:46 - HTTP 000 ERROR <<<<<<<<<
14:23:46 - HTTP 200 OK
14:23:47 - HTTP 200 OK
14:23:47 - HTTP 200 OK
14:23:47 - HTTP 000 ERROR <<<<<<<<<
14:23:48 - HTTP 200 OK
14:23:48 - HTTP 200 OK
14:23:48 - HTTP 200 OK

大半のリクエストは正常に処理されていますが、数件のエラーが発生していることに注目してください。

これは「エンドポイント除外の伝播遅延」が原因です。Kubernetesでは、Podの終了決定とServiceのエンドポイント除外がほぼ同時に発生しますが、エンドポイント除外がクラスタ全体に伝播するまでにわずかな時間差があります。その間、終了処理中のPodにリクエストが届いてしまうことがあるのです。

「あれ、無停止じゃないの?」 と思われたかもしれません。ご安心ください。6.5.1で解説する preStop フックを設定することで、この問題は解消できます。本番環境では必須の設定です。

ここで重要なのは、VM時代の手動オペレーションと比較すると、エラー数が圧倒的に少ないということ。そして何より、人間が一切介入せずにこの結果が得られるということです。


6.3 救済:アップデート失敗時のロールバック(巻き戻し)

「新しいバージョンをデプロイしたら、アプリが起動しない!」

そんな緊急事態でも、Kubernetesなら数秒で元に戻せます。

6.3.1 「あ、間違えた!」を救う kubectl rollout undo の威力

まず、わざと「存在しないイメージ」を指定して、失敗するアップデートを体験しましょう。

[Execution User: developer]

# 作業ディレクトリに移動
cd ~/k8s-practice/04-declarative

# 存在しないタグを指定してマニフェストを変更
sed -i 's|nginx:1.28|nginx:1.99-does-not-exist|g' web-deployment.yaml

# アップデート適用
kubectl apply -f web-deployment.yaml

Podの状態を確認すると、悲しい光景が見えます。

[Execution User: developer]

# Podの状態を確認
kubectl get pods -l app=web

出力例:

NAME                          READY   STATUS             RESTARTS   AGE
web-server-7f9b8c6d5-jkl78    1/1     Running            0          10m
web-server-7f9b8c6d5-mno90    1/1     Running            0          10m
web-server-8a5b9d7e6-xyz99    0/1     ErrImagePull       0          30s

ErrImagePull(ImagePullBackOff) —— 指定されたイメージが見つからず、新しいPodが起動できない状態です。

しかし、注目してください。古いPodはまだ動いています。 ローリングアップデートは「新しいPodが正常に起動するまで古いPodを消さない」ため、サービスは継続しているのです。

とはいえ、このままでは更新が完了しません。すぐにロールバックしましょう。

[Execution User: developer]

# 直前のバージョンにロールバック
kubectl rollout undo deployment/web-server

出力:

deployment.apps/web-server rolled back

たった1コマンド、数秒で完了です。

[Execution User: developer]

# Podの状態を確認(正常に戻っている)
kubectl get pods -l app=web

出力例:

NAME                          READY   STATUS    RESTARTS   AGE
web-server-7f9b8c6d5-jkl78    1/1     Running   0          12m
web-server-7f9b8c6d5-mno90    1/1     Running   0          12m
web-server-7f9b8c6d5-pqr12    1/1     Running   0          10s

VM時代のロールバックを思い出してください。

手法A(ブルーグリーン)の場合:

  • 旧環境を残していれば、LBの向き先を戻すだけ(比較的楽)
  • ただし、旧環境を破棄済みなら再構築が必要

手法B(ローリング)の場合:

  • 更新済みのサーバーを1台ずつ旧バージョンに戻す
  • 手順書の「逆再生」を深夜に実行する緊張感

Kubernetesのロールバックはサービス無停止で数秒。深夜3時にトラブルが起きても、冷静に kubectl rollout undo を打てば、すぐに正常な状態に戻れるのです。

6.3.2 履歴管理:kubectl rollout history で過去のバージョンを俯瞰する

Kubernetesは、デプロイの履歴を自動で記録しています。

[Execution User: developer]

# デプロイ履歴を確認
kubectl rollout history deployment/web-server

出力例:

deployment.apps/web-server 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         <none>

CHANGE-CAUSE<none> なのは、変更理由を記録していなかったためです。運用では、変更理由を記録しておくと後から追跡しやすくなります。

[Execution User: developer]

# 変更理由をアノテーションとして記録(推奨される運用方法)
kubectl annotate deployment/web-server kubernetes.io/change-cause="Update nginx to 1.28 for security patch"

特定のリビジョンの詳細を確認したり、任意のリビジョンにロールバックすることも可能です。

[Execution User: developer]

# リビジョン1の詳細を確認
kubectl rollout history deployment/web-server --revision=1

# リビジョン1にロールバック(必要な場合)
kubectl rollout undo deployment/web-server --to-revision=1

6.3.3 【次回予告】隣のコンテナに負けない「リソース制限(Requests/Limits)」

今回のローリングアップデートでは、Pod同士が協力してサービスを継続する姿を見てきました。しかし、1つのNodeで複数のPodが動く場合、「隣のPodがCPUを使いすぎて、自分のPodが遅くなる」という問題が起こりえます。

次回は、各Podに「あなたはCPUをここまで、メモリはここまで使っていいですよ」と制限をかける Requests/Limits を学びます。深夜のトラフィック急増時にも、大切なサービスのリソースを守る方法です。


6.4 トラブルシューティングのTips

maxSurge と maxUnavailable のパラメータ調整

デフォルト設定(各25%)は多くの場合に適切ですが、状況に応じて調整できます。

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0

maxSurge: 1 は一度に追加で作成するPod数、maxUnavailable: 0 は同時に停止できるPod数(0なら完全無停止)を表します。

設定パターンmaxSurgemaxUnavailable特徴
安全重視10常にフル稼働を維持。更新は遅いが最も安全
高速更新50%50%一気に入れ替え。リソースに余裕がある場合向け
デフォルト25%25%バランス型。多くの場合に適切

アップデートが途中で止まった時の確認ポイント

症状1:ImagePullBackOff

[Execution User: developer]

# Podの詳細を確認
kubectl describe pod <pod-name>

原因:イメージ名やタグの typo、プライベートレジストリの認証エラー

症状2:CrashLoopBackOff

[Execution User: developer]

# コンテナのログを確認
kubectl logs <pod-name>

原因:アプリケーションの起動エラー、設定ファイルの不備

症状3:Pending のまま進まない

[Execution User: developer]

# Podのイベントを確認
kubectl describe pod <pod-name>

# Nodeのリソース状況を確認
kubectl describe nodes

原因:Nodeのリソース(CPU/メモリ)不足、PersistentVolumeのバインド待ち

緊急対応:更新を一時停止する

[Execution User: developer]

# ローリングアップデートを一時停止
kubectl rollout pause deployment/web-server

# 原因を調査後、再開
kubectl rollout resume deployment/web-server

6.5 本番運用に向けた考慮事項(加筆)

ここまでの内容で、ローリングアップデートの基本は理解できました。しかし、本番環境では「Podが入れ替わる瞬間に何が起きるか」をもう少し深く理解しておく必要があります。

6.5.1 処理中リクエストの保護(Graceful Shutdown)

ローリングアップデート中、旧Podは「終了」されます。このとき、そのPodが処理中だったリクエストはどうなるのでしょうか?

VM時代、ロードバランサーからサーバーを切り離す際には「コネクションドレイン」を設定していたはずです。新規接続は受け付けないが、既存の接続は処理完了まで待つ——あの機能です。

Kubernetesにも同様の仕組みがあります。Podの終了プロセスを見てみましょう。

1. kubectl apply(新イメージ指定)
        ↓
2. 新Podが起動し、Ready になる
        ↓
3. 旧Podの終了プロセス開始:
   ├─ (a) Serviceのエンドポイントから除外 → 新規リクエストは来なくなる
   ├─ (b) SIGTERM シグナルをコンテナに送信
   └─ (c) terminationGracePeriodSeconds の猶予期間開始(デフォルト30秒)
        ↓
4. 猶予期間内にプロセスが終了 → 正常終了
   または
   猶予期間を超過 → SIGKILL で強制終了

つまり、新規リクエストは来なくなるが、処理中のリクエストを完了させる時間は与えられるという設計です。

注意が必要なケース

状況問題対策
処理に30秒以上かかるリクエスト猶予期間切れで強制終了され、クライアントにエラーが返るterminationGracePeriodSeconds を延長
アプリがSIGTERMを適切に処理しない新規接続を即座に拒否しない、または処理中のリクエストを中断してしまうアプリ側でシグナルハンドリングを実装
エンドポイント除外の伝播遅延Serviceからの除外がクラスタ全体に伝わる前にPodが終了し始め、一瞬だけ新規リクエストが失敗する可能性preStop フックで数秒待機

本番環境向けの推奨設定

以下は、Graceful Shutdownを確実にするためのマニフェスト例です。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: nginx
        image: nginx:1.28
        ports:
        - containerPort: 80
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 5"]

terminationGracePeriodSeconds: 60 で猶予期間を60秒に延長しています。

preStop フックは、SIGTERMが送信されるに実行されます。この5秒間で、Serviceのエンドポイント除外がクラスタ全体に伝播し、新規リクエストが確実に来なくなってからシャットダウン処理を開始できます。
ぜひ、ターミナルを3つ立ち上げてpreStopの有無で何がどう変わるか、試してみてください。

ターミナル1:作業用(deployment差し替え適用)
ターミナル2:Podsの状態遷移ウォッチ用(watch -n 1 kubectl get pods -l app=web -o wide)
ターミナル3:Podへのリクエスト連打用

6.5.2 セッション管理:Podが消えてもユーザーを困らせない

VM時代、Webアプリケーションのセッション情報は「そのサーバーのメモリ」や「ローカルディスク」に保持されることが多かったのではないでしょうか。ロードバランサーの「スティッキーセッション」機能で、同じユーザーを同じサーバーに振り分けることで、これが成り立っていました。

Kubernetesでも同じ構成を取ると、Podが入れ替わるたびにユーザーがログアウトされるという悲劇が起きます。

【問題のあるパターン】

ユーザー → Service → Pod-A(セッション情報をメモリに保持)
                         ↓
            ローリングアップデートで Pod-A 終了
                         ↓
ユーザー → Service → Pod-B(セッション情報がない!)
                         ↓
              「ログインし直してください」😢

ローリングアップデートは頻繁に行われます。そのたびにユーザーがログアウトされるようでは、本末転倒です。

解決策:セッションをPodの外に出す

方式説明適用シナリオ
外部セッションストアRedis、Memcached、データベースなどにセッションを保存最も一般的で推奨される方式
ステートレス設計JWT(JSON Web Token)などでクライアント側にトークンを保持API中心のモダンなアプリケーション
Session Affinity同一ユーザーを同一Podに振り分けレガシーアプリの移行期(非推奨)

Session Affinity の罠

「同じユーザーは同じPodに振り分ければいいのでは?」と思うかもしれません。KubernetesのServiceにも sessionAffinity という機能があります。

apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 80
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 3600

sessionAffinity: ClientIP は同一IPからのリクエストを同一Podへ振り分け、timeoutSeconds: 3600 でその紐づけを1時間維持します。

しかし、この方式はローリングアップデートとの相性が悪いのです。

問題説明
Podが消えればセッションも消える結局、Podの終了時にセッションは失われる
負荷の偏り特定のPodにリクエストが集中しやすい
NAT環境での誤動作企業のプロキシ経由だと、多数のユーザーが同一IPに見える

結論:Podはステートレスに保ち、セッションは外部ストアに保存するのがKubernetesの王道です。

実践的なアーキテクチャ例

┌─────────────────────────────────────────┐
│                      Kubernetes Cluster                                          │
│                                                                                  │
│   ┌─────┐    ┌─────┐    ┌─────┐                             │
│   │  Pod-A   │    │  Pod-B   │    │  Pod-C   │   ← ステートレス           │
│   │ (nginx)  │    │ (nginx)  │    │ (nginx)  │                             │
│   └──┬──┘    └──┬──┘    └──┬──┘                             │
│         │                │                │                                   │
│         └────────┼────────┘                                   │
│                           │                                                     │
│                           ▼                                                     │
│                  ┌────────┐                                            │
│                  │     Redis      │   ← セッションストア                      │
│                  │  (StatefulSet) │                                            │
│                  └────────┘                                            │
└─────────────────────────────────────────┘

この構成であれば、どのPodが終了しても、どのPodにリクエストが振り分けられても、ユーザーのセッションは維持されます。ローリングアップデートを何度実行しても、ユーザーは何も気づきません。


まとめ:手順書からの解放、安心のデプロイの始まり

今回学んだことを振り返りましょう。

VM時代の常識Kubernetesの新常識
手順書を作成してレビューマニフェストの差分をGitでレビュー
LBから1台ずつ手動で切り離しkubectl apply で自動的にバトンタッチ
ヘルスチェック通過を目視確認Readiness Probeが自動判定
ロールバックは逆手順を実行kubectl rollout undo で数秒
深夜メンテナンス窓を確保平日日中にいつでもデプロイ可能
スティッキーセッションでサーバーに紐づけ外部ストアでセッションを管理

VM時代でも無停止更新は可能でした。しかし、それを実現するための手順の複雑さ人的オペレーションの負荷が、インフラエンジニアの神経をすり減らしていました。

Kubernetesのローリングアップデートは、その複雑さを吸収し、「マニフェストを書き換えて apply するだけ」というシンプルな操作に落とし込みます。これは単なる効率化ではなく、運用の質を根本から変えるパラダイムシフトです。

そして本番運用では、Graceful Shutdownとセッション管理という「Podが消える瞬間」への配慮が欠かせません。Podはいつでも消える可能性がある——この前提でアプリケーションを設計することが、Kubernetes時代の「当たり前」なのです。

次回は、Pod同士がリソースを奪い合わないようにする「Requests/Limits」を学びます。第7回「リソース制限で隣のコンテナから身を守る」でお会いしましょう。