0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KubernetesのStatefulSetを理解する

0
Posted at

はじめに

前回は、Volume・PersistentVolume・PersistentVolumeClaimについて学びました。

Container
    ↓
Volume
    ↓
PVC
    ↓
PV
    ↓
EBS・EFSなどの実ストレージ

PVCを利用すると、Podが再作成されてもデータを残せます。

では、次のようなアプリケーションを複数Podで動かす場合はどうでしょうか。

データベース
メッセージブローカー
分散システム
Podごとに専用データを持つアプリケーション

これらは、すべてのPodを同じものとして扱えない場合があります。

Pod 0はVolume 0を使う
Pod 1はVolume 1を使う
Pod 2はVolume 2を使う

Podが再作成されても、Pod 0には再びVolume 0を割り当てる必要があります。

また、ほかのPodから特定のPodを名前で見つけたい場合もあります。

そこで登場するのが StatefulSet(ステートフルセット) です。

この記事では、次の内容を整理します。

  • StatefulSetとは何か
  • Deploymentとの違い
  • Pod名に連番が付く理由
  • 安定したネットワークIDとは何か
  • Headless Serviceとの関係
  • volumeClaimTemplatesの役割
  • PodとPVCがどのように対応するか
  • Podの作成・削除順序
  • StatefulSetの更新方法
  • StatefulSetを削除したときPVCはどうなるか
  • EKS・EBSで利用するときの注意点
  • Rails・Sidekiq・MySQLをどのControllerで動かすか

結論

StatefulSetとは、Podごとに安定した名前・ネットワークID・永続ストレージを持たせながら、複数のPodを管理するKubernetesのWorkloadリソースです。

StatefulSet: mysql
├── mysql-0 → data-mysql-0 → PV 0
├── mysql-1 → data-mysql-1 → PV 1
└── mysql-2 → data-mysql-2 → PV 2

mysql-1が故障して作り直されても、新しいPodは再びmysql-1という名前になり、data-mysql-1を使います。

古いmysql-1が停止
        ↓
新しいmysql-1を作成
        ↓
以前のdata-mysql-1へ再接続

DeploymentではPodを基本的に交換可能なものとして扱います。

StatefulSetでは、それぞれのPodが固有の身元を持ちます。

Statefulとは何か

Statefulは、アプリケーションが状態を持つことを表します。

状態の例は次のとおりです。

  • データベースへ保存されたデータ
  • メッセージキューのデータ
  • ノードごとの役割
  • クラスターを構成するメンバー情報
  • Podごとに異なる永続ファイル

反対に、状態をPodの外へ分離し、どのPodが処理しても同じように動くものをStatelessと呼びます。

Stateless
どのPodも交換可能

Stateful
Podごとの名前・データ・役割が重要

ただし、StatefulSetを使うだけで、アプリケーションが自動的にStatefulになるわけではありません。

データの複製・リーダー選出・バックアップ・障害復旧などは、アプリケーション側の仕組みも必要です。

Deploymentでは何が足りないのか

Deploymentは、RailsやWeb APIなど、交換可能なPodを管理する用途に向いています。

Deployment: rails
├── rails-7bd8d7f9cd-a1b2c
├── rails-7bd8d7f9cd-d3e4f
└── rails-7bd8d7f9cd-g5h6i

1つのPodが故障すると、別の名前を持つ新しいPodが作られます。

rails-7bd8d7f9cd-a1b2c
        ↓ 削除
rails-7bd8d7f9cd-z9y8x
        ↓ 新規作成

Rails Podが次の条件を満たしていれば、名前が変わっても問題ありません。

  • セッションを外部へ保存している
  • データベースをRDSなどへ分離している
  • アップロードファイルをS3へ保存している
  • キャッシュやJobをElastiCacheへ分離している

どのRails Podへリクエストが届いても、同じように処理できます。

一方、データベースの各Podが専用Volumeを持つ場合、PodとVolumeの対応を維持する必要があります。

DB Pod A → Volume A
DB Pod B → Volume B
DB Pod C → Volume C

このような固有の身元を管理するのがStatefulSetです。

DeploymentとStatefulSetの違い

