KubernetesにおけるStatefulSetsと永続ストレージ

順序付きスケーリングと永続的なデータを使用してステートフルなアプリを展開する

Kubernetes StatefulSets は、安定したアイデンティティ、永続的なストレージ、および順序付きデプロイメントパターンを必要とするステートフルなアプリケーションを管理するための最適なソリューションです。データベース、分散システム、キャッシュレイヤーなどに不可欠です。

Kubernetes に初めて触れるか、クラスターを設定している場合は、開発用に k3s や MicroK8s のような Kubernetes distributions を検討し、本番環境では Kubespray で Kubernetes をインストール することをおすすめします。

presentation in the coffee shop この素晴らしい画像は AI model Flux 1 dev によって生成されました。

StatefulSets とは

StatefulSets は、ステートフルなアプリケーションを管理するために特別に設計された Kubernetes のワークロード API オブジェクトです。すべてのポッドを交換可能と見なす Deployments とは異なり、StatefulSets はポッドごとに一意のアイデンティティを維持し、順序と一意性に関する保証を提供します。

主な特徴:

  • 安定したネットワーク識別子: 各ポッドは再起動しても変化しない予測可能なホスト名を取得します
  • 永続ストレージ: ポッドが再スケジュールされても続く PersistentVolumeClaims が提供されます
  • 順序付きデプロイメント: ポッドは順序に従って作成され(0, 1, 2…)、終了は逆順で行われます
  • 順序付き更新: ローリング更新は順序に従って行われ、アプリケーションの安定性を確保します

PostgreSQL、MySQL、MongoDB、Cassandra、Elasticsearch、Kafka、ZooKeeper、Redis、etcd など、ポッドのアイデンティティとデータの永続性が重要なワークロードでは、StatefulSets が不可欠です。

Kubernetes における永続ストレージの理解

Kubernetes は、ポッドライフサイクルからストレージ管理を分離する高度なストレージ抽象化レイヤーを提供しています。

ストレージコンポーネント

PersistentVolume (PV): 管理者によってクラスター内でプロビジョニングされるストレージの一部、または StorageClass を介して動的に作成されるストレージ。PV はポッドとは独立して存在します。

PersistentVolumeClaim (PVC): ポッドによるストレージの要求。PVC はサイズ、アクセスモード、ストレージクラスなどの要件に合致する利用可能な PV にバインドされます。

StorageClass: AWS EBS、GCE PD、Azure Disk、NFS などのさまざまなプロビジョナーと、レプリケーション、パフォーマンス階層、バックアップポリシーなどのパラメータを定義するストレージの「クラス」を定義します。

アクセスモード

  • ReadWriteOnce (RWO): 単一ノードで読み書き可能なボリューム
  • ReadOnlyMany (ROX): 複数ノードで読み取り専用のボリューム
  • ReadWriteMany (RWX): 複数ノードで読み書き可能なボリューム(特別なストレージバックエンドが必要)

StatefulSet のストレージアーキテクチャ

StatefulSets は volumeClaimTemplates を使用して、各ポッドレプリカに対して自動的に PersistentVolumeClaims を作成します。これは Deployments とは根本的に異なります。

volumeClaimTemplates の動作方法

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-service
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "fast-ssd"
      resources:
        requests:
          storage: 10Gi

この StatefulSet を作成すると:

  1. Kubernetes はポッド mysql-0 と PVC data-mysql-0 を作成します
  2. 次にポッド mysql-1 と PVC data-mysql-1 を作成します
  3. 最後にポッド mysql-2 と PVC data-mysql-2 を作成します

各ポッドは独自の 10GB の永続ボリュームを取得します。mysql-1 が削除または再スケジュールされた場合、Kubernetes は同じ data-mysql-1 PVC を再びアタッチし、すべてのデータを保持します。

StatefulSets 用のヘッドレスサービスの作成

StatefulSets は安定したネットワークアイデンティティを提供するためにヘッドレスサービスを必要とします:

apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  clusterIP: None  # これによりヘッドレスサービスになります
  selector:
    app: mysql
  ports:
  - port: 3306
    name: mysql

これにより、各ポッドに対して DNS エントリが作成されます:

  • mysql-0.mysql-service.default.svc.cluster.local
  • mysql-1.mysql-service.default.svc.cluster.local
  • mysql-2.mysql-service.default.svc.cluster.local

