PersistentVolume と PVC の仕組み
v1.37 時点(2026年10月26日 確認)
目次
この記事では、Pod とは別の期間で存在するストレージを扱う PersistentVolume と PersistentVolumeClaim(PVC)の仕組みを、図で説明します。ストレージを用意する側と使う側がどう分かれているのか、PVC を作るとボリュームがどうやって用意されるのかがわかります。後半では kind のクラスタで PVC と Pod を作り、ボリュームが自動で用意されて PVC に結び付く流れを観察する手順を示します。
第26回では Pod と同じ期間だけ存在する emptyDir と、ノードのファイルをマウントする hostPath を扱いました。この回はその続きで、第26回で「第27回で扱う」とした永続的なボリュームの話です。まだの方は先にこちらをどうぞ。
emptyDir と hostPath の違いPod の中のコンテナがファイルを読み書きするボリュームの仕組みを図で説明します。Pod と同じ期間だけ存在する emptyDir と、ノードのファイルやディレクトリをマウントする hostPath の違いと、hostPath のセキュリティのリスクを押さえ、kind のクラスタで Pod を作り直したときに消えるデータと残るデータを比べる手順を紹介します。以下の図は、後半の「手元で試す」で作る Pod、PVC、PersistentVolume と、PersistentVolume を用意する StorageClass が、どう結び付いているかを表しています。
Pod は PVC を名前で参照し、PVC は 1 つの PersistentVolume に結び付きます(バインド)。PersistentVolume は、ノードのディレクトリやネットワークの向こうのストレージのような、実際の記憶領域の詳細を持つオブジェクトです。この図は、PVC の要求に応じて、StorageClass の provisioner が PersistentVolume を作った場合を描いています。StorageClass はどの provisioner を使うかなどを書いた設定のオブジェクトです。PersistentVolume を実際に作るのは provisioner というプログラムで、kind のクラスタでは local-path-storage の Namespace で動く Pod がそれにあたると考えられます(後述)。Pod と PVC は Namespace に属し、PersistentVolume と StorageClass はどの Namespace にも属しません。
図では PVC から StorageClass へ「storageClassName で指定」の線を引いていますが、後半の手順の PVC には storageClassName を書きません。書かなかった PVC には既定の StorageClass の名前が補われ、結果として図と同じ結び付きになるはずです(「既定の StorageClass」で説明します)。
参考:Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes、Namespaces | Kubernetes、StorageClass | Kubernetes API Reference、Storage Classes | Kubernetes、Admission Control in Kubernetes | Kubernetes
PersistentVolume と PVC とは
PersistentVolume(以下、PV)は クラスタの中の、ひとまとまりのストレージ です。公式ドキュメントによると、PV は管理者が用意するか、StorageClass を使って動的に用意されます。ノードがクラスタの資源であるのと同じく、PV もクラスタの資源です。PV は第26回のボリュームと同じくボリュームの種類の 1 つ(公式ドキュメントではボリュームのプラグインと呼びます)ですが、PV を使う個々の Pod とは関係なく存在します(a lifecycle independent of any individual Pod)。PV のオブジェクトには NFS や iSCSI、クラウドの提供元ごとのストレージなど、ストレージの実装の詳細が書かれます。NFS と iSCSI は一般に、ネットワークの向こうのストレージを使うための方式の名前です。
PersistentVolumeClaim(以下、PVC)は 利用者がストレージを求める要求 です。公式ドキュメントは PVC を Pod にたとえています。Pod がノードの資源を使うように、PVC は PV の資源を使います。Pod が CPU やメモリーの量を求められるように、PVC は大きさとアクセスモード(後述)を求められます。
利用者は大きさとアクセスモードだけでなく性能などの性質が違う PV を、用途に応じて使い分けたいことがよくあります。公式ドキュメントによると、管理者がその実装の詳細を利用者に見せずに、いろいろな PV を用意できるようにするのが StorageClass です。StorageClass は管理者が提供するストレージの種類(クラス)を表します。クラスが何を表すか(性能の水準、バックアップの方針など)は管理者が決めるもので、Kubernetes 自身は決めていません。
参考:Persistent Volumes | Kubernetes、Storage Classes | Kubernetes
この回で扱う 3 つのリソースを次の表にまとめます。略称は kubectl のコマンドで使える短い名前です。
| リソース | 公式ドキュメントの説明 | Namespace | 略称 |
|---|---|---|---|
| PersistentVolume | 管理者が用意するか、StorageClass で動的に用意された、クラスタの中のストレージ | 属さない | pv |
| PersistentVolumeClaim | 利用者のストレージの要求。大きさとアクセスモードを求める | 属する | pvc |
| StorageClass | 管理者が提供するストレージのクラス。動的に PV を用意するときの設定を持つ | 属さない | sc |
参考:Persistent Volumes | Kubernetes、Command line tool (kubectl) | Kubernetes、Namespaces | Kubernetes、Persistent Volumes | Kubernetes、StorageClass | Kubernetes API Reference
なぜ PersistentVolume と PVC が必要なのか
第26回で見たとおり、emptyDir のデータは Pod がノードから消えると消えます。hostPath はノードのファイルを使うので、Pod を消してもノードに残ると考えられます。ただしデータはそのノードに縛られ、公式ドキュメントは hostPath には多くのセキュリティのリスクがあると警告しています。また、どちらも Pod のマニフェストにボリュームの種類と中身を直接書く使い方でした。
公式ドキュメントは PV の仕組みの出発点として、ストレージの管理は計算資源の管理とは別の問題 だと述べています。PV の仕組み(PersistentVolume subsystem)は、ストレージをどう用意するかの詳細を、どう使うかから切り離す API を、利用者と管理者に提供します。そのために PV と PVC という 2 つの API のリソースがあります。
用意する側と使う側が分かれていることは、持ち運べるマニフェストの書き方の説明にも表れています。公式ドキュメントは、さまざまなクラスタで動かす、永続的なストレージが必要なマニフェストのひな形や例の書き方を勧めています。マニフェスト一式に PVC は入れ、PV は入れません。マニフェストを使う利用者が、PV を作る権限を持っていないかもしれないからです。StorageClass の名前はマニフェストを使う人が選べるようにし、指定がなければ storageClassName を書かずに既定の StorageClass に任せる、という書き方も挙がっています。利用者は「1Gi の読み書きできるストレージがほしい」という要求だけを PVC に書き、どのストレージで応えるかは管理者とクラスタに任せられる、と読めます。Gi は容量の単位で、一般に 1Gi は 1,073,741,824 バイトです。公式ドキュメントも、インストールの方法によっては既定の StorageClass が入ることや、多くのクラスタ環境に既定の StorageClass があることに触れています。後半の手順の kind のクラスタでは、kind が管理者の役として既定の StorageClass を入れていると考えられます。読者は利用者として PVC と Pod だけを書きます。
参考:Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes
PersistentVolume と PVC の仕組み
PV と PVC のライフサイクル
公式ドキュメントによると、PV はクラスタの資源で、PVC はその資源の要求であり、資源の引換券(claim check)の役目もします。PV と PVC は、用意する(Provisioning)、結び付ける(Binding)、使う(Using)、取り戻す(Reclaiming)の順に進みます。この節では、この順に見ていきます。
プロビジョニング:静的と動的
PV を用意すること(プロビジョニング)には、静的(static)と動的(dynamic)の 2 つの方法があります。公式ドキュメントの説明を次の表にまとめます。
| 方法 | 公式ドキュメントの説明 |
|---|---|
| 静的 | クラスタの管理者が、前もっていくつかの PV を作る。PV には実際のストレージの詳細が書かれ、利用者が使える状態で Kubernetes の API にある |
| 動的 | 管理者が作った静的な PV のどれも PVC に合わないとき、クラスタはその PVC のためにボリュームを動的に用意しようとすることがある。用意は StorageClass に基づく |
公式ドキュメントによると、動的なプロビジョニングが行われるには、PVC がストレージのクラスを求めていて、かつ管理者がそのクラスを作って動的なプロビジョニング向けに設定している必要があります(must)。クラスに ""(空の文字列)を求める PVC は、自分への動的なプロビジョニングを実質的に無効にします。また、StorageClass に基づく動的なプロビジョニングを使えるようにするには、管理者が kube-apiserver の DefaultStorageClass のアドミッションコントローラーを有効にする必要がある、とも書かれています。アドミッションコントローラーは、kube-apiserver がリクエストを受け付けるときに中身を確かめたり書き換えたりする仕組みです(第40回)。アドミッションコントローラーの説明によると、DefaultStorageClass は特定のクラスを求めない PVC が作られるときに働き、その PVC に既定のクラスを書き加えます。クラスを名前で指定した PVC は、このアドミッションコントローラーの対象ではないと読めます。詳しくは後の「既定の StorageClass」で見ます。
参考:Persistent Volumes | Kubernetes、Admission Control in Kubernetes | Kubernetes、Admission Control in Kubernetes | Kubernetes
動的なプロビジョニングを説明するページは、動的なプロビジョニングがないと、管理者がクラウドやストレージの提供元に頼んでストレージを作り、それを表す PV を Kubernetes に作る必要がある、と説明しています。動的なプロビジョニングを使うと、管理者が前もってストレージを用意しておく必要がなくなり、利用者が PVC を作ったときにストレージが自動で用意されます。
バインド
PVC と PV を結び付けることをバインドと呼びます。公式ドキュメントによると、コントロールプレーンの制御ループが新しい PVC を見張り、合う PV があれば見つけて、PVC と結び付けます。第6回で見たとおり、コントローラーは実際の状態を望ましい状態に近づける制御ループで、このシリーズでは調整ループと呼んでいます。この制御ループは kube-controller-manager の persistentvolume-binder-controller だと考えられます(リファレンスのコントローラーの一覧にあり、既定で無効なものには入っていません)。
参考:Persistent Volumes | Kubernetes、Controllers | Kubernetes、kube-controller-manager | Kubernetes
バインドについて公式ドキュメントが説明していることを、次の表にまとめます。
| 項目 | 公式ドキュメントの説明 |
|---|---|
| 動的に用意した PV | 新しい PVC のために動的に用意した PV なら、制御ループは必ずその PV をその PVC にバインドする |
| 静的な PV | 利用者は少なくとも求めた量を得るが、ボリュームは求めた量より大きいことがある |
| 1 対 1 | PVC と PV のバインドは 1 対 1。どうバインドされたかによらず、バインドは排他的(exclusive)になる |
| 合う PV が無いとき | PVC はいつまでもバインドされないまま残る。合うボリュームが使えるようになれば、バインドされる |
1 対 1 の結び付きは PV の spec.claimRef と PVC の spec.volumeName の双方向で記録されます(公式ドキュメントは bi-directional binding と呼んでいます)。API リファレンスによると、2 つのうち結び付きを決める正式な値は PVC の volumeName です。
公式ドキュメントは、合う PV が無いときの例も挙げています。50Gi の PV がたくさんあるクラスタでも、100Gi を求める PVC には合いません。100Gi の PV がクラスタに加われば、その PVC はバインドされます。
参考:Persistent Volumes | Kubernetes、PersistentVolume | Kubernetes API Reference、PersistentVolumeClaim | Kubernetes API Reference
公式のチュートリアルには、静的な PV に PVC をバインドする例があります。管理者の役で、次の hostPath の PV を作っておきます。ノードの /mnt/data を使う 10Gi の PV で、StorageClass の名前を manual にしています。
apiVersion: v1
kind: PersistentVolume
metadata:
name: task-pv-volume
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"参考:Configure a Pod to Use a PersistentVolume for Storage | Kubernetes
続けて、storageClassName: manual で 3Gi を求める PVC を作ると、コントロールプレーンは同じ StorageClass の、要求を満たす PV を探してバインドします。PVC を一覧すると、次のように表示されると書かれています。
参考:Configure a Pod to Use a PersistentVolume for Storage | Kubernetes
STATUS 列の Bound はバインドされたことを、VOLUME 列はバインドされた PV の名前を表します。3Gi を求めた PVC の CAPACITY 列が 10Gi なのは、表の「静的な PV」の行のとおり、求めた量より大きい PV にバインドされたからだと読めます。
Pod から PVC を使う
公式ドキュメントによると、Pod は PVC をボリュームとして使います。Pod の volumes に persistentVolumeClaim の項目を書き、claimName に PVC の名前を入れます。クラスタは PVC を調べてバインドされた PV を見つけ、そのボリュームを Pod のためにマウントします。PVC はそれを使う Pod と同じ Namespace になければなりません。以下は公式ドキュメントにある、myclaim という PVC をコンテナの /var/www/html にマウントする Pod の例です。
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: myfrontend
image: nginx
volumeMounts:
- mountPath: "/var/www/html"
name: mypd
volumes:
- name: mypd
persistentVolumeClaim:
claimName: myclaim参考:Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes、Pod | Kubernetes API Reference
volumeMounts の書き方は第26回の emptyDir や hostPath と同じです。違うのは volumes の中身で、ストレージの詳細を書かずに PVC の名前だけを書きます。公式ドキュメントによると、PVC がバインドされていれば、バインドされた PV は、利用者が必要とするあいだその利用者のものになります。
使っている最中の PVC や PV が消されないようにする仕組み(Storage Object in Use Protection)もあります。公式ドキュメントによると、Pod が使っている PVC を利用者が消しても、PVC はすぐには消えず、どの Pod にも使われなくなるまで削除が先送りされます。PVC にバインドされている PV を管理者が消しても、PVC とのバインドが無くなるまで削除が先送りされます。このとき PVC には kubernetes.io/pvc-protection という、PV には kubernetes.io/pv-protection というファイナライザーが付いています。ファイナライザーは、消す印が付いたオブジェクトを、決まった条件を満たすまで完全には消さないよう Kubernetes に伝えるキーです。
回収ポリシー
利用者がボリュームを使い終えたら、PVC を消します。公式ドキュメントによると、PV の回収ポリシー(reclaim policy)は、PVC から解放された後のボリュームをどうするかをクラスタに伝えます。v1.37 時点の公式ドキュメントに沿って、回収ポリシーを次の表にまとめます。
| 回収ポリシー | PVC を消した後に起きること |
|---|---|
Retain | PV は残り、解放された(Released)状態になる。前の利用者のデータが残っているので、ほかの PVC にはまだ使えず、管理者が手で取り戻せる |
Delete | 対応しているボリュームのプラグインなら、PV のオブジェクトと、外部の実際のストレージの両方を消す |
Recycle | 非推奨。対応しているプラグインなら、ボリュームの中身を簡単に消して(rm -rf /thevolume/*)、新しい PVC に使えるようにする |
Retain の PV を管理者が手で取り戻す手順は、PV を消す、実際のストレージのデータを手で片付ける、実際のストレージを手で消す、の 3 つです。PV を消しても、外部の実際のストレージは残ります。同じストレージを使い直したいときは、同じストレージを指す PV を新しく作ります。
Recycle の回収ポリシーは非推奨です。公式ドキュメントは警告(Warning)でそう示し、代わりに動的なプロビジョニングを使うのが推奨の方法だとしています。v1.37 時点で Recycle に対応しているのは nfs と hostPath のボリュームの種類だけです。
参考:Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes
回収ポリシーの既定は PV の作られ方で違います。API リファレンスによると、手で作った PV の既定は Retain、動的に用意した PV の既定は Delete です。公式ドキュメントによると、動的に用意した PV は StorageClass の reclaimPolicy を受け継ぎます。StorageClass の reclaimPolicy に書けるのは Delete か Retain で、書かなければ Delete になります。公式ドキュメントは、管理者が利用者の期待に合わせて StorageClass を設定すべきで(should)、そうでなければ PV を作った後に書き換える必要がある(must)、としています。
参考:PersistentVolume | Kubernetes API Reference、Persistent Volumes | Kubernetes、Storage Classes | Kubernetes
アクセスモード
アクセスモードはボリュームをどうマウントできるかを表します。公式ドキュメントによると、ボリュームの提供元によってできることが違い、PV のアクセスモードはその PV が対応するものに設定されます。v1.37 時点のアクセスモードを次の表にまとめます。
| アクセスモード | 略称 | 公式ドキュメントの説明 |
|---|---|---|
ReadWriteOnce | RWO | 1 つのノードから読み書きできる形でマウントできる。同じノードで動く複数の Pod からはアクセスできる場合がある |
ReadOnlyMany | ROX | 多くのノードから読み取り専用でマウントできる |
ReadWriteMany | RWX | 多くのノードから読み書きできる形でマウントできる |
ReadWriteOncePod | RWOP | 1 つの Pod から読み書きできる形でマウントできる。v1.29 から stable で、CSI のボリュームだけが対応する |
ReadWriteOnce の「Once」は 1 つのノードのことで、1 つの Pod のことではありません。クラスタ全体で 1 つの Pod だけに読み書きさせたいときは ReadWriteOncePod を使う、と公式ドキュメントは説明しています。CSI は第18回で触れた、外部のストレージを Kubernetes につなぐための共通の仕組みです。
公式ドキュメントはアクセスモードについて、次の 2 つを注意しています。
- Kubernetes はアクセスモードを PVC と PV を合わせるために使い、マウントした後の書き込みは禁止しない
- ボリュームが多くのモードに対応していても、一度に使えるのは 1 つのモードだけ
1 つ目について、ReadWriteOnce、ReadOnlyMany、ReadWriteMany を指定しても、ボリュームに制約は設けられないと書かれています。たとえば ReadOnlyMany で作った PV でも、読み取り専用になる保証はありません。ReadWriteOncePod を指定した場合は、ボリュームは制約され、1 つの Pod にしかマウントできません。
同じ注意(Note)には、場合によってはアクセスモードが PV をマウントできる場所を制約することもある、とも書かれています。ここでの制約はマウントした後の書き込みではなく、どこにマウントできるかの話です。どんな場合かの具体例は、同じ注意の中には挙がっていません。
どのアクセスモードに対応するかはボリュームのプラグインごとに違い、公式ドキュメントの表の例では HostPath は ReadWriteOnce だけ、NFS は ReadWriteOncePod 以外の 3 つに対応しています。公式ドキュメントによると、PV のバインドは排他的で、PVC は Namespace に属するので、「Many」のモード(ROX、RWX)で PVC をマウントできるのは 1 つの Namespace の中だけです。
参考:Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes
PV と PVC のマニフェスト
v1.37 時点の API リファレンスと公式ドキュメントに沿って、PVC と PV の主なフィールドを次の表にまとめます。
| フィールド | 内容 |
|---|---|
PVC の spec.accessModes | ボリュームに求めるアクセスモード |
PVC の spec.resources.requests.storage | ボリュームに求める最小の大きさ |
PVC の spec.storageClassName | 求める StorageClass の名前。書かない場合と "" の場合で扱いが違う(後述) |
PVC の spec.selector | バインドする PV をラベルで絞り込む。今のところ、空でない selector を持つ PVC には PV を動的に用意できない |
PVC の spec.volumeName | この PVC を支える PV への結び付き |
PV の spec.capacity.storage | PV の大きさ。今のところ設定できる資源は大きさだけ |
PV の spec.persistentVolumeReclaimPolicy | 回収ポリシー |
PV の spec.storageClassName | PV が属する StorageClass の名前。空なら、どの StorageClass にも属さない |
PV と PVC の spec.volumeMode | Filesystem(ファイルシステムとして使う)か Block(ファイルシステムを作らずに使う。この回では使わない)。書かなければ Filesystem |
参考:PersistentVolumeClaim | Kubernetes API Reference、PersistentVolume | Kubernetes API Reference、Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes
StorageClass の名前は PV と PVC を合わせる条件にもなります。公式ドキュメントによると、あるクラスの PV は、そのクラスを求める PVC にだけバインドできます。storageClassName が無い PV はクラスを持たず、特定のクラスを求めない PVC にだけバインドできます。PVC の側から見ても、求めたクラスと同じ storageClassName を持つ PV だけがバインドできます。
参考:Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes
StorageClass と volumeBindingMode
StorageClass は動的なプロビジョニングの設定を持ちます。公式ドキュメントによると、各 StorageClass には provisioner、parameters、reclaimPolicy のフィールドがあり、そのクラスの PV を、PVC に応えて動的に用意するときに使われます。StorageClass の名前には意味があり、利用者はその名前で特定のクラスを求めます。v1.37 時点の公式ドキュメントと API リファレンスに沿って、StorageClass の主なフィールドを次の表にまとめます。
| フィールド | 内容 |
|---|---|
provisioner | PV を用意するボリュームのプラグインを決める。必須 |
parameters | このクラスのボリュームを作る provisioner に渡す設定 |
reclaimPolicy | 動的に作った PV の回収ポリシー。Delete か Retain で、書かなければ Delete |
volumeBindingMode | バインドと動的なプロビジョニングをいつ行うか。書かなければ Immediate |
allowVolumeExpansion | このクラスでボリュームの拡張を許すかどうか |
参考:Storage Classes | Kubernetes、Storage Classes | Kubernetes、StorageClass | Kubernetes API Reference
provisioner には Kubernetes に同梱された「内部」の provisioner(名前が kubernetes.io で始まるもの)のほか、外部の provisioner も指定できます。公式ドキュメントによると、外部の provisioner は Kubernetes が定めた仕様に従う独立したプログラムで、作者がコードの置き場所や動かし方を自由に決められます。後で見る kind の既定の StorageClass の provisioner は名前が kubernetes.io で始まらないので、外部の provisioner だと考えられます。
volumeBindingMode はこの回の手順の結果を左右するフィールドです。v1.37 時点の公式ドキュメントに沿って、2 つの値を次の表にまとめます。
| 値 | バインドと動的なプロビジョニングが起きるとき |
|---|---|
Immediate(既定) | PVC が作られたらすぐ。すべてのノードから使えるとは限らないストレージでは、Pod のスケジューリングの条件を知らないまま PV がバインドされたり用意されたりする |
WaitForFirstConsumer | その PVC を使う Pod が作られるまで遅らせる。PV は Pod のスケジューリングの条件に合うように選ばれるか、用意される |
参考:Storage Classes | Kubernetes、StorageClass | Kubernetes API Reference
公式ドキュメントによると、Immediate には弱点があります。すべてのノードから使えるとは限らない、置き場所に制約のあるストレージでは、Pod のスケジューリングの条件を知らずに PV がバインドされたり用意されたりします。その結果、Pod をどのノードにも置けなくなることがあります。管理者は WaitForFirstConsumer を指定して、この問題に対処できます。スケジューリングの条件には資源の要求(第29回)、nodeSelector(第31回)、Pod のアフィニティとアンチアフィニティ(第33回)、Taint と Toleration(第32回)などが含まれます。API リファレンスでは、WaitForFirstConsumer のときは Pod のスケジューリングの中で、ボリュームのプロビジョニングとバインドが行われると説明されています。スケジューリングは、第4回で見た kube-scheduler が Pod を置くノードを選ぶことです。
参考:Storage Classes | Kubernetes、StorageClass | Kubernetes API Reference
以下の図は、volumeBindingMode が WaitForFirstConsumer の既定の StorageClass があるクラスタで、PVC と Pod を作ってから、PV が Pod にマウントされるまでの流れを表しています。後半の「手元で試す」の kind のクラスタで起きると考えられる流れです。
矢印は処理の順番を表していて、コンポーネントどうしが直接やり取りすることを表すものではありません。図では利用者からの矢印が 1 にだけ入っていますが、1 の PVC と 2 の Pod は、利用者がそれぞれ別に作ります。Pod が自動で作られるわけではなく、手順でも PVC を作ってから、間を置いて Pod を作ります。standard が既定の StorageClass なら、1 でそれを補うのは DefaultStorageClass のアドミッションコントローラーです(「既定の StorageClass」で説明します)。standard の volumeBindingMode が WaitForFirstConsumer なら、1 で PVC が作られてもプロビジョニングもバインドも始まらず、PVC は Pending のままになるはずです。2 で PVC を使う Pod が作られてはじめて、PV が用意され、バインドされると考えられます。
API リファレンスによると、WaitForFirstConsumer のときのプロビジョニングとバインドは Pod のスケジューリングの中で行われます。図では順に並べていますが、4 と 5 は 3 のスケジューリングの途中で起きると読めます。kube-scheduler が選んだノードを provisioner がどうやって知るかは、この回では扱いません。4 の local-path-provisioner は kind の既定の StorageClass の provisioner で、後で見ます。local-path-provisioner の README によると、ボリュームを作る前に setup のスクリプトが動き、ノードにボリュームのディレクトリを用意します。5 は前に見た persistentvolume-binder-controller が行うと考えられます。6 でマウントするのはノードの kubelet だと考えられます。公式ドキュメントの PVC をボリュームとして使う節は、ボリュームがホストと Pod にマウントされると説明していて、コンポーネントの名前は挙げていません。
参考:Storage Classes | Kubernetes、StorageClass | Kubernetes API Reference、Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes、rancher/local-path-provisioner
この図の流れには、公式ドキュメントで確かめられない部分があります。公式ドキュメントは、WaitForFirstConsumer で動的なプロビジョニングに対応するものとして、CSI のボリューム(その CSI のドライバーが対応している場合)だけを挙げています。一方、README によると local-path-provisioner が作る PV は hostPath か local で、PV の種類の一覧で csi とは別の種類です。README の StorageClass の例が volumeBindingMode: WaitForFirstConsumer を使っていることから、local-path-provisioner は WaitForFirstConsumer に対応していると考えられます。
参考:Storage Classes | Kubernetes、Persistent Volumes | Kubernetes、rancher/local-path-provisioner
既定の StorageClass
PVC に storageClassName を書かなかったときに使われるのが、既定の StorageClass です。公式ドキュメントによると、StorageClass に storageclass.kubernetes.io/is-default-class というアノテーションを true で付けると、既定の StorageClass になります。アノテーションはリソースに付けるキーと値の組のうち、選ぶためではなく情報を書いておくためのものです(第42回)。
参考:Storage Classes | Kubernetes、Change the default StorageClass | Kubernetes、Annotations | Kubernetes
PVC を作るときに既定の StorageClass を当てはめるのは、kube-apiserver の DefaultStorageClass のアドミッションコントローラーです。公式ドキュメントによると、このアドミッションコントローラーは、特定のストレージのクラスを求めない PVC が作られるのを見て、既定のストレージのクラスを自動で書き加えます。既定の StorageClass が無ければ何もしません。PVC の作成にだけ働き、更新は無視します。v1.37 時点では、DefaultStorageClass は既定で有効なアドミッションコントローラーの一覧に入っています。既定で有効なものは、kube-apiserver のフラグで指定しなくても有効です。ほかのものを足すときは --enable-admission-plugins のフラグで指定します。アドミッションコントローラーは公式ドキュメントではアドミッションプラグインとも呼ばれています。
参考:Admission Control in Kubernetes | Kubernetes、Admission Control in Kubernetes | Kubernetes、Admission Control in Kubernetes | Kubernetes、kube-apiserver | Kubernetes、Dynamic Volume Provisioning | Kubernetes
PVC の storageClassName は書かない場合と "" にした場合で扱いが違います。公式ドキュメントの説明を次の表にまとめます。
PVC の storageClassName | 扱い |
|---|---|
| クラスの名前 | そのクラスの PV だけがバインドできる |
"" | 常にクラスを持たない PV を求めているとみなされ、クラスを持たない PV にだけバインドできる |
書かない(DefaultStorageClass が有効) | 既定の StorageClass があれば、その名前が補われ、既定のクラスの PV にだけバインドできる。既定が無ければ、クラスタは PVC の作成に、無効なときと同じように応える |
書かない(DefaultStorageClass が無効) | 既定の StorageClass という考え方が無く、storageClassName は既定が使えるようになるまで書かれないまま残る。後で既定の StorageClass ができると既定の名前に書き換えられることがあり、その後は storageClassName が "" の PV にはバインドしなくなる |
参考:Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes、Admission Control in Kubernetes | Kubernetes
表の最後の行は v1.28 から stable の、既定の StorageClass をさかのぼって当てはめる機能(retroactive default StorageClass assignment)のことです。公式ドキュメントによると、既定の StorageClass が無いクラスタでも、storageClassName を書かずに PVC を作れます。その PVC の storageClassName は、既定ができるまで書かれないままです。既定の StorageClass ができると、コントロールプレーンは storageClassName を書いていない PVC を探して、新しい既定に合わせて書き換えます。storageClassName を "" と明示した PVC は書き換えません。
参考:Persistent Volumes | Kubernetes、Storage Classes | Kubernetes
既定の StorageClass はいくつも作れてしまいます。公式ドキュメントによると、複数の StorageClass が既定になっているクラスタで storageClassName を書かずに PVC を作ると、Kubernetes はいちばん新しく作られた既定の StorageClass を使います。そのうえで、既定にする StorageClass は 1 つだけにするよう努めるべきだ(should)としています。複数を許しているのは、移行(migration)をなめらかに行えるようにするためです。何から何への移行かは書かれていませんが、既定の StorageClass を古いものから新しいものへ切り替えるあいだのことだと考えられます。インストールの方法によっては、クラスタを作るときに既定の StorageClass が入ることがある、とも書かれています。
参考:Storage Classes | Kubernetes、Persistent Volumes | Kubernetes
kind の既定の StorageClass
kind のクラスタにも、既定の StorageClass が入っていると考えられます。ただし、kind の公式ドキュメント(kind.sigs.k8s.io の設定、既知の問題、ノードイメージのページ)には、既定の StorageClass の説明が見当たりませんでした。kind のリリースノートで確かめられたのは次の 3 つです。
- v0.6.0 のリリースノートに、既定のストレージのクラスで GA(正式版)のストレージのクラスのアノテーションを使うように変えた、と書かれている
- v0.7.0 のリリースノートに、rancher の provisioner を最初から組み込んで動的な PV への対応を大きく改善した、既定のストレージのクラスがそれまでの
k8s.io/host-pathを使う実装から変わった、と書かれている - v0.33.0 のリリースノートにも、local-path-provisioner への修正が載っている
参考:Release v0.6.0 · kubernetes-sigs/kind、Release v0.7.0 · kubernetes-sigs/kind、Release v0.33.0 · kubernetes-sigs/kind
このことから、kind v0.33.0 でも local-path-provisioner が既定の StorageClass の provisioner として入っていると考えられます。第4回で見た local-path-storage の Namespace の Pod が、それにあたります。StorageClass の中身は kind v0.33.0 のソースコードを補助として読み、次の表にまとめました。ドキュメントで確かめた値ではないので推測として扱い、後半の「手元で試す」でクラスタから実際の値を読みます。
| 項目 | kind v0.33.0 のソースコードにある値 |
|---|---|
| 名前 | standard |
| 既定かどうか | storageclass.kubernetes.io/is-default-class: "true" のアノテーションがある |
provisioner | rancher.io/local-path |
volumeBindingMode | WaitForFirstConsumer |
reclaimPolicy | Delete |
| ノードのデータの置き場所 | local-path-provisioner の設定で /var/local-path-provisioner |
公式ドキュメントによると、Kubernetes の local のボリュームは、前もって作った PV としてだけ使え、動的なプロビジョニングには対応していません。Kubernetes 自身にはノードのローカルのストレージを動的に用意する仕組みが無いので、local-path-provisioner のような外部の provisioner が PV を作る、と読めます。local-path-provisioner の README によると、local-path-provisioner は各ノードのローカルのストレージを使えるようにするもので、設定に応じて hostPath か local の PV をノードに自動で作ります。どちらを使うかのアノテーションが無ければ、既定は hostPath です。ボリュームのディレクトリの名前は既定では {{ .PVName }}_{{ .PVC.Namespace }}_{{ .PVC.Name }} のひな形で付けられます。kind のソースコードの StorageClass には、この種類を選ぶアノテーションは見当たらなかったので、kind のクラスタでは hostPath の PV が作られると考えられます。第26回で見た hostPath と同じく、データはノードのディレクトリに置かれ、そのノードに縛られるはずです。第26回の注意点で見たとおり、公式ドキュメントは hostPath の PV を、シングルノードのクラスタで試すためだけのもので、マルチノードのクラスタでは動かない、としています。このため、この回の手順ではシングルノードのクラスタを使います。
参考:Volumes | Kubernetes、Storage Classes | Kubernetes、rancher/local-path-provisioner、Persistent Volumes | Kubernetes
PV と PVC のフェーズ
PV と PVC には、それぞれ今の段階を表すフェーズ(status.phase)があります。手順で一覧するときに STATUS 列に出る値です。v1.37 時点の公式ドキュメントと API リファレンスに沿って、次の表にまとめます。
| リソース | フェーズ | 意味 |
|---|---|---|
| PV | Pending | まだ使える状態になっていない。公式ドキュメントによると、新しく作られた PV のフェーズは Pending になる |
| PV | Available | まだ PVC にバインドされていない、空いている資源 |
| PV | Bound | PVC にバインドされている |
| PV | Released | PVC は消されたが、ストレージがまだクラスタに取り戻されていない |
| PV | Failed | 自動での取り戻しに失敗した |
| PVC | Pending | まだバインドされていない |
| PVC | Bound | バインドされている |
| PVC | Lost | バインドされていた PV が無くなり、データが失われた |
参考:Persistent Volumes | Kubernetes、PersistentVolume | Kubernetes API Reference、PersistentVolumeClaim | Kubernetes API Reference
手元で試す
ここまでの内容を手元の kind のクラスタで試す手順を示します。まず既定の StorageClass を確かめ、storageClassName を書かない PVC を作ります。続けてその PVC を使う Pod を作り、PV が自動で用意されて PVC にバインドされる流れを観察します。Pod を作り直してもデータが残ることを確かめたあと、PVC を消して PV も消えることを見ます。コマンドは 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
kind でクラスタを作る手順は、以下の記事で解説しています。この回では第26回と同じシングルノードのクラスタを使います。「kind の既定の StorageClass」で見たとおり、kind で作られるのも hostPath の PV だと考えられ、データがノードごとにあるので、Pod を作り直しても同じノードに置かれるようにするためです。
シングルノードのクラスタを用意する
まず、前の回のクラスタが残っていないかを確かめます。
$ 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 になっていなければ、少し待ってからもう一度実行します。
既定の StorageClass を確かめる
クラスタにある StorageClass を一覧します。
$ kubectl get storageclass --context kind-kind
ソースコードから考えると、手元では見出しの行に続いて StorageClass の行が 1 行だけ表示されるはずです。その行の NAME 列は standard (default)、PROVISIONER 列は rancher.io/local-path になると考えられます。手元の kubectl では、RECLAIMPOLICY や VOLUMEBINDINGMODE のような列が増えて表示されることもあると考えられます。
公式ドキュメントの既定の StorageClass を変える手順のページには、kubernetes.io/gce-pd(名前から、Google Compute Engine の永続ディスクと考えられます)を使う次のような出力の例が載っています。既定の StorageClass には (default) の印が付くと書かれています。
この例の PROVISIONER 列は kubernetes.io/gce-pd で、手元とは違います。例にある gold の行も、手元には無いはずです。
続けて、standard の中身を YAML の形で表示します。-o yaml は第5回で使った、オブジェクトの中身をそのまま表示する出力の形式です。
$ kubectl get storageclass standard -o yaml --context kind-kind
「kind の既定の StorageClass」の表のとおりなら、metadata.annotations に storageclass.kubernetes.io/is-default-class: "true"、provisioner に rancher.io/local-path、reclaimPolicy に Delete、volumeBindingMode に WaitForFirstConsumer が見えるはずです。表と違う値が表示されたら、手元のクラスタの値を正として読み進めてください。特に volumeBindingMode が Immediate なら、次の節で PVC は Pod を待たずにバインドされるはずです。
参考:StorageClass | Kubernetes API Reference、Storage Classes | Kubernetes
provisioner の本体の Pod も見ておきます。
$ kubectl get pods -n local-path-storage --context kind-kind
local-path-provisioner で始まる名前の Pod が 1 つあり、STATUS 列が Running になっているはずです。kind v0.33.0 のソースコードでは、この Pod は local-path-storage の Namespace の Deployment(第12回)が作っています。
PVC を作る
以下はこの回で使う PVC のマニフェストです。1Gi の、1 つのノードから読み書きできるストレージを求めます。storageClassName はわざと書いていません。pv-demo-pvc.yaml という名前で、コマンドを実行するディレクトリに保存します。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pv-demo-claim
labels:
app: pv-demo
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi参考:PersistentVolumeClaim | Kubernetes API Reference、Persistent Volumes | Kubernetes
公式ドキュメントの PVC の例から、volumeMode、storageClassName、selector を外し、大きさを 1Gi にしたものです。volumeMode は書かなければ Filesystem になります。今のところ、selector を書くと PV を動的に用意できないので外しました。app: pv-demo のラベルは次の監視でこの PVC だけを表示するために付けています。
ここからは端末を 2 つ使います。第26回と同じく、1 つ目の端末で変化を追い続け、2 つ目の端末でマニフェストを渡したり、ほかのリソースを見たりします。この回で 1 つ目の端末が追うのは PVC です。2 つ目の端末を開いたら、cd でマニフェストを保存したディレクトリに移動しておきます。
1 つ目の端末で、app=pv-demo のラベルを持つ PVC を監視します。この監視は片付けの前まで止めずに続けます。
$ kubectl get pvc --watch -l app=pv-demo --context kind-kind
PVC がまだ無いので、何も表示されないか、見つからないことを知らせる 1 行が出たあと、止まったように見えると考えられます。
2 つ目の端末で、PVC のマニフェストを渡します。
$ kubectl apply -f pv-demo-pvc.yaml --context kind-kind
persistentvolumeclaim/pv-demo-claim が作られたことを知らせる 1 行が出るはずです。1 つ目の端末には pv-demo-claim の行が書き足され、STATUS 列は Pending のまま変わらず、VOLUME 列も空のはずです。前の節で volumeBindingMode が WaitForFirstConsumer と表示されたなら、PVC を使う Pod が作られるまで、プロビジョニングもバインドも遅らされるからです。
2 つ目の端末で、PVC の storageClassName を取り出します。-o jsonpath は第5回で使った、フィールドを指定して取り出す出力の形式です。
$ kubectl get pvc pv-demo-claim -o jsonpath='{.spec.storageClassName}{"\n"}' --context kind-kind
マニフェストには書かなかったのに、standard と表示されるはずです。DefaultStorageClass のアドミッションコントローラーが、PVC を作るときに既定の StorageClass の名前を書き加えたからだと考えられます。
参考:Admission Control in Kubernetes | Kubernetes、JSONPath Support | Kubernetes
この時点では、PV はまだ無いはずです。PV を一覧して確かめます。
$ kubectl get pv --context kind-kind
見つからないことを知らせる 1 行が出るはずです。PV は Namespace に属さないので、-n を付けなくても、クラスタのすべての PV が一覧されます。
ノードの側の、local-path-provisioner がボリュームを置くと考えられるディレクトリも見ておきます。第26回と同じく、docker exec でノードのコンテナの中でコマンドを実行します。kind の Quick Start の例では、クラスタ名 kind のノードのコンテナの名前は kind-control-plane です。
$ docker exec kind-control-plane ls /var/local-path-provisioner
参考:docker container exec | Docker Docs、Quick Start | kind、kind のソースコード(local-path-provisioner)
まだボリュームは用意されていないので、何も表示されないはずです。このディレクトリ自体がまだ作られておらず、見つからないことを知らせるエラーの行が出ることもあると考えられます。どちらでも、この時点でボリュームのディレクトリが無いことに変わりはありません。
PVC が待っている理由は、kubectl describe の Events の欄でも見られます。
$ kubectl describe pvc pv-demo-claim --context kind-kind
Status の欄は Pending、StorageClass の欄は standard になっているはずです。Events の欄には、最初に使う Pod を待っていることを知らせるイベントが出ると考えられます。イベントの文面は公式ドキュメントでは確かめられませんでしたが、モードの名前から、WaitForFirstConsumer や「first consumer」のような語が含まれると考えられます。イベントが出ていなくても、Status が Pending のままなら、この節の手順としては問題ありません。
Pod を作って PV が用意されるのを見る
以下は PVC pv-demo-claim をコンテナの /data にマウントする Pod のマニフェストです。pv-demo-pod.yaml という名前で保存します。
apiVersion: v1
kind: Pod
metadata:
name: pv-demo
spec:
containers:
- name: busybox
image: busybox:1.28
command: ['sh', '-c', 'sleep 3600']
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: pv-demo-claim参考:Persistent Volumes | Kubernetes、Pod | Kubernetes API Reference
公式ドキュメントの PVC を使う Pod の例をもとに、イメージとコマンドを第26回と同じ busybox:1.28 と sleep 3600 にしたものです。sleep 3600 は 3600 秒(1 時間)待つだけのコマンドで、手順を終えるまでコンテナを動かしておくためのものです。
2 つ目の端末で、Pod のマニフェストを渡します。
$ kubectl apply -f pv-demo-pod.yaml --context kind-kind
pod/pv-demo が作られたことを知らせる 1 行が出るはずです。PVC を使う Pod ができたので、スケジューリングの中で PV が用意され、PVC にバインドされるはずです。1 つ目の端末には pv-demo-claim の行が書き足され、STATUS 列が Bound に変わり、VOLUME 列に PV の名前が入るはずです。STORAGECLASS 列は standard のはずです。PV を用意するのに少し時間がかかることがあります。
参考:Storage Classes | Kubernetes、Persistent Volumes | Kubernetes
2 つ目の端末で、PV を一覧します。
$ kubectl get pv --context kind-kind
PV が 1 つ表示されるはずです。公式のチュートリアルでは PVC にバインドされた PV を一覧すると、次のように表示されると書かれています。
参考:Configure a Pod to Use a PersistentVolume for Storage | Kubernetes
チュートリアルの例は管理者が手で作った静的な PV なので、手元の表示とはいくつか違うはずです。違いを次の表にまとめます。列の名前や並びも、手元の kubectl では違うことがあると考えられます。
| 列 | チュートリアルの例 | 手元で表示されるはずの値 |
|---|---|---|
NAME | 手で付けた task-pv-volume | 自動で付いた名前。local-path-provisioner の README の出力例では pvc- で始まる長い名前 |
CAPACITY | 10Gi | PVC が求めた大きさに応じた値と考えられる。ただし local-path-provisioner では上限として効かないと考えられる(「注意点」) |
RECLAIMPOLICY | Retain(手で作った PV の既定) | standard から受け継いだ Delete |
STATUS | Bound | Bound |
CLAIM | default/task-pv-claim | default/pv-demo-claim(Namespace と PVC の名前) |
STORAGECLASS | manual | standard |
参考:Persistent Volumes | Kubernetes、rancher/local-path-provisioner
CLAIM 列は、この PV が 1 対 1 でバインドされた PVC を表しています。1 つ目の端末の VOLUME 列の名前と、この PV の NAME 列が同じであることを確かめてください。管理者が前もって PV を作っていないのに PV ができていることが、動的なプロビジョニングです。
Pod も一覧します。
$ kubectl get pod pv-demo --context kind-kind
STATUS 列が Running になっていれば、コンテナが動き出していて、PV も Pod にマウントされたと考えられます。クラスタを作ったばかりなら、イメージの取得に時間がかかるので、Pending や ContainerCreating のまましばらく止まって見えることがあります(第9回)。
PV の中身を詳しく見ます。<PV の名前> には kubectl get pv の NAME 列の名前を入れます。
$ kubectl describe pv <PV の名前> --context kind-kind
公式ドキュメントには、hostPath の PV を kubectl describe で見た例が載っています。次の例は消す操作をしたあとで、ファイナライザーに守られている PV を見たものです。
手元の PV はまだ消していないので、表示はいくつか違うはずです。主な欄の違いを次の表にまとめます。
| 欄 | 例の値 | 手元で表示されるはずの値 |
|---|---|---|
Name | task-pv-volume | kubectl get pv の NAME 列と同じ、自動で付いた名前 |
Finalizers | [kubernetes.io/pv-protection] | 例と同じファイナライザーが含まれるはず(StorageObjectInUseProtection が付ける)。ほかのファイナライザーが並ぶこともあると考えられる |
Status | Terminating | Bound |
Claim | 空 | default/pv-demo-claim |
Source の Type | HostPath (bare host directory volume) | kind で hostPath の PV が作られると考えたとおりなら、例と同じく HostPath で始まる |
Source の Path | /tmp/data | /var/local-path-provisioner/ の下のディレクトリ。名前は README の既定のひな形のとおりなら <PV の名前>_default_pv-demo-claim と考えられる |
ラベルやアノテーションも例とは違うはずです。Source の欄が HostPath でなければ、手元の表示を正としてください。
参考:Persistent Volumes | Kubernetes、Admission Control in Kubernetes | Kubernetes、rancher/local-path-provisioner
さきほどと同じコマンドで、ノードの側からもそのディレクトリを見ます。
$ docker exec kind-control-plane ls /var/local-path-provisioner
kubectl describe pv の Path の欄の最後の部分と同じ名前のディレクトリが 1 つ表示されるはずです。PVC を作っただけの段階では無かったディレクトリが、Pod を作ったあとで用意されたと考えられます。README の、ボリュームを作る前に setup のスクリプトがノードにディレクトリを用意する、という説明にあたります。
参考:docker container exec | Docker Docs、kind のソースコード(local-path-provisioner)、rancher/local-path-provisioner
Pod を作り直してもデータが残ることを確かめる
第26回の emptyDir と比べるため、PV にファイルを書いてから Pod を作り直します。2 つ目の端末で、コンテナの /data にファイルを書きます。
$ kubectl exec pv-demo --context kind-kind -- sh -c 'echo persistent > /data/hello.txt'
何も表示されずに終われば、書き込めています。続けて Pod を削除します。
$ kubectl delete pod pv-demo --context kind-kind
Pod には終了までの猶予の時間(既定では 30 秒)があるので、kubectl delete が終わるまで少しかかることがあります。1 つ目の端末の PVC の行は、Bound のまま変わらないはずです。PV は個々の Pod とは関係なく存在し、PVC も Pod とは別のオブジェクトだからです。2 つ目の端末で kubectl get pv --context kind-kind を実行すると、PV も Bound のまま残っているはずです。
参考:Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes、Pod Lifecycle | Kubernetes
kubectl delete が終わったら、同じマニフェストで Pod を作り直し、ファイルを読みます。kubectl get pod pv-demo --context kind-kind で STATUS 列が Running になったのを確かめてから、cat を実行します。
$ kubectl apply -f pv-demo-pod.yaml --context kind-kind
$ kubectl get pod pv-demo --context kind-kind
$ kubectl exec pv-demo --context kind-kind -- cat /data/hello.txt
persistent と表示されるはずです。作り直した Pod は UID が違う別の Pod ですが(第26回)、同じ PVC を通して、同じ PV を使っています。第26回の emptyDir のデータは Pod を作り直すと消えましたが、PV のデータは残ると考えられます。StatefulSet と組み合わせて、Pod ごとに PVC を持たせる方法は第28回で扱います。
PVC を消して PV が消えるのを見る
最後に、使い終わった PVC を消すと、回収ポリシーに従って PV がどうなるかを見ます。わざと Pod より先に PVC を消して、使っている最中の PVC が守られることも確かめます。2 つ目の端末で、PVC を削除します。
$ kubectl delete pvc pv-demo-claim --wait=false --context kind-kind
--wait=false を付けたのは、kubectl delete が既定ではファイナライザーも含めて、リソースが消えるまで待つからです。この PVC は Pod が使っているので、付けないと、Pod を消すまでコマンドが終わらないはずです。
1 つ目の端末に何が表示されるかは、公式ドキュメントでは確かめられませんでした。PVC に消す印が付いて PVC が更新されるので、pv-demo-claim の行が書き足されることがあると考えられます。その行の STATUS 列が Terminating になるか、Bound のままに見えるかも確かめられませんでした。PVC が守られているかどうかは、次のコマンドで確かめます。
$ kubectl describe pvc pv-demo-claim --context kind-kind
公式ドキュメントによると、守られている PVC は Status が Terminating で、Finalizers の一覧に kubernetes.io/pvc-protection があります。手元でも、そう表示されるはずです。Pod が使っているあいだは、PVC の削除が先送りされているということです。
続けて、PVC を使っている Pod を削除します。
$ kubectl delete pod pv-demo --context kind-kind
Pod が消えると、PVC はどの Pod にも使われなくなるので、ファイナライザーが外されて PVC が消えるはずです。このときも 1 つ目の端末に行が書き足されることがあると考えられますが、表示は確かめられませんでした。PVC が消えると、PV は PVC から解放されます。「既定の StorageClass を確かめる」で standard の reclaimPolicy が Delete と表示されたなら、PV のオブジェクトも消されるはずです。少し待ってから、PVC と PV を一覧します。
$ kubectl get pvc -l app=pv-demo --context kind-kind
$ kubectl get pv --context kind-kind
どちらも、見つからないことを知らせる 1 行が出るはずです。PV が残っていたら、provisioner がまだ消していないことも考えられるので、少し待ってからもう一度一覧します。それでも残り続けるときは、STATUS 列と RECLAIMPOLICY 列を見ます。RECLAIMPOLICY 列が Retain で STATUS 列が Released なら、「回収ポリシー」で見たとおり、PV は手で消す必要があります。STATUS 列が Failed なら、自動での取り戻しに失敗しています。PV の片付けは次の「片付け」で行います。
参考:Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes、Persistent Volumes | Kubernetes
local-path-provisioner の README によると、ボリュームが消されたあとで teardown のスクリプトが動き、ノードのボリュームのディレクトリを片付けます。回収ポリシーが Delete で PV が消されたなら、ノードのディレクトリも片付けられるはずです。Retain で PV が残っている場合は、ディレクトリも残ると考えられます。ノードの側から確かめます。
$ docker exec kind-control-plane ls /var/local-path-provisioner
PV が消えていれば、さきほどのディレクトリが無くなり、何も表示されないはずです。PV が消えているのにディレクトリが残っていたら、片付けが終わっていないと考えられるので、少し待ってからもう一度見ます。
片付け
1 つ目の端末で Ctrl+C を押して、PVC の監視を止めます。PVC と Pod は前の節で消したので、2 つ目の端末で、残っていないことを確かめます。
$ kubectl get pvc,pod --context kind-kind
default の Namespace にリソースが無いことを知らせる 1 行が出るはずです。pv-demo-claim や pv-demo が残っていたら、次のコマンドで消します。--ignore-not-found はすでに消えたリソースがあってもエラーにしないためのオプションです。
$ kubectl delete -f pv-demo-pod.yaml -f pv-demo-pvc.yaml --ignore-not-found --context kind-kind
PV も残っていないことを確かめます。
$ kubectl get pv --context kind-kind
見つからないことを知らせる 1 行が出るはずです。PV が残っていたら、NAME 列の名前を <PV の名前> に入れて消します。
$ kubectl delete pv <PV の名前> --context kind-kind
Retain の PV を手で消しても、ノードのディレクトリは残ると考えられます。「回収ポリシー」で見たとおり、PV を消しても外部の実際のストレージは残るからです。このあとクラスタを削除すればノードのコンテナごと消えるので、ディレクトリを手で消さなくても、そこにあったファイルも一緒に消えると考えられます。
参考:Persistent Volumes | Kubernetes、kubectl delete | Kubernetes
手元に保存したマニフェストのファイル(pv-demo-pvc.yaml、pv-demo-pod.yaml)は、クラスタには影響しないので残しておいても構いません。要らなければ rm pv-demo-pvc.yaml pv-demo-pod.yaml で消します。rm は一般に、ファイルを消すコマンドです。
最後に、クラスタを削除し、消えたことを確かめます。
$ kind delete cluster
$ kind get clusters
kind get clusters に kind が表示されなくなったことを確かめます。kubeconfig に kind-kind のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-kind で消します。
注意点
PV と PVC でつまずきやすい点と、バージョンで変わる点を、次の表にまとめます。
| 項目 | 内容 |
|---|---|
合う PV が無い PVC は Pending のまま | 合うボリュームが無ければ、PVC はいつまでもバインドされない。動的なプロビジョニングやストレージの仕組みがクラスタに無いことを示している場合がある |
WaitForFirstConsumer では Pod を作るまで Pending | 異常ではなく、PVC を使う Pod が作られるまでバインドとプロビジョニングが遅らされている |
WaitForFirstConsumer と nodeName | Pod の nodeName でノードを直接指定すると、スケジューラーが飛ばされ、PVC は Pending のままになる。代わりに kubernetes.io/hostname の nodeSelector を使える(第31回) |
storageClassName を書かないのと "" は違う | 書かなければ既定の StorageClass が補われる。"" ならクラスを持たない PV にだけバインドする |
selector を書くと動的に用意されない | 今のところ、空でない selector を持つ PVC には、PV を動的に用意できない |
動的に用意した PV の既定は Delete | 対応するプラグインなら、PVC を消すと PV と実際のストレージも消える。管理者は StorageClass を利用者の期待に合わせて設定すべき(should)で、そうでなければ PV を作った後に書き換える必要がある(must) |
Recycle は非推奨 | 代わりに動的なプロビジョニングを使うのが推奨。v1.37 時点で対応するのは nfs と hostPath だけ |
| アクセスモードは書き込みを防がない | PVC と PV を合わせるためのもので、マウントした後の書き込みを禁止しない。ボリュームに制約を設けるのは、1 つの Pod にしかマウントさせない ReadWriteOncePod だけ |
ReadWriteOnce は 1 ノード単位 | 同じノードの複数の Pod からアクセスできる場合がある。公式のチュートリアルは、本番では ReadWriteOncePod を使うことを勧めている(CSI のボリュームだけが対応) |
| 既定の StorageClass は 1 つに | 複数あると、いちばん新しく作られた既定が使われる。既定は 1 つだけにするよう努めるべき(should) |
local は動的に用意できない | v1.37 時点では、Kubernetes の local のボリュームは動的なプロビジョニングに対応しない。ノードのローカルのストレージを動的に用意するには、外部の provisioner を使う方法があると考えられる(例:local-path-provisioner。README) |
| kind の StorageClass は推測を含む | 名前や設定は kind v0.33.0 のソースコードから読んだもので、kind の公式ドキュメントでは確かめられなかった。手元のクラスタの値を正とする |
| local-path-provisioner は容量を制限しない | README によると、ボリュームの容量の上限には今のところ対応していない。PVC の 1Gi は上限として働かないと考えられる |
参考:Persistent Volumes | Kubernetes、Storage Classes | Kubernetes、Storage Classes | Kubernetes、Volumes | Kubernetes、Storage Classes | Kubernetes、Configure a Pod to Use a PersistentVolume for Storage | Kubernetes、rancher/local-path-provisioner
第26回の注意点で触れた local のボリュームは、v1.37 時点では動的なプロビジョニングに対応していません。Kubernetes 自身にはこれを動的に用意する仕組みが無く、「kind の既定の StorageClass」で見た local-path-provisioner のような外部の provisioner が、その役を担うと読めます。それでも StorageClass は作るべきで(should)、volumeBindingMode を WaitForFirstConsumer にして、Pod が適切なノードにスケジュールされるまでバインドを遅らせる、と公式ドキュメントは説明しています。バインドを遅らせることで、スケジューラーは PVC に合う PV を選ぶときに、Pod のスケジューリングの条件をすべて考えに入れられます。公式ドキュメントの例では provisioner に自動のプロビジョニングをしないことを表す kubernetes.io/no-provisioner を指定しています。
参考:Storage Classes | Kubernetes、Volumes | Kubernetes、rancher/local-path-provisioner
関連記事
StatefulSet と PVC を組み合わせて、Pod を消しても同じボリュームにつなぎ直し、データを残す方法については、以下の記事で解説しています。
StatefulSet と PVC でデータを残すStatefulSet の volumeClaimTemplates が Pod ごとに PersistentVolumeClaim を作り、Pod を作り直しても同じボリュームにつなぎ直す仕組みを図で説明します。PVC の名前の付き方、消したり数を減らしたりしても PVC が残る既定の動き、persistentVolumeClaimRetentionPolicy を押さえ、kind のクラスタでデータが残ることを確かめる手順を示します。Pod に順番と固定の名前を付ける StatefulSet そのものについては、以下の記事で解説しています。
StatefulSet を動かして理解するPod に番号付きの固定の名前を与え、番号の順に作って止める StatefulSet の仕組みを図で説明します。web-0 から順に作り、番号の大きい順に止める決まり、headless Service が受け持つ名前、Deployment との違いを押さえ、kind のクラスタで Pod を消しても同じ名前で戻ることと、起動と停止の順番を確かめる手順を紹介します。DefaultStorageClass のようなアドミッションコントローラーが、kube-apiserver の中のどこで働くかについては、以下の記事で解説しています。