項目 Deployment StatefulSet
Podの考え方 交換可能 Podごとに固有の身元を持つ
Pod名 ランダムな文字列を含む 連番で固定される
例 rails-7bd8d7f9cd-a1b2c mysql-0
ストレージ 同じPVCを参照する構成など PodごとのPVCを自動作成できる
作成順序 基本的に順序を保証しない 標準では順番に作成する
削除順序 基本的に順序を保証しない 標準では逆順に削除する
ネットワークID 個々のPod名を前提にしない Podごとの安定したDNS名を持てる
主な用途 Rails、API、Web、Worker DB、Kafka、ZooKeeperなど

アプリケーションが安定した名前・個別Volume・順序を必要としないなら、Deploymentの方がシンプルです。

StatefulSetが提供する3つの身元

StatefulSetのPodは、主に次の身元を持ちます。

1. Ordinal
   Podごとの連番

2. Network Identity
   安定したホスト名・DNS名

3. Storage Identity
   Podごとに対応する永続ストレージ

これらの対応がPodの再作成後も維持されます。

mysql-0
├── DNS:mysql-0.mysql.default.svc.cluster.local
└── PVC:data-mysql-0

Ordinalとは

Ordinalは、StatefulSetの各Podへ付く順序番号です。

StatefulSet名がmysqlで、Replica数が3の場合、Pod名は次のようになります。

mysql-0
mysql-1
mysql-2

標準では0から始まり、replicas - 1まで割り当てられます。

replicas: 3
        ↓
0・1・2

Podが別のNodeへ再配置されても、同じOrdinalを持つPodとして作られます。

Node Aのmysql-1が停止
        ↓
Node Cへmysql-1を再作成

Nodeは変わっても、Podの身元はmysql-1のままです。

StatefulSetを定義するYAML

次は、3つのPodとPodごとのPVCを作成する基本例です。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.4
          ports:
            - name: mysql
              containerPort: 3306
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-secret
                  key: root-password
          volumeMounts:
            - name: data
              mountPath: /var/lib/mysql
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes:
          - ReadWriteOnce
        storageClassName: gp3
        resources:
          requests:
            storage: 20Gi

このYAMLだけでは、StatefulSetが必要とするHeadless Serviceがありません。

後ほどServiceも作成します。

また、このMySQL例はStatefulSetの構造を理解するための簡略例です。

複数MySQL Podを並べただけでは、データのレプリケーションやPrimary・Replica構成は作られません。

YAMLの各項目

apiVersionとkind

apiVersion: apps/v1
kind: StatefulSet
項目 意味
apiVersion: apps/v1 StatefulSetを扱うKubernetes APIのバージョン
kind: StatefulSet 作成するリソースがStatefulSetであることを示す

metadata.name

metadata:
  name: mysql

StatefulSetの名前です。

この名前がPod名の先頭に使われます。

mysql-0
mysql-1
mysql-2

serviceName

spec:
  serviceName: mysql

PodのネットワークIDを管理するHeadless Serviceの名前です。

この例では、mysqlというHeadless Serviceを別途作成します。

StatefulSet.spec.serviceName
        ↓
Headless Service.metadata.name

replicas

replicas: 3

維持するPod数です。

mysql-0
mysql-1
mysql-2

selectorとlabels

selector:
  matchLabels:
    app: mysql
template:
  metadata:
    labels:
      app: mysql

StatefulSetが管理するPodをラベルで特定します。

selector.matchLabelsとtemplate.metadata.labelsは一致させます。

template

template:
  metadata:
    labels:
      app: mysql
  spec:
    containers:
      - name: mysql
        image: mysql:8.4

作成するPodのテンプレートです。

DeploymentのPodテンプレートと同様に、Container・環境変数・Port・Probeなどを定義します。

volumeClaimTemplates

volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes:
        - ReadWriteOnce
      storageClassName: gp3
      resources:
        requests:
          storage: 20Gi

PodごとにPVCを作るためのテンプレートです。

StatefulSetの各Podへ、同じ条件の別々のPVCが作成されます。

mysql-0 → data-mysql-0
mysql-1 → data-mysql-1
mysql-2 → data-mysql-2

volumeMounts[].nameと、volumeClaimTemplates[].metadata.nameを一致させます。

volumeMounts.name: data
        ↓ 一致
volumeClaimTemplates.metadata.name: data

volumeClaimTemplatesで何が作られるのか

