StatefulSet と PVC でデータを残す

v1.37 時点(2026年10月26日 確認)

Kubernetes 基本仕様第28回 / 全45回 ・ 第5部 ストレージ
目次

この記事では 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 を扱いました。まだの方は先にこちらをどうぞ。

基本仕様 ・ 第14回 StatefulSet を動かして理解するPod に番号付きの固定の名前を与え、番号の順に作って止める StatefulSet の仕組みを図で説明します。web-0 から順に作り、番号の大きい順に止める決まり、headless Service が受け持つ名前、Deployment との違いを押さえ、kind のクラスタで Pod を消しても同じ名前で戻ることと、起動と停止の順番を確かめる手順を紹介します。 基本仕様 ・ 第27回 PersistentVolume と PVC の仕組みPod の期間を超えてデータを残す PersistentVolume、PVC、StorageClass の関係を図で説明します。静的と動的のプロビジョニング、1 対 1 のバインド、アクセスモード、回収ポリシー、volumeBindingMode を押さえ、kind のクラスタで PVC とそれを使う Pod を作り、ボリュームが自動で用意される流れを観察します。

以下の図は後半の「手元で試す」で作る web という StatefulSet と、その Pod、Pod ごとの PVC、PVC に結び付いた PersistentVolume の関係を表しています。

StatefulSet、Pod、Pod ごとの PersistentVolumeClaim、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 の仕組みの上に成り立っているということです。

参考:StatefulSets | Kubernetes、StatefulSets | Kubernetes

公式ドキュメントのストレージの節(Stable Storage)によると、StatefulSet に書いた volumeClaimTemplates の項目ごとに、各 Pod は PVC を 1 つ受け取ります。ひな形が 1 つで Pod が 2 つなら、PVC は 2 つです。

参考:StatefulSets | Kubernetes

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 つずつ作らずに実現するためのものだと考えられます。

参考:StatefulSets | Kubernetes、StatefulSets | Kubernetes

volumeClaimTemplates の仕組み

マニフェストに書くフィールド

以下は公式のチュートリアル(StatefulSet Basics)の web.yaml です。第14回のマニフェストはここから volumeMounts と volumeClaimTemplates を外し、replicas を 3 にしたものでした。この回は replicas をチュートリアルと同じ 2 にし、外した 2 か所も入れて使います。後半の「手元で試す」でも、このマニフェストを使います。

yaml
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[].specPVC の spec。アクセスモード、大きさ、StorageClass など
spec.template.spec.containers[].volumeMountsひな形と同じ名前(www)で、マウントする場所を書く。ひな形ごとに、どれかのコンテナに同じ名前の volumeMounts が少なくとも 1 つ必要
spec.persistentVolumeClaimRetentionPolicyStatefulSet を消したときと数を減らしたときに、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)だ、と書かれています。

NAME        STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
www-web-0   Bound     pvc-15c268c7-b507-11e6-932f-42010a800002   1Gi        RWO           48s
www-web-1   Bound     pvc-15c79307-b507-11e6-932f-42010a800002   1Gi        RWO           48s

参考:StatefulSet Basics | Kubernetes

チュートリアルは、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 も同じです。

(初期)web-0 が PVC www-web-0 を参照し、バインドされた PV がマウントされている。PV には web-0 と書いた index.html がある
(1/3)利用者が kubectl delete で web-0 を消す。web-0 は終了中になる。PVC www-web-0 と PV は Bound のまま
(2/3)web-0 が消えた。PVC www-web-0 と PV は残り、PV の中の index.html もそのまま
(3/3)statefulset-controller が同じ名前の web-0 を作る。新しい web-0 は UID が違う別の Pod だが、同じ PVC www-web-0 を参照し、同じ PV がマウントされる

図の「マウントされる」は、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)と書かれています。

参考:StatefulSets | Kubernetes、StatefulSets | Kubernetes

チュートリアルでも、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 を消すかどうか、どう消すかを決めます。

参考:StatefulSets | Kubernetes

この機能は 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
whenDeletedStatefulSet を消したときPVC は StatefulSet の削除の影響を受けないvolumeClaimTemplates から作ったすべての PVC を、Pod が消されたあとで消す
whenScaledStatefulSet の数を減らしたときPVC は数を減らす操作の影響を受けない減らされて消える Pod の PVC だけを、その Pod が消されたあとで消す

参考:StatefulSets | Kubernetes、StatefulSet | Kubernetes API Reference

Retain はこの機能ができる前の StatefulSet と同じ動きです。以下は公式ドキュメントの例で、StatefulSet を消したときは PVC を残し、数を減らしたときは消す設定です(... は省略を表し、このままでは apply できません)。

yaml
apiVersion: apps/v1
kind: StatefulSet
...
spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain
    whenScaled: Delete
...

参考:StatefulSets | Kubernetes