アプリケーションはこれらの安定した DNS 名を使用して、特定のポッドインスタンスに直接接続できます。

StatefulSet のデプロイメントパターン

複雑な Kubernetes デプロイメントを管理するチームにとって、Helm Charts は、テンプレート、バージョン管理、依存関係管理を使用して StatefulSets をパッケージ化およびデプロイする強力な方法を提供します。Helm は、さまざまな環境における StatefulSet の構成を管理することを簡略化します。

順序付きスケーリング

3 から 5 レプリカにスケーリングする場合:

kubectl scale statefulset mysql --replicas=5

Kubernetes は順序に従ってポッドを作成します: mysql-3 → Ready になるまで待機 → mysql-4

5 から 3 レプリカにスケーリングする場合:

kubectl scale statefulset mysql --replicas=3

Kubernetes は逆順で終了します: mysql-4 → 終了まで待機 → mysql-3

ローリング更新

StatefulSets は 2 つの更新戦略をサポートします:

OnDelete: 手動更新 — ポッドは削除するまで更新されません RollingUpdate: 逆順のオーディナル順で自動的に順序付き更新

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2  # 2 以上のオーディナルを持つポッドのみを更新

partition パラメータはカナリーデプロイメントを可能にします — 高番号のポッドを最初に更新し、すべてのレプリカに展開する前にテストできます。

ストレージのベストプラクティス

動的プロビジョニング

常に StorageClasses を使用してボリュームの動的プロビジョニングを行ってください:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
allowVolumeExpansion: true
reclaimPolicy: Retain

allowVolumeExpansion: PVC を再作成せずにサイズ変更を可能にします reclaimPolicy: Retain は PVC 削除後も PV データを保持し、Delete は自動的に削除します

PVC 保留ポリシー

Kubernetes 1.23+ は persistentVolumeClaimRetentionPolicy をサポートしています:

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain    # StatefulSet が削除されたときに PVC を保持
    whenScaled: Delete     # スケーリングダウン時に PVC を削除

オプション:

  • Retain: PVC を保持(デフォルトの動作、最も安全)
  • Delete: 自動的に PVC を削除(開発環境に適しています)

バックアップ戦略

ボリュームスナップショット: VolumeSnapshot リソースを使用してポイントインタイムバックアップを作成します

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: mysql-snapshot-20251113
spec:
  volumeSnapshotClassName: csi-snapclass
  source:
    persistentVolumeClaimName: data-mysql-0

アプリケーションレベルのバックアップ: mysqldumppg_dump、または Velero を使用してデータベース固有のバックアップを実施します

分散レプリケーション: MySQL レプリケーションや PostgreSQL ストリーミングレプリケーションなどのアプリケーションレベルのレプリケーションを最初の防御手段として構成します

実際のユースケース

データベースクラスター (PostgreSQL)

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "standard"
      resources:
        requests:
          storage: 20Gi

分散キャッシュ (Redis)

Redis クラスターでは、StatefulSet と慎重な設定が必要です:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis-service
  replicas: 6
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        command:
        - redis-server
        - "--appendonly"
        - "yes"
        - "--appendfsync"
        - "everysec"
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 5Gi

メッセージキュー (Kafka)

Kafka は、ログの永続ストレージとブローカー間の安定したネットワークアイデンティティの両方を必要とします:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kafka
spec:
  serviceName: kafka-service
  replicas: 3
  selector:
    matchLabels:
      app: kafka
  template:
    metadata:
      labels:
        app: kafka
    spec:
      containers:
      - name: kafka
        image: confluentinc/cp-kafka:7.5.0
        ports:
        - containerPort: 9092
          name: kafka
        env:
        - name: KAFKA_BROKER_ID
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        volumeMounts:
        - name: data
          mountPath: /var/lib/kafka/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 100Gi

モニタリングとトラブルシューティング

このセクションで使用される Kubernetes コマンドの包括的なリファレンスについては、Kubernetes Cheatsheet をご参照ください。

StatefulSet のステータスを確認する

# StatefulSet の詳細を表示
kubectl get statefulset mysql
kubectl describe statefulset mysql

# ポッドの作成順とステータスを確認
kubectl get pods -l app=mysql -w

# PVC のステータスを確認
kubectl get pvc
kubectl describe pvc data-mysql-0

一般的な問題

