- Published on
Kubernetes StatefulSetとPersistent Volume完全ガイド:CSI Driver、StorageClass、動的プロビジョニングの本番運用
- Authors
- Name
- はじめに
- StatefulSetの核心概念
- PV/PVC/StorageClassアーキテクチャ
- CSI Driver深層分析
- 動的プロビジョニングの実践実装
- ボリューム拡張とスナップショット
- トラブルシューティング事例
- 運用チェックリスト
- おわりに
- 参考資料

はじめに
KubernetesにおいてStatelessワークロードはDeploymentとReplicaSetで比較的簡単に管理できるが、データベース、メッセージキュー、分散キャッシュなど状態を保持するワークロードは全く異なる次元の複雑さを要求する。Podが再起動されたり他のノードに移動したりしてもデータが失われてはならず、各Podは固有のネットワークIdentityと安定したストレージを持つ必要があり、スケーリング時にも順序が保証されなければならない。
Kubernetesはこれらの要件に対応するため、StatefulSet、PersistentVolume(PV)、PersistentVolumeClaim(PVC)、StorageClass、そしてContainer Storage Interface(CSI) Driverというストレージエコシステムを提供している。しかし、これらの関係と動作方式を正しく理解しなければ、本番環境でデータ損失、ボリュームのスタック、プロビジョニング失敗といった深刻な障害に直面することになる。
本記事では、StatefulSetの核心的な動作原理から始めて、PV/PVC/StorageClassのアーキテクチャを深く分析し、CSI Driverの内部構造と主要な実装(EBS CSI、EFS CSI、Ceph CSI、Longhorn)を比較する。続いて動的プロビジョニングの実践的な実装、ボリューム拡張とスナップショット、実運用で頻繁に発生するトラブルシューティング事例、そして本番運用チェックリストまで総合的に解説する。
StatefulSetの核心概念
DeploymentとStatefulSetの違い
DeploymentはPodを交換可能な同一ユニットとして扱う。Pod名にはランダムハッシュが含まれ、スケジューリング順序も保証されず、どのPodが削除されても同じ役割を持つ新しいPodが即座に作成される。一方、StatefulSetは各Podに**序数インデックス(Ordinal Index)**を割り当て、ネットワークIdentityとストレージをPodのIdentityに紐付ける。
StatefulSetが提供する核心的な保証は以下の通りである。
- 安定したネットワークIdentity: Pod名が
statefulset-name-0、statefulset-name-1の形式で固定され、Headless Serviceを通じて各Podに固有のDNSレコードが作成される。 - 安定したストレージ: volumeClaimTemplatesにより各Podに専用のPVCが自動作成され、Podが再スケジュールされても同じPVCにバインドされる。
- 順序保証: Pod作成は0番から順番に進行し、削除は逆順で進行する。
- ローリングアップデートの順序保証: アップデート時も最も高いインデックスから逆順で進行する。
StatefulSetスペック定義
以下は3つのレプリカを持つPostgreSQL StatefulSetの実践的な例である。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgresql
namespace: database
spec:
serviceName: postgresql-headless
replicas: 3
podManagementPolicy: OrderedReady
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
selector:
matchLabels:
app: postgresql
template:
metadata:
labels:
app: postgresql
spec:
terminationGracePeriodSeconds: 120
securityContext:
fsGroup: 999
runAsUser: 999
containers:
- name: postgresql
image: postgres:16.2-alpine
ports:
- containerPort: 5432
name: postgresql
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgresql-secret
key: password
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2000m
memory: 4Gi
livenessProbe:
exec:
command:
- pg_isready
- -U
- postgres
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- pg_isready
- -U
- postgres
initialDelaySeconds: 5
periodSeconds: 5
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: gp3-encrypted
resources:
requests:
storage: 100Gi
---
apiVersion: v1
kind: Service
metadata:
name: postgresql-headless
namespace: database
spec:
type: ClusterIP
clusterIP: None
selector:
app: postgresql
ports:
- port: 5432
targetPort: postgresql
このマニフェストの注目すべきポイントは以下の通りである。
- volumeClaimTemplates: StatefulSetが各Podに対して
data-postgresql-0、data-postgresql-1、data-postgresql-2の形式でPVCを自動作成する。 - podManagementPolicy: OrderedReady: デフォルト値で、Pod 0がReady状態になってからPod 1が作成される。並列起動が必要な場合は
Parallelに変更できる。 - terminationGracePeriodSeconds: 120: データベースは終了時にクリーンアップ処理が必要なため、十分な猶予時間を設定する。
- fsGroup: 999: PVマウント時のファイルシステムグループ権限をPostgreSQLユーザーに合わせる。
PVC保持ポリシー
Kubernetes 1.27以降、StatefulSetのPVC保持動作を細かく制御できるようになった。デフォルトではStatefulSetを削除またはスケールダウンしてもPVCは削除されない(データ安全のため)。この動作を変更するにはpersistentVolumeClaimRetentionPolicyを使用する。
spec:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Delete
whenScaled: Retain
- whenDeleted: Delete: StatefulSet自体が削除されるとPVCも一緒に削除される。
- whenScaled: Retain: スケールダウン時にはPVCを保持する。再度スケールアップすると既存のPVCが再利用される。
PV/PVC/StorageClassアーキテクチャ
3階層ストレージモデル
Kubernetesのストレージは3つの抽象化レイヤーで構成される。
- PersistentVolume(PV): クラスタレベルのストレージリソースである。実際の物理またはクラウドストレージボリュームを表し、管理者が直接作成するか、StorageClassを通じて動的にプロビジョニングされる。
- PersistentVolumeClaim(PVC): ユーザー(開発者)がストレージを要求する仕様である。容量、アクセスモード、StorageClassを指定すると、条件に合うPVにバインドされる。
- StorageClass: 動的プロビジョニングの設計図である。どのプロビジョナー(CSI Driver)を使用するか、どのパラメータ(ディスクタイプ、IOPS、暗号化など)でボリュームを作成するかを定義する。
アクセスモード比較
| アクセスモード | 略称 | 説明 | 代表的な使用例 |
|---|---|---|---|
| ReadWriteOnce | RWO | 単一ノードで読み書きマウント | データベース、単一インスタンスアプリ |
| ReadOnlyMany | ROX | 複数ノードで読み取り専用マウント | 静的アセット、設定ファイル共有 |
| ReadWriteMany | RWX | 複数ノードで読み書きマウント | 共有ファイルシステム、CMSアップロード |
| ReadWriteOncePod | RWOP | 単一Podのみ読み書き (1.29 GA) | 強力な排他ロックが必要なDB |
Reclaim Policy
PVCが削除された後のPVの運命を決定するポリシーである。
- Retain: PVとデータをそのまま保持する。手動でクリーンアップするか、別のPVCに再バインドできる。本番データベースに推奨される。
- Delete: PVと共にバックエンドストレージ(EBSボリュームなど)も自動削除される。一時データや開発環境に適している。
- Recycle(非推奨): 使用禁止。CSI Driverベースの動的プロビジョニングに置き換えられた。
CSI Driver深層分析
CSIアーキテクチャ概要
Container Storage Interface(CSI)は、Kubernetesとストレージシステム間の標準インターフェースである。CSI以前はストレージプラグインがKubernetesのコアコードに含まれており(in-tree plugin)、新しいストレージを追加するにはKubernetes自体を修正する必要があった。CSIはこの結合を切り離し、ストレージベンダーが独立してドライバーを開発・デプロイできるようにした。
CSI Driverは2つの主要コンポーネントで構成される。
- Controller Plugin (Deployment): ボリュームの作成・削除・拡張・スナップショットなどクラスタレベルの操作を処理する。External Provisioner、External Attacher、External Snapshotterなどのサイドカーコンテナと共に実行される。
- Node Plugin (DaemonSet): 各ワーカーノードで実行され、ボリュームのマウント・アンマウント、フォーマット、デバイスパス管理を担当する。kubeletと直接通信する。
CSI Driver比較
| 項目 | AWS EBS CSI | AWS EFS CSI | Ceph CSI (Rook) | Longhorn |
|---|---|---|---|---|
| ストレージ種別 | ブロック (Block) | ファイル (NFS) | ブロック + ファイル + オブジェクト | ブロック (Replicated) |
| アクセスモード | RWO, RWOP | RWX, ROX, RWO | RWO, RWX, ROX | RWO, RWX |
| 動的プロビジョニング | 対応 | 対応 (Access Point) | 対応 | 対応 |
| ボリューム拡張 | 対応 (オンライン) | 該当なし (弾力的) | 対応 | 対応 |
| スナップショット | 対応 | 非対応 | 対応 | 対応 |
| 暗号化 | KMS対応 | 転送中暗号化 | 対応 (LUKS) | 対応 |
| トポロジー認識 | AZ単位 | リージョン単位 | CRUSH Map | ノード単位 |
| 適した環境 | AWS EKS | AWS EKS (共有ストレージ) | オンプレミス、大規模 | エッジ、中小規模 |
| 運用複雑度 | 低い | 低い | 高い (CRUSH Map管理) | 中程度 (UI提供) |
AWS EBS CSI Driverのインストール
EKS環境でEBS CSI Driverをインストールする実践的な手順である。
# 1. IAM OIDC Providerの確認
eksctl utils associate-iam-oidc-provider \
--cluster my-cluster \
--approve
# 2. EBS CSI Driver用IAM ServiceAccountの作成
eksctl create iamserviceaccount \
--name ebs-csi-controller-sa \
--namespace kube-system \
--cluster my-cluster \
--role-name AmazonEKS_EBS_CSI_DriverRole \
--role-only \
--attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \
--approve
# 3. EKS AddonとしてEBS CSI Driverをインストール
aws eks create-addon \
--cluster-name my-cluster \
--addon-name aws-ebs-csi-driver \
--service-account-role-arn arn:aws:iam::ACCOUNT_ID:role/AmazonEKS_EBS_CSI_DriverRole
# 4. インストール確認
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-ebs-csi-driver
kubectl get csidriver
動的プロビジョニングの実践実装
StorageClass設計
本番環境ではワークロードの特性に応じて複数のStorageClassを定義する必要がある。
# 汎用SSD - 一般ワークロード用
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3-standard
annotations:
storageclass.kubernetes.io/is-default-class: 'true'
provisioner: ebs.csi.aws.com
parameters:
type: gp3
fsType: ext4
encrypted: 'true'
kmsKeyId: 'arn:aws:kms:ap-northeast-2:ACCOUNT_ID:key/KEY_ID'
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
# 高性能SSD - データベース用
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: io2-database
provisioner: ebs.csi.aws.com
parameters:
type: io2
iopsPerGB: '50'
fsType: ext4
encrypted: 'true'
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
# 共有ファイルシステム - RWXアクセス用
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: efs-shared
provisioner: efs.csi.aws.com
parameters:
provisioningMode: efs-ap
fileSystemId: fs-0123456789abcdef0
directoryPerms: '700'
basePath: '/dynamic_provisioning'
reclaimPolicy: Delete
StorageClass設計時の重要なポイントは以下の通りである。
- volumeBindingMode: WaitForFirstConsumer: マルチAZ環境では必須である。Podがスケジュールされるノードのアゾンを確認してから該当AZにボリュームを作成するため、ボリュームとPodのAZ不一致問題を防止する。
- allowVolumeExpansion: true: 後でディスク容量を増やせるよう必ず有効にする。このオプションがfalseのStorageClassで作成されたPVCは拡張が不可能である。
- reclaimPolicy: データベースのような重要なデータを保存するボリュームには
Retainを、一時データにはDeleteを使用する。 - encrypted: "true": セキュリティコンプライアンスのために全ボリュームに暗号化を適用する。
動的プロビジョニングのフロー
動的プロビジョニングの全体フローは以下の通りである。
- ユーザーがPVCを作成すると、StorageClassのprovisionerフィールドに指定されたCSI Driverが呼び出される。
- CSI Controller PluginのCreateVolume RPCが実行され、クラウドAPIを通じて実際のボリュームが作成される。
- PVオブジェクトが自動作成されPVCにバインドされる。
- Podが該当PVCを参照してスケジュールされると、CSI Controller PluginのControllerPublishVolume RPCが呼び出されボリュームがノードにAttachされる。
- CSI Node PluginのNodeStageVolumeとNodePublishVolume RPCが呼び出され、ボリュームがフォーマットされPod内部のパスにマウントされる。
ボリューム拡張とスナップショット
オンラインボリューム拡張
EBS CSI Driverはオンラインボリューム拡張をサポートしている。Podを再起動せずにディスク容量を増やすことができる。
# PVCの要求容量を変更
kubectl patch pvc data-postgresql-0 -n database \
-p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'
# 拡張状態の確認
kubectl get pvc data-postgresql-0 -n database -o jsonpath='{.status.conditions}'
# 実際のファイルシステムサイズの確認(Pod内部で実行)
kubectl exec -it postgresql-0 -n database -- df -h /var/lib/postgresql/data
拡張作業は2段階で進行する。まずバックエンドボリューム(EBS)のサイズが増加し、次にファイルシステムが拡張される。ファイルシステムの拡張が完了するまでPVCのconditionにFileSystemResizePending状態が表示される。
ボリュームスナップショット
CSIスナップショットを活用したデータバックアップと復元の手順である。
# VolumeSnapshotClassの定義
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: ebs-snapshot-class
driver: ebs.csi.aws.com
deletionPolicy: Retain
parameters:
tagSpecification_1: 'Environment=production'
tagSpecification_2: 'BackupType=scheduled'
---
# スナップショットの作成
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: postgresql-snapshot-20260313
namespace: database
spec:
volumeSnapshotClassName: ebs-snapshot-class
source:
persistentVolumeClaimName: data-postgresql-0
---
# スナップショットから新しいPVCを復元
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-postgresql-restored
namespace: database
spec:
accessModes:
- ReadWriteOnce
storageClassName: gp3-standard
resources:
requests:
storage: 100Gi
dataSource:
name: postgresql-snapshot-20260313
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
スナップショット運用時の注意事項は以下の通りである。
- スナップショットは**クラッシュ一貫性(crash-consistent)**のみを保証する。アプリケーション一貫性が必要な場合はスナップショット前に
CHECKPOINTコマンドなどを実行する必要がある。 - VolumeSnapshotClassの
deletionPolicyをRetainに設定すると、VolumeSnapshotオブジェクトを削除しても実際のスナップショットデータは保持される。 - スナップショットCRD(VolumeSnapshot、VolumeSnapshotContent、VolumeSnapshotClass)がクラスタにインストールされている必要がある。snapshot-controllerも別途デプロイが必要である。
トラブルシューティング事例
事例1: PVCがPending状態でスタック
症状: PVCを作成したがPending状態のまま変わらない。
# PVC状態の確認
kubectl describe pvc data-postgresql-0 -n database
# 一般的なイベントメッセージの例:
# waiting for first consumer to be created before binding
# no persistent volumes available for this claim
# storageclass "gp3-standard" not found
原因と解決策:
- StorageClassが存在しない場合: PVCに指定したstorageClassNameがクラスタに存在しない。
kubectl get storageclassで確認する。 - WaitForFirstConsumerモード: volumeBindingModeがWaitForFirstConsumerの場合、PVCを使用するPodが作成されるまでPending状態は正常である。
- CSI Driver未インストール: プロビジョナーとして指定したCSI Driverがインストールされていない。
kubectl get csidriverで確認する。 - リソースクォータ超過: ネームスペースにResourceQuotaが設定されておりストレージ上限を超えた場合である。
- AZ制約: Podが特定のAZにスケジュールされたが、そのAZでボリュームを作成できない場合である。
事例2: PV/PVCがTerminating状態で固着
症状: PVCを削除したがTerminating状態で無限に待機し続ける。
# Finalizerの確認
kubectl get pvc data-postgresql-0 -n database -o jsonpath='{.metadata.finalizers}'
# 出力例: ["kubernetes.io/pvc-protection"]
# 該当PVCを使用中のPodの確認
kubectl get pods -n database -o json | \
jq '.items[] | select(.spec.volumes[]?.persistentVolumeClaim.claimName == "data-postgresql-0") | .metadata.name'
原因と解決策:
kubernetes.io/pvc-protection finalizerは、PVCを使用するPodがある場合、削除をブロックする。まず該当PVCを参照しているすべてのPodを削除してからPVC削除を再試行する。Podを削除済みにもかかわらず固着している場合にのみ、finalizerを手動で削除する。
# 注意: データ損失の可能性があるため必ずバックアップ後に実行
kubectl patch pvc data-postgresql-0 -n database \
-p '{"metadata":{"finalizers":null}}'
PVがTerminating状態で固着するケースも同様である。VolumeAttachmentが残っている場合や、CSI Driverが正常にボリュームを削除できない場合に発生する。
# VolumeAttachmentの確認
kubectl get volumeattachment | grep "pv-name"
# CSI Driver Podログの確認
kubectl logs -n kube-system -l app.kubernetes.io/name=aws-ebs-csi-driver \
-c ebs-plugin --tail=100
事例3: CSI Driverクラッシュとデータ復旧
症状: CSI Driver PodがCrashLoopBackOff状態で、新規作成されるPodがボリュームをマウントできない。
診断手順:
# CSI Driver Pod状態の確認
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-ebs-csi-driver
# CSI Driverイベントの確認
kubectl describe pod -n kube-system ebs-csi-controller-0
# CSI Node Pluginログの確認(各ノード)
kubectl logs -n kube-system -l app=ebs-csi-node -c ebs-plugin --tail=50
# VolumeAttachment状態の確認
kubectl get volumeattachment -o wide
復旧手順:
- CSI Driverを再デプロイする。HelmまたはEKS Addonで再インストールする。
- 固着したVolumeAttachmentがあれば手動でクリーンアップする。
- Podを削除してkubeletにボリュームマウントを再試行させる。
- 最悪の場合、ノード自体をdrainして交換する。
事例4: StatefulSetスケールダウン後のデータ損失懸念
StatefulSetを3から2にスケールダウンするとpostgresql-2 Podは削除されるが、data-postgresql-2 PVCはデフォルトで保持される。再び3にスケールアップすると既存のPVCが再利用される。
しかし、persistentVolumeClaimRetentionPolicy.whenScaled: Deleteに設定している場合、または手動でPVCを削除した場合にはデータが失われる。そのためスケールダウン前に必ずデータマイグレーションまたはバックアップを実行する必要がある。
運用チェックリスト
本番環境でStatefulSetとPersistent Volumeを運用する際に必ず確認すべき項目である。
デプロイ前チェックリスト:
- StorageClassに
volumeBindingMode: WaitForFirstConsumerが設定されているか - StorageClassに
allowVolumeExpansion: trueが有効化されているか - データベースボリュームのreclaimPolicyが
Retainに設定されているか - CSI Driverが正常にインストールされcsidriverオブジェクトが登録されているか
- VolumeSnapshot CRDとsnapshot-controllerがデプロイされているか
- PodDisruptionBudgetが設定されているか
- 十分なterminationGracePeriodSecondsが設定されているか
日常運用チェックリスト:
- PVC容量使用率を監視しているか(kubelet_volume_stats_used_bytesメトリクス)
- ボリュームスナップショットが定期的に作成されているか
- スナップショット復元テストを定期的に実施しているか
- CSI DriverバージョンがKubernetesバージョンと互換性があるか
- StorageClassのパラメータがセキュリティ要件(暗号化など)を満たしているか
- PendingまたはTerminating状態のPVC/PVが存在しないか
障害対応チェックリスト:
- PVC Pending時: StorageClass存在有無、CSI Driver状態、ResourceQuota、AZ制約を確認
- PVC Terminating時: 参照しているPodの存在有無、Finalizerを確認
- ボリュームマウント失敗時: VolumeAttachment状態、CSI Node Pluginログを確認
- データ復旧時: 最新スナップショットを確認、スナップショットからPVC復元手順を実行
- CSI Driver障害時: Driver Podの再デプロイ、固着したVolumeAttachmentのクリーンアップ
おわりに
Kubernetesでステートフルワークロードを安定的に運用するには、StatefulSet、PV、PVC、StorageClass、CSI Driverがどのように相互作用するかを深く理解する必要がある。単にマニフェストをコピーして適用するのではなく、各コンポーネントの動作原理を把握し、障害シナリオを事前に準備することが重要である。
特に本番環境では以下の原則を守るべきである。
- WaitForFirstConsumerをデフォルトで使用してAZ不一致問題を根本的に防止する。
- ボリューム暗号化をすべてのStorageClassに適用する。
- スナップショットベースのバックアップを定期的に実行し、復元テストも必ず含める。
- モニタリングを通じてディスク使用率、IOPS、レイテンシーをリアルタイムで追跡する。
- reclaimPolicyをデータの重要度に合わせて設定し、意図しないデータ削除を防止する。
CSI Driverエコシステムは引き続き進化している。Kubernetes 1.29でGAになったReadWriteOncePodアクセスモード、Volume Group Snapshot機能、SELinuxマウントオプション対応など、新機能が継続的に追加されている。リリースノートを継続的に確認し、ストレージ運用戦略をアップデートしていく必要がある。
参考資料
- Kubernetes公式ドキュメント - StatefulSets
- Kubernetes公式ドキュメント - Persistent Volumes
- Kubernetes公式ドキュメント - Storage Classes
- Kubernetes公式ドキュメント - Volume Snapshots
- Kubernetes公式ドキュメント - Dynamic Volume Provisioning
- AWS EBS CSI Driver GitHub
- AWS EFS CSI Driver GitHub
- Kubernetes CSI Developer Documentation - Drivers
- Longhorn CSI Snapshot Documentation
- Kubernetes Storage Layers: Ceph vs. Longhorn