StatefulSetを作成すると、PodごとにPVCが作成されます。

StatefulSet: mysql
replicas: 3
        ↓
Pod
├── mysql-0
├── mysql-1
└── mysql-2

PVC
├── data-mysql-0
├── data-mysql-1
└── data-mysql-2

StorageClassによる動的プロビジョニングを使う場合、それぞれのPVCに対応するPVと実ストレージも作成されます。

data-mysql-0 → PV 0 → EBS 0
data-mysql-1 → PV 1 → EBS 1
data-mysql-2 → PV 2 → EBS 2

それぞれのPodが、自分専用のVolumeを利用します。

Podを削除するとどうなるのか

mysql-1を手動で削除したとします。

kubectl delete pod mysql-1

StatefulSetは、Replica数を維持するために新しいmysql-1を作成します。

古いmysql-1
        ↓ 削除
PVC data-mysql-1は残る
        ↓
新しいmysql-1
        ↓
data-mysql-1を再利用

新しいランダム名のPodとして作るのではなく、同じOrdinalを持つPodを復元します。

そのため、Pod名とストレージの対応が維持されます。

Headless Serviceとは

通常のClusterIP Serviceには、仮想IPアドレスが割り当てられます。

Client
    ↓ ServiceのClusterIP
Service
    ↓ 負荷分散
Pod A・Pod B・Pod C

ClientはServiceへ接続し、どのPodへ届くかを意識しません。

一方、StatefulSetでは、特定のPodへ直接接続したい場合があります。

mysql-0へ接続したい
mysql-1へ接続したい

Headless Serviceは、ClusterIPを持たず、個々のPodをDNSから見つけられるようにするServiceです。

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  clusterIP: None
  selector:
    app: mysql
  ports:
    - name: mysql
      port: 3306
      targetPort: 3306

ポイントは次の部分です。

clusterIP: None

これにより、Service用の仮想IPは割り当てられません。

KubernetesのDNSは、条件に一致する各PodのIPアドレスを返せるようになります。

Headless Service自体は、通常のClusterIP Serviceのような負荷分散を行いません。

StatefulSetの安定したDNS名

StatefulSet名がmysql、Headless Service名がmysql、Namespaceがproductionの場合、各Podは次のようなDNS名を持ちます。

mysql-0.mysql.production.svc.cluster.local
mysql-1.mysql.production.svc.cluster.local
mysql-2.mysql.production.svc.cluster.local

形式は次のとおりです。

<Pod名>.<Service名>.<Namespace>.svc.cluster.local

同じNamespace内では、短い名前で接続できる場合もあります。

mysql-0.mysql
mysql-1.mysql
mysql-2.mysql

PodのIPアドレスが変わっても、同じPod名に対応するDNS名を使えます。

古いmysql-1:10.0.1.15
        ↓ 再作成
新しいmysql-1:10.0.2.28

DNS名:mysql-1.mysql
        ↓ 同じ名前を利用できる

Headless Serviceと通常のServiceは併用できる

Headless Serviceは、PodごとのDNS名を提供するために利用します。

アプリケーションから複数Podへ通常のService経由で接続したい場合は、別のClusterIP Serviceも作成できます。

Headless Service
└── Podごとの発見に使う
    ├── mysql-0.mysql
    ├── mysql-1.mysql
    └── mysql-2.mysql

ClusterIP Service
└── 通常の接続先として使う
    └── mysql-client.production.svc.cluster.local

分散システム内部のメンバー発見にはHeadless Service、Clientからの一般的な接続には通常のService、というように役割を分けられます。

Podの作成順序

StatefulSetは標準でOrderedReadyというPod管理方式を使います。

Replica数が3の場合、次の順序で作成します。

1. mysql-0を作成
2. mysql-0がReadyになるまで待つ
3. mysql-1を作成
4. mysql-1がReadyになるまで待つ
5. mysql-2を作成

前のPodがReadyにならないと、次のPodを作成しません。

mysql-0:Ready
        ↓
mysql-1:Ready
        ↓
mysql-2:Ready

起動順序が重要なシステムで利用できます。

そのため、Readiness Probeが正しく設定されていないと、後続Podが作成されないことがあります。

Podの削除・スケールダウン順序

標準では、番号の大きいPodから逆順に停止します。

mysql-2を停止
        ↓
