StatefulSet と PVC でデータを残す
v1.37 時点(2026年10月26日 確認)
目次
この記事では StatefulSet の Pod ごとにストレージの要求を作るフィールドである volumeClaimTemplates の仕組みを、図で説明します。Pod ごとに PersistentVolumeClaim(PVC)がどんな名前で作られ、Pod を消して作り直しても同じ PVC につなぎ直されるのはなぜかがわかります。StatefulSet を消したり数を減らしたりしたときに PVC がどうなるかと、それを変える persistentVolumeClaimRetentionPolicy もわかります。後半では kind のクラスタで StatefulSet を作り、Pod を消してもデータが残ることを確かめる手順を示します。
この回は 2 つの回を前提にしています。第14回では StatefulSet の Pod の番号付きの名前と順番を扱い、volumeClaimTemplates は「第28回で扱う」としていました。第27回では PersistentVolume、PVC、StorageClass と、kind の既定の StorageClass を扱いました。まだの方は先にこちらをどうぞ。
以下の図は後半の「手元で試す」で作る web という StatefulSet と、その Pod、Pod ごとの PVC、PVC に結び付いた PersistentVolume の関係を表しています。
StatefulSet の volumeClaimTemplates に www という名前のひな形を 1 つ書くと、Pod web-0 には PVC www-web-0、Pod web-1 には PVC www-web-1 が作られます。どちらの PVC も、第27回で見たとおり 1 つの PersistentVolume に 1 対 1 でバインドされます。破線は、第14回の図と同じく「作る・持つ」を表します。Pod へは「持つ」、PVC へは「ひな形から作る」と書き分けています。「ひな形から作る」は StatefulSet が PVC を作ることだけを表し、StatefulSet が PVC の持ち主になることは表しません(「persistentVolumeClaimRetentionPolicy」で説明します)。
参考:StatefulSets | Kubernetes、StatefulSet Basics | Kubernetes、Persistent Volumes | Kubernetes
volumeClaimTemplates とは
volumeClaimTemplates は StatefulSet の Pod ごとに PersistentVolumeClaim を作るためのひな形 です。StatefulSet の spec に書きます。公式ドキュメントによると、spec.volumeClaimTemplates を設定すると PVC を作れます。この設定は、次のどちらかのとき、StatefulSet に固定されたストレージ(stable storage)を提供します。
- PVC に指定した StorageClass が、動的なプロビジョニングを使うように設定されている
- PVC が求めたのと同じ StorageClass で、空きの容量が十分な PersistentVolume が、すでにクラスタにある
1 つ目は第27回で見た動的なプロビジョニングで、PVC に応じて PersistentVolume(以下、PV)が自動で用意されます。2 つ目は管理者が前もって PV を作っておく静的なプロビジョニングにあたります。公式ドキュメントは StatefulSet の制限(Limitations)としても、Pod のストレージは、求めた StorageClass に基づいて PV の provisioner(第27回で見た、PV を実際に作るプログラム)が用意するか、管理者が前もって用意するかの、どちらかでなければならない(must)としています。StatefulSet 自身がストレージを作るわけではなく、第27回の PV と PVC の仕組みの上に成り立っているということです。
公式ドキュメントのストレージの節(Stable Storage)によると、StatefulSet に書いた volumeClaimTemplates の項目ごとに、各 Pod は PVC を 1 つ受け取ります。ひな形が 1 つで Pod が 2 つなら、PVC は 2 つです。
v1.37 時点の API リファレンスは、volumeClaimTemplates を Pod が参照できる PVC の一覧だと説明しています。StatefulSet のコントローラーは、Pod の素性を保つように、ネットワーク上の識別子と PVC を対応付けます。上の図の例でいえば、web-0 という名前(番号 0)の Pod と PVC www-web-0 を組にすることだと読めます。第14回で見たとおり、Pod の素性は番号、ネットワーク上の名前、ストレージの 3 つでできています。この回で扱うのは、そのうちのストレージです。
参考:StatefulSet | Kubernetes API Reference、StatefulSets | Kubernetes
なぜ volumeClaimTemplates が必要なのか
第27回では Pod の volumes に persistentVolumeClaim を書き、claimName で PVC を名前で参照しました。この書き方を StatefulSet の Pod のひな形(spec.template)に入れることもできます。ただし API リファレンスによると、StatefulSet の Pod はどれもひな形どおりに作られます。ひな形は全 Pod に共通なので、Pod ごとに違う claimName は書けないと考えられます。たとえば replicas: 2 のひな形に claimName: data と書くと、web-0 も web-1 も PVC data を指すはずです。PVC と PV のバインドは 1 対 1 なので、2 つの Pod が同じ PVC を使おうとすることになり、Pod ごとに別々のデータは持たせられないはずです。
参考:StatefulSet | Kubernetes API Reference、Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes
第14回で見たとおり、StatefulSet が向いているのは Pod を 1 つずつ見分けたいアプリケーションです。StatefulSet の Pod は識別子が変わらないので、失敗した Pod の代わりの Pod にもとのボリュームを結び付けやすくなります。公式ドキュメントが挙げる使いどころにも、固定された、データが残るストレージ(stable, persistent storage)が要るアプリケーションが入っています。volumeClaimTemplates は、この「Pod ごとのストレージ」と「代わりの Pod にもとのストレージを結び付けること」を、利用者が PVC を 1 つずつ作らずに実現するためのものだと考えられます。
volumeClaimTemplates の仕組み
マニフェストに書くフィールド
以下は公式のチュートリアル(StatefulSet Basics)の web.yaml です。第14回のマニフェストはここから volumeMounts と volumeClaimTemplates を外し、replicas を 3 にしたものでした。この回は replicas をチュートリアルと同じ 2 にし、外した 2 か所も入れて使います。後半の「手元で試す」でも、このマニフェストを使います。
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: registry.k8s.io/nginx-slim:0.21
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi参考:StatefulSet Basics | Kubernetes、StatefulSet | Kubernetes API Reference
volumeClaimTemplates の 1 つの項目は PVC の metadata と spec を書いたものです。spec の中身は第27回の PVC と同じで、1Gi の、1 つのノードから読み書きできる(ReadWriteOnce)ストレージを求めています。storageClassName は書いていないので、既定の StorageClass が使われます。チュートリアルも、既定の StorageClass があることを前提にしています。
参考:StatefulSets | Kubernetes、StatefulSet Basics | Kubernetes
ストレージにかかわる部分を、v1.37 時点の API リファレンスと公式ドキュメントに沿って次の表にまとめます。
| 書く場所 | 内容 |
|---|---|
spec.volumeClaimTemplates[].metadata.name | ひな形の名前(例では www)。Pod ごとの PVC の名前のもとになる |
spec.volumeClaimTemplates[].spec | PVC の spec。アクセスモード、大きさ、StorageClass など |
spec.template.spec.containers[].volumeMounts | ひな形と同じ名前(www)で、マウントする場所を書く。ひな形ごとに、どれかのコンテナに同じ名前の volumeMounts が少なくとも 1 つ必要 |
spec.persistentVolumeClaimRetentionPolicy | StatefulSet を消したときと数を減らしたときに、PVC を残すか消すか。書かなければ両方とも残す(後述) |
参考:StatefulSet | Kubernetes API Reference、StatefulSets | Kubernetes
Pod のひな形には volumes を書いていません。第27回の Pod では volumes に persistentVolumeClaim を書きましたが、ここでは volumeMounts の name が volumeClaimTemplates の名前を指しています。それでも PVC がマウントされるのは、StatefulSet のコントローラーが Pod を作るときに、volumes に PVC の名前を補うからだと考えられます(「Pod を作り直しても同じ PVC に結び付く」で説明し、手順でも確かめます)。
チュートリアルによると、NGINX の Web サーバーは既定で /usr/share/nginx/html/index.html のファイルを返します。volumeMounts によって、この /usr/share/nginx/html のディレクトリの中身は PV に置かれます。
参考:StatefulSet | Kubernetes API Reference、StatefulSet Basics | Kubernetes
Pod ごとに作られる PVC と、その名前
公式のチュートリアルには、このマニフェストで作った PVC を kubectl get pvc -l app=nginx で一覧した出力は次のようなもの(similar to)だ、と書かれています。
チュートリアルは、StatefulSet のコントローラーが 2 つの PVC を作り、それらが 2 つの PV にバインドされた、と説明しています。チュートリアルで使うクラスタは動的に PV を用意するように設定されているので、PV は自動で作られ、バインドされました。
PVC の名前の決まりを文章で定めた箇所は、公式ドキュメントには見当たりませんでした。ただし公式の例はどれも <ひな形の名前>-<Pod の名前> の形です(www-web-0、MySQL の例の data-mysql-0、ZooKeeper の例の datadir-zk-0)。v1.37.0 のソースコードを補助として読むと、StatefulSet のコントローラーも PVC の名前をこの形で組み立てているので、この形で付けられると考えられます。PV の名前(VOLUME 列)は、第27回で見たとおり provisioner が自動で付けたものです。
参考:Run a Replicated Stateful Application | Kubernetes、Running ZooKeeper, A Distributed System Coordinator | Kubernetes、Kubernetes のソースコード(StatefulSet のコントローラー)
第27回で見たとおり、kind の既定の StorageClass は volumeBindingMode が WaitForFirstConsumer だと考えられます。この場合、PVC を使う Pod が作られるまで、PV の用意とバインドが遅らされます。ソースコードを補助として読むと、StatefulSet のコントローラーは Pod を作る前に、その Pod の PVC を作っています。そのため、PVC が作られてから Pod が置かれるノードが決まるまでは、PVC は Pending のままで、その後 Bound になると考えられます。
参考:Storage Classes | Kubernetes、Kubernetes のソースコード(StatefulSet の Pod の作成)
Pod を作り直しても同じ PVC に結び付く
公式ドキュメントのストレージの節によると、Pod がノードに(置き直しも含めて)スケジュールされると、その Pod の volumeMounts は、その Pod の PVC に結び付いた PV をマウントします。チュートリアルでは web-0 と web-1 を消して作り直したあとも、Pod が前に書いたファイルを返し続けます。チュートリアルは、PVC に結び付いた PV が volumeMounts にマウントし直されるからだ、と説明しています。Pod がどのノードに置かれても、PV は適切な場所にマウントされる、とも書いています。
参考:StatefulSets | Kubernetes、StatefulSet Basics | Kubernetes
以下の図は web-0 を消して作り直したときに、PVC と PV がどうなるかをコマ送りで表しています。チュートリアルの「Writing to stable storage」の流れに沿っています。図では web-0 だけを描いていますが、web-1 も同じです。
図の「マウントされる」は、Pod が PVC を通して、その先の PV をマウントすることを指します。図ではこれを、Pod から PVC への claimName の線と、PVC から PV へのバインドの線の 2 本で表しています。
第14回で見たとおり、ふつうに Pod を消したときは、前の web-0 が止まって名前が空いてから、新しい web-0 が作られます。PVC と PV は Pod とは別のオブジェクトなので、Pod が消えても残ります。新しい web-0 も同じ番号を持つので、同じ名前の PVC www-web-0 を参照し、そこにバインドされた PV がマウントされると考えられます。
参考:Force Delete StatefulSet Pods | Kubernetes、StatefulSets | Kubernetes
新しい Pod が前と同じ PVC を参照する仕組みは、公式ドキュメントでは「コントローラーが Pod の素性を保つように識別子と PVC を対応付ける」という説明にとどまります。ソースコードを補助として読むと、StatefulSet のコントローラーは Pod を作るとき、Pod の volumes に、その番号の PVC の名前を claimName として書き込んでいます。また、Pod を作る前に PVC を探し、まだ無いときにだけ PVC を作っています。どちらも推測として扱い、後半の手順で claimName を確かめます。
参考:StatefulSet | Kubernetes API Reference、Kubernetes のソースコード(StatefulSet のコントローラー)、Kubernetes のソースコード(StatefulSet の Pod の作成)
StatefulSet を消しても、数を減らしても PVC は残る
公式ドキュメントは StatefulSet の制限として、StatefulSet を消しても、数を減らしても、StatefulSet に結び付いたボリュームは消えない と書いています。データを安全に残すためで、それは StatefulSet にかかわるリソースを自動ですべて消すことより、一般に価値が高いからだ、と説明しています。ストレージの節にも、Pod や StatefulSet を消しても、Pod の PVC に結び付いた PV は消えず、手で消さなければならない(must)と書かれています。
チュートリアルでも、Pod を 5 つから 3 つに減らしたあと、PVC と PV は 5 つずつ残っています。Delete a StatefulSet のページは、Pod を消してもボリュームを消さないのは、消す前にボリュームからデータを写す機会を残すためだ、と説明しています。
参考:StatefulSet Basics | Kubernetes、Delete a StatefulSet | Kubernetes
PVC が残っていれば、PV もバインドされたまま残ります。PVC を消すと、そのあと PV がどうなるかは第27回の回収ポリシーで決まります。Delete a StatefulSet のページは、Pod が止まったあとで PVC を消すと、StorageClass と回収ポリシーによっては、PV が消されることがある、と書いています。そして、PVC を消したあとでボリュームにアクセスできると決して思い込むべきではない(should never)としています。
参考:Delete a StatefulSet | Kubernetes、Persistent Volumes | Kubernetes
persistentVolumeClaimRetentionPolicy
StatefulSet を消したときや数を減らしたときに、PVC も消したい場合があります。そのためのフィールドが spec.persistentVolumeClaimRetentionPolicy です。公式ドキュメントによると、このフィールドは StatefulSet のライフサイクルの中で PVC を消すかどうか、どう消すかを決めます。
この機能は v1.23 で入り、v1.32 から GA(stable)です。GA の機能は常に有効で無効にできず、対応する機能ゲート(機能ごとに有効・無効を切り替える設定)も要らなくなるので、v1.37 では何も設定せずに使えます。StatefulSet のページには、この機能ゲートを有効にしなければならない(must)という文もありますが、GA の機能では機能ゲートは要らなくなっています。
参考:StatefulSets | Kubernetes、Feature Gates | Kubernetes、Feature Gates | Kubernetes
このフィールドには 2 つの設定があり、どちらにも Retain と Delete のいずれかを書けます。v1.37 時点の公式ドキュメントと API リファレンスに沿って、次の表にまとめます。
| 設定 | 当てはまるとき | Retain(既定) | Delete |
|---|---|---|---|
whenDeleted | StatefulSet を消したとき | PVC は StatefulSet の削除の影響を受けない | volumeClaimTemplates から作ったすべての PVC を、Pod が消されたあとで消す |
whenScaled | StatefulSet の数を減らしたとき | PVC は数を減らす操作の影響を受けない | 減らされて消える Pod の PVC だけを、その Pod が消されたあとで消す |
参考:StatefulSets | Kubernetes、StatefulSet | Kubernetes API Reference
Retain はこの機能ができる前の StatefulSet と同じ動きです。以下は公式ドキュメントの例で、StatefulSet を消したときは PVC を残し、数を減らしたときは消す設定です(... は省略を表し、このままでは apply できません)。
apiVersion: apps/v1
kind: StatefulSet
...
spec:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Retain
whenScaled: Delete
...この 2 つの設定が当てはまるのは、StatefulSet を消したか数を減らしたために Pod が消されるときだけです。公式ドキュメントは例として、ノードの障害で Pod が失敗し、コントロールプレーンが代わりの Pod を作る場合を挙げています。このとき StatefulSet は今の PVC を残し、ボリュームは影響を受けず、新しい Pod が起動するノードにつながれます。whenScaled が PVC を消すのは、数を減らして Pod が消されるときだけでなければならず(must)、ほかの理由で Pod が消されたときには消しません。後半の手順のように利用者が kubectl delete pod で Pod を消した場合も、StatefulSet の削除でも縮小でもないので、Delete にしていても PVC は消えないと考えられます。
Delete のときは、第6回で見た持ち主の関係(ownerReferences)にもとづいて、ガベージコレクター(不要になった従属物を片付けるコントローラー)が PVC を消します。公式ドキュメントの説明を次の表にまとめます。
| 設定 | StatefulSet のコントローラーがすること |
|---|---|
whenDeleted: Delete | StatefulSet のすべての PVC に、持ち主として StatefulSet を書き込む。StatefulSet が消えると、PVC はガベージコレクターに消される |
whenScaled: Delete | 数を減らすときは、消す Pod を PVC の持ち主として書き込んでから、その Pod を消す。PVC はその Pod が終了したあとでガベージコレクターに消される |
参考:StatefulSets | Kubernetes、Garbage Collection | Kubernetes
公式ドキュメントによると、PVC が Pod の終了のあとで消されるので、Pod は PVC が消される前にすべてのボリュームのマウントをきれいに外せます。公式ドキュメントが持ち主を書き込むと説明しているのは Delete のときだけです。v1.37.0 のソースコードを補助として読むと、既定(両方とも Retain)では PVC に持ち主を書き込んでいません。そのため既定では StatefulSet や Pod を消しても、ガベージコレクターは PVC を消さないと考えられます。
参考:StatefulSets | Kubernetes、Kubernetes のソースコード(StatefulSet のコントローラー)
whenScaled: Delete で数を減らしてから戻すと、消えた PVC の代わりに、ひな形から新しい PVC が作られると考えられます。前のデータは引き継がれないはずです。既定の Retain のままなら、数を戻したときに残っていた PVC がそのまま使われ、データも引き継がれると考えられます。MySQL の例のページも、減らしても PVC を残しておけば(keep those initialized PVCs around)、数を戻すのが速くなる、と書いています。後半の手順で、既定の動きを確かめます。
参考:StatefulSet | Kubernetes API Reference、Run a Replicated Stateful Application | Kubernetes
手元で試す
ここまでの内容を手元の kind のクラスタで試す手順を示します。チュートリアルの web.yaml で StatefulSet を作り、Pod ごとに PVC と PV ができることを確かめます。続けて、PV にファイルを書いてから Pod を消し、作り直された Pod が同じボリュームにつながってデータが残ることを確かめます。最後に数を減らして戻し、PVC が残って使い直されることも見ます。コマンドは macOS や Linux のシェル(bash や zsh)で実行する前提で書いています。Windows では WSL(Windows の中で Linux を動かす仕組み)などを使ってください。試す環境は次のとおりです。
| 項目 | 内容 |
|---|---|
| kind | v0.33.0 |
| Kubernetes | v1.37.0(kind v0.33.0 の既定のノードイメージ) |
| ノード構成 | シングルノード(クラスタ名 kind):コントロールプレーン 1 |
| 必要なもの | Docker、kubectl(v1.36〜v1.38)、テキストエディタ、端末(ターミナル)2 つ |
参考:Release v0.33.0 · kubernetes-sigs/kind、Version Skew Policy | Kubernetes
公式のチュートリアルはコントロールプレーンを兼ねないノードが 2 つ以上のクラスタを勧めています(recommended)が、この回の手順は複数のノードを前提にしないので、第27回と同じシングルノードのクラスタを使います。なお local-path-provisioner の README によると、作られる PV は既定で kubernetes.io/hostname を使った PV の nodeAffinity を持ち、作られたノードでだけ使えます(Pod のノードアフィニティは第31回で扱います)。公式ドキュメントによると、PV の nodeAffinity は、その PV を使う Pod を置けるノードを絞ります。そのため、マルチノードのクラスタでも、作り直した Pod は PV と同じノードに置かれると考えられます。
参考:StatefulSet Basics | Kubernetes、rancher/local-path-provisioner、Persistent Volumes | Kubernetes
kind でクラスタを作る手順は、以下の記事で解説しています。
kind で検証用のクラスタを作るDocker のコンテナをノードにして、手元のマシンに Kubernetes のクラスタを作るツール kind の仕組みを図で説明します。kind create cluster で何が起きるかを押さえ、シングルノードとマルチノードのクラスタを作って、ノードの正体が Docker のコンテナであることを確かめる手順を紹介します。シングルノードのクラスタを用意する
まず、前の回のクラスタが残っていないかを確かめます。
$ kind get clusters
kind が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。
$ kind create cluster
$ kind get clusters
kind get clusters に kind が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-kind を付けて、操作するクラスタをはっきりさせます。続けて、ノードを一覧します。
$ kubectl get nodes --context kind-kind
ノードが 1 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。
前の回のクラスタを使い続けている場合は、default の Namespace に PVC、PV、StatefulSet、app: nginx の Pod が残っていないことを確かめます。
$ kubectl get pvc,pv,statefulsets --context kind-kind
$ kubectl get pods -l app=nginx --context kind-kind
StatefulSet をラベルで絞らずに一覧しているのは、第14回とこの回の web.yaml では、StatefulSet 自身にはラベルを付けていないからです。どちらのコマンドでも、見つからないことを知らせる 1 行が出れば、そのまま進めます。前の回の PVC や PV が残っていたら、第27回の片付けの手順で消してから進みます。この回の手順では PVC と PV を一覧して www-web-0 と www-web-1 に結び付いたものを見るので、ほかのものが無いほうが読みやすいからです。
第14回の StatefulSet web が残っていたら、kubectl delete statefulset web --context kind-kind と kubectl delete service nginx --context kind-kind で消してから進みます。残ったままこの回の web.yaml を渡すと、kubectl apply は今ある web を書き換えようとします。v1.37.0 のソースコードを補助として読むと、volumeClaimTemplates は作ったあとで変えられないフィールドなので、書き換えはエラーになると考えられます。
参考:Delete a StatefulSet | Kubernetes、Kubernetes のソースコード(StatefulSet の検証)
既定の StorageClass を確かめる
web.yaml は storageClassName を書いていないので、「マニフェストに書くフィールド」で見たとおり、既定の StorageClass が使われます。第27回と同じく、クラスタの StorageClass を一覧します。
$ kubectl get storageclass --context kind-kind
第27回で kind v0.33.0 のソースコードから考えたとおりなら、standard (default) の行が 1 行だけ表示され、PROVISIONER 列は rancher.io/local-path になるはずです。手元の kubectl では、RECLAIMPOLICY や VOLUMEBINDINGMODE のような列が増えて表示されることもあると考えられます。続けて、standard の中身を YAML の形で表示します。
$ kubectl get storageclass standard -o yaml --context kind-kind
第27回の推測どおりなら、reclaimPolicy に Delete、volumeBindingMode に WaitForFirstConsumer が見えるはずです。違う値が表示されたら、手元のクラスタの値を正として読み進めてください。特に reclaimPolicy が Retain なら、片付けで PV が残るはずです。
参考:Change the default StorageClass | Kubernetes、StorageClass | Kubernetes API Reference、kind のソースコード(local-path-provisioner)
StatefulSet を作る
「マニフェストに書くフィールド」のマニフェストを、web.yaml という名前で、コマンドを実行するディレクトリに保存します。第14回の web.yaml とは中身が違うので、同じディレクトリなら上書きするか、別のディレクトリに保存します。このファイルはあとでエディタで書き換えます。
ここからは端末を 2 つ使います。第14回と同じく、1 つ目の端末で Pod の変化を追い続け、2 つ目の端末でマニフェストを渡したり、PVC や PV を見たりします。2 つ目の端末を開いたら、cd でマニフェストを保存したディレクトリに移動しておきます。
1 つ目の端末で、app: nginx の Pod を監視します。この監視は片付けの前まで止めずに続けます。
$ kubectl get pods --watch -l app=nginx --context kind-kind
Pod がまだ無いので、何も表示されないか、見つからないことを知らせる 1 行が出たあと、止まったように見えると考えられます。
2 つ目の端末で、マニフェストを渡します。
$ kubectl apply -f web.yaml --context kind-kind
service/nginx と statefulset.apps/web がそれぞれ created になったことを知らせる 2 行が出るはずです(第14回)。
1 つ目の端末には、第14回と同じく、web-0 の行が Pending、ContainerCreating、Running と書き足され、web-0 が Running で Ready になってから web-1 の行が同じように続くはずです。第14回と違い、この回の Pod は PVC を使います。WaitForFirstConsumer なら PV を用意してバインドするのを待つので、Pending の行が第14回より長く続くことがあると考えられます。
参考:StatefulSet Basics | Kubernetes、Storage Classes | Kubernetes
Pod ごとの PVC と PV を確かめる
1 つ目の端末で 2 つの Pod が Running になったら、2 つ目の端末で PVC を一覧します。
$ kubectl get pvc --context kind-kind
www-web-0 と www-web-1 の 2 行が表示されるはずです。「Pod ごとに作られる PVC と、その名前」で引用したチュートリアルの例と比べて、次の表にまとめます。列の名前や並びも、手元の kubectl では違うことがあると考えられます。
| 列 | チュートリアルの例 | 手元で表示されるはずの値 |
|---|---|---|
NAME | www-web-0、www-web-1 | 例と同じ |
STATUS | Bound | 例と同じ |
VOLUME | pvc-15c268c7-… など | 自動で付いた別の名前。第27回で見たとおり pvc- で始まると考えられる |
STORAGECLASS | 例には無い列 | standard |
参考:StatefulSet Basics | Kubernetes、StatefulSet Basics | Kubernetes、rancher/local-path-provisioner
VOLUME 列の 2 つの名前を、メモ帳などに控えておきます。Pod を作り直したあとで比べます。続けて PV を一覧します。
$ kubectl get pv --context kind-kind
PV が 2 つ表示されるはずです。チュートリアルの片付けの節の例と同じく、CLAIM 列は default/www-web-0 と default/www-web-1、STATUS 列は Bound になるはずです。RECLAIM POLICY 列は standard から受け継いだ値(第27回の推測どおりなら Delete)になると考えられます。
参考:StatefulSet Basics | Kubernetes、Persistent Volumes | Kubernetes
Pod の側から、どの PVC を参照しているかを見ます。-o jsonpath は第5回で使った、フィールドを指定して取り出す出力の形式です。?(@.name=="www") は volumes の中から name が www の項目だけを選ぶ指定です。
$ kubectl get pod web-0 -o jsonpath='{.spec.volumes[?(@.name=="www")].persistentVolumeClaim.claimName}{"\n"}' --context kind-kind
マニフェストの Pod のひな形には volumes を書いていませんが、www-web-0 と表示されるはずです。「Pod を作り直しても同じ PVC に結び付く」で見たとおり、StatefulSet のコントローラーが Pod を作るときに書き込んだと考えられます。web-0 を web-1 に変えて実行すると、www-web-1 と表示されるはずです。
続けて、StatefulSet の persistentVolumeClaimRetentionPolicy を取り出します。マニフェストには書いていないフィールドです。
$ kubectl get statefulset web -o jsonpath='{.spec.persistentVolumeClaimRetentionPolicy}{"\n"}' --context kind-kind
{"whenDeleted":"Retain","whenScaled":"Retain"} のような、2 つの設定がどちらも Retain であることを表す 1 行が出ると考えられます。キーの順番は違うこともあると考えられます。公式ドキュメントで確かめられるのは既定の動きが Retain だということまでで、書かなかったフィールドに値が書き込まれるかどうかは、v1.37.0 のソースコードから読んだ推測です。何も表示されなかった場合は、フィールドに値が書き込まれていないだけで、既定の Retain として扱われると読めます。
参考:StatefulSet | Kubernetes API Reference、Kubernetes のソースコード(StatefulSet の既定値)
第27回と同じく、ノードの側からボリュームのディレクトリも見ておきます。
$ docker exec kind-control-plane ls /var/local-path-provisioner
ディレクトリが 2 つ表示されるはずです。第27回で見た local-path-provisioner の README の既定のひな形のとおりなら、名前は <PV の名前>_default_www-web-0 と <PV の名前>_default_www-web-1 の形になると考えられます。
参考:docker container exec | Docker Docs、rancher/local-path-provisioner
ここまでのコマンドは Pod を変えないので、1 つ目の端末に行は書き足されないと考えられます。
データを書いてから Pod を消す
チュートリアルと同じく、それぞれの Pod のホスト名を、PV にある index.html に書きます。第14回で見たとおり、ホスト名は Pod の名前と同じです。2 つ目の端末で実行します。
$ kubectl exec web-0 --context kind-kind -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'
$ kubectl exec web-1 --context kind-kind -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'
何も表示されずに終われば、書き込めています。全体を単一引用符(')で囲んでいるので、$(hostname) は手元のシェルではなく、コンテナの中のシェルで実行されます。書いた中身を読みます。
$ kubectl exec web-0 --context kind-kind -- cat /usr/share/nginx/html/index.html
$ kubectl exec web-1 --context kind-kind -- cat /usr/share/nginx/html/index.html
それぞれ web-0、web-1 と表示されるはずです。チュートリアルは NGINX にリクエストを送って(curl)中身を確かめていますが、この回ではファイルを直接読みます。これらのコマンドでも Pod は変わらないので、1 つ目の端末に行は書き足されないと考えられます。
参考:StatefulSet Basics | Kubernetes、kubectl exec | Kubernetes
作り直す前の Pod の UID を表示して控えておきます。第14回と同じく、custom-columns で名前と UID の列だけを表示します。
$ kubectl get pods -l app=nginx -o 'custom-columns=NAME:.metadata.name,UID:.metadata.uid' --context kind-kind
2 つ目の端末で、チュートリアルと同じく、StatefulSet の Pod をラベルセレクタでまとめて消します。app: nginx のラベルは Pod のひな形に書いたものです。
$ kubectl delete pod -l app=nginx --context kind-kind
チュートリアルの例では、出力は pod "web-0" deleted と pod "web-1" deleted の 2 行です。v1.35 以降の kubectl のソースコードを読む限り、手元の kubectl では、それぞれの行の後ろに from default namespace が付くと考えられます。kubectl delete は既定では Pod が消えるまで待つので、終わるまで少しかかることがあります。
参考:StatefulSet Basics | Kubernetes、kubectl のソースコード(kubectl delete)
1 つ目の端末を見ると、行が書き足されていくはずです。公式のチュートリアルの例では、Pod を消したあとの表示は次のとおりです。
第14回で見たとおり、この例には消された Pod の Terminating の行がありませんが、手元では出るはずです。作り直された Pod の行は web-0、web-1 の順に、同じ名前で現れるはずです。
1 つ目の端末で 2 つの Pod が Running になったら、2 つ目の端末で、名前と UID をもう一度表示します。
$ kubectl get pods -l app=nginx -o 'custom-columns=NAME:.metadata.name,UID:.metadata.uid' --context kind-kind
NAME 列は前と同じで、UID 列は前に控えた値と違うはずです(第14回)。名前は同じでも、新しく作られた別の Pod です。
作り直した Pod でデータが残っていることを確かめる
2 つ目の端末で、作り直された Pod の index.html を読みます。
$ kubectl exec web-0 --context kind-kind -- cat /usr/share/nginx/html/index.html
$ kubectl exec web-1 --context kind-kind -- cat /usr/share/nginx/html/index.html
前と同じく、それぞれ web-0、web-1 と表示されるはずです。チュートリアルの例でも、作り直したあとに web-0 と web-1 が返っています。「Pod を作り直しても同じ PVC に結び付く」で見たとおり、PVC に結び付いた PV がマウントし直されたからです。
本当に同じ PVC と PV か、2 つ目の端末で確かめます。
$ kubectl get pvc --context kind-kind
$ kubectl get pod web-0 -o jsonpath='{.spec.volumes[?(@.name=="www")].persistentVolumeClaim.claimName}{"\n"}' --context kind-kind
PVC は前と同じ www-web-0 と www-web-1 の 2 つで、VOLUME 列は前に控えた名前と同じはずです。Pod を消しても PVC は消えず、PVC と PV のバインドも変わらないからです。新しい web-0 の claimName も www-web-0 と表示されるはずです。新しい Pod は UID が違う別の Pod ですが、同じ PVC を通して、同じ PV を使っています。
数を減らして戻しても PVC が使い直されることを確かめる
最後に、既定の whenScaled: Retain の動きを見ます。2 つ目の端末で、web.yaml をテキストエディタで開き、StatefulSet の replicas を 2 から 1 に書き換えて保存します。ほかの行は変えません。書き換えたら、マニフェストを渡します。
$ kubectl apply -f web.yaml --context kind-kind
1 つ目の端末には、番号のいちばん大きい web-1 の行が Terminating で書き足されるはずです(第14回)。web-0 の行には変化が無いはずです。少し待ってから、2 つ目の端末で Pod と PVC を一覧します。
$ kubectl get pods -l app=nginx --context kind-kind
$ kubectl get pvc --context kind-kind
Pod は web-0 だけになっても、PVC は www-web-0 と www-web-1 の 2 つが Bound のまま残っているはずです。チュートリアルで 5 つから 3 つに減らしたときに、PVC が 5 つ残っていたのと同じです。web-1 が Terminating で表示されていたら、少し待ってからもう一度実行します。
参考:StatefulSet Basics | Kubernetes、StatefulSets | Kubernetes
web.yaml の replicas を 1 から 2 に戻して保存し、もう一度マニフェストを渡します。
$ kubectl apply -f web.yaml --context kind-kind
1 つ目の端末には、web-1 の行が Pending、ContainerCreating、Running と書き足されるはずです。Running になったら、2 つ目の端末で web-1 の index.html を読みます。
$ kubectl exec web-1 --context kind-kind -- cat /usr/share/nginx/html/index.html
web-1 と表示されるはずです。「persistentVolumeClaimRetentionPolicy」で見たとおり、残っていた PVC www-web-1 が新しい web-1 にそのまま使われ、前に書いたデータが引き継がれたと考えられます。kubectl get pvc --context kind-kind を実行すると、PVC は増えずに 2 つのままで、www-web-1 の VOLUME 列も前と同じはずです。
参考:Run a Replicated Stateful Application | Kubernetes、StatefulSets | Kubernetes
片付け
1 つ目の端末で Ctrl+C を押して、Pod の監視を止めます。
2 つ目の端末で、作った Service と StatefulSet を、マニフェストを指定して削除します。web.yaml には 2 つのリソースが書いてあるので、両方が消えます。
$ kubectl delete -f web.yaml --context kind-kind
StatefulSet を消すと、Pod も消されます。少し待ってから、StatefulSet と Pod が残っていないことと、PVC が残っていることを確かめます。
$ kubectl get statefulsets,pods --context kind-kind
$ kubectl get pvc --context kind-kind
1 つ目のコマンドで見つからないことを知らせる 1 行が出るはずです。Pod が Terminating で表示されていたら、少し待ってからもう一度実行します。2 つ目のコマンドでは、www-web-0 と www-web-1 が Bound のまま表示されるはずです。既定の whenDeleted: Retain では、StatefulSet を消しても PVC は消えないからです。
残った PVC は、利用者が自分で消します。チュートリアルの片付けと同じく、PVC を名前で指定して消します。Pod がすべて消えたことを確かめてから実行してください。Pod が使っているあいだは、第27回で見た使用中の保護(Storage Object in Use Protection)で、PVC の削除が先送りされるからです。
$ kubectl delete pvc www-web-0 www-web-1 --context kind-kind
参考:StatefulSet Basics | Kubernetes、Delete a StatefulSet | Kubernetes、Persistent Volumes | Kubernetes
PVC が消えると、PV は PVC から解放され、回収ポリシーに従って扱われます。「既定の StorageClass を確かめる」で reclaimPolicy が Delete だったなら、PV も消されるはずです。少し待ってから、PVC と PV を一覧します。
$ kubectl get pvc,pv --context kind-kind
見つからないことを知らせる 1 行が出るはずです。PV が残っていたら、provisioner がまだ消していないことも考えられるので、少し待ってからもう一度一覧します。それでも残り、RECLAIM POLICY 列が Retain で STATUS 列が Released なら、第27回の回収ポリシーで見たとおり、PV は手で消す必要があります。NAME 列の名前を <PV の名前> に入れて消します。
$ kubectl delete pv <PV の名前> --context kind-kind
参考:Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes
PV が Delete で消されたなら、第27回で見た local-path-provisioner の teardown のスクリプトで、ノードのディレクトリも片付けられるはずです。docker exec kind-control-plane ls /var/local-path-provisioner を実行すると、何も表示されないはずです。PV を手で消した場合は、ディレクトリは残ると考えられますが、このあとクラスタを削除すればノードのコンテナごと消えると考えられます。
手元に保存した web.yaml はクラスタには影響しないので、残しておいても構いません。要らなければ rm web.yaml で消します。最後に、クラスタを削除し、消えたことを確かめます。
$ kind delete cluster
$ kind get clusters
kind get clusters に kind が表示されなくなったことを確かめます。kubeconfig に kind-kind のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-kind で消します。
注意点
StatefulSet と PVC の組み合わせでつまずきやすい点と、バージョンで変わる点を、次の表にまとめます。
| 項目 | 内容 |
|---|---|
| PVC は既定では残る | StatefulSet を消しても、数を減らしても、PVC と PV は消えない。要らなければ手で消す |
whenScaled: Delete では縮小のときに PVC が消える | 数を戻すと新しい PVC が作られ、前のデータは引き継がれないと考えられる。PV の回収ポリシーが Retain なら、元のデータは Released の PV に残る |
volumeClaimTemplates は後から変えにくい | v1.37.0 のソースコードでは、作ったあとで変えられないフィールドになっている(推測)。volumeClaimTemplates が違う同じ名前の StatefulSet(例:第14回の web)があると、kubectl apply は失敗すると考えられる |
volumeMounts の名前を合わせる | volumeClaimTemplates の項目ごとに、同じ名前の volumeMounts がどれかのコンテナに少なくとも 1 つ必要 |
本番では ReadWriteOncePod | 公式ドキュメントの例は簡単のため ReadWriteOnce を使っている。本番では ReadWriteOncePod を使うことを Kubernetes のプロジェクトは勧めている(CSI のボリュームだけが対応。第27回) |
| コントローラーが止まっているときの強制削除 | Delete のポリシーで、コントローラーが止まっているあいだに Pod を強制削除すると、PVC の持ち主の情報が書き込まれていないことがある。コントローラーが戻るのを待つことが勧められている |
| 名前の決まりと kind の値は推測を含む | PVC の名前の決まりは公式の例とソースコードから、kind の StorageClass は第27回と同じく kind v0.33.0 のソースコードから読んだもの。手元のクラスタの値を正とする |
参考:StatefulSets | Kubernetes、StatefulSets | Kubernetes、StatefulSets | Kubernetes、StatefulSet | Kubernetes API Reference、Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes、Kubernetes のソースコード(StatefulSet の検証)
関連記事
Pod が使う CPU やメモリーの量を要求し、上限を決める requests と limits については、以下の記事で解説しています。第6部の最初の回です。
requests と limits の仕組みコンテナが使う CPU とメモリの量を書く requests と limits の仕組みを図で説明します。requests で Pod を置くノードが選ばれること、limits を超えようとした CPU とメモリに起きること、QoS クラスの決まり方を押さえ、kind のクラスタで OOMKilled を観察する手順を紹介します。Pod に順番と固定の名前を付ける StatefulSet そのものについては、以下の記事で解説しています。
StatefulSet を動かして理解するPod に番号付きの固定の名前を与え、番号の順に作って止める StatefulSet の仕組みを図で説明します。web-0 から順に作り、番号の大きい順に止める決まり、headless Service が受け持つ名前、Deployment との違いを押さえ、kind のクラスタで Pod を消しても同じ名前で戻ることと、起動と停止の順番を確かめる手順を紹介します。PV、PVC、StorageClass と、回収ポリシーについては、以下の記事で解説しています。
PersistentVolume と PVC の仕組みPod の期間を超えてデータを残す PersistentVolume、PVC、StorageClass の関係を図で説明します。静的と動的のプロビジョニング、1 対 1 のバインド、アクセスモード、回収ポリシー、volumeBindingMode を押さえ、kind のクラスタで PVC とそれを使う Pod を作り、ボリュームが自動で用意される流れを観察します。