はじめに
前回は、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 について整理します。