mysql-1を停止
        ↓
mysql-0を停止

replicas: 3からreplicas: 1へ変更した場合、mysql-2、mysql-1の順に削除されます。

変更前
mysql-0・mysql-1・mysql-2

変更後
mysql-0

Parallelによる同時作成

Podの起動順序が不要な場合、podManagementPolicy: Parallelを指定できます。

spec:
  podManagementPolicy: Parallel

これにより、Podを順番に待たず作成・削除できます。

mysql-0 ─┐
mysql-1 ─┼→ 並行して作成
mysql-2 ─┘

ただし、Pod固有の名前やストレージは維持されます。

順序を緩めても問題ないアプリケーションでのみ使用します。

StatefulSetをスケールする

Replica数を変更します。

kubectl scale statefulset mysql --replicas=5

標準のOrderedReadyでは、次のPodが順番に追加されます。

mysql-3
        ↓ Ready
mysql-4

volumeClaimTemplatesを利用していれば、対応するPVCも作成されます。

mysql-3 → data-mysql-3
mysql-4 → data-mysql-4

Replica数を減らした場合、Podは削除されますが、対応するPVCは標準では残ります。

スケールダウンしてもPVCが残る理由

たとえば、Replica数を3から1へ減らします。

削除されるPod
├── mysql-2
└── mysql-1

残るPVC
├── data-mysql-2
└── data-mysql-1

PVCを自動削除すると、操作ミスによって重要なデータまで失う危険があります。

そのため、StatefulSetは標準では関連Volumeを自動削除しません。

後からReplica数を3へ戻すと、同じOrdinalに対応するPVCを再利用できます。

replicas: 1
        ↓ 3へ戻す
mysql-1 → data-mysql-1を再利用
mysql-2 → data-mysql-2を再利用

不要になったPVCや実ストレージは、データを確認してから明示的に削除します。

PVC Retention Policy

要件によっては、StatefulSetの削除時やスケールダウン時にPVCを削除する設定もできます。

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain
    whenScaled: Retain

指定できる主な値は次のとおりです。

項目 タイミング
whenDeleted StatefulSet自体を削除したとき
whenScaled Replica数を減らしたとき
値 動作
Retain PVCを残す
Delete 対象のPVCを削除する

標準動作はRetainです。

Deleteを選ぶと自動化できますが、PVのReclaim PolicyがDeleteの場合は実ストレージまで削除される可能性があります。

本番データでは、Snapshot・バックアップ・復元手順を確認してから設定します。

StatefulSetを更新する

Container ImageなどPodテンプレートを変更すると、標準ではRollingUpdateが行われます。

spec:
  updateStrategy:
    type: RollingUpdate

StatefulSetは、番号の大きいPodから順番に更新します。

mysql-2を更新
        ↓ Readyを待つ
mysql-1を更新
        ↓ Readyを待つ
mysql-0を更新

1つのPodがReadyにならない場合、後続の更新が止まります。

状態を確認します。

kubectl rollout status statefulset/mysql

更新履歴を確認します。

kubectl rollout history statefulset/mysql

partitionを使った段階的更新

rollingUpdate.partitionを指定すると、指定したOrdinal以上のPodだけを更新できます。

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2

Replica数が3の場合、まずmysql-2だけが更新対象になります。

mysql-0:旧バージョン
mysql-1:旧バージョン
mysql-2:新バージョン

新しいバージョンを一部のPodで確認してから、Partitionを下げて更新範囲を広げられます。

partition: 2
        ↓ 動作確認
partition: 1
        ↓ 動作確認
partition: 0

OnDelete更新方式

OnDeleteを指定した場合、Podテンプレートを更新しても既存Podは自動更新されません。

spec:
  updateStrategy:
    type: OnDelete

利用者がPodを削除すると、新しいPodテンプレートを使って再作成されます。

StatefulSetのYAMLを更新
        ↓
既存Podはそのまま
        ↓
mysql-2を手動削除
        ↓
新設定でmysql-2を再作成

更新タイミングを厳密に制御したい場合に利用できますが、手動運用が増えます。

StatefulSetを作成する

Headless ServiceとStatefulSetをそれぞれ保存した場合、次のように適用します。

kubectl apply -f headless-service.yaml
kubectl apply -f statefulset.yaml