この 2 つの設定が当てはまるのは、StatefulSet を消したか数を減らしたために Pod が消されるときだけです。公式ドキュメントは例として、ノードの障害で Pod が失敗し、コントロールプレーンが代わりの Pod を作る場合を挙げています。このとき StatefulSet は今の PVC を残し、ボリュームは影響を受けず、新しい Pod が起動するノードにつながれます。whenScaled が PVC を消すのは、数を減らして Pod が消されるときだけでなければならず(must)、ほかの理由で Pod が消されたときには消しません。後半の手順のように利用者が kubectl delete pod で Pod を消した場合も、StatefulSet の削除でも縮小でもないので、Delete にしていても PVC は消えないと考えられます。

参考:StatefulSets | Kubernetes

Delete のときは、第6回で見た持ち主の関係(ownerReferences)にもとづいて、ガベージコレクター(不要になった従属物を片付けるコントローラー)が PVC を消します。公式ドキュメントの説明を次の表にまとめます。

設定StatefulSet のコントローラーがすること
whenDeleted: DeleteStatefulSet のすべての 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 を動かす仕組み)などを使ってください。試す環境は次のとおりです。

項目内容
kindv0.33.0
Kubernetesv1.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 でクラスタを作る手順は、以下の記事で解説しています。

基本仕様 ・ 第2回 kind で検証用のクラスタを作るDocker のコンテナをノードにして、手元のマシンに Kubernetes のクラスタを作るツール kind の仕組みを図で説明します。kind create cluster で何が起きるかを押さえ、シングルノードとマルチノードのクラスタを作って、ノードの正体が Docker のコンテナであることを確かめる手順を紹介します。

シングルノードのクラスタを用意する

まず、前の回のクラスタが残っていないかを確かめます。

console
$ kind get clusters

kind が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。

console
$ kind create cluster
$ kind get clusters

kind get clusters に kind が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-kind を付けて、操作するクラスタをはっきりさせます。続けて、ノードを一覧します。

console
$ kubectl get nodes --context kind-kind

ノードが 1 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。

参考:Quick Start | kind、kubectl get | Kubernetes

前の回のクラスタを使い続けている場合は、default の Namespace に PVC、PV、StatefulSet、app: nginx の Pod が残っていないことを確かめます。

console
$ 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 を一覧します。

console
$ kubectl get storageclass --context kind-kind

第27回で kind v0.33.0 のソースコードから考えたとおりなら、standard (default) の行が 1 行だけ表示され、PROVISIONER 列は rancher.io/local-path になるはずです。手元の kubectl では、RECLAIMPOLICY や VOLUMEBINDINGMODE のような列が増えて表示されることもあると考えられます。続けて、standard の中身を YAML の形で表示します。

console
$ 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 を監視します。この監視は片付けの前まで止めずに続けます。

console
$ kubectl get pods --watch -l app=nginx --context kind-kind

参考:StatefulSet Basics | Kubernetes、kubectl get | Kubernetes

Pod がまだ無いので、何も表示されないか、見つからないことを知らせる 1 行が出たあと、止まったように見えると考えられます。

2 つ目の端末で、マニフェストを渡します。

console
$ 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 を一覧します。

console
$ kubectl get pvc --context kind-kind

www-web-0 と www-web-1 の 2 行が表示されるはずです。「Pod ごとに作られる PVC と、その名前」で引用したチュートリアルの例と比べて、次の表にまとめます。列の名前や並びも、手元の kubectl では違うことがあると考えられます。

列チュートリアルの例手元で表示されるはずの値
NAMEwww-web-0、www-web-1例と同じ
STATUSBound例と同じ
VOLUMEpvc-15c268c7-… など自動で付いた別の名前。第27回で見たとおり pvc- で始まると考えられる
STORAGECLASS例には無い列standard

参考:StatefulSet Basics | Kubernetes、StatefulSet Basics | Kubernetes、rancher/local-path-provisioner

VOLUME 列の 2 つの名前を、メモ帳などに控えておきます。Pod を作り直したあとで比べます。続けて PV を一覧します。

console
$ 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 の項目だけを選ぶ指定です。

console
$ kubectl get pod web-0 -o jsonpath='{.spec.volumes[?(@.name=="www")].persistentVolumeClaim.claimName}{"\n"}' --context kind-kind

参考:JSONPath Support | Kubernetes

マニフェストの Pod のひな形には volumes を書いていませんが、www-web-0 と表示されるはずです。「Pod を作り直しても同じ PVC に結び付く」で見たとおり、StatefulSet のコントローラーが Pod を作るときに書き込んだと考えられます。web-0 を web-1 に変えて実行すると、www-web-1 と表示されるはずです。

続けて、StatefulSet の persistentVolumeClaimRetentionPolicy を取り出します。マニフェストには書いていないフィールドです。

console
$ 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回と同じく、ノードの側からボリュームのディレクトリも見ておきます。

console
$ 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 つ目の端末で実行します。

console
$ 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) は手元のシェルではなく、コンテナの中のシェルで実行されます。書いた中身を読みます。

console
$ 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 の列だけを表示します。

console
$ kubectl get pods -l app=nginx -o 'custom-columns=NAME:.metadata.name,UID:.metadata.uid' --context kind-kind

