ステートフルワークロードの更新は、Podの入れ替えで完結しません。ストレージの付け替え順序、スキーマの互換性、フォールバック設計まで含めた一連の判断が必要であり、その設計を誤るとデータの不整合や想定外のダウンタイムが生じます。本記事では「どう止めずに入れ替えるか」を軸に、StatefulSetの更新戦略からPVC設計、データベース更新の切り離し、許容ダウンタイムからの逆算まで、構築担当者が下すべき判断の構造を整理します。
ステートフルとステートレスで更新設計が変わる理由の整理
「交換可能性」という前提の有無
ステートレスなWebアプリケーションは、各Podが独立してリクエストを処理するため、新旧のPodが一時的に共存しても問題が生じません。この「交換可能性」があるから、ローリングアップデートで並列に動かしながら順次切り替えていく方式が成立します。ところがデータベースやキャッシュクラスタのように、ノード間でデータを同期・レプリケーションする構成では、異なるバージョンが混在するとデータ形式の不一致でレプリケーションが停止するリスクがあります。この前提の違いを意識せずに更新設計に入ると、運用中に想定外の問題に行き詰まります。
依存グラフの把握が更新設計の起点
更新設計を立てる前に確認すべきは「何がどのデータを持ち、どこに依存しているか」という依存グラフです。単一のデータソースを参照するだけのサービスと、複数のサービスから書き込みを受けるデータベースとでは、更新時の影響範囲がまったく異なります。本番環境20サービス規模の案件で、依存グラフの整理に2人日かけた結果、更新作業自体が半日で終わったという話もあります。依存グラフを可視化せずに更新設計に入ると、後から「このサービスを更新すると隣のサービスのスキーマとズレる」という問題に気づき、手戻りが生じやすくなります。
失敗時のデータ整合性を出発点に据える
更新設計の出発点は「成功したらどう動くか」ではなく「失敗したらデータがどうなるか」に置くべきです。ステートレスなアプリであれば、失敗時には旧バージョンのPodがそのまま動き続けるため、影響は限定的です。データを持つコンポーネントでは、新バージョンへの移行途中でロールバックしようとしたとき、スキーマ変更が済んでいた場合に旧バージョンのアプリがそのスキーマを読めずに起動しないという状況が生まれます。この非対称性を前提として設計するかどうかが、更新後のリスクを大きく左右します。
StatefulSetとDeploymentの更新戦略比較 — 何が違い、何を選ぶか
Deploymentが前提とする「交換可能性」の限界
DeploymentのRollingUpdateは、Podが交換可能であることを前提に設計されています。新旧のPodを並列に動かして問題がなければ、リクエストを少しずつ新しいPodに流し替え、古いPodを削除していく流れです。しかしデータ層では、各PodがそれぞれのPVCを持ち、独自のIDで他ノードから識別される必要があります。Deploymentを誤ってステートフルなワークロードに使うと、複数のPodが同一のPVCに競合書き込みを起こす、あるいは新旧Podが別々のPVCを参照してデータが分断されるという問題が発生します。
StatefulSetの順序保証と2つの更新戦略
StatefulSetはPod名に連番(pod-0、pod-1 …)を付与し、削除と作成を決まった順序で行うことを保証します。更新戦略はRollingUpdateとOnDeleteの2種類があり、前者はKubernetesが順次更新を自動で行い、後者は担当者が手動でPodを削除したタイミングで新バージョンのPodが起動します。自動化を優先する場合はRollingUpdate、更新前にバックアップや整合性チェックを手動で挟みたい場合はOnDeleteが向いています。どちらを選ぶかはチームの自動化成熟度と更新リスクの許容度によって決まります。
| 観点 | Deployment(RollingUpdate) | StatefulSet(RollingUpdate) | StatefulSet(OnDelete) |
|---|---|---|---|
| Pod交換可能性 | 前提とする | IDを保持しつつ更新 | IDを保持しつつ更新 |
| 更新順序 | 不定(maxSurge / maxUnavailable制御) | 高インデックスから逆順 | 担当者が制御 |
| ストレージ | 共有型またはemptyDir前提 | PVC per Pod(更新後も保持) | PVC per Pod(更新後も保持) |
| 向くユースケース | ステートレスアプリ | DB・MQ等(自動更新可能な場合) | 更新前に手動確認が必要な場合 |
| ロールバックの容易さ | 高 | 中(PVCは残る) | 高(タイミング制御で安全) |
ストレージのバインドをどう扱うか — PVCの更新と付け替えの設計
PVCはPodより長生きする
StatefulSetのPodが削除されても、volumeClaimTemplatesで生成されたPVCはデフォルトで残ります。新バージョンのPodが起動するとき、以前のPVCをそのまま引き継ぐため、旧バージョンのデータが残った状態で動き始めます。手動マイグレーションを前提とした設計であれば、「Podが起動してからマイグレーションを実行する」という手順を明示的に組み込んでおく必要があります。この点を見落とすと、新バージョンのアプリが古いスキーマのまま動き続けるという状況が静かに進行します。
付け替えの設計判断と確認すべき前提
ストレージクラスによっては動的リサイズが可能ですが、サポートしないクラスでは新しいPVCを作成してデータを移行する手順が必要です。3ノード構成のMySQLクラスタで60GBから120GBへのリサイズをした案件では、ダウンタイムなしで完了させるための事前設計に半日かかったという話があります。設計判断として重要なのは「このストレージクラスはリサイズをサポートするか」を選定時に確認しておくことです。手を動かす詳細手順は実践の場に委ねるとして、判断軸を先に整理しておくことが先決です。
PVC付け替え時の設計チェックリスト
- 対象PVCのReclaimPolicyが
Retainになっているか(誤削除防止) - 新PVCのアクセスモード(ReadWriteOnce / ReadWriteMany)が要件に合っているか
- データコピー中に書き込みが発生しないよう、アプリケーション側を停止またはread-only化しているか
- コピー後に元PVCと新PVCのサイズ・ファイル数を比較確認しているか
- StatefulSet定義のvolumeClaimTemplates更新と既存PVCの参照変更を分けて適用しているか
- 移行完了後、旧PVCを即時削除せず一定期間保持する方針を決めているか
データベースの更新をアプリの更新と切り離す設計
カップリングしたまま更新するとなぜ問題になるか
アプリとデータベースをまとめて一度に更新しようとすると、「アプリの新バージョンが新しいスキーマを要求するが、DBはまだ古い」という瞬間が必ず生まれます。先にDBを更新すれば旧アプリがスキーマを読めなくなるリスクがあり、先にアプリを更新すれば新アプリがエラーを返し続けます。この問題を回避するには、スキーマを後方互換性を保った形で段階的に変更するアプローチが有効です。新しいカラムをNULL許容で追加し、旧アプリはそのカラムを無視して動き、新アプリだけがそれを活用する形にすることで、両バージョンが共存できる期間を設けます。
Expand/Contract パターンの設計思想
スキーマの段階的移行を整理したパターンとして「Expand/Contract」があります。Expandフェーズでは後方互換性を保ちながら新しい構造(カラム・テーブル)を追加し、アプリをその構造に対応させます。Contractフェーズでは古い構造を削除します。この2段階に分けることで、アプリとDBを独立してデプロイできる期間が生まれます。「Expandだけ先に本番適用して1週間様子を見てからContractに進む」という運用を取る現場もあり、問題が起きたときに旧状態に戻せる余地を確保できます。
マイグレーションの実行タイミングの設計
アプリ起動時に自動でマイグレーションを実行する設定は開発環境では便利ですが、本番環境で複数のPodが同時に起動するとマイグレーションの競合が起きる場合があります。本番では「initContainerまたはJobとしてマイグレーションを一度だけ実行し、完了を確認してからアプリを起動する」という設計が多く選ばれています。5つ以上のレプリカを持つ環境では、マイグレーション専用のKubernetes Jobを起動前に実行する構成が特に有効です。アプリの起動と切り離して管理することで、マイグレーションの進捗追跡と失敗時の切り戻し判断も容易になります。
ダウンタイムの許容値から逆算する更新方式の選定
許容値を明確にしないまま設計に入る危険
「できるだけ短くしたい」という要件でプロジェクトが始まると、どの更新方式をどこまでのコストで実装するかが決まりません。SLA 99.9%(月約44分停止許容)のサービスと、SLA 99.99%(月約4分停止許容)のサービスとでは、更新設計のアーキテクチャが変わります。前者なら計画メンテナンス時間内に停止して更新すれば十分であり、後者ではゼロダウンタイム更新の仕組みが必要になります。設計の前に「何秒・何分のダウンタイムまで許容できるか」「それを超えた場合に何が発生するか」を明確にすることが、方式選定の起点になります。
| 許容ダウンタイム | 推奨方式 | 必要なインフラ前提 |
|---|---|---|
| 数分〜数十分(計画停止内) | 停止更新(stop-and-go) | メンテナンス通知の仕組み |
| 数十秒〜数分 | StatefulSet RollingUpdate + PodDisruptionBudget | クラスタ構成(2ノード以上) |
| 数秒以下 | Blue/Green 切り替え + 読み取りレプリカ活用 | 2系統のDB環境・LBによる切り替え機構 |
| 実質ゼロ(超高可用性) | Galera / Patroni 等の高可用クラスタ + 段階スキーマ移行 | 3ノード以上の同期レプリケーション構成 |
PodDisruptionBudgetによる同時停止数の制御
ローリングアップデート中に一度に何台まで停止を許容するかを指定するのがPodDisruptionBudget(PDB)です。3レプリカのStatefulSetでminAvailable: 2と設定しておけば、常に2台が稼働している状態でしか更新が進まないよう制御できます。ただしPDBだけで安全が保証されるわけではなく、クォーラムを必要とするシステム(etcd、Zookeeperなど)では、停止中のノード数がクォーラム割れを起こさないよう別途確認が必要です。PDBはあくまで計画的な中断に対するものであり、ノード障害による意図せぬ停止には別の設計が必要です。
フォールバックをどこに仕込み、どう検証するか
フォールバックを4層で設計する
更新失敗時の対応は「Kubernetes側での旧バージョン復元」「アプリ設定の切り戻し」「スキーマのリバート」「データのポイントインタイムリカバリ」という4層に分けて考えるのが整理しやすいです。Deployment / StatefulSetのロールバックはリビジョン履歴を元に旧Podを復元しますが、スキーマがExpand後のまま残っていると、旧バージョンのアプリがContractされていないカラムを使おうとしてエラーになるケースがあります。スキーマのリバートは別途対応が必要であり、マイグレーションツールのdown機能を事前に検証しておくことが重要です。
バックアップの取得タイミングと世代管理
フォールバックの最後の砦はデータバックアップです。バックアップの取得タイミングは更新直前が望ましく、更新から時間が経つほどリカバリ時のデータロスが増えます。前回更新前・今回更新前・直近定期バックアップの最低3世代を保持する設計が多く採用されています。月1回程度の更新頻度であっても、更新前スナップショットを定期バックアップとは別系統で保持しておくことで、「先月の更新が今月になって問題を起こした」という状況にも対応できます。
フォールバック検証をどの環境で行うか
フォールバック手順を本番環境で初めて確認するのは避けるべきです。ステージング環境がコスト等の都合で本番の10分の1程度のスケールになっているケースも少なくありませんが、そのような場合でも「フォールバックの手順そのもの」はステージングで検証しておき、本番固有の問題(データ量によるタイムアウトなど)は別途テストケースを設計するという分業が現実的です。年2〜3回程度の頻度でフォールバック訓練を実施している現場では、実際の更新作業に対する担当者の自信と品質が上がるという傾向があります。
落とし穴 — 更新失敗後に状態が半端になる設計パターン
バージョン混在によるレプリケーション断絶
最も厄介な失敗パターンの一つは、更新が完全な成功でも完全な失敗でもなく途中の状態で止まってしまうケースです。3ノード構成のデータベースでNode 0とNode 1が新バージョンに移行済みで、Node 2の更新中にPodが落ちた場合、クラスタは2台が新バージョン・1台が旧バージョンという混在状態になります。新バージョンのノードが書き込んだデータ形式を旧バージョンのノードが解釈できなければ、レプリケーションが止まり気づかぬままデータ不整合が進行します。「更新途中でノード障害が起きたときに自動でどう処理されるか」を事前にシミュレーションしておくことが重要です。
ヘルスチェックの粒度が粗いことによる誤った成功判定
KubernetesのReadinessProbeはPodプロセスが起動して特定エンドポイントが応答するかを確認するものですが、「DBへの接続が確立しているか」「スキーマの移行が完了しているか」という内部状態は確認できません。新バージョンのPodがReadyになっても、内部的にはスキーマ不整合でエラーを返し続けているという状況はよく発生します。より深いヘルスチェックを設計するには、アプリが実際にDBへのクエリを発行して正常応答を確認するカスタムエンドポイントを用意し、それをReadinessProbeに使う設計が有効です。
partitionを使わず全ノードを一気に更新する罠
StatefulSetのupdateStrategy.rollingUpdate.partitionパラメータを使うと、指定インデックス以降のPodだけを更新し、それ以前のPodは旧バージョンのまま残すカナリアリリース的な更新が可能です。このパラメータを活用せずに全Pod更新を一気に進めてしまうと、問題が発覚した時点では全ノードが新バージョンになっており、ロールバックのためには全ノードを旧バージョンに戻す必要が生じます。最後の1台だけ更新する設定値から始めて問題がなければ段階的に値を下げていく設計は、5台以上の規模でリスクを下げる有効な手段です。
▶ 手を動かして本番まで到達する
その設計判断を、RHEL10の本番環境で手を動かして固めませんか
ここまでの設計判断は、読めば腹に落ちます。ですが「自分の手で本番まで通しきる自信」は、それとは別の話になります。
最大6名の少人数で、構築実務者の到達点まで引き上げます。座学で終わらせず、本番相当の環境で判断を検証する2日間です。
ステートフルワークロードの更新設計を実装で確かめる上級Linuxハンズオンへ →
Linuxセミナー上級編(RHEL10)|最大6名の少人数ハンズオン