StatefulSetを確認します。

kubectl get statefulset

短縮形も利用できます。

kubectl get sts

Podを確認します。

kubectl get pods

PVCを確認します。

kubectl get pvc

Headless Serviceを確認します。

kubectl get service mysql

詳細やイベントを確認します。

kubectl describe statefulset mysql

PodとPVCの対応を確認する

作成されたPodを確認します。

kubectl get pods -l app=mysql

出力例です。

NAME      READY   STATUS
mysql-0   1/1     Running
mysql-1   1/1     Running
mysql-2   1/1     Running

PVCを確認します。

kubectl get pvc

出力例です。

NAME           STATUS   CAPACITY
data-mysql-0   Bound    20Gi
data-mysql-1   Bound    20Gi
data-mysql-2   Bound    20Gi

特定のPodがマウントするPVCを確認します。

kubectl describe pod mysql-1

DNS名を確認する

クラスター内のPodから名前解決を確認します。

nslookup mysql-0.mysql

各Podの完全なDNS名は次の形式です。

<Pod名>.<Headless Service名>.<Namespace>.svc.cluster.local

DNS名が解決できない場合は、次を確認します。

  • Headless Serviceが存在するか
  • clusterIP: Noneになっているか
  • StatefulSetのserviceNameとService名が一致しているか
  • ServiceのSelectorとPodのLabelが一致しているか
  • PodがReadyか
  • CoreDNSが正常に動いているか

StatefulSetを削除するとどうなるのか

StatefulSetを削除すると、管理しているPodは削除されます。

kubectl delete statefulset mysql

しかし、標準ではvolumeClaimTemplatesから作成されたPVCは残ります。

StatefulSetを削除
        ↓
Podを削除
        ↓
PVCは残る
        ↓
PV・実ストレージも設定に応じて残る

StatefulSetを削除する前に、停止順序を重視する場合はReplica数を0へ下げる方法があります。

kubectl scale statefulset mysql --replicas=0

Podの停止とデータの整合性については、アプリケーション固有の手順も確認します。

StatefulSetを削除して作り直す場合

同じ名前・同じvolumeClaimTemplates名でStatefulSetを作り直すと、残っているPVCを再利用できる構成があります。

以前
mysql-0 → data-mysql-0

StatefulSetを削除
PVC data-mysql-0は残る

同じStatefulSetを再作成
mysql-0 → data-mysql-0を再利用

ただし、StorageClass・PVC・PV・Label・Reclaim Policyなどの状態を事前に確認します。

誤った名前や設定で別のVolumeを作成しないように注意します。

EKSとEBSで利用するときの流れ

EKSでvolumeClaimTemplatesとEBSを使う場合、次の流れになります。

StatefulSet
        ↓ PodごとのPVCを作成
PVC
        ↓ StorageClassを参照
StorageClass
        ↓
Amazon EBS CSI Driver
        ↓ AWS API
PodごとのEBS Volumeを作成
        ↓
EBSをNodeへ接続
        ↓
各Podへマウント

Replica数が3なら、通常はPodごとに3つのPVC・PV・EBS Volumeが作られます。

mysql-0 → PVC 0 → PV 0 → EBS 0
mysql-1 → PVC 1 → PV 1 → EBS 1
mysql-2 → PVC 2 → PV 2 → EBS 2

EBSとAZの注意点

EBS Volumeは1つのAvailability Zoneに属します。

AZ-a
├── mysql-0
└── EBS 0

AZ-c
├── mysql-1
└── EBS 1

mysql-0が別のNodeへ再配置される場合も、EBS 0と同じAZのNodeへ配置する必要があります。

EBS 0:AZ-a
        ↓
mysql-0:AZ-aのNodeへ配置

AZ-a全体に障害が起きた場合、同じEBS VolumeをそのままAZ-cへ接続することはできません。

StatefulSetがPod名とVolumeの対応を維持しても、ストレージ自体の可用性やAZ障害対策まで自動的に解決するわけではありません。

次も別途設計します。

  • EBS Snapshot
  • 別AZ・別リージョンへの復旧方法
  • アプリケーション側のデータ複製
  • バックアップとリストア
  • Pod Disruption Budget
  • NodeやAZの分散配置

StatefulSetはデータを複製しない