ポッドがPending状態にとどまる: PVC のステータスとストレージの可用性を確認してください

kubectl describe pod mysql-0
kubectl get events --sort-by='.lastTimestamp'

ストレージがいっぱい: StorageClass が許可する場合、PVC を拡張してください

kubectl patch pvc data-mysql-0 -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

ポッドが終了しない: アプリケーションレベルのロックやボリュームのアンマウント問題を確認してください

kubectl delete pod mysql-0 --grace-period=0 --force

モニタリングするメトリクス

  • ストレージ使用量: PVC の容量と使用率パーセンテージをモニタリング
  • I/O パフォーマンス: IOPS、スループット、レイテンシーを追跡
  • ポッド再起動: 頻繁な再起動はストレージの問題を示唆
  • PVC バインディング時間: 慢性的なバインディングはプロビジョニングの問題を示唆

マイグレーション戦略

StatefulSets に移行する際には、Kubernetes クラスターが適切に構成されていることを確認してください。ホームラボや小規模クラスターの設定では、Kubernetes distributions の包括的な比較 をご確認いただき、ワークロード要件に合ったプラットフォームを選択してください。

Deployment から StatefulSet への移行

  1. volumeClaimTemplates 付き StatefulSet を作成
  2. Deployment をグラフィカルにスケーリングダウン
  3. バックアップから StatefulSet ポッドにデータを復元
  4. DNS/Service の参照を更新
  5. 古い Deployment と PVC を削除

移行前のバックアップ

# 既存の PVC をスナップショット
kubectl get pvc -o yaml > pvc-backup.yaml

# ボリュームスナップショットを作成
kubectl apply -f volume-snapshot.yaml

セキュリティの考慮事項

ストレージ暗号化

StorageClass パラメータを使用して暗号化を有効にします:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: encrypted-storage
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  encrypted: "true"
  kmsKeyId: arn:aws:kms:us-east-1:123456789012:key/abcd-1234

アクセス制御

RBAC を使用して、StatefulSets と PVC の作成/変更を誰が行えるかを制限します:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: statefulset-manager
rules:
- apiGroups: ["apps"]
  resources: ["statefulsets"]
  verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: [""]
  resources: ["persistentvolumeclaims"]
  verbs: ["get", "list", "create", "delete"]

ネットワークポリシー

ポッド間の通信を制限します:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mysql-network-policy
spec:
  podSelector:
    matchLabels:
      app: mysql
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: backend
    ports:
    - protocol: TCP
      port: 3306

パフォーマンス最適化

ストレージパフォーマンス階層

ワークロードに応じて適切な StorageClasses を選択してください:

  • 高IOPS: 重いランダム読み書きを持つデータベース(gp3、io2)
  • 高スループット: ログ集約、分析(st1、sc1)
  • バランス: 一般的な用途(gp3)

ポッドの分散

可用性ゾーンにわたって StatefulSet ポッドを分散させるために pod anti-affinity を使用します:

spec:
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: mysql
            topologyKey: topology.kubernetes.io/zone

リソースリクエストと制限

一貫したパフォーマンスのために適切なリソースを設定します:

resources:
  requests:
    cpu: "2"
    memory: "4Gi"
    ephemeral-storage: "10Gi"
  limits:
    cpu: "4"
    memory: "8Gi"
    ephemeral-storage: "20Gi"

高度なパターン

初期コンテナ付きのステートフルアプリケーション

データベースの初期化に初期コンテナを使用します:

spec:
  template:
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:8.0
        command:
        - bash
        - "-c"
        - |
          if [[ ! -d /var/lib/mysql/mysql ]]; then
            mysqld --initialize-insecure --datadir=/var/lib/mysql
          fi          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql

サイドカー付きのマルチコンテナポッド

バックアップサイドカーまたはモニタリングエージェントを追加します:

spec:
  template:
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        # ... mysql config ...
      - name: backup-sidecar
        image: mysql-backup:latest
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          readOnly: true

ダイナミック構成用の ConfigMaps

StatefulSet 定義から構成を分離します:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  my.cnf: |
    [mysqld]
    max_connections=200
    innodb_buffer_pool_size=2G    
---
spec:
  template:
    spec:
      containers:
      - name: mysql
        volumeMounts:
        - name: config
          mountPath: /etc/mysql/conf.d
      volumes:
      - name: config
        configMap:
          name: mysql-config

有用なリンク