Skip to content
Published on

Kubernetes StatefulSetとPersistent Volume完全ガイド:CSI Driver、StorageClass、動的プロビジョニングの本番運用

Authors
  • Name
    Twitter
Kubernetes StatefulSet Persistent Volume

はじめに

KubernetesにおいてStatelessワークロードはDeploymentとReplicaSetで比較的簡単に管理できるが、データベース、メッセージキュー、分散キャッシュなど状態を保持するワークロードは全く異なる次元の複雑さを要求する。Podが再起動されたり他のノードに移動したりしてもデータが失われてはならず、各Podは固有のネットワークIdentityと安定したストレージを持つ必要があり、スケーリング時にも順序が保証されなければならない。

Kubernetesはこれらの要件に対応するため、StatefulSetPersistentVolume(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が提供する核心的な保証は以下の通りである。

  1. 安定したネットワークIdentity: Pod名がstatefulset-name-0statefulset-name-1の形式で固定され、Headless Serviceを通じて各Podに固有のDNSレコードが作成される。
  2. 安定したストレージ: volumeClaimTemplatesにより各Podに専用のPVCが自動作成され、Podが再スケジュールされても同じPVCにバインドされる。
  3. 順序保証: Pod作成は0番から順番に進行し、削除は逆順で進行する。
  4. ローリングアップデートの順序保証: アップデート時も最も高いインデックスから逆順で進行する。

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-0data-postgresql-1data-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つの抽象化レイヤーで構成される。

  1. PersistentVolume(PV): クラスタレベルのストレージリソースである。実際の物理またはクラウドストレージボリュームを表し、管理者が直接作成するか、StorageClassを通じて動的にプロビジョニングされる。
  2. PersistentVolumeClaim(PVC): ユーザー(開発者)がストレージを要求する仕様である。容量、アクセスモード、StorageClassを指定すると、条件に合うPVにバインドされる。
  3. StorageClass: 動的プロビジョニングの設計図である。どのプロビジョナー(CSI Driver)を使用するか、どのパラメータ(ディスクタイプ、IOPS、暗号化など)でボリュームを作成するかを定義する。

アクセスモード比較

アクセスモード略称説明代表的な使用例
ReadWriteOnceRWO単一ノードで読み書きマウントデータベース、単一インスタンスアプリ
ReadOnlyManyROX複数ノードで読み取り専用マウント静的アセット、設定ファイル共有
ReadWriteManyRWX複数ノードで読み書きマウント共有ファイルシステム、CMSアップロード
ReadWriteOncePodRWOP単一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つの主要コンポーネントで構成される。

  1. Controller Plugin (Deployment): ボリュームの作成・削除・拡張・スナップショットなどクラスタレベルの操作を処理する。External Provisioner、External Attacher、External Snapshotterなどのサイドカーコンテナと共に実行される。
  2. Node Plugin (DaemonSet): 各ワーカーノードで実行され、ボリュームのマウント・アンマウント、フォーマット、デバイスパス管理を担当する。kubeletと直接通信する。

CSI Driver比較

項目AWS EBS CSIAWS EFS CSICeph CSI (Rook)Longhorn
ストレージ種別ブロック (Block)ファイル (NFS)ブロック + ファイル + オブジェクトブロック (Replicated)
アクセスモードRWO, RWOPRWX, ROX, RWORWO, RWX, ROXRWO, RWX
動的プロビジョニング対応対応 (Access Point)対応対応
ボリューム拡張対応 (オンライン)該当なし (弾力的)対応対応
スナップショット対応非対応対応対応
暗号化KMS対応転送中暗号化対応 (LUKS)対応
トポロジー認識AZ単位リージョン単位CRUSH Mapノード単位
適した環境AWS EKSAWS 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": セキュリティコンプライアンスのために全ボリュームに暗号化を適用する。

動的プロビジョニングのフロー

動的プロビジョニングの全体フローは以下の通りである。

  1. ユーザーがPVCを作成すると、StorageClassのprovisionerフィールドに指定されたCSI Driverが呼び出される。
  2. CSI Controller PluginのCreateVolume RPCが実行され、クラウドAPIを通じて実際のボリュームが作成される。
  3. PVオブジェクトが自動作成されPVCにバインドされる。
  4. Podが該当PVCを参照してスケジュールされると、CSI Controller PluginのControllerPublishVolume RPCが呼び出されボリュームがノードにAttachされる。
  5. 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のdeletionPolicyRetainに設定すると、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

原因と解決策:

  1. StorageClassが存在しない場合: PVCに指定したstorageClassNameがクラスタに存在しない。kubectl get storageclassで確認する。
  2. WaitForFirstConsumerモード: volumeBindingModeがWaitForFirstConsumerの場合、PVCを使用するPodが作成されるまでPending状態は正常である。
  3. CSI Driver未インストール: プロビジョナーとして指定したCSI Driverがインストールされていない。kubectl get csidriverで確認する。
  4. リソースクォータ超過: ネームスペースにResourceQuotaが設定されておりストレージ上限を超えた場合である。
  5. 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

復旧手順:

  1. CSI Driverを再デプロイする。HelmまたはEKS Addonで再インストールする。
  2. 固着したVolumeAttachmentがあれば手動でクリーンアップする。
  3. Podを削除してkubeletにボリュームマウントを再試行させる。
  4. 最悪の場合、ノード自体を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がどのように相互作用するかを深く理解する必要がある。単にマニフェストをコピーして適用するのではなく、各コンポーネントの動作原理を把握し、障害シナリオを事前に準備することが重要である。

特に本番環境では以下の原則を守るべきである。

  1. WaitForFirstConsumerをデフォルトで使用してAZ不一致問題を根本的に防止する。
  2. ボリューム暗号化をすべてのStorageClassに適用する。
  3. スナップショットベースのバックアップを定期的に実行し、復元テストも必ず含める。
  4. モニタリングを通じてディスク使用率、IOPS、レイテンシーをリアルタイムで追跡する。
  5. reclaimPolicyをデータの重要度に合わせて設定し、意図しないデータ削除を防止する。

CSI Driverエコシステムは引き続き進化している。Kubernetes 1.29でGAになったReadWriteOncePodアクセスモード、Volume Group Snapshot機能、SELinuxマウントオプション対応など、新機能が継続的に追加されている。リリースノートを継続的に確認し、ストレージ運用戦略をアップデートしていく必要がある。

参考資料