StatefulSetでMySQL Podを3つ作っても、データが自動的に3つへ複製されるわけではありません。

StatefulSet
├── mysql-0 + EBS 0
├── mysql-1 + EBS 1
└── mysql-2 + EBS 2

× データは自動同期されない

StatefulSetが提供するのは、Podの身元・順序・ストレージとの対応です。

次の機能は、MySQLやOperatorなどアプリケーション側で構成します。

  • PrimaryとReplica
  • データレプリケーション
  • Leader Election
  • Failover
  • Quorum
  • Backup
  • Restore

「StatefulSetを使ったからデータベースが高可用になった」という理解は誤りです。

Operatorとの関係

データベースや分散システムには、製品固有の複雑な運用があります。

初期構築
レプリケーション
バージョンアップ
バックアップ
フェイルオーバー
障害復旧

Operatorは、Custom ResourceとControllerを使って、これらの製品固有の運用を自動化する仕組みです。

Operator
        ↓ 製品固有の運用を制御
StatefulSet・Service・Secret・PVCなど

StatefulSetは土台の1つであり、OperatorがStatefulSetなどを作成・管理する構成もあります。

Rails・Sidekiq・MySQLは何を使うのか

一般的な構成例は次のとおりです。

Workload 選択肢の例 理由
Rails・Puma Deployment Podを交換可能にしやすい
Sidekiq Deployment Workerを増減・交換しやすい
Nginx・Envoy DeploymentまたはDaemonSet 配置目的による
自前MySQL StatefulSetを検討 Podごとの名前・Volumeが必要
自前Redis・Valkey Cluster StatefulSetを検討 メンバーIDや永続Volumeが必要な場合がある

拓真さんのAIナビのようなRailsアプリケーションでは、次のように状態を外部サービスへ分離する方が運用しやすい構成です。

Rails・Puma
└── Deployment

Sidekiq
└── Deployment

MySQL
└── RDS・Aurora

Job・Cache
└── ElastiCache for Redis・Valkey

アップロードファイル
└── S3

RDS・AuroraやElastiCacheを利用する場合、データベース自体をEKSのStatefulSetとして運用する必要はありません。

マネージドサービス側が、バックアップ・パッチ・レプリケーションなどの多くを担当します。

StatefulSetは便利ですが、データベース運用の責任まで消してくれるものではありません。

StatefulSetが向いているもの

  • Podごとに専用の永続Volumeが必要
  • Podごとに安定した名前が必要
  • クラスター内のメンバーを個別のDNS名で見つけたい
  • 作成・更新・停止の順序が重要
  • アプリケーション自身がデータ複製やクラスタリングを行う
  • Kubernetes上でStatefulな製品を運用する必要がある

代表例として、次のような製品で利用されることがあります。

  • MySQL・PostgreSQL
  • Redis・Valkey
  • Kafka
  • ZooKeeper
  • Elasticsearch・OpenSearch
  • Cassandra

ただし、製品名だけでStatefulSetを選ぶのではなく、公式Operatorやマネージドサービスも含めて判断します。

StatefulSetが向いていないもの

  • すべてのPodを交換可能にできる
  • Pod固有の名前を利用しない
  • データをRDS・ElastiCache・S3などへ分離している
  • 起動や停止の順序が不要
  • Podごとの永続Volumeが不要

このような場合はDeploymentの方が適しています。

RailsをStatefulSetにすれば高性能・高可用になるわけではありません。

不要なStatefulSetは、PVCやPod IDの管理を複雑にします。

よくあるトラブル

mysql-0がReadyにならず、mysql-1が作られない

標準のOrderedReadyでは、前のPodがReadyになるまで次のPodを作成しません。

kubectl describe pod mysql-0
kubectl logs mysql-0

Readiness Probe・Volume Mount・Secret・アプリケーション起動エラーを確認します。

PVCがPendingのまま

StorageClassやEBS CSI Driverで問題が起きている可能性があります。

kubectl describe pvc data-mysql-0
kubectl get storageclass
kubectl get pods -n kube-system

次を確認します。

  • StorageClassが存在するか
  • Provisionerが正しいか
  • EBS CSI Driverが動いているか
  • IAM権限があるか
  • PodとEBSのAZを決められるか
  • 要求した容量やAccess Modeに対応できるか

StatefulSetを消したのにEBS料金が続いている