2 つ目の端末で、チュートリアルと同じく、StatefulSet の Pod をラベルセレクタでまとめて消します。app: nginx のラベルは Pod のひな形に書いたものです。

console
$ 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 を消したあとの表示は次のとおりです。

NAME      READY     STATUS              RESTARTS   AGE
web-0     0/1       ContainerCreating   0          0s
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          2s
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         34s

参考:StatefulSet Basics | Kubernetes

第14回で見たとおり、この例には消された Pod の Terminating の行がありませんが、手元では出るはずです。作り直された Pod の行は web-0、web-1 の順に、同じ名前で現れるはずです。

1 つ目の端末で 2 つの Pod が Running になったら、2 つ目の端末で、名前と UID をもう一度表示します。

console
$ 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 を読みます。

console
$ 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 つ目の端末で確かめます。

console
$ 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 を使っています。

参考:StatefulSets | Kubernetes、StatefulSets | Kubernetes

数を減らして戻しても PVC が使い直されることを確かめる

最後に、既定の whenScaled: Retain の動きを見ます。2 つ目の端末で、web.yaml をテキストエディタで開き、StatefulSet の replicas を 2 から 1 に書き換えて保存します。ほかの行は変えません。書き換えたら、マニフェストを渡します。

console
$ kubectl apply -f web.yaml --context kind-kind

1 つ目の端末には、番号のいちばん大きい web-1 の行が Terminating で書き足されるはずです(第14回)。web-0 の行には変化が無いはずです。少し待ってから、2 つ目の端末で Pod と PVC を一覧します。

console
$ 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 に戻して保存し、もう一度マニフェストを渡します。

console
$ kubectl apply -f web.yaml --context kind-kind

1 つ目の端末には、web-1 の行が Pending、ContainerCreating、Running と書き足されるはずです。Running になったら、2 つ目の端末で web-1 の index.html を読みます。

console
$ 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 つのリソースが書いてあるので、両方が消えます。

console
$ kubectl delete -f web.yaml --context kind-kind

参考:Delete a StatefulSet | Kubernetes

StatefulSet を消すと、Pod も消されます。少し待ってから、StatefulSet と Pod が残っていないことと、PVC が残っていることを確かめます。

console
$ 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 は消えないからです。

参考:StatefulSets | Kubernetes

残った PVC は、利用者が自分で消します。チュートリアルの片付けと同じく、PVC を名前で指定して消します。Pod がすべて消えたことを確かめてから実行してください。Pod が使っているあいだは、第27回で見た使用中の保護(Storage Object in Use Protection)で、PVC の削除が先送りされるからです。

console
$ 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 を一覧します。

console
$ kubectl get pvc,pv --context kind-kind

見つからないことを知らせる 1 行が出るはずです。PV が残っていたら、provisioner がまだ消していないことも考えられるので、少し待ってからもう一度一覧します。それでも残り、RECLAIM POLICY 列が Retain で STATUS 列が Released なら、第27回の回収ポリシーで見たとおり、PV は手で消す必要があります。NAME 列の名前を <PV の名前> に入れて消します。

console
$ 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 を手で消した場合は、ディレクトリは残ると考えられますが、このあとクラスタを削除すればノードのコンテナごと消えると考えられます。

参考:rancher/local-path-provisioner

手元に保存した web.yaml はクラスタには影響しないので、残しておいても構いません。要らなければ rm web.yaml で消します。最後に、クラスタを削除し、消えたことを確かめます。

console
$ kind delete cluster
$ kind get clusters

kind get clusters に kind が表示されなくなったことを確かめます。kubeconfig に kind-kind のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-kind で消します。

参考:Quick Start | kind、kubectl config | Kubernetes

注意点

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部の最初の回です。

基本仕様 ・ 第29回 requests と limits の仕組みコンテナが使う CPU とメモリの量を書く requests と limits の仕組みを図で説明します。requests で Pod を置くノードが選ばれること、limits を超えようとした CPU とメモリに起きること、QoS クラスの決まり方を押さえ、kind のクラスタで OOMKilled を観察する手順を紹介します。

Pod に順番と固定の名前を付ける StatefulSet そのものについては、以下の記事で解説しています。

基本仕様 ・ 第14回 StatefulSet を動かして理解するPod に番号付きの固定の名前を与え、番号の順に作って止める StatefulSet の仕組みを図で説明します。web-0 から順に作り、番号の大きい順に止める決まり、headless Service が受け持つ名前、Deployment との違いを押さえ、kind のクラスタで Pod を消しても同じ名前で戻ることと、起動と停止の順番を確かめる手順を紹介します。

PV、PVC、StorageClass と、回収ポリシーについては、以下の記事で解説しています。

基本仕様 ・ 第27回 PersistentVolume と PVC の仕組みPod の期間を超えてデータを残す PersistentVolume、PVC、StorageClass の関係を図で説明します。静的と動的のプロビジョニング、1 対 1 のバインド、アクセスモード、回収ポリシー、volumeBindingMode を押さえ、kind のクラスタで PVC とそれを使う Pod を作り、ボリュームが自動で用意される流れを観察します。

Kubernetes 基本仕様の目次へ