K8s 中級 #2 PV / PVC / StorageClass — 永続データモデル
K8s 中級シリーズの 2 番目の記事です。基礎シリーズ #6 まで私たちがマニフェストから分離してきたのは 設定 と シークレット でした。イメージタグ・DB ホスト・API キーといった値が外部オブジェクトに抜き出され、ワークロード定義が環境に縛られなくなりました。しかしもう 1 次元残っています — データそのもの です。コンテナ内のファイルシステムはコンテナが死ねば一緒に消える一時空間で、DB データ・ユーザーアップロードファイル・Prometheus のメトリクス時系列のようなものは Pod のライフサイクルの先まで生き残らなければなりません。この記事ではその永続ディスクのモデルを K8s がどう表現するのかを — PersistentVolume、PersistentVolumeClaim、StorageClass の三角関係で 1 サイクル追います。
このシリーズは K8s 中級 7 編です。
- #1 StatefulSet / DaemonSet / Job / CronJob — Deployment ではない他のコントローラ
- #2 PV / PVC / StorageClass — 永続データモデル ← この記事
- #3 Ingress と Ingress Controller — 外部入口
- #4 resources.requests / limits — Pod のリソース要求と上限
- #5 Health check — liveness / readiness / startup probe
- #6 オートスケーリング — HPA / VPA / Cluster Autoscaler
- #7 RBAC / NetworkPolicy / ResourceQuota — セキュリティとリソースポリシー
コンテナファイルシステムの一時性 — 出発点 #
#1 で StatefulSet が解いてくれるものの 1 つとして「Pod ごとに 1:1 の永続ボリューム」に触れ、その PVC の詳しいモデルは次の記事に持ち越しました。この記事がその持ち越した内容をすべて解きほぐす記事です。出発点から整理します — なぜ永続ボリュームというオブジェクトが必要か です。
コンテナの基本ファイルシステムはそのコンテナの中に閉じ込められた一時空間です。コンテナが終了するとその中に積まれたファイルも一緒に消えます。Pod の中でコンテナが再起動されると、同じ Pod でもファイルシステムは新しいものでまっさらに始まります。Pod 自体が別のノードに移ると、その一時空間はなおさら残りません。基礎 #4 で見た 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 つのオブジェクトの責務を 1 行ずつに分けると次のとおりです。
| オブジェクト | 何か | スコープ | 誰が作るか |
|---|---|---|---|
| PersistentVolume (PV) | ディスクそのものの表現。クラスタ内の 1 片のストレージ。 | クラスタスコープ | 管理者が直接作るか、StorageClass が動的に作る |
| PersistentVolumeClaim (PVC) | 「これだけのディスクをこのモードでくれ」という要求 | namespace スコープ | アプリ開発者がマニフェストに書く |
| 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 を直接見ることはありません。この 1 段階の間接化のおかげで、ディスクのバックエンドが変わってもワークロードマニフェストはそのまま置いておけます。
静的プロビジョニング — もっとも単純なモデル #
動的プロビジョニングに入る前に、もっとも単純なモデルから追います — 管理者が PV を直接作っておき PVC がそれにバインドされる流れです。このモデルは運用でよく使われるわけではありませんが、PV と PVC のマッチングルールを理解するには最良の出発点です。
minikube や kind のようなローカルクラスタでは、ノードのホストファイルシステムの 1 パスを 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 | ファイル |
1 行でまとめると — ブロックストレージは RWO、ファイルストレージは RWX まで可能 です。RWX が必要なワークロード(複数 Pod が同じディレクトリを共有するパターン、例: WordPress のアップロードディレクトリ、共有キャッシュ)は NFS 系をバックエンドに選ばなければなりません。ブロックディスクに RWX を要求する PVC は絶対にバインドされません。
ReadWriteOncePod — DB の split-brain 防止 #
1.22 から安定化された ReadWriteOncePod は RWO よりさらに狭い制約です。RWO は「1 つのノード内では複数 Pod が一緒にマウント可能」ですが、RWOP は「クラスタ全体でただ 1 つの Pod のみマウント」です。データベースのように 2 つのプロセスが同時に同じデータファイルを触ると壊れるワークロードの安全装置として使われます — 同じノード内の別 namespace の Pod が誤って同じ PVC を引き寄せる事故も防いでくれます。
StorageClass と動的プロビジョニング #
静的プロビジョニングが自明に持つ負担があります — クラスタ管理者が PV を 事前に人の手で 作っておかなければならない点です。ディスクが新たに必要になるたびにクラウドコンソールでディスクを作り、PV マニフェストを書いて apply するサイクルが人の仕事になります。運用クラスタではこの形がすぐにボトルネックになります。
StorageClass がその空白を埋めます。SC を事前に 1 度作っておけば、その 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 つだけ にするのが運用の標準です。
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 でコンテナ内パスにアタッチします。
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 つの道から 1 つを選ばなければならない時点です。
StatefulSet の volumeClaimTemplates 再訪 #
#1 で短く見た 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 の組み合わせは #1 で見たマルチ AZ 環境でもうまく回ります — 各 Pod がどの AZ にスケジュールされるかを見てその AZ に EBS ボリュームが作られるので、AZ ミスマッチの事故が自動で遮断されます。
スケールダウン時に PVC が残るのは reclaimPolicy とは別 #
#1 で触れた「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 のような condition が見えます。Pod を再起動すれば(例: Deployment のローリングアップデート、StatefulSet の 1 つの Pod 削除)その段階が完了します。
ディスクの縮小(shrink)は K8s が対応していません。 PVC の storage を減らして書いても拒否されます。ディスクを縮めたいなら、より小さい新しい PVC を作ってデータを移し、古い PVC を整理する道しかありません。
バックアップとスナップショットの位置 #
PVC と PV のモデルの上にもう 1 層乗るのが 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 でスナップショットを指す)。深く入るのは K8s 実戦トラックに譲りますが、永続データモデルの上にバックアップ・リカバリがどう乗るかの形だけ押さえておく程度で十分です。
運用でデータバックアップは通常 2 つの道のどちらかを行きます。
- K8s VolumeSnapshot 上のツール — Velero、Kasten K10 のような K8s ネイティブバックアップツールが VolumeSnapshot をまとめて管理してくれます。
- アプリレベルのダンプ — DB の場合は
pg_dump/mysqldump/ Redis RDB のようなアプリレベルのダンプを別ストレージ(S3 など)に送る方式。ディスクスナップショットより整合性がより保証されます。
まとめ #
この記事で押さえた流れをまとめます。
- コンテナファイルシステムは一時的 — Pod のライフサイクルとともに消える。DB・アップロード・メトリクスのようなデータはその外のディスクに分離しなければなりません。
- 3 つのオブジェクトの分離 —
PV(ディスクそのもの、クラスタスコープ)、PVC(要求、namespace スコープ)、StorageClass(どう PV を作るかの設計図、クラスタスコープ)。アプリ開発者は PVC だけ書き、残りは SC と provisioner が自動で埋める。 - 静的 vs 動的プロビジョニング — 静的は人が PV を事前に作って PVC がマッチするモデル、動的は SC が PVC 登場時に PV を自動で作るモデル。運用の標準は動的。
- accessModes — ブロック(EBS / PD / Azure Disk)は RWO、ファイル(EFS / Filestore / NFS)は RWX まで。
ReadWriteOncePod(1.22+)はクラスタ全体でただ 1 つの Pod のみ。 - SC の主要フィールド —
provisioner(どの CSI)、reclaimPolicy(Delete/Retain、運用は Retain が安全)、volumeBindingMode(WaitForFirstConsumerがマルチ AZ 安全デフォルト)、allowVolumeExpansion(最初から true 推奨)。 - Pod マウント —
spec.volumesで PVC 参照 +spec.containers[].volumeMountsでパスにアタッチ。RWO PVC を複数 Pod が同時に使うとノードミスマッチ事故 — 解決は RWX または StatefulSet。 - StatefulSet の volumeClaimTemplates — Pod ごとに PVC を自動で作るテンプレート。PVC 名は
<template>-<sts>-<ordinal>。スケールダウン時の PVC 保存(StatefulSet ポリシー)と PVC 削除時のディスク処理(SC reclaimPolicy)は別の 2 層。 - 拡張とバックアップ —
allowVolumeExpansion: trueの SC で PVC storage を増やしてディスク拡張、縮小は非対応。バックアップ・スナップショットはVolumeSnapshot+ Velero のようなツールの位置。
このモデルまで手に入れば、会社のクラスタのマニフェストディレクトリで PV・PVC・SC オブジェクトに出会ったときに、誰が何を作りどうマッチするかを 1 行で読めます。
次 — Ingress と Ingress Controller #
この記事まで扱ったのは Pod 内のデータ がクラスタ内でどう生き残るかのモデルでした。次回のテーマは視点をクラスタの外に向けます — 外部トラフィックがどうクラスタ内の Service へ入ってくるか です。
基礎 #5 で Service の 3 タイプ(ClusterIP / NodePort / LoadBalancer)のうち LoadBalancer が外部入口の標準だと触れましたが、1 つのクラスタに外部公開が必要な Service が数十個あれば LoadBalancer をその数だけ立てるのはコスト・管理の両面で負担になります。ドメインやパスごとにルーティングを分けなければならない要求も LoadBalancer 1 段では解けません。
#3 Ingress と Ingress Controller — 外部入口 ではその負担を 1 か所に集めるオブジェクト Ingress と、そのマニフェストを実際のトラフィックルーティングに解いてくれる Ingress Controller(nginx / Traefik / GKE Ingress など)のモデルを 1 サイクルで追います。HTTP / HTTPS ルーティング、TLS 終端、バーチャルホスト、パスベースルーティングまでマニフェスト 1 枚の形にまとめます。