Kubernetes入門
第6回:ローリングアップデート(無停止メンテナンス)
「来週のリリース、手順書できた?」
VM時代、アプリケーションの更新作業には必ず「手順書」がありました。ロードバランサーからサーバーを切り離す順番、更新コマンドの実行、ヘルスチェックの確認、そしてロードバランサーへの再登録——これらを1台ずつ、手作業で繰り返す。深夜のメンテナンス窓で、眠い目をこすりながら手順書とにらめっこした経験、ありませんか?
第6回となる今回は、その手作業を過去のものにするローリングアップデートを体験します。Kubernetesでは、マニフェストのイメージタグを書き換えて kubectl apply するだけ。新しいバージョンのコンテナが1つ起動したら、古いコンテナが1つ退場する——この「バトンタッチ」が自動で行われるのです。
前回、3つのPodが協力してリクエストを捌く「レプリケーション」を学びました。今回はその3つのPodを、サービスを止めずに新しいバージョンへ入れ替える方法を実践します。
6.1 バージョン管理:深夜メンテをゼロにする戦略
6.1.1 VM時代の「手動ローリング更新」がなぜ神経をすり減らしたのか
誤解のないように言っておくと、VM時代でも「無停止更新」は可能でした。むしろ、本番環境のWebサーバー更新で「全台停止して一括入れ替え」を選ぶエンジニアはほとんどいません。
一般的だったのは、以下の2つの手法です。
手法A:ブルーグリーン方式
- 新バージョンのVMを新規に構築(グリーン環境)
- 動作検証後、ロードバランサーの向き先をグリーンに切り替え
- 問題なければ旧VM(ブルー環境)を破棄
手法B:ローリング方式
- VMの半数をロードバランサーから切り離す
- 切り離したVMをバージョンアップ
- ロードバランサーに再登録
- 残り半数に対して同じ作業を繰り返す
どちらも「サービスを止めない」という目的は達成できます。しかし、問題は別のところにありました。
| 課題 | 具体的なシナリオ |
|---|---|
| 手順書の複雑さ | 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なら完全無停止)を表します。
| 設定パターン | maxSurge | maxUnavailable | 特徴 |
|---|---|---|---|
| 安全重視 | 1 | 0 | 常にフル稼働を維持。更新は遅いが最も安全 |
| 高速更新 | 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回「リソース制限で隣のコンテナから身を守る」でお会いしましょう。