標準ではPVCが残るため、PVやEBS Volumeも残っている可能性があります。

kubectl get pvc
kubectl get pv

データ・Snapshot・Reclaim Policyを確認してから不要なリソースを削除します。

Podを作り直したら別の空Volumeになった

次を確認します。

  • StatefulSet名を変更していないか
  • volumeClaimTemplatesの名前を変更していないか
  • 以前のPVCが残っているか
  • PodのOrdinalが同じか
  • 新しいStorageClassへ意図せず切り替わっていないか

特定PodのDNS名を解決できない

Headless ServiceとStatefulSetの対応を確認します。

kubectl get service mysql -o yaml
kubectl get endpointslice
kubectl get pods -l app=mysql --show-labels

よくある勘違い

StatefulSetを使えばデータは自動的に複製される

StatefulSetは、Podとストレージの対応を維持します。

データ複製はアプリケーションやOperator側で構成します。

StatefulSetを使えば高可用になる

Podを再作成することはできますが、データのレプリケーションやAZ障害からの復旧まで自動的に保証しません。

StatefulSetはPVCそのものである

StatefulSetはPodを管理するWorkloadリソースです。

volumeClaimTemplatesを使ってPodごとのPVCを作成できます。

StatefulSetのPodは削除できない

削除できます。

StatefulSetが同じ名前・同じOrdinalのPodを再作成します。

StatefulSetを削除するとPVCも必ず消える

標準ではPVCは残ります。

自動削除する場合はRetention PolicyとPVのReclaim Policyを慎重に確認します。

Headless Serviceも負荷分散する

Headless ServiceにはClusterIPがなく、Kubernetesによる通常のService負荷分散を行いません。

DNSから個々のPodを発見するために利用します。

RailsもStatefulSetにした方がデータが安全になる

Rails Podの状態をRDS・ElastiCache・S3へ分離できているなら、Deploymentが適しています。

StatefulSetへ変更しても、外部データの安全性は高まりません。

これまでの流れへStatefulSetを加える

ここまで学んだWorkloadと周辺リソースを整理します。

Deployment
└── 交換可能なPodを管理する
    ├── Rails Pod
    └── Sidekiq Pod

StatefulSet
└── 固有の身元を持つPodを管理する
    ├── mysql-0 → PVC 0 → PV 0 → EBS 0
    ├── mysql-1 → PVC 1 → PV 1 → EBS 1
    └── mysql-2 → PVC 2 → PV 2 → EBS 2

Headless Service
└── StatefulSetの各Podへ安定したDNS名を提供する

StatefulSetを中心に見ると次の関係になります。

Headless Service
        ↓ PodごとのDNS名
StatefulSet
        ↓ Podを順序付きで管理
Pod
        ↓ PodごとのPVC
PVC
        ↓
PV
        ↓
EBSなどの実ストレージ

まとめ

StatefulSetは、Podごとに安定した名前・ネットワークID・永続ストレージを持たせるWorkloadリソースです。

StatefulSet
├── Stable Pod Name
├── Stable Network Identity
├── Stable Storage Identity
└── Ordered Operation

重要な点は次のとおりです。

  • StatefulSetのPodには0から始まるOrdinalが付く
  • Podを再作成しても同じ名前とOrdinalを引き継ぐ
  • 同じOrdinalのPodは以前のPVCを再利用する
  • volumeClaimTemplatesでPodごとのPVCを作成できる
  • Headless ServiceによってPodごとのDNS名を提供する
  • 標準ではPodをOrdinal順に作成し、逆順に削除する
  • RollingUpdateは番号の大きいPodから進む
  • スケールダウンやStatefulSet削除でPVCは標準では消えない
  • EBSはAZに属するためPodの再配置先に制約がある
  • StatefulSetはデータ複製やFailoverを自動構成しない
  • Rails・Sidekiqには通常Deploymentが向いている
  • RDS・ElastiCache・S3を使えばRailsの状態をPod外へ分離できる

一言でまとめると、次のようになります。

StatefulSetは、mysql-1を作り直しても、再びmysql-1として同じVolumeへ戻せるように、Podの身元を維持する仕組みです。

次は、すべてのNodeまたは条件に合うNodeへ1つずつPodを配置する DaemonSet について整理します。

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?