目次
9 章

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 がどう表現するか — PersistentVolumePersistentVolumeClaimStorageClass の三角関係を一連の流れで追いかけます。

本章の終わりには 第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 が PV にバインディングされる流れ
アプリマニフェスト
PVC (5Gi, RWO, storageClassName=fast-ssd)
   ├── (静的) 管理者があらかじめ作った PV の中からマッチするものを探して Bound
   └── (動的) StorageClass(fast-ssd) の provisioner が
              新しいディスクを作って PV として登録 → その PV に Bound

Bound という状態は PVC と PV が 1:1 で対になったという意味です。1つの PVC は1つの PV だけに結ばれ、1つの PV も1つの PVC だけに結ばれます。Pod は PVC だけを見てマウントし、PV を直接見ません。この一段の間接化のおかげで、ディスクのバックエンドが変わってもワークロードのマニフェストはそのままにできます。

静的プロビジョニング — 最も単純なモデル #

動的プロビジョニングに入る前に、最も単純なモデルから追いかけます — 管理者が PV を直接作っておいて PVC がそれにバインディングされる流れです。このモデルは運用でよく使われはしませんが、PV と PVC のマッチングルールを理解するのに最もよい出発点です。

minikube や kind のようなローカルクラスタでは、ノードのホストファイルシステムの一つのパスを PV のバッキングとして借りて使う hostPath がよくあります。運用環境には適しませんが、学習用には十分です。

pv-static.yaml
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-1g

spec.capacity.storage がディスクのサイズ、spec.accessModes が一度に何人がどういう形でマウントできるか (すぐ扱います)、persistentVolumeReclaimPolicy が PVC が消えたときこの PV をどう処理するかです。storageClassName: manual は「どの SC にも属さない、手で作った PV」という表示です — このラベルが PVC とのマッチングに使われます。

一緒に使う PVC です。

pvc-static.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data
  namespace: default
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: manual

PVC が 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,pvc
出力例
NAME                          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         5s

PV と 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種類があります。

モード略語意味
ReadWriteOnceRWO1つのノードで読み · 書きでマウント。同じノードの中の複数 Pod は一緒にマウント可能
ReadOnlyManyROX複数のノードで同時に読み取り専用でマウント
ReadWriteManyRWX複数のノードで同時に読み · 書きでマウント
ReadWriteOncePodRWOPクラスタ全体でただ1つの Pod だけが読み · 書きでマウント (1.22+ stable)

運用で最もよく見るのは RWO と RWX の2つです。そしてどのモードが可能かは バックエンドディスクの種類 が決めます。

バックエンド支援モード備考
AWS EBSRWO1つのアベイラビリティゾーンの1つのノードにだけ付く
GCP Persistent DiskRWO (regional は ROX 可能)基本は1つのノード
Azure DiskRWO単一ノード
AWS EFS / GCP Filestore / Azure FilesRWXNFS ベースのファイルストレージ
オンプレミス NFSROX, RWXファイルストレージ
Ceph RBD (block)RWOブロック
CephFS / GlusterFSRWXファイル

一行に整理すると — ブロックストレージは 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 の段階から抜けます。

storageclass-fast.yaml — AWS EBS 例
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 は次のように軽くなります。

pvc-dynamic.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data
  namespace: default
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: fast-ssd

この PVC が適用されると次のことが自動で起こります。

  1. K8s が PVC の storageClassName を見て fast-ssd SC を探します。
  2. SC の provisioner (ebs.csi.aws.com) が呼ばれて AWS EBS に 5Gi gp3 ボリュームを新しく作ります。
  3. その EBS ボリュームが PV オブジェクトとして自動登録されます。
  4. 新しい PV が PVC と Bound 状態になります。

クラスタごとの provisioner の例を短く表に整理すると次のとおりです。

環境provisionerSC 基本
AWS EKSebs.csi.aws.com (ブロック)、efs.csi.aws.com (ファイル)gp3 EBS
GCP GKEpd.csi.storage.gke.io (ブロック)、filestore.csi.storage.gke.io (ファイル)balanced PD
Azure AKSdisk.csi.azure.comfile.csi.azure.comStandard SSD
minikubek8s.io/minikube-hostpathhostPath
kindrancher.io/local-pathhostPath
オンプレミスNFS subdir、Ceph RBD / CSI、Longhorn など環境ごとに異なる

運用クラスタには普通 基本 SC (default StorageClass) が1つ指定されていて、PVC で storageClassName を省略するとその基本 SC が自動で適用されます。どの SC が基本かは SC のアノテーション storageclass.kubernetes.io/is-default-class: "true" で表示されます。

基本 SC 確認
kubectl get sc
出力例
NAME                 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つの値が実質的な選択肢です。

動作
DeletePVC 削除と同時に PV も削除、クラウドディスクも一緒に削除。クラウド環境の動的プロビジョニングの基本
RetainPV が Released 状態で残り、クラウドディスクも保存。人が意図的に片付けて初めて再利用可能

Recycle という値が古いドキュメントに見えることがありますが deprecated 状態なので新しいマニフェストでは使いません。

Delete は楽ですが危険です。運用クラスタで誰かが PVC を誤って消すとディスクも一緒に消えて復旧不可状態になります。そのため運用では SC の reclaimPolicyRetain で固めておくパターン がよくあります。PVC が消えても PV は Released 状態で残っていて、その中のデータも保存されます。片付けが必要だと判断されたら、人が PV を明示的に消すか、別の PVC と再び結んでデータを復旧できます。

Retain — PVC 削除後の PV 状態
NAME           CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM             AGE
pv-...         5Gi        RWO            Retain           Released   default/data      30m

Released は PVC との縁が解けたがディスクは生きているという意味です。この PV を再び使う予定がないなら kubectl delete pv ... で明示的に片付けます — ただしこのときクラウドディスクは自動では消えないので、クラウドコンソールで別途片付けて初めて費用が止まります。

volumeBindingMode #

PVC が作られたとき PV (とディスク) を いつ 作るかのポリシーです。

動作
ImmediatePVC が作られるとすぐにディスク生成
WaitForFirstConsumerPVC をマウントする 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 変更時に分単位で更新) でこちらは永続だという違いがあります。

deployment-with-pvc.yaml — 抜粋
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: data

spec.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 されているのでマウントに失敗し PendingContainerCreating 状態で止まります。

この事故を避ける道は2つです。

  • ワークロードを RWX バックエンド (NFS / EFS など) へ移行accessModes: ReadWriteMany の PVC を作って複数の Pod が一緒に使えるようにします。
  • ワークロードを StatefulSet へ移して Pod ごとに自分の PVC を使わせる — 各 Pod が自分のディスクを持ちます。すぐ次の節の volumeClaimTemplates です。

stateless な Web サーバのようにディスクにデータを置かないワークロードは、そもそも PVC 自体がないのでこの事故が起きません。PVC マウントが必要な時点は、すぐこの2つの道のどちらかを選ばなければならない時点です。

StatefulSet の volumeClaimTemplates 再訪 #

第8章 で短く見た StatefulSet マニフェストをもう一度広げてみます。

web-statefulset.yaml — 抜粋
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: 1Gi

volumeClaimTemplates を正確に表現すると Pod ごとに PVC を自動で作り出すテンプレート です。上のマニフェストが replicas: 3 で適用されると K8s が次のことをします。

  1. Pod web-0 を作るとき、PVC data-web-0 を自動で作ります。
  2. Pod web-1 を作るとき、PVC data-web-1 を自動で作ります。
  3. Pod web-2 を作るとき、PVC data-web-2 を自動で作ります。

PVC 名のルールは <volumeClaimTemplates.metadata.name>-<statefulset.metadata.name>-<ordinal> です。上の例ではテンプレート名が data、StatefulSet 名が web なので data-web-0data-web-1data-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 を増やしてディスクを一緒に増やせます。

PVC 拡張 — kubectl edit
kubectl edit pvc data
修正部分
spec:
  resources:
    requests:
      storage: 10Gi   # 5Gi -> 10Gi

K8s が次の段階を自動で進めます。

  1. CSI ドライバにクラウドディスク拡張 (例: EBS volume modification) を要求します。
  2. クラウドディスクが新しいサイズに増えます。
  3. そのディスクをマウントしているコンテナの中のファイルシステムを拡張します (xfs_growfs / resize2fs)。

ほとんどの CSI ドライバは上の3つを Pod 再起動なしで 進めます (online expansion)。ただし一部の環境 · ファイルシステムの組み合わせでは Pod の再起動が必要な場合もあり、この場合 PVC の状態に FileSystemResizePending のようなコンディションが見えます。Pod を再起動すれば (例: Deployment のローリングアップデート、StatefulSet の1つの Pod 削除) その段階が仕上がります。

ディスク縮小 (shrink) は K8s が支援しません。 PVC の storage を減らして書いても拒否されます。ディスクを減らしたいなら、より小さい新しい PVC を作ってデータを移し、古い PVC を片付ける道しかありません。

バックアップとスナップショットの位置 #

PVC と PV のモデルの上にもう一層載るのが VolumeSnapshotVolumeSnapshotClass オブジェクトです。クラウドディスクのスナップショット機能を K8s マニフェストで表現する方式で、CSI ドライバがスナップショットを支援しなければなりません (AWS EBS · GCP PD · Azure Disk の CSI ドライバはすべて支援します)。

volumesnapshot-data.yaml — 短い例
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 など) へ送る方式です。ディスクスナップショットより整合性がより保証されます。

練習問題 #

  1. 上の本文どおり pv-static.yaml (1Gi、manual SC) と pvc-static.yaml を順に apply したあと、PVC の accessModesReadWriteMany に変えたり storage2Gi に増やして再び apply してみてください。K8s がどのエラーで拒否するか (または Pending で止まるか) を時間順に記録し、§「PVC が PV を選ぶルール」の3条件のどれが破られたかを一行でメモします。
  2. 動的プロビジョニング SC を作るとき volumeBindingModeImmediateWaitForFirstConsumer の2つのうち一つずつ試してみてください。マルチ AZ 環境 (EKS / GKE) なら Immediate で作った PVC が Pod と別の AZ に落ちて Pod が Pending で止まるシナリオを直接再現できます。2つの場合の kubectl describe pod Events を比較し、WaitForFirstConsumer がなぜマルチ AZ の安全な基本値なのかを自分の表現で一段落にまとめます。
  3. 第8章web-statefulset.yamlreplicas: 3 で立ち上げたあと、kubectl delete pod web-0 で Pod を強制削除し、新しく立ち上がった web-0 が同じ PVC (data-web-0) を再びマウントするかを確認します。その次に replicas: 1 でスケールダウンしたとき PVC data-web-1data-web-2 が残っているか、そして再び replicas: 3 に増やしたとき新しい Pod たちが古い PVC を再びマウントして以前のデータをそのまま見るかを一連の流れで記録します。この動作が SC の reclaimPolicy とどう別の安全網なのかを §「スケールダウン時に PVC が残るのは reclaimPolicy とは別」のモデルで整理します。

一行まとめ: 永続データモデルは3つのオブジェクトの分離の上に立つ — PV (ディスクそのもの、クラスタスコープ)、PVC (要求、ネームスペーススコープ)、StorageClass (どう PV を作るかの設計図)。アプリ開発者は PVC だけを書き、SC の provisioner が動的に PV を作る。運用の安全な基本値は reclaimPolicy: RetainvolumeBindingMode: WaitForFirstConsumerallowVolumeExpansion: 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枚の形で整理します。

X