Kubernetes入門 第8回:永続ストレージ(PVC/CSI)の正体

Kubernetes入門
第8回:永続ストレージ(PVC/CSI)の正体


「コンテナを消したらデータも消える」——その常識を、今日ここで覆します。

前回までで、私たちはPodにCPUとメモリの「資源制限」をかける方法を学びました。これでコンテナが暴走してもノード全体を道連れにすることはなくなりました。

しかし、ベテランのインフラエンジニアであるあなたの頭には、まだ一つの大きな疑問符が残っているはずです。

「で、データベースはどうするの?」

VMの世界では、仮想ディスク(VMDK, VHDX)がVMのライフサイクルとは独立して存在し、VMを削除してもディスクを残す選択ができました。ストレージvMotionで別のデータストアに移動することもできました。あの安心感を、Kubernetesでも得られるのか?

答えは「Yes」です。しかも、もっとスマートに。

今回は、Kubernetesが提供する「永続ストレージ」の仕組みを解き明かします。Podという「使い捨ての計算資源」と、PersistentVolume(PV)という「長寿命のデータ資源」が、どのように疎結合で繋がり、どのように自動的に紐付くのか。その全貌を、手を動かしながら体験しましょう。


8.1 データの置き場所:コンテナが消えてもデータは残す

8.1.1 VMエンジニアの疑問:Podが使い捨てならDBのデータはどうなる?

第3回で学んだ通り、Podは「畜産モデル」です。病気に罹れば殺処分され、新しい個体に置き換えられます。ReplicaSetは常に「望ましい数」のPodを維持しようとし、古いPodに未練はありません。

この設計思想は、ステートレスなWebサーバーには完璧にフィットします。しかし、データベースやファイルサーバーのような「状態を持つ(Stateful)」アプリケーションにとっては、一見すると悪夢です。

VMエンジニアの脳内シミュレーション:

「MySQLが動いているPodが死んだ。ReplicaSetが新しいPodを立ち上げた。でも、データディレクトリ /var/lib/mysql の中身は? まっさらな新品のMySQLが起動するだけじゃないのか?」

この疑問は、100%正しい直感です。何も対策をしなければ、その通りになります。

コンテナのファイルシステムは、デフォルトでは「エフェメラル(揮発性)」です。コンテナイメージのレイヤーの上に、書き込み可能な薄いレイヤーが重なっているだけ。コンテナが削除されれば、その書き込みレイヤーも一緒に消えます。

┌─────────────────────────┐
│  書き込みレイヤー(コンテナ固有・揮発性)        │  ← Podと運命を共にする
├─────────────────────────┤
│  読み取り専用レイヤー(イメージ由来)            │
├─────────────────────────┤
│  読み取り専用レイヤー(イメージ由来)            │
└─────────────────────────┘

では、VMの世界ではどうだったでしょうか?

vSphereでは、VMを削除する際に「ディスクを残すか削除するか」を選べました。ディスクを残せば、新しいVMにアタッチし直すことができました。つまり、計算資源(VM本体)とデータ資源(仮想ディスク)が分離可能だったのです。

Kubernetesも、この「分離」の思想を受け継いでいます。ただし、そのやり方はもっと抽象化され、もっと自動化されています。


8.1.2 K8sストレージの3兄弟:StorageClass, PV, PVC の役割分担を理解する

Kubernetesのストレージを理解するには、3つの登場人物を押さえる必要があります。これを、VMエンジニアに馴染みのある比喩で説明しましょう。

登場人物を「不動産」に例える

K8sリソース不動産の比喩VMware の比喩役割
StorageClass不動産会社データストアの種類(NFS, iSCSI, VMFS)「どんな種類のストレージを提供するか」を定義するテンプレート
PersistentVolume (PV)土地・物件仮想ディスク(.vmdk)実際にデータが格納される「ストレージの実体」
PersistentVolumeClaim (PVC)賃貸申込書VMに仮想ディスクを追加する操作「これだけの容量が欲しい」というユーザーからの要求
┌──────────────────────────────────┐
│                        Kubernetes Cluster                          │
│                                                                    │
│  ┌───────┐                                                │
│  │ StorageClass │  ← 「不動産会社」: 物件の供給ルールを定義     │
│  │  (standard)  │     "私はLocal Pathタイプの物件を扱います"     │
│  └───┬───┘                                                │
│          │ 動的プロビジョニング                                   │
│          ▼                                                        │
│  ┌───────┐      Bound      ┌───────┐             │
│  │      PV      │◄───────►│     PVC      │             │
│  │  (物件実体)  │                 │  (申込書)    │             │
│  │  100Mi 確保  │                 │  100Mi 要求  │             │
│  └───┬───┘                 └───┬───┘             │
│          │                                 │                     │
│          │  マウント                       │ 参照                │
│          ▼                                 ▼                     │
│  ┌──────────────────────────────┐  │
│  │                          Pod                               │  │
│  │   ┌──────────────────────────┐ │  │
│  │   │  Container                                         │ │  │
│  │   │   /data  ←────── mountPath で PVC をマウント│ │  │
│  │   └──────────────────────────┘ │  │
│  └──────────────────────────────┘  │
└──────────────────────────────────┘

ワークフローを追ってみる

  1. クラスタ管理者StorageClass を用意する(多くの場合、クラスタ構築時にデフォルトで存在)
  2. アプリ開発者が「100MiBのストレージが欲しい」と PVC を作成する
  3. Kubernetesが StorageClass のルールに従い、PV自動的に作成して PVC と紐付ける(Bound)
  4. Podのマニフェストで「この PVC/data にマウントして」と指定する
  5. Podが起動すると、/data への書き込みは PV の実体に永続化される

ここが革命的なポイントです。 VMの世界では、仮想ディスクの作成、VMへのアタッチ、ゲストOS内でのマウントという3つの作業を、それぞれ手動(またはスクリプト)で行っていました。Kubernetesでは、PVC を宣言するだけで、その全てが自動的に行われます。

これが「動的プロビジョニング(Dynamic Provisioning)」と呼ばれる仕組みです。


8.1.3 【比較】VMの「仮想ディスク」とK8sの「ボリュームマウント」の違い

両者の違いを、もう少し深掘りしてみましょう。

観点VMware vSphereKubernetes
ストレージの実体VMDK ファイル(データストア上)PV(バックエンドは多様:Local, NFS, iSCSI, クラウドブロックストレージ等)
割り当て単位VM単位(1つのVMDKは1つのVMにアタッチ)Pod単位(PVCを参照するPodにマウント)
容量変更VMDKの拡張 → ゲストOS内でパーティション拡張PVCの spec.resources.requests.storage を変更(StorageClassが対応していれば自動拡張)
プロビジョニングvCenterから手動作成、またはスクリプトPVCを作成すると自動的にPVが作成される(動的プロビジョニング)
マイグレーションStorage vMotion(VMを止めずにVMDKを移動)実装による(CSIドライバがサポートしていれば可能)
削除時の挙動VMを削除する際に「ディスクを残す」を選択可能PersistentVolumeReclaimPolicy で制御(Retain, Delete)

特筆すべきは「宣言的」であること。

VMの世界では、「このVMに40GBのディスクを追加する」という命令を出していました。Kubernetesでは、「このPodには40GiBのストレージが必要である」という状態を宣言します。Kubernetesは、その宣言を実現するために、必要なPVを自動的に調達します。

まるで、「会議室を予約したい」とリクエストを出せば、空いている会議室が自動的にアサインされるようなものです。どの会議室かを指定する必要はありません(もちろん、指定することもできます)。


8.2 実践:永続ボリュームのマウントとデータ書き込み

理論はここまでにして、手を動かしましょう。今回の目標は以下の通りです。

  1. kind環境のStorageClassを確認する
  2. PVCを作成し、動的プロビジョニングを目撃する
  3. PodにPVCをマウントし、データを書き込む
  4. Podを削除し、新しいPodを立てて、データが残っていることを確認する

8.2.1 StorageClass の確認と、ディスク容量を要求する PersistentVolumeClaim の作成

StorageClassの確認

まず、kindクラスタにどんな StorageClass が存在するか確認しましょう。

[Execution User: developer]

kubectl get storageclass

実行結果例:

NAME                 PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
standard (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  2d

kindクラスタには、デフォルトで standard という StorageClass が用意されています。これは「Local Path Provisioner」と呼ばれる仕組みで、ノードのローカルディスク上にディレクトリを作成してPVとして提供します。

VMエンジニア向け解説: これは、vSphereで言えば「ローカルデータストア」に相当します。共有ストレージ(NFS, iSCSI)ではないため、Podは特定のノードに縛られます。本番環境では、クラウドのブロックストレージ(AWS EBS, GCP Persistent Disk等)やNFSを使うのが一般的ですが、学習目的には十分です。

(default) と表示されているのは、PVCで storageClassName を省略した場合に、この StorageClass が自動的に選ばれることを意味します。

PVCの作成

では、100MiBのストレージを要求する PVC を作成しましょう。

[Execution User: developer]

cat <<'EOF' > pvc-demo.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-data-claim
spec:
  accessModes:
    - ReadWriteOnce          # 単一ノードから読み書き可能
  resources:
    requests:
      storage: 100Mi         # 100MiBを要求
  storageClassName: standard # 明示的に指定(省略してもOK)
EOF

[Execution User: developer]

kubectl apply -f pvc-demo.yaml

実行結果:

persistentvolumeclaim/my-data-claim created

PVCの状態を確認

[Execution User: developer]

kubectl get pvc

実行結果例:

NAME            STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
my-data-claim   Pending                                      standard       <unset>                 5s

おや、STATUSPending のままです。 これはバグではありません。

standard StorageClassの VOLUMEBINDINGMODEWaitForFirstConsumer になっていたことを思い出してください。これは「PVCを参照するPodが実際にスケジュールされるまで、PVの作成を遅延させる」という設定です。

なぜ遅延させるのか? Local Path ProvisionerはノードのローカルディスクにPVを作成します。もしPodがスケジュールされる前にPVを作ってしまうと、PVが作られたノードとPodがスケジュールされたノードが異なる可能性があります。WaitForFirstConsumer は、Podのスケジューリング先が決まってから、そのノードにPVを作成することで、この問題を回避します。


8.2.2 マニフェスト:Podにボリュームを紐付け(mountPath)て起動する

PVCを参照するPodを作成しましょう。シンプルなBusyBoxコンテナで、永続ボリュームを /data にマウントします。

[Execution User: developer]

cat <<'EOF' > pod-with-pvc.yaml
apiVersion: v1
kind: Pod
metadata:
  name: data-writer
  labels:
    app: storage-demo
spec:
  containers:
    - name: busybox
      image: busybox:1.36
      command: ["sleep", "3600"]   # 1時間スリープ(検証用)
      volumeMounts:
        - name: my-storage         # volumes[].name と一致させる
          mountPath: /data         # コンテナ内のマウント先パス
  volumes:
    - name: my-storage
      persistentVolumeClaim:
        claimName: my-data-claim   # 先ほど作成したPVCの名前
EOF

マニフェストの解説:

フィールド説明
spec.volumes[]Podで使用するボリュームを宣言する。ここでPVCを参照する。
spec.volumes[].persistentVolumeClaim.claimName参照するPVCの名前。
spec.containers[].volumeMounts[]コンテナ内のどのパスにボリュームをマウントするかを指定する。
spec.containers[].volumeMounts[].namevolumes[].name と一致させて、どのボリュームをマウントするか紐付ける。

[Execution User: developer]

kubectl apply -f pod-with-pvc.yaml

実行結果:

pod/data-writer created

PodとPVCの状態を確認

[Execution User: developer]

kubectl get pod,pvc

実行結果例:

NAME              READY   STATUS    RESTARTS   AGE
pod/data-writer   1/1     Running   0          15s

NAME                                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/my-data-claim   Bound    pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890   100Mi      RWO            standard       <unset>                 2m

注目ポイント:

  1. PVCのSTATUSが Bound に変わった — Podがスケジュールされたことで、動的プロビジョニングが発動し、PVが作成されてPVCと紐付きました。
  2. VOLUME列にPVの名前が表示された — 自動生成されたPVの名前です。私たちが命名する必要はありません。

自動生成されたPVも確認してみましょう。

[Execution User: developer]

kubectl get pv

実行結果例:

NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890   100Mi      RWO            Delete           Bound    default/my-data-claim   standard       <unset>                          45s

CLAIM 列に default/my-data-claim と表示されています。これは、このPVが default Namespaceの my-data-claim というPVCに紐付いていることを示しています。


8.2.3 検証:Podの中に潜り、指定ディレクトリに「証拠ファイル」を作成する

PVが正しくマウントされているか確認しましょう。Podの中に入り、/data ディレクトリにファイルを作成します。

[Execution User: developer]

kubectl exec -it data-writer -- /bin/sh

Podのシェルに入ったら、マウント状況を確認します。

# マウント状況を確認(Pod内で実行)
df -h /data

実行結果例:

Filesystem                Size      Used Available Use% Mounted on
/dev/sda1                38.7G      5.2G     31.5G  14% /data

/data がノードのディスクからマウントされていることがわかります。

では、「証拠ファイル」を作成しましょう。

# 証拠ファイルを作成(Pod内で実行)
echo "I was written by the first Pod at $(date)" > /data/evidence.txt
cat /data/evidence.txt

実行結果:

I was written by the first Pod at Mon Jan 19 10:30:00 UTC 2026
# シェルを抜ける
exit

これで、永続ボリューム上にデータが書き込まれました。このファイルが、Podの削除後も生き残るかどうかが、次のセクションのテーマです。


8.3 検証:Podを削除・再作成してもデータが追いかけてくる様子

8.3.1 Podを容赦なく delete する。新しいPodにデータは引き継がれるか?

いよいよクライマックスです。VMエンジニアの最大の関心事——「Podを消したらデータはどうなるのか」を実証します。

Podの削除

[Execution User: developer]

kubectl delete pod data-writer

実行結果:

pod "data-writer" deleted

PVCとPVの状態を確認

[Execution User: developer]

kubectl get pvc,pv

実行結果例:

NAME                                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/my-data-claim   Bound    pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890   100Mi      RWO            standard       <unset>                 10m

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/pvc-a1b2c3d4-e5f6-7890-abcd-ef1234567890   100Mi      RWO            Delete           Bound    default/my-data-claim   standard       <unset>                          9m

Podは消えました。しかし、PVCもPVも健在です。 両者の STATUS は依然として Bound(相互に紐付いた状態)のままです。

これが「疎結合」の真髄です。Podはあくまで「PVCを参照する」だけであり、PVCやPVの所有者ではありません。Podが死んでも、PVCは生き残ります。

新しいPodを作成

同じPVCを参照する、新しいPodを作成しましょう。名前を data-reader に変えて、別のPodであることを明確にします。

[Execution User: developer]

cat <<'EOF' > pod-with-pvc-v2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: data-reader
  labels:
    app: storage-demo
spec:
  containers:
    - name: busybox
      image: busybox:1.36
      command: ["sleep", "3600"]
      volumeMounts:
        - name: my-storage
          mountPath: /data
  volumes:
    - name: my-storage
      persistentVolumeClaim:
        claimName: my-data-claim   # 同じPVCを参照
EOF

[Execution User: developer]

kubectl apply -f pod-with-pvc-v2.yaml

実行結果:

pod/data-reader created

データの確認

[Execution User: developer]

kubectl exec data-reader -- cat /data/evidence.txt

実行結果:

I was written by the first Pod at Mon Jan 19 10:30:00 UTC 2026

データは生き残りました。

最初のPod data-writer が書き込んだファイルが、新しいPod data-reader からそのまま読み取れました。これが、Kubernetesにおける「永続ストレージ」の本質です。

  時間軸 ──────────────────────────►

  ┌───────┐                    ┌──────┐
  │ data-writer  │ ── delete ──► │ data-reader │
  │    Pod       │                    │    Pod     │
  └───┬───┘                    └───┬──┘
          │                                    │
          │ mount                              │ mount
          ▼                                    ▼
  ┌────────────────────────┐
  │             my-data-claim (PVC)                │
  │                    ↓ Bound                    │
  │             pvc-a1b2c3d4... (PV)               │
  │                                                │
  │           evidence.txt は生存                  │
  └────────────────────────┘

8.3.2 2026年の標準:CSI(Container Storage Interface)がもたらす「外部ストレージとの対話」

ここまでで、「Podとデータの分離」という概念を体験しました。しかし、本番環境では「ノードのローカルディスク」ではなく、「外部の共有ストレージ」を使いたいケースがほとんどです。

そこで登場するのが CSI(Container Storage Interface) です。

CSIとは何か

CSIは、Kubernetesと外部ストレージシステムを繋ぐ標準化されたインターフェースです。

VMwareで言えば、vSphereがNFS、iSCSI、FC(Fibre Channel)など、様々なストレージプロトコルに対応しているのと似ています。ただし、vSphereではストレージの種類ごとにVMkernelアダプターの設定が必要でした。CSIでは、ストレージベンダーが提供する「CSIドライバ」をクラスタにデプロイするだけで、そのストレージが使えるようになります。

┌───────────────────────────────┐
│                    Kubernetes Cluster                        │
│                                                              │
│  ┌───────────────────────────┐  │
│  │                 Kubernetes Core                      │  │
│  │    (PVC, PV, StorageClass, Volume Attach/Mount)      │  │
│  └────────────┬──────────────┘  │
│                            │ CSI Interface (標準化)         │
│  ┌────────────┴──────────────┐  │
│  │              CSI Driver (ベンダー提供)               │  │
│  │   AWS EBS │ GCP PD │ Azure Disk │ NetApp │ etc.  │  │
│  └────────────┬──────────────┘  │
└──────────────┼────────────────┘
                              │ ストレージAPI呼び出し
                              ▼
┌───────────────────────────────┐
│              External Storage System                         │
│   (AWS EBS, Google Persistent Disk, NetApp ONTAP, etc.)      │
└───────────────────────────────┘

主要なCSIドライバ(2026年現在)

ストレージCSIドライバ特徴
AWS EBSebs.csi.aws.comAWSのブロックストレージ。スナップショット、暗号化対応。
GCP Persistent Diskpd.csi.storage.gke.ioGCPのブロックストレージ。リージョナルPD対応。
Azure Diskdisk.csi.azure.comAzureのマネージドディスク。
NFSnfs.csi.k8s.io既存のNFSサーバーをK8sから利用。
Ceph RBDrbd.csi.ceph.comオープンソースの分散ストレージ。
VMware vSpherecsi.vsphere.vmware.comvSphereデータストア上にPVを動的プロビジョニング。

VMエンジニアへの朗報: vSphere CSIドライバを使えば、既存のvSphereインフラ上でKubernetesを運用しつつ、VMFSやvSANをPVのバックエンドとして活用できます。これなら、新しいストレージを買わずにKubernetesへの移行を始められます。

kindでの限界と本番環境への展望

今回使用した standard StorageClass(Local Path Provisioner)は、学習目的には十分ですが、以下の制約があります。

  • ノードに縛られる: PVが作成されたノードでしかPodを起動できない
  • 冗長性がない: ノードが故障するとデータも失われる
  • 容量拡張に非対応: PVCの容量を後から増やせない

本番環境では、CSIドライバを介して外部の冗長化されたストレージを使うのが鉄則です。クラウド環境ならマネージドKubernetes(EKS, GKE, AKS)が標準でCSIドライバを提供しており、追加設定なしで動的プロビジョニングが使えます。


8.3.3 【次回予告】環境依存の設定を切り離す「ConfigMapとSecret」

今回、Podとデータの分離を学びました。次回は、Podとコンフィグ(設定情報)の分離に進みます。

VMの世界では、設定ファイルを仮想ディスク内に配置し、環境ごとに異なるディスクイメージを用意していたかもしれません。あるいは、Ansibleで設定ファイルを各VMに配布していたでしょう。

Kubernetesでは、ConfigMapSecret というリソースを使って、設定情報をコンテナイメージの外に切り出します。同じイメージを開発環境・ステージング環境・本番環境で使い回しつつ、環境変数や設定ファイルだけを差し替える——そんな運用が標準です。

次回もお楽しみに。


8.4 トラブルシューティングのTips

永続ボリュームは便利ですが、「マウントできない」トラブルは意外と頻発します。以下は、現場で遭遇しやすいエラーと対処法です。

Podが ContainerCreating のまま動かない場合

kubectl describe pod <pod-name> でEventsセクションを確認しましょう。

よくあるエラー:

Warning  FailedMount  3m (x5 over 5m)  kubelet  Unable to attach or mount volumes: 
  unmounted volumes=[my-storage], unattached volumes=[], failed to process volumes=[]: 
  timed out waiting for the condition

チェックリスト:

チェック項目確認コマンド対処法
PVCが Bound になっているかkubectl get pvcPending なら StorageClass が存在するか確認
PVが正しいノードに作成されているかkubectl describe pv <pv-name>Local Path の場合、Pod のノード指定と一致しているか
CSIドライバがインストールされているかkubectl get pods -n kube-system | grep csiCSI関連のPodが Running か確認
ノードのディスク容量は足りているかkubectl describe node <node-name>Allocatableephemeral-storage を確認

accessModes の指定ミスによるマウントエラー

PVCには accessModes(アクセスモード)を指定しますが、ストレージの種類によってサポートされるモードが異なります。

アクセスモード略称意味Local PathNFSクラウドブロック (EBS等)
ReadWriteOnceRWO単一ノードから読み書き
ReadOnlyManyROX複数ノードから読み取り専用
ReadWriteManyRWX複数ノードから読み書き△(一部対応)
ReadWriteOncePodRWOP単一Podから読み書き(K8s 1.29+)

ありがちなミス:

「NFSを使っていると思っていたら、実はEBSだった。ReadWriteMany を指定したら、複数Podで同時マウントできなかった」

対処法:

  1. kubectl get storageclass <storageclass-name> -o yaml でプロビジョナーを確認
  2. そのプロビジョナーがサポートするアクセスモードをドキュメントで確認
  3. PVCの accessModes をサポートされているものに修正

後片付け

検証が終わったら、作成したリソースを削除しておきましょう。

[Execution User: developer]

kubectl delete pod data-reader
kubectl delete pvc my-data-claim

注意: PVCを削除すると、standard StorageClassの RECLAIM POLICYDelete のため、PVも自動的に削除されます。本番環境で Retain ポリシーのPVCを使っている場合は、PVが残るため、手動で削除するか、別のPVCに再利用するかを検討してください。


まとめ

今回学んだことを整理しましょう。

概念VMの対応物K8sでの実現方法
ストレージの実体仮想ディスク (VMDK)PersistentVolume (PV)
ストレージの要求VM作成時のディスク追加PersistentVolumeClaim (PVC)
ストレージの種類定義データストアの種類StorageClass
自動プロビジョニングPowerCLI スクリプト等動的プロビジョニング(StorageClass経由)
VMとディスクの分離VMを削除してもディスクを残すPodを削除してもPVC/PVは残る

Kubernetesの永続ストレージは、VMの仮想ディスクの「良いところ」を受け継ぎつつ、宣言的な管理と自動プロビジョニングによって、運用負荷を大幅に軽減します。

もう「コンテナを消したらデータも消える」とは言わせません。正しくPVCを使えば、データはPodのライフサイクルから解放され、あなたの大切な情報は安全に守られます。

次回は、アプリケーションの設定情報を外部化する ConfigMapSecret を学びます。コンテナイメージをビルドし直すことなく、設定を変更する方法をマスターしましょう。