PV / PVC / StorageClass
Pod のライフサイクルを越えて生き残る永続データモデルを扱います。PV · PVC · StorageClass の三角関係、静的 · 動的プロビジョニング、accessModes (RWO · RWX · RWOP)、reclaimPolicy、volumeBindingMode の WaitForFirstConsumer、allowVolumeExpansion、StatefulSet の volumeClaimTemplates がその上で何を作るのかまでを一連の流れで整理します。
第6章 ConfigMap と Secret まで、私たちがマニフェストから切り出してきたのは 設定 と 秘密 でした。イメージタグ · DB ホスト · API キーのような値が外部オブジェクトへ抜け出し、ワークロード定義が環境に縛られないようになりました。しかしもう一つ次元が残っています — データそのもの です。コンテナの中のファイルシステムはコンテナが死ねば一緒に消える一時的な空間で、DB データ · ユーザのアップロードファイル · Prometheus のメトリクス時系列のようなものは Pod のライフサイクルを越えて生き残らなければなりません。本章では、その永続ディスクのモデルを K8s がどう表現するか — PersistentVolume、PersistentVolumeClaim、StorageClass の三角関係を一連の流れで追いかけます。
本章の終わりには 第8章 StatefulSet / DaemonSet / Job / CronJob で短く押さえた volumeClaimTemplates がその上で本当に何を作り出すのかまで一行で読み取れる状態になります。
コンテナファイルシステムの一時性 — 出発点 #
第8章 で StatefulSet が解いてくれるものの一つとして「Pod ごとに 1:1 の永続ボリューム」を押さえ、その PVC の詳しいモデルは次の章へ後回しにしました。本章がその後回しにした内容をすべて扱う章です。出発点から整理します — なぜ永続ボリュームというオブジェクトが必要なのか です。
コンテナの基本ファイルシステムは、そのコンテナの中に閉じ込められた一時的な空間です。コンテナが終了するとその中に溜まったファイルも一緒に消えます。Pod の中でコンテナが再起動されると、同じ Pod でもファイルシステムは新しいものとしてきれいに始まります。Pod 自体が別のノードへ移っていけば、その一時的な空間はなおさら残りません。第4章 Deployment と ReplicaSet で見た Deployment のモデルは「Pod はいつでも死んでまた立ち上がってよい」でしたが、その一時性は stateless ワークロードには自然でも 状態を持っていなければならないワークロード には困ります。
データを生かしておくには、Pod のファイルシステムの中ではなく その外のディスク に書かなければなりません。そのディスクは Pod が死んでも生きていて、新しい Pod が立ち上がったときそのまま再びマウントできなければなりません。K8s がこの要求を表現する方式が PV / PVC / StorageClass の3オブジェクトの分離です。
emptyDir のような非永続ボリュームもある #
K8s のボリュームがすべて永続なわけではありません。emptyDir のように Pod が生きている間だけ維持されるボリュームもあります — 1つの Pod の中の2つのコンテナがファイルをやり取りする用途、大きな一時ファイル作業のスクラッチ空間のような所に使います。emptyDir は Pod が消えると一緒に消えます。本章のテーマはその反対 — Pod のライフサイクルと分離されて生き残るディスク のモデルです。
三角関係 — PV / PVC / StorageClass #
3つのオブジェクトの責任を一行ずつ分けておくと次のとおりです。
| オブジェクト | 何か | スコープ | 誰が作るか |
|---|---|---|---|
| PersistentVolume (PV) | ディスクそのものの表現。クラスタの中の一片のストレージ | クラスタスコープ | 管理者が直接作るか、StorageClass が動的に作る |
| PersistentVolumeClaim (PVC) | 「これだけのディスクをこういうモードでくれ」という要求 | ネームスペーススコープ | アプリ開発者がマニフェストに書く |
| StorageClass (SC) | PVC が入ってきたとき PV をどう作るかの設計図 | クラスタスコープ | 管理者があらかじめ作っておく |
この分離が K8s 永続データモデルの核心です。アプリ開発者は PVC だけを書きます — 「5Gi の RWO ディスクが必要だ」と書くわけです。どのクラウドのどのディスクタイプがその要求を満たすかは SC が持っていて、実際のディスクの表現は PV にマッピングされます。このおかげで同じマニフェストが AWS クラスタでは EBS で、GCP では PD で、オンプレミスクラスタでは NFS や Ceph へ流れていけます。
頭の中の図では次のように描けます。
アプリマニフェスト
│
▼
PVC (5Gi, RWO, storageClassName=fast-ssd)
│
├── (静的) 管理者があらかじめ作った PV の中からマッチするものを探して Bound
│
└── (動的) StorageClass(fast-ssd) の provisioner が
新しいディスクを作って PV として登録 → その PV に BoundBound という状態は PVC と PV が 1:1 で対になったという意味です。1つの PVC は1つの PV だけに結ばれ、1つの PV も1つの PVC だけに結ばれます。Pod は PVC だけを見てマウントし、PV を直接見ません。この一段の間接化のおかげで、ディスクのバックエンドが変わってもワークロードのマニフェストはそのままにできます。
静的プロビジョニング — 最も単純なモデル #
動的プロビジョニングに入る前に、最も単純なモデルから追いかけます — 管理者が PV を直接作っておいて PVC がそれにバインディングされる流れです。このモデルは運用でよく使われはしませんが、PV と PVC のマッチングルールを理解するのに最もよい出発点です。
minikube や kind のようなローカルクラスタでは、ノードのホストファイルシステムの一つのパスを PV のバッキングとして借りて使う hostPath がよくあります。運用環境には適しませんが、学習用には十分です。
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-local-1g
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: /mnt/data/pv-local-1gspec.capacity.storage がディスクのサイズ、spec.accessModes が一度に何人がどういう形でマウントできるか (すぐ扱います)、persistentVolumeReclaimPolicy が PVC が消えたときこの PV をどう処理するかです。storageClassName: manual は「どの SC にも属さない、手で作った PV」という表示です — このラベルが PVC とのマッチングに使われます。
一緒に使う PVC です。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: manualPVC が PV を選ぶルールは明快です。次の3つがすべて合致すればバインディングされます。
storageClassNameが同じか — 上の例は両方ともmanualです。PVC にstorageClassName: ""(空文字列) を書くと SC がない PV だけ、フィールド自体を省略するとクラスタの基本 SC に従います。accessModesが PVC の要求を PV が満たすか — PVC が RWO を要求したのに PV が RWO / RWX をどちらも支援すれば OK、その反対 (PV は RWO だけ可能なのに PVC が RWX を要求) ならマッチしません。capacityが PVC の要求以上か — PVC が 1Gi を要求して PV が 1Gi 以上ならマッチ可能です。ただし大きな PV が小さな PVC に結ばれると、その差額分だけ無駄になります。
kubectl apply -f pv-static.yaml
kubectl apply -f pvc-static.yaml
kubectl get pv,pvcNAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
persistentvolume/pv-local-1g 1Gi RWO Retain Bound default/data manual 10s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/data Bound pv-local-1g 1Gi RWO manual 5sPV と PVC のどちらも STATUS: Bound で、互いの名前を CLAIM / VOLUME のカラムに持っている形が正常です。この PVC をある Pod がマウントすると、その Pod のコンテナの中ではただの普通のディレクトリのように見えますが、そのディレクトリは実はノードの /mnt/data/pv-local-1g にマッピングされています。
1:1 の直接バインディング — claimRef / volumeName #
上のマッチングは storageClassName + accessModes + capacity で K8s が自動で探す方式ですが、本当に特定の PV を特定の PVC にだけ対応づけたいなら明示的にも書けます。
- PV の
spec.claimRefに PVC の namespace + name を書く - PVC の
spec.volumeNameに PV の名前を書く
この2つが一緒に入ると、K8s は他のマッチング候補を無視してその 1:1 で結んでくれます。運用でこのパターンを使う場合は稀ですが、マイグレーションやディスク復旧のシナリオで時々登場します。
accessModes — 誰がどうマウントできるか #
accessModes はディスクの同時マウント可能性を表現するフィールドです。4種類があります。
| モード | 略語 | 意味 |
|---|---|---|
ReadWriteOnce | RWO | 1つのノードで読み · 書きでマウント。同じノードの中の複数 Pod は一緒にマウント可能 |
ReadOnlyMany | ROX | 複数のノードで同時に読み取り専用でマウント |
ReadWriteMany | RWX | 複数のノードで同時に読み · 書きでマウント |
ReadWriteOncePod | RWOP | クラスタ全体でただ1つの Pod だけが読み · 書きでマウント (1.22+ stable) |
運用で最もよく見るのは RWO と RWX の2つです。そしてどのモードが可能かは バックエンドディスクの種類 が決めます。
| バックエンド | 支援モード | 備考 |
|---|---|---|
| AWS EBS | RWO | 1つのアベイラビリティゾーンの1つのノードにだけ付く |
| GCP Persistent Disk | RWO (regional は ROX 可能) | 基本は1つのノード |
| Azure Disk | RWO | 単一ノード |
| AWS EFS / GCP Filestore / Azure Files | RWX | NFS ベースのファイルストレージ |
| オンプレミス NFS | ROX, RWX | ファイルストレージ |
| Ceph RBD (block) | RWO | ブロック |
| CephFS / GlusterFS | RWX | ファイル |
一行に整理すると — ブロックストレージは RWO、ファイルストレージは RWX まで可能 です。RWX が必要なワークロード (複数の Pod が同じディレクトリを一緒に使うパターン、例: WordPress のアップロードディレクトリ、共有キャッシュ) は NFS 系をバックエンドに選ばなければなりません。ブロックディスクに RWX を要求する PVC は絶対にバインディングされません。
ReadWriteOncePod — DB の split-brain 防止 #
1.22 から安定化された ReadWriteOncePod は RWO よりさらに狭い制約です。RWO は「1つのノードの中では複数の Pod が一緒にマウント可能」ですが、RWOP は「クラスタ全体でただ1つの Pod だけマウント」です。データベースのように2つのプロセスが同時に同じデータファイルを触ると壊れるワークロードの安全装置として使われます — 同じノードの中の別のネームスペースの Pod が誤って同じ PVC を引っ張る事故も防いでくれます。
StorageClass と動的プロビジョニング #
静的プロビジョニングが自明に持つ負担があります — クラスタ管理者が PV を あらかじめ人の手で 作っておかなければならないという点です。ディスクが新しく必要になるたびにクラウドコンソールでディスクを作り、PV マニフェストを書き、apply するサイクルが人の仕事になります。運用クラスタではこの形がすぐにボトルネックになります。
StorageClass がその空白を埋めます。SC をあらかじめ一度だけ作っておけば、その SC を指す PVC が入ってきたとき K8s の provisioner が自動でディスクを作って PV として登録し、PVC とバインディングします。人の手が PV の段階から抜けます。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
type: gp3
encrypted: "true"
iops: "3000"
throughput: "125"
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: trueこの SC を指す PVC は次のように軽くなります。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: fast-ssdこの PVC が適用されると次のことが自動で起こります。
- K8s が PVC の
storageClassNameを見てfast-ssdSC を探します。 - SC の
provisioner(ebs.csi.aws.com) が呼ばれて AWS EBS に 5Gi gp3 ボリュームを新しく作ります。 - その EBS ボリュームが PV オブジェクトとして自動登録されます。
- 新しい PV が PVC と
Bound状態になります。
クラスタごとの provisioner の例を短く表に整理すると次のとおりです。
| 環境 | provisioner | SC 基本 |
|---|---|---|
| AWS EKS | ebs.csi.aws.com (ブロック)、efs.csi.aws.com (ファイル) | gp3 EBS |
| GCP GKE | pd.csi.storage.gke.io (ブロック)、filestore.csi.storage.gke.io (ファイル) | balanced PD |
| Azure AKS | disk.csi.azure.com、file.csi.azure.com | Standard SSD |
| minikube | k8s.io/minikube-hostpath | hostPath |
| kind | rancher.io/local-path | hostPath |
| オンプレミス | NFS subdir、Ceph RBD / CSI、Longhorn など | 環境ごとに異なる |
運用クラスタには普通 基本 SC (default StorageClass) が1つ指定されていて、PVC で storageClassName を省略するとその基本 SC が自動で適用されます。どの SC が基本かは SC のアノテーション storageclass.kubernetes.io/is-default-class: "true" で表示されます。
kubectl get scNAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
fast-ssd (default) ebs.csi.aws.com Retain WaitForFirstConsumer true 10d
slow-hdd ebs.csi.aws.com Delete WaitForFirstConsumer true 10d(default) の表示が付いた SC が基本です。1つのクラスタに2つの SC がどちらも default として表示されていると新しく作った PVC の動作が曖昧になるので、基本は 1つだけ にするのが運用の標準です。EKS の上での EBS CSI · gp3 の実際のコストトレードオフは 第28章 コスト最適化 でもう一度扱います。
StorageClass の核心フィールド4つ #
SC マニフェストでよく触ることになるフィールド4つを押さえておきます。
provisioner #
どの CSI ドライバがディスクを作るかを指すフィールドです。上の表どおりクラスタ環境ごとに値が決まっています。CSI (Container Storage Interface) は K8s が外部ストレージドライバと話す標準インターフェースで、1.13 で stable になり、それ以降 in-tree ドライバ (K8s 本体に含まれていた古いドライバ) はすべて CSI 外部ドライバへ移されました。新しいクラスタで触る provisioner はほとんどすべて *.csi.* の形です。
reclaimPolicy #
PVC が消えたとき、その PVC に結ばれていた PV (とその後ろの本物のディスク) をどうするかのポリシーです。2つの値が実質的な選択肢です。
| 値 | 動作 |
|---|---|
Delete | PVC 削除と同時に PV も削除、クラウドディスクも一緒に削除。クラウド環境の動的プロビジョニングの基本 |
Retain | PV が Released 状態で残り、クラウドディスクも保存。人が意図的に片付けて初めて再利用可能 |
Recycle という値が古いドキュメントに見えることがありますが deprecated 状態なので新しいマニフェストでは使いません。
Delete は楽ですが危険です。運用クラスタで誰かが PVC を誤って消すとディスクも一緒に消えて復旧不可状態になります。そのため運用では SC の reclaimPolicy を Retain で固めておくパターン がよくあります。PVC が消えても PV は Released 状態で残っていて、その中のデータも保存されます。片付けが必要だと判断されたら、人が PV を明示的に消すか、別の PVC と再び結んでデータを復旧できます。
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM AGE
pv-... 5Gi RWO Retain Released default/data 30mReleased は PVC との縁が解けたがディスクは生きているという意味です。この PV を再び使う予定がないなら kubectl delete pv ... で明示的に片付けます — ただしこのときクラウドディスクは自動では消えないので、クラウドコンソールで別途片付けて初めて費用が止まります。
volumeBindingMode #
PVC が作られたとき PV (とディスク) を いつ 作るかのポリシーです。
| 値 | 動作 |
|---|---|
Immediate | PVC が作られるとすぐにディスク生成 |
WaitForFirstConsumer | PVC をマウントする Pod がどのノードにスケジュールされるかが決まったあとにディスク生成 |
Immediate は単純ですがマルチアベイラビリティゾーン (AZ) 環境で事故を呼びます。例えば AWS EBS は 1つの AZ に結ばれたディスク です。Immediate でディスクが ap-northeast-2a に作られたのに、その PVC をマウントする Pod がスケジューラによって ap-northeast-2c のノードに落とされると — その Pod は当該ノードでそのディスクを永遠にマウントできません。マニフェストとしてはまともに見えますが Pod が Pending 状態で止まる事故になります。
WaitForFirstConsumer はその事故を根本から遮断します。PVC が作られてもディスクを作らずに待ちます — その PVC をマウントする Pod が登場すると、スケジューラがその Pod をどのノード (どの AZ) に置くか決めたあと、その AZ にディスクを作ります。運用の安全な基本値 で、上の SC マニフェストもこの値にしておきました。単一 AZ クラスタなら Immediate でも大きな問題はありませんが、マルチ AZ 環境ではほぼ義務に近く WaitForFirstConsumer を書かなければなりません。
allowVolumeExpansion #
true にしておくと後で PVC の spec.resources.requests.storage を増やしてディスクを一緒に増やせます。基本値は false で、一度 true にした SC を再び false に戻すような形では運用しません — 最初に SC を作るとき true にしておく方がほぼ常に良いです。 ディスクは運用中に減らすのが厄介ですが、増やすことはよくあります。詳しい動作は後の節でもう一度見ます。
Pod に PVC をマウントする #
PVC が Bound 状態になると、Pod がそれをマウントして使う番です。Pod マニフェストの形はどのワークロード (Pod / Deployment / StatefulSet) でも同じです — spec.volumes に PVC を参照 + spec.containers[].volumeMounts でコンテナの中のパスに付けます。第6章 の ConfigMap ボリュームマウントと同じ形ですが、そちらは一時的 (ConfigMap 変更時に分単位で更新) でこちらは永続だという違いがあります。
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
persistentVolumeClaim:
claimName: dataspec.volumes[].persistentVolumeClaim.claimName が上で作った PVC data を指し、そのボリュームがコンテナの中の /usr/share/nginx/html にマウントされます。コンテナのコードの立場ではそのパスはただの普通のディレクトリで、その中に書いたファイルは Pod が死んでまた立ち上がっても生き残ります。
1つの PVC を複数の Pod がマウントすると #
上の Deployment の replicas を2に増やすとどうなるでしょうか。PVC data が RWO で EBS のようなブロックバックエンドなら 2つの Pod が同じノードに落ちたときだけ 同時にマウント可能です。別のノードに落ちた2つ目の Pod は、そのディスクがすでに別のノードに attach されているのでマウントに失敗し Pending や ContainerCreating 状態で止まります。
この事故を避ける道は2つです。
- ワークロードを RWX バックエンド (NFS / EFS など) へ移行 —
accessModes: ReadWriteManyの PVC を作って複数の Pod が一緒に使えるようにします。 - ワークロードを StatefulSet へ移して Pod ごとに自分の PVC を使わせる — 各 Pod が自分のディスクを持ちます。すぐ次の節の
volumeClaimTemplatesです。
stateless な Web サーバのようにディスクにデータを置かないワークロードは、そもそも PVC 自体がないのでこの事故が起きません。PVC マウントが必要な時点は、すぐこの2つの道のどちらかを選ばなければならない時点です。
StatefulSet の volumeClaimTemplates 再訪 #
第8章 で短く見た StatefulSet マニフェストをもう一度広げてみます。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: web
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 1GivolumeClaimTemplates を正確に表現すると Pod ごとに PVC を自動で作り出すテンプレート です。上のマニフェストが replicas: 3 で適用されると K8s が次のことをします。
- Pod
web-0を作るとき、PVCdata-web-0を自動で作ります。 - Pod
web-1を作るとき、PVCdata-web-1を自動で作ります。 - Pod
web-2を作るとき、PVCdata-web-2を自動で作ります。
PVC 名のルールは <volumeClaimTemplates.metadata.name>-<statefulset.metadata.name>-<ordinal> です。上の例ではテンプレート名が data、StatefulSet 名が web なので data-web-0、data-web-1、data-web-2 になります。
各 PVC は storageClassName: fast-ssd で指定された SC の動的プロビジョニングを経て PV にマッピングされます。AWS 環境なら EBS ボリュームが3つ新しく作られ、それぞれ1つの Pod に 1:1 で結ばれます。Pod が死んでまた立ち上がっても同じインデックス (例: web-0) は同じ PVC (data-web-0) を再びマウントするのでデータがそのまま見えます。
この volumeClaimTemplates + WaitForFirstConsumer SC の組み合わせは 第8章 で見たマルチ AZ 環境でもうまく回ります — 各 Pod がどの AZ にスケジュールされるかを見てその AZ に EBS ボリュームが作られるので、AZ ミスマッチの事故が自動で遮断されます。
スケールダウン時に PVC が残るのは reclaimPolicy とは別 #
第8章 で押さえた「StatefulSet をスケールダウンすると PVC は残る」という動作は、PV の reclaimPolicy と混同しやすい部分です。2つの動作は互いに別の層のポリシーです。
- StatefulSet スケールダウン時の PVC 保存 — StatefulSet コントローラの動作です。
replicasを減らしても PVC オブジェクトはそのまま残ります。1.27 からのspec.persistentVolumeClaimRetentionPolicyでこの動作を変えられます。 - PVC 削除時の PV (とディスク) 処理 — SC の
reclaimPolicyです。PVC が本当に消えたとき、その PVC が結んでいた PV とその後ろのディスクをどうするかのポリシーです。
スケールダウンしたからといって PVC が自動で消えないので reclaimPolicy が発動することはなく、人が明示的に PVC を消したときに初めて SC の reclaimPolicy が動作します。この2つの層が一緒に安全網になって、運用事故でデータが生き残る形を作ります。
PVC 拡張 — allowVolumeExpansion #
運用中の PVC のディスクが足りなくなることがよくあります。allowVolumeExpansion: true で作った SC なら PVC の spec.resources.requests.storage を増やしてディスクを一緒に増やせます。
kubectl edit pvc dataspec:
resources:
requests:
storage: 10Gi # 5Gi -> 10GiK8s が次の段階を自動で進めます。
- CSI ドライバにクラウドディスク拡張 (例: EBS volume modification) を要求します。
- クラウドディスクが新しいサイズに増えます。
- そのディスクをマウントしているコンテナの中のファイルシステムを拡張します (
xfs_growfs/resize2fs)。
ほとんどの CSI ドライバは上の3つを Pod 再起動なしで 進めます (online expansion)。ただし一部の環境 · ファイルシステムの組み合わせでは Pod の再起動が必要な場合もあり、この場合 PVC の状態に FileSystemResizePending のようなコンディションが見えます。Pod を再起動すれば (例: Deployment のローリングアップデート、StatefulSet の1つの Pod 削除) その段階が仕上がります。
ディスク縮小 (shrink) は K8s が支援しません。 PVC の storage を減らして書いても拒否されます。ディスクを減らしたいなら、より小さい新しい PVC を作ってデータを移し、古い PVC を片付ける道しかありません。
バックアップとスナップショットの位置 #
PVC と PV のモデルの上にもう一層載るのが VolumeSnapshot と VolumeSnapshotClass オブジェクトです。クラウドディスクのスナップショット機能を K8s マニフェストで表現する方式で、CSI ドライバがスナップショットを支援しなければなりません (AWS EBS · GCP PD · Azure Disk の CSI ドライバはすべて支援します)。
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: data-snap-2026-05-09
spec:
volumeSnapshotClassName: ebs-snap
source:
persistentVolumeClaimName: dataこのオブジェクトが作られると CSI ドライバがクラウドディスクのスナップショットを取り、そのハンドルを K8s の中の VolumeSnapshotContent として登録します。後でそのスナップショットから新しい PVC を復元することもマニフェストでできます (PVC の spec.dataSource でスナップショットを指す)。深く入るのは 第26章 運用チェックリスト と 第30章 アップグレード戦略 へ後回しにしますが、永続データモデルの上にバックアップ · 復旧がどう載るかの形だけ押さえておく程度で十分です。
運用でデータバックアップは普通2つの道のどちらかを行きます。
- K8s VolumeSnapshot の上のツール — Velero、Kasten K10 のような K8s ネイティブのバックアップツールが VolumeSnapshot をまとめて管理してくれます。
- アプリ次元のダンプ — DB の場合
pg_dump/mysqldump/ Redis RDB のようなアプリ次元のダンプを別途ストレージ (S3 など) へ送る方式です。ディスクスナップショットより整合性がより保証されます。
練習問題 #
- 上の本文どおり
pv-static.yaml(1Gi、manual SC) とpvc-static.yamlを順に apply したあと、PVC のaccessModesをReadWriteManyに変えたりstorageを2Giに増やして再び apply してみてください。K8s がどのエラーで拒否するか (またはPendingで止まるか) を時間順に記録し、§「PVC が PV を選ぶルール」の3条件のどれが破られたかを一行でメモします。 - 動的プロビジョニング SC を作るとき
volumeBindingModeをImmediateとWaitForFirstConsumerの2つのうち一つずつ試してみてください。マルチ AZ 環境 (EKS / GKE) ならImmediateで作った PVC が Pod と別の AZ に落ちて Pod がPendingで止まるシナリオを直接再現できます。2つの場合のkubectl describe podEvents を比較し、WaitForFirstConsumerがなぜマルチ AZ の安全な基本値なのかを自分の表現で一段落にまとめます。 - 第8章 の
web-statefulset.yamlをreplicas: 3で立ち上げたあと、kubectl delete pod web-0で Pod を強制削除し、新しく立ち上がったweb-0が同じ PVC (data-web-0) を再びマウントするかを確認します。その次にreplicas: 1でスケールダウンしたとき PVCdata-web-1、data-web-2が残っているか、そして再びreplicas: 3に増やしたとき新しい Pod たちが古い PVC を再びマウントして以前のデータをそのまま見るかを一連の流れで記録します。この動作が SC のreclaimPolicyとどう別の安全網なのかを §「スケールダウン時に PVC が残るのは reclaimPolicy とは別」のモデルで整理します。
一行まとめ: 永続データモデルは3つのオブジェクトの分離の上に立つ — PV (ディスクそのもの、クラスタスコープ)、PVC (要求、ネームスペーススコープ)、StorageClass (どう PV を作るかの設計図)。アプリ開発者は PVC だけを書き、SC の provisioner が動的に PV を作る。運用の安全な基本値は
reclaimPolicy: Retain、volumeBindingMode: WaitForFirstConsumer、allowVolumeExpansion: trueの3行で、StatefulSet の volumeClaimTemplates がこのモデルの上で Pod ごとに PVC を自動で作り出す。
次の章 #
本章まで扱ったのは Pod の中のデータ がクラスタの中でどう生き残るかのモデルでした。次の章のテーマは視点をクラスタの外へ向けます — 外部トラフィックがどうクラスタの中の Service へ入ってくるか です。
第5章 で Service の3つのタイプ (ClusterIP / NodePort / LoadBalancer) のうち LoadBalancer が外部進入の標準だと押さえましたが、1つのクラスタに外部公開が必要な Service が数十個あるなら LoadBalancer をその分立ち上げるのは費用 · 管理の両面で負担になります。ドメインやパスごとにルーティングを分けなければならない要求も LoadBalancer 一段では解けません。
第10章 Ingress と Ingress Controller では、その負担を集約するオブジェクト Ingress と、そのマニフェストを実際のトラフィックルーティングへ解いてくれる Ingress Controller (nginx / Traefik / GKE Ingress など) のモデルを一連の流れで追いかけます。HTTP / HTTPS ルーティング、TLS 終端、仮想ホスト、パスベースのルーティングまでマニフェスト1枚の形で整理します。