emptyDir と hostPath の違い
v1.37 時点(2026年10月26日 確認)
目次
この記事では、Pod の中のコンテナがファイルを読み書きできるディレクトリである ボリューム の仕組みと、その種類のうち emptyDir と hostPath の違いを、図で説明します。コンテナの中に書いたファイルがいつ消えるのか、emptyDir と hostPath のデータはどこに置かれ、Pod を作り直すとどうなるのかがわかります。後半では kind のクラスタで、コンテナのファイルシステム、emptyDir、hostPath の 3 か所にファイルを書いてから Pod を作り直し、消えるデータと残るデータを比べる手順を示します。
この回から第5部(ストレージ)に入ります。Pod の中のコンテナがネットワークとストレージを共有することを扱った第8回の内容を前提にしています。まだの方は先にこちらをどうぞ。
Pod を動かして理解するKubernetes で作って管理できる最小の単位である Pod の仕組みを図で説明します。Pod とコンテナの関係、Pod の中のコンテナが共有するもの、Pod が 1 つのノードに置かれて移らないことを押さえ、kind のクラスタで Pod を直接作って削除し、もう戻らないことを確かめる手順を紹介します。以下の図は、後半の「手元で試す」で作る Pod と、その Pod が使う 2 つのボリュームが、Pod とどう結び付いているかを表しています。
図では emptyDir を Pod の枠の中に、hostPath のディレクトリを Pod の枠の外に描いています。emptyDir は Pod のために作られる、最初は空のディレクトリで、Pod がノードから消えると中のデータも消えます。hostPath はノードのファイルシステムにあるファイルやディレクトリを、Pod にマウントします。どちらもコンテナから決まったパスで読み書きしますが、いつまで存在するかが違います。ノードのディレクトリへの線は Pod の枠から引いていますが、読み書きするのはコンテナです。/container.txt はボリュームではなく、コンテナ自身のファイルシステムに書いたファイルです。
参考:Volumes | Kubernetes、Volumes | Kubernetes、Ephemeral Volumes | Kubernetes
ボリュームとは
ボリューム(Volume)は Pod の中のコンテナから使える、データが入っていることもあるディレクトリ です。公式ドキュメントによると、そのディレクトリがどのように用意されるか、何の上に置かれるか、中身が何かは、ボリュームの種類で決まります。たとえば第17回の configMap のボリュームには最初から ConfigMap のデータが入っていて、第10回の emptyDir のボリュームは空から始まります。Kubernetes は多くの種類のボリュームを扱え、1 つの Pod でいくつの種類のボリュームでも同時に使えます。
公式ドキュメントはボリュームの使い道をいくつか挙げています。そのうち、このシリーズにかかわる主なものは次の 5 つです。
- ConfigMap や Secret をもとに、設定ファイルを用意する
- Pod に一時的な作業場所を用意する
- 同じ Pod の 2 つのコンテナで、ファイルシステムを共有する
- Pod が再起動したり置き換えられたりしても使えるように、データを長く保存する
- コンテナが動く Pod の情報をもとに、アプリケーションに設定を渡す
ボリュームの種類には一時的なもの(ephemeral)と永続的なもの(persistent)があります。公式ドキュメントによると、一時的なボリュームの種類は特定の Pod と結び付いた期間だけ存在し、Pod が存在しなくなると、Kubernetes が消します。永続的なボリュームは個々の Pod の期間を超えて存在し、Kubernetes は消しません。どちらでも、同じ Pod の中では、コンテナが再起動してもデータは残ります。永続的なボリュームは第27回で扱います。
これまでの回のマニフェストに書いたボリュームは、どれも一時的なボリュームです。公式ドキュメントが挙げる一時的なボリュームの種類のうち、このシリーズで扱うものを次の表にまとめます。
| 種類 | 公式ドキュメントの説明 | 扱った回 |
|---|---|---|
emptyDir | Pod の起動時には空。ストレージは kubelet の基準のディレクトリ(ふつうはルートディスク)か RAM から用意される | 第10回、この回 |
configMap | ConfigMap のデータを Pod に入れる | 第17回 |
secret | Secret のデータを Pod に入れる | 第18回 |
downwardAPI | Pod とコンテナのフィールドの値を Pod に入れる | 第19回 |
公式ドキュメントによると、この 4 つはノードのローカルの一時的なストレージ(local ephemeral storage)として用意され、各ノードの kubelet(第4回)が管理します。ローカルの一時的なストレージは、ノードに直接つながった書き込める記憶装置か、場合によっては RAM が支える、ノードの記憶領域です。ここでの「一時的」は長く残る保証がない(no long-term guarantee about durability)という意味です。「ローカル」はノードに直接つながった(locally-attached)記憶装置を指していると読めます。RAM が支える場合もあるので、ディスクとは限りません。第18回で見た Secret のボリュームは RAM の上に置かれますが、これも RAM が支える場合にあたると読めます。
参考:Ephemeral Volumes | Kubernetes、Local ephemeral storage | Kubernetes
一時的なボリュームにはほかにも、コンテナイメージの中身をマウントする image、CSI のドライバーが用意するもの、永続的なボリュームに対応したストレージのドライバーが用意する generic ephemeral volumes などがあります。CSI は第18回で触れたとおり、外部のストレージを Kubernetes につなぐための共通の仕組みです。この回で扱う hostPath はこの一時的なボリュームの一覧には入っていません。
第19回で触れた projected は、公式ドキュメントによると、すでにあるいくつかのボリュームの中身を、同じ 1 つのディレクトリにまとめるボリュームです。第19回で見たとおり、secret、downwardAPI、configMap などをまとめられます。
なぜボリュームが必要なのか
コンテナの中に書いたファイルは、コンテナと同じ期間しか存在しません。公式ドキュメントによると、コンテナのディスクの上のファイルは一時的なもの(ephemeral)です。コンテナが異常終了したり止められたりすると、コンテナの状態は保存されず、コンテナが動いていたあいだに作ったり変えたりしたファイルはすべて失われます。異常終了のあと、kubelet はコンテナをまっさらな状態(clean state)で再起動します。第9回で見たコンテナの再起動では、コンテナの中に書いたファイルは残らないということです。
公式ドキュメントのボリュームの使い方のページも、コンテナのファイルシステムはコンテナが存在するあいだだけ存在し、コンテナが終了して再起動すると、ファイルシステムへの変更は失われる、と説明しています。コンテナから独立した、より一貫したストレージにはボリュームを使えます。これは Redis のようなキーと値を保存するソフトウェアや、データベースのような、状態を持つアプリケーションで特に重要だと書かれています。
公式ドキュメントはもう 1 つの問題として、1 つの Pod で動く複数のコンテナがファイルを共有したい場合を挙げています。すべてのコンテナから使える共有のファイルシステムを用意するのは、難しいことがあります。第10回では、init コンテナが書いたファイルを nginx のコンテナが読むために、emptyDir のボリュームを使いました。公式ドキュメントによると、ボリュームという仕組みはこの 2 つの問題を解く助けになります。
ボリュームの仕組み
マニフェストに書く場所
第10回で見たとおり、ボリュームを使うには、Pod に用意するボリュームを .spec.volumes に書き、コンテナのどこにマウントするかを .spec.containers[*].volumeMounts に書きます。マウントはボリュームのディレクトリをコンテナの中の決まったパスから見えるようにすることです。この回で使うフィールドを、v1.37 時点の API リファレンスに沿って次の表にまとめます。
| フィールド | 内容 |
|---|---|
spec.volumes[*].name | ボリュームの名前。DNS ラベルの決まり(第8回)で付け、Pod の中で一意にする |
spec.volumes[*].emptyDir | Pod と同じ期間だけ存在する、一時的なディレクトリ |
spec.volumes[*].hostPath | ホストのマシンにすでにあるファイルやディレクトリを、コンテナに直接見せる |
spec.containers[*].volumeMounts[*].name | マウントするボリュームの名前。spec.volumes[*].name と合わせる |
spec.containers[*].volumeMounts[*].mountPath | コンテナの中で、ボリュームをマウントするパス |
spec.containers[*].volumeMounts[*].readOnly | true なら読み取り専用でマウントする。既定は false(読み書きできる) |
参考:Pod | Kubernetes API Reference、Pod | Kubernetes API Reference、Volumes | Kubernetes
公式ドキュメントによると、Pod が起動すると、コンテナの中のプロセスからは、コンテナイメージの最初の中身に、マウントしたボリュームを加えたファイルシステムが見えます。ボリュームはコンテナのファイルシステムの中の、指定したパスにマウントされます。マウントする場所はボリュームを使うコンテナごとに別々に指定する必要があります。
emptyDir
公式ドキュメントによると、emptyDir のボリュームは、Pod がノードに割り当てられたときに作られます。名前のとおり、最初は空です。Pod のすべてのコンテナが emptyDir の同じファイルを読み書きできます。マウントするパスはコンテナごとに同じでも違っていても構いません。Pod がどんな理由でノードから消えても、emptyDir のデータは完全に消えます。一方で、コンテナが異常終了しても Pod はノードから消えないので、emptyDir のデータは残ります。第10回で見たとおりです。
API リファレンスも emptyDir を、Pod と同じ期間(a pod’s lifetime)だけ存在する一時的なディレクトリと説明しています。「Pod と同じ期間」の意味は Pod のライフサイクルのページに書かれています。Pod と同じ期間存在するもの(ボリュームなど)は、その UID の Pod が存在するあいだだけ存在します。その Pod がどんな理由で削除されても、たとえまったく同じ代わりの Pod が作られても、ボリュームも消され、新しく作り直されます。第8回で見たとおり、同じ名前で作り直した Pod も UID が違う別の Pod なので、emptyDir も新しい空のディレクトリになります。
参考:Pod | Kubernetes API Reference、Pod Lifecycle | Kubernetes
公式ドキュメントが挙げる emptyDir の使い道は、次の 3 つです。
- ディスクを使うマージソート(データの並べ替えの方法の 1 つ)のような処理の作業場所
- 長い計算の途中経過を保存して、異常終了から立ち直れるようにする
- 中身を取ってくるコンテナが取ってきたファイルを、Web サーバーのコンテナが配信する
3 つ目は第10回の、init コンテナがファイルを書き、nginx がそれを配信する Pod と同じ形です。
emptyDir には置き場所と大きさの上限を指定できます。v1.37 時点の API リファレンスに沿って、emptyDir に書けるフィールドを次の表にまとめます。
| フィールド | 内容 |
|---|---|
medium | ディレクトリを置く記憶装置の種類。既定は "" で、ノードの既定の記憶装置を使う。"" か Memory のどちらかにする |
sizeLimit | このボリュームに必要なローカルのストレージの合計(容量の上限として働く)。Memory のときにも当てはまる。既定は書かない状態(nil)で、上限は決まっていない |
mode | ディレクトリの権限(Unix のパーミッションのビット)。v1.37 時点ではアルファの機能で、既定では無効。機能ゲート EmptyDirVolumeMode を有効にしたときだけ使える |
公式ドキュメントによると、既定では、emptyDir はノードを支える記憶装置に置かれます。それがディスクなのか、SSD なのか、ネットワークのストレージなのかは、環境によって違います。ボリュームの種類のページの別の節には、emptyDir の記憶装置は kubelet のルートディレクトリ(ふつうは /var/lib/kubelet)があるファイルシステムの記憶装置で決まる、と書かれています。一時的なボリュームの種類をまとめた表の「kubelet の基準のディレクトリ(ふつうはルートディスク)」も、言い方は違いますが同じものを指していると読めます。
既定の記憶装置のときは、sizeLimit で emptyDir の容量を制限できます。公式ドキュメントによると、この容量はノードのローカルの一時的なストレージから割り当てられます。そのストレージがほかの用途(ログのファイルや、イメージのデータなど)で埋まると、emptyDir は sizeLimit に届く前に容量が尽きることがあります。
参考:Volumes | Kubernetes、Local ephemeral storage | Kubernetes
medium を Memory にすると、Kubernetes はディスクではなく tmpfs をマウントします。tmpfs は第18回の Secret のボリュームでも使われていた、RAM(メモリー)の上に置くファイルシステムです。公式ドキュメントによると、tmpfs はとても速い一方で、ディスクとは違い、書いたファイルはそのファイルを書いたコンテナのメモリーの上限(limits)に数えられます。API リファレンスによると、メモリーの上の emptyDir が使える最大の量は、sizeLimit と、Pod のすべてのコンテナのメモリーの上限の合計の、小さいほうです。メモリーの上限(limits)の詳しい仕組みは第29回で扱います。この回では、メモリーの上の emptyDir がノードのメモリーを使うことと、sizeLimit を書かないとメモリーを使いすぎるおそれがあると公式ドキュメントが注意していることを押さえておけば十分です。後者は例の後で見ます。
以下は公式ドキュメントにある、メモリーの上に置く emptyDir の例です。sizeLimit で上限を 500 MiB にしています。
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: registry.k8s.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir:
sizeLimit: 500Mi
medium: MemorysizeLimit を書かなかったときについて、公式ドキュメントは注意を促しています。ボリュームの種類のページによると、sizeLimit を書かなければ、メモリーの上の emptyDir の大きさはノードの割り当て可能なメモリーの大きさになります。割り当て可能なメモリーは、公式ドキュメントの定義では、Pod が使える計算資源の量です。ノードの資源から、Kubernetes や OS のデーモン(裏で動き続けるプログラム)のために取っておく分と、退避を始めるしきい値の分を除いたものです。ただし、リソース管理のページによると、そのボリュームが使うことがあるのは Pod のメモリーの上限までです。メモリーの上限も設定していなければ、Pod のメモリーの使用量に上限はなく、ノードの使えるメモリーをすべて使いかねません。その結果、ノードの OS がメモリー不足(OOM)の処理をすることがあります。ボリュームのファイルは Kubernetes も OS も自動では消さないので、メモリーが足りなくなっても、そのファイルが使うメモリーは取り戻せない、とも書かれています。公式ドキュメントによると、kubelet はメモリーの上の emptyDir を、ローカルの一時的なストレージではなくコンテナのメモリーの使用量として扱います。
参考:Volumes | Kubernetes、Resource Management for Pods and Containers | Kubernetes、Local ephemeral storage | Kubernetes、Reserve Compute Resources for System Daemons | Kubernetes、Reserve Compute Resources for System Daemons | Kubernetes
hostPath
公式ドキュメントによると、hostPath のボリュームは、ホストのノードのファイルシステムにあるファイルやディレクトリを Pod にマウントします。ほとんどの Pod には必要ないものの、一部のアプリケーションには強力な抜け道(escape hatch)になる、と説明されています。ここでの抜け道は Kubernetes の仕組みを通さずに、ノードのファイルに直接触れる手段のことだと読めます。API リファレンスもホストのマシンにすでにあるファイルやディレクトリを、コンテナに直接見せるものとしています。主にシステムのエージェントなど、ホストのマシンを見ることが許された、特権を持つものに使い、ほとんどのコンテナには必要ない(Most containers will NOT need this)と書かれています。特権(privileged)は一般に、ふつうのプログラムには許されない強い権限のことです。
hostPath は前に見た一時的なボリュームの一覧には入っていません。この回のように Pod のマニフェストに直接書く hostPath は、第27回で扱う永続的なボリュームの仕組み(PersistentVolume)を通さずに、ノードのファイルを使います。hostPath を PersistentVolume として使う方法もあり、「注意点」の最後で触れます。データはそのノードのファイルシステムにあるので、1 つのノードに縛られます。公式ドキュメントからは hostPath を一時的と永続的のどちらに分けているかは読み取れませんでした。Pod とは別にノードにあるデータなので、Pod が消えても Kubernetes が消すものではないと考えられます。
公式ドキュメントは hostPath について警告(Warning)を載せています。hostPath のボリュームを使うことには、多くのセキュリティのリスクがあります。hostPath を使わずに済むなら、使わないようにすべきだ(you should)と書かれています。代わりの方法の例として挙がっているのは、local の PersistentVolume を定義して使うことです。PersistentVolume は第27回で扱います。
同じ警告には読み取り専用のマウントについての注意もあります。Pod を受け付けるときの検証(アドミッション。第40回)で、ノードの特定のディレクトリへのアクセスを制限している場合、その制限が効くのは、その hostPath のマウントを読み取り専用にすることもあわせて求めたときだけです。信頼できない Pod に任意のホストのパスを読み書きできる形でマウントさせると、その Pod のコンテナは、読み書きできるホストのマウントを使って、その制限をくぐり抜けられるおそれがある(may be able to subvert)と書かれています。
公式ドキュメントは、読み取り専用でも読み書きできる形でも、hostPath を使うときは気を付けるよう求めています。理由は次の 3 つです。
- ホストのファイルシステムにアクセスできると、特権を持つ認証情報(kubelet のものなど)や、特権を持つ API(コンテナランタイムのソケットなど)がさらされるおそれがある。それらはコンテナの外への脱出や、クラスタのほかの部分への攻撃に使われうる
- 同じ設定の Pod(同じひな形から作った Pod など)でも、ノードごとにファイルが違うため、ノードによって動きが変わることがある
hostPathの使用量は一時的なストレージの使用量として扱われない。使いすぎるとノードのディスクが逼迫するので、使用量を自分で見張る必要がある
コンテナランタイムはノードの上でコンテナを動かすソフトウェア(第4回)です。ソケットは一般に、プログラムどうしがやり取りするための窓口で、ここではコンテナランタイムを操作する API の窓口を指すと考えられます。
公式ドキュメントが挙げる hostPath の使い道は、次の 2 つです。
- ノードのレベルのシステムのコンポーネントにアクセスする必要があるコンテナを動かす(たとえば、
/var/logを読み取り専用でマウントしてシステムのログを読み、1 か所に集めるコンテナ) - ホストにある設定ファイルを、static Pod(第4回)に読み取り専用で見せる。static Pod はふつうの Pod と違って ConfigMap を使えない
1 つ目の使い道のログを集めるコンテナは、第15回の DaemonSet と組み合わせて使うことが考えられます。公式ドキュメントも DaemonSet の典型的な使い道として、各ノードでログを集めるデーモンを動かすことを挙げています。2 つ目の使い道で触れた、static Pod が ConfigMap を使えないことは第17回で見たとおりで、Secret も同じです(第18回)。
Kubernetes 自身のコンポーネントにも hostPath を使うものがあります。kubeadm の実装の詳細のページによると、kubeadm は etcd(第4回)の static Pod のマニフェストを作るとき、etcd のデータのディレクトリ(dataDir)から、ホストのファイルシステムへの hostPath のマウントを書きます。kind はノードを kubeadm で初期化するので(第2回)、kind のクラスタの etcd の Pod にも、hostPath のボリュームがあるはずです。後半の「手元で試す」で見ます。
hostPath の type
hostPath には必須の path に加えて、type を指定できます。type はマウントする前に、そのパスに何があるべきかを決めるものです。v1.37 時点の公式ドキュメントに沿って、type の値を次の表にまとめます。
| 値 | 動き |
|---|---|
""(既定) | 以前との互換のための値。マウントする前に何も確かめない |
DirectoryOrCreate | パスに何も無ければ、空のディレクトリを必要に応じて作る。権限は 0755 で、グループと所有者は kubelet と同じ |
Directory | パスにディレクトリがなければならない |
FileOrCreate | パスに何も無ければ、空のファイルを必要に応じて作る。権限は 0644 で、グループと所有者は kubelet と同じ |
File | パスにファイルがなければならない |
Socket | パスに UNIX ソケットがなければならない |
CharDevice | (Linux のノードだけ)パスにキャラクターデバイスがなければならない |
BlockDevice | (Linux のノードだけ)パスにブロックデバイスがなければならない |
参考:Volumes | Kubernetes、PersistentVolume | Kubernetes API Reference
表のことばを補います。UNIX ソケットは一般に、同じマシンの中のプログラムどうしがやり取りするための、ファイルの形をした窓口です。キャラクターデバイスとブロックデバイスは一般に、ディスクなどの装置をファイルとして扱うための特別なファイルです。API リファレンスによると、path はホストの上のディレクトリのパスで、パスがシンボリックリンク(ほかのファイルを指す特別なファイル)なら、リンクをたどった先の本当のパスが使われます。
公式ドキュメントは FileOrCreate について、ファイルの親のディレクトリは作らない、と注意しています。親のディレクトリが無いと、Pod は起動に失敗します。このため公式ドキュメントの例では親のディレクトリを DirectoryOrCreate で、ファイルを FileOrCreate で、別々にマウントしています。その例の説明によると、ノードにパスが無ければ、kubelet がディレクトリを作ってから Pod にマウントします。
ホストの上で作られたファイルやディレクトリの中には、root(管理者のユーザー)だけがアクセスできるものがあります。公式ドキュメントによると、その場合は、特権を持つコンテナの中でプロセスを root として動かすか、ホストの側でファイルの権限を変えないと、hostPath のボリュームを読み書きできません。Pod Security Standards(後述)の説明によると、特権を持つ(privileged)Pod は、ほとんどのセキュリティの仕組みを無効にします。
以下は公式ドキュメントにある、Linux のノード向けの hostPath の例です。
---
# This manifest mounts /data/foo on the host as /foo inside the
# single container that runs within the hostpath-example-linux Pod.
#
# The mount into the container is read-only.
apiVersion: v1
kind: Pod
metadata:
name: hostpath-example-linux
spec:
os: { name: linux }
nodeSelector:
kubernetes.io/os: linux
containers:
- name: example-container
image: registry.k8s.io/test-webserver
volumeMounts:
- mountPath: /foo
name: example-volume
readOnly: true
volumes:
- name: example-volume
# mount /data/foo, but only if that directory already exists
hostPath:
path: /data/foo # directory location on host
type: Directory # this field is optional冒頭のコメントのとおり、ホストの /data/foo を、コンテナの中の /foo に読み取り専用(readOnly: true)でマウントする例です。type: Directory なので、/data/foo がすでにディレクトリとしてある場合だけマウントします。公式ドキュメントは同じ例の Windows のノード向けの版も載せていて、os と nodeSelector は、この Pod を Linux のノードで動かすための指定だと読めます。nodeSelector は第31回で扱います。
hostPath は第39回で扱う Pod Security Standards(Pod の設定の安全さの基準)でも制限されています。3 つの水準のうち、知られている権限の昇格を防ぐ Baseline の水準では、hostPath のボリュームは禁止です(spec.volumes[*].hostPath は書かない状態でなければならない)。権限の昇格(privilege escalation)は一般に、本来与えられた以上の権限を手に入れることです。
kind のクラスタでの「ホスト」
kind のクラスタでは「ホスト」という言葉が 2 つの意味で出てくるので、先に整理します。後半の手順では次の 3 つの層を区別します。
| 層 | 何か | 手順で出てくるもの |
|---|---|---|
| 手元のマシン | kind と Docker を動かしているパソコン | 手元のマシンのディレクトリを直接見る ls /var/local/volume-demo |
ノード(kind-control-plane のコンテナ) | 手元のマシンの Docker で動くコンテナ。hostPath の「ホスト」はここ | ノードの /var/local/volume-demo(docker exec kind-control-plane で見る) |
Pod の busybox のコンテナ | ノードの上で動くコンテナ | /container.txt、/cache、/host-data(kubectl exec で見る) |
hostPath の「ホスト」は Pod が動くノードのことです。第2回で見たとおり、kind のノードは手元のマシンで動くコンテナです。kind の公式ドキュメントも、ノードは kind が作ったコンテナだと説明しています。そのため kind のクラスタでは、hostPath が指すのはノードのコンテナの中のファイルシステムで、手元のマシンのディレクトリそのものではないと考えられます。
一方、kind には手元のマシンのディレクトリをノードに渡す設定(extraMounts)があります。kind の公式ドキュメントの extraMounts の説明でいう「ホスト」は手元のマシンのことで、hostPath の「ホスト」(ノード)とは別のもの です。kind の公式ドキュメントによると、extraMounts はデータを残したり、コードをマウントしたりするために、ホストのストレージを kind のノードに渡すのに使えます。設定の例のコメントでも、ホスト(手元のマシン)のパスとノードのパスは、別のものとして書き分けられています。このことからも、extraMounts を設定していなければ、ノードの中に書いたファイルは手元のマシンからは見えないと考えられます。この回の手順では extraMounts を使いません。
Pod を作り直したときに消えるもの・残るもの
ここまでの内容をデータの置き場所ごとに比べます。以下の図は、コンテナのファイルシステム、emptyDir、hostPath の 3 か所にファイルを書いた Pod を消し、同じマニフェストで作り直したときの様子をコマ送りで表しています。シングルノードのクラスタで、作り直した Pod が同じノードに置かれる場合です。後半の「手元で試す」で行う手順にあたります。
(1/2)で hostpath.txt が残ると考えられるのは、hostPath がマウントしているのが、Pod とは別にあるノードのファイルシステムだからです。公式ドキュメントは、Pod が存在しなくなると一時的なボリュームは消す、と説明していますが、hostPath はその一時的なボリュームの一覧には入っていません。公式のチュートリアル(hostPath の PersistentVolume を使う例)では、片付けのときに Pod などを消したあとで、ノードに手で作ったファイルとディレクトリを手で消しています。チュートリアルは Pod を消したあとのファイルの扱いを直接は説明していませんが、この手順からも、Pod を消してもノードのファイルは残ると読めます。
参考:Volumes | Kubernetes、Ephemeral Volumes | Kubernetes、Configure a Pod to Use a PersistentVolume for Storage | Kubernetes
コンテナが再起動したときと、Pod を消して作り直したときに、データがどうなるかを次の表にまとめます。
| 置き場所 | コンテナが再起動したとき | Pod を消して作り直したとき |
|---|---|---|
| コンテナのファイルシステム | 消える。kubelet はコンテナをまっさらな状態で再起動する | 消える。新しい Pod のコンテナは、イメージの最初の中身から始まる |
emptyDir | 残る | 消える。新しい Pod には新しい空の emptyDir が作られる |
hostPath | 残る | ノードのファイルなので残ると考えられる。別のノードに置かれた場合は、そのノードのファイルが見えると考えられる |
参考:Volumes | Kubernetes、Volumes | Kubernetes、Volumes | Kubernetes、Volumes | Kubernetes、Pod Lifecycle | Kubernetes
表の hostPath の行の 2 つ目の文は、公式ドキュメントの「同じ設定の Pod でも、ノードごとにファイルが違うため、ノードによって動きが変わることがある」という注意にあたります。第8回で見たとおり、作り直した Pod が前と同じノードに置かれる保証はありません。マルチノードのクラスタでは、前の Pod が書いたファイルが、作り直した Pod から見えないこともあると考えられます。
コンテナの再起動で emptyDir のデータが残ることは、公式ドキュメントのボリュームの使い方のページで試されています。Redis(キーと値を保存するソフトウェア)のコンテナを 1 つ持つ Pod に emptyDir をマウントし、ファイルを書いてから、コンテナの中の Redis のプロセスを止める例です。元の端末で kubectl get pod redis --watch を実行して Pod を見ておき、別の端末でコンテナの中に入ってファイルを書き、プロセスを止めます。すると、元の端末に次のように表示されると書かれています。
Pod の名前は redis のままで、RESTARTS 列が 0 から 1 に増えています。公式ドキュメントによると、Pod の restartPolicy が Always なので、コンテナが終了して再起動しました(第9回)。再起動したコンテナの中で emptyDir のディレクトリを ls で見ると、書いたファイル test-file がまだあることが示されています。この回の「手元で試す」ではコンテナの再起動ではなく、Pod を作り直したときを試します。
手元で試す
ここまでの内容を手元の kind のクラスタで試す手順を示します。emptyDir と hostPath の 2 つのボリュームを持つ Pod を作り、コンテナのファイルシステム、emptyDir、hostPath の 3 か所にファイルを書きます。hostPath のファイルがノードのコンテナの中にあることも確かめます。続けて Pod を消して同じマニフェストで作り直し、3 つのファイルのうちどれが残るかを比べます。最後に、kind のクラスタの etcd の Pod が使う hostPath を見ます。コマンドは 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 でクラスタを作る手順は、以下の記事で解説しています。この回ではシングルノードのクラスタを使います。hostPath のデータはノードごとにあるので、作り直した Pod が前と同じノードに置かれるようにするためです。
シングルノードのクラスタを用意する
まず、前の回のクラスタが残っていないかを確かめます。
$ kind get clusters
kind が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。前の回の multi-node が表示されても、この回では使いません。手元のマシンの資源(メモリなど)に余裕がなければ、kind delete cluster --name multi-node で消してから進めてください。
$ kind create cluster
$ kind get clusters
kind get clusters に kind が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-kind を付けて、操作するクラスタをはっきりさせます。
続けて、ノードを一覧します。
$ kubectl get nodes --context kind-kind
ノードが 1 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。
2 つのボリュームを持つ Pod を作る
以下はこの回で使う Pod のマニフェストです。emptyDir のボリューム cache-volume をコンテナの /cache に、hostPath のボリューム host-volume をコンテナの /host-data にマウントします。volume-demo.yaml という名前で、コマンドを実行するディレクトリに保存します。
apiVersion: v1
kind: Pod
metadata:
name: volume-demo
labels:
app: volume-demo
spec:
containers:
- name: busybox
image: busybox:1.28
command: ['sh', '-c', 'sleep 3600']
volumeMounts:
- name: cache-volume
mountPath: /cache
- name: host-volume
mountPath: /host-data
volumes:
- name: cache-volume
emptyDir: {}
- name: host-volume
hostPath:
path: /var/local/volume-demo
type: DirectoryOrCreate参考:Volumes | Kubernetes、Volumes | Kubernetes、Pod | Kubernetes API Reference、PersistentVolume | Kubernetes API Reference
公式ドキュメントの emptyDir の例と、hostPath の FileOrCreate の例をもとに、この回に合わせて変えたマニフェストです。emptyDir には sizeLimit を書かず、第10回と同じく {}(設定を何も指定しない)にしました。hostPath は FileOrCreate の例の、/var/local/aaa を DirectoryOrCreate でマウントする部分にならい、パスを /var/local/volume-demo に変えています。公式ドキュメントの例の説明によると、ノードに /var/local/aaa が無ければ、kubelet がディレクトリとして作ってから Pod にマウントします。type の表の DirectoryOrCreate の説明も、パスに何も無ければ空のディレクトリを作る、としています。イメージは第18回〜第20回と同じ busybox:1.28 で、sleep 3600 は 3600 秒(1 時間)待つだけのコマンドです。手順を終えるまでコンテナを動かしておくためのものです。
ここからは端末を 2 つ使います。第11回・第13回〜第20回と同じく、1 つ目の端末で変化を追い続け、2 つ目の端末でマニフェストを渡したり、コンテナの中でコマンドを動かしたりします。この回で 1 つ目の端末が追うのは Pod です。2 つ目の端末を開いたら、cd でマニフェストを保存したディレクトリに移動しておきます。
1 つ目の端末で、app=volume-demo のラベルを持つ Pod を監視します。第3回で見たとおり、--watch を付けると、一覧を表示したあとも変化を追い続けます。この監視は片付けの前まで止めずに続けます。
$ kubectl get pods --watch -l app=volume-demo --context kind-kind
Pod がまだ無いので、何も表示されないか、見つからないことを知らせる 1 行が出たあと、止まったように見えると考えられます。
2 つ目の端末で、Pod のマニフェストを渡します。
$ kubectl apply -f volume-demo.yaml --context kind-kind
pod/volume-demo が作られたことを知らせる 1 行が出るはずです。1 つ目の端末には volume-demo の行が書き足され、STATUS 列が Pending、ContainerCreating、Running と移っていくと考えられます。クラスタを作ったばかりなら、busybox:1.28 のイメージの取得に時間がかかるので、ContainerCreating のまましばらく止まって見えることがあります(第9回)。
3 か所にファイルを書く
1 つ目の端末で Running になったのを確かめたら、2 つ目の端末で、コンテナの中の 3 か所にファイルを書きます。第10回で見たとおり、kubectl exec はコンテナの中でコマンドを動かすコマンドで、-- の後ろがコンテナの中で動かすコマンドです。sh -c に渡した文字列は、コンテナの中のシェルが ; で区切って順に実行します(第9回)。echo は一般に文字列を書き出すコマンドで、> はその出力をファイルに書き込むシェルの記号です(第10回)。
$ kubectl exec volume-demo --context kind-kind -- sh -c 'echo container > /container.txt; echo emptyDir > /cache/emptydir.txt; echo hostPath > /host-data/hostpath.txt'
書いたファイルは次の 3 つです。中身はどこに書いたかがわかる 1 語にしました。
| ファイル | 置き場所 | 中身 |
|---|---|---|
/container.txt | コンテナのファイルシステム(ボリュームではない / の直下) | container |
/cache/emptydir.txt | emptyDir のボリューム cache-volume | emptyDir |
/host-data/hostpath.txt | hostPath のボリューム host-volume(ノードの /var/local/volume-demo) | hostPath |
何も表示されずに終われば、書き込めています。/host-data への書き込みでエラーになった場合は、「hostPath の type」で見た、root だけがアクセスできるファイルやディレクトリの注意にあたると考えられます。そのときは、次の節で説明する docker exec を使い、ノードの側のディレクトリの権限と所有者を見ます。ls -ld は一般に、ディレクトリの中身ではなく、ディレクトリそのものの権限と所有者を表示するコマンドです。
$ docker exec kind-control-plane ls -ld /var/local/volume-demo
公式ドキュメントによると、DirectoryOrCreate で新しく作られたディレクトリの権限は 0755 で、グループと所有者は kubelet と同じです。0755 は ls -ld の表示では一般に drwxr-xr-x にあたります。これと違っていたら、この手順より前に同じパスのディレクトリがノードにあったと考えられます。
続けて、3 つのファイルを読みます。cat は一般に、ファイルの中身を表示するコマンドです。
$ kubectl exec volume-demo --context kind-kind -- cat /container.txt
$ kubectl exec volume-demo --context kind-kind -- cat /cache/emptydir.txt
$ kubectl exec volume-demo --context kind-kind -- cat /host-data/hostpath.txt
順に container、emptyDir、hostPath と表示されるはずです。今はまだ同じ Pod なので、3 つとも読めます。
あとで比べるために、Pod の UID(第5回)をファイルに書き出しておきます。-o jsonpath は第5回で使った、フィールドを指定して取り出す出力の形式です。
$ kubectl get pod volume-demo -o jsonpath='{.metadata.uid}{"\n"}' --context kind-kind > uid-before.txt
hostPath のファイルをノードの側から見る
hostPath のファイルがノードのファイルシステムにあることを確かめます。第2回・第4回と同じく、docker exec でノードのコンテナの中でコマンドを実行します。kind の Quick Start の例では、クラスタ名 kind のノードのコンテナの名前は kind-control-plane です。名前が違うときは、docker ps の NAMES 列で確かめて置き換えてください。
$ docker exec kind-control-plane cat /var/local/volume-demo/hostpath.txt
hostPath と表示されるはずです。コンテナの中で /host-data/hostpath.txt に書いたファイルが、ノードでは /var/local/volume-demo/hostpath.txt として見えています。コンテナが見つからないことを知らせるエラーになったら、ノードのコンテナの名前が docker ps の NAMES 列と合っているかを確かめます。
続けて、手元のマシンで同じパスを見ます。
$ ls /var/local/volume-demo
「kind のクラスタでの「ホスト」」で考えたとおりなら、手元のマシンにはこのディレクトリが無く、見つからないことを知らせるエラーの行が出るはずです。kind のクラスタでの hostPath の「ホスト」は、手元のマシンではなく、ノードのコンテナだと読めます。エラーにならずにディレクトリが見えた場合は、手元のマシンにもともと同じパスのディレクトリがあったか、extraMounts を設定したクラスタを使っていると考えられます。その中に hostpath.txt が無ければ、ノードのディレクトリとは別のものです。
Pod を消して作り直す
2 つ目の端末で、Pod を削除します。
$ kubectl delete pod volume-demo --context kind-kind
kubectl delete は既定では、Pod が消えるまで待ってから終わります。Pod には終了までの猶予の時間(既定では 30 秒)があるので、終わるまで少しかかることがあります。1 つ目の端末には、volume-demo の行の STATUS 列が Terminating になった行が書き足されるはずです。第8回で見たとおり、直接作った Pod には作り直すコントローラーがいないので、新しい Pod の行は現れないはずです。
参考:kubectl delete | Kubernetes、Pod Lifecycle | Kubernetes、Pod Lifecycle | Kubernetes
2 つ目の端末の kubectl delete が終わったら、同じマニフェストで Pod をもう一度作ります。
$ kubectl apply -f volume-demo.yaml --context kind-kind
1 つ目の端末には、同じ名前の volume-demo の行が書き足され、STATUS 列が Running まで移っていくはずです。AGE 列は作り直した時点から数え直されると考えられます。シングルノードのクラスタなので、作り直した Pod も同じノードに置かれるはずです。置かれたノードは次のコマンドの NODE 列で確かめられます。-o wide はノードの名前などを加えて一覧する出力の形式です。このクラスタのノードは 1 つなので、kind-control-plane が表示されるはずです。
$ kubectl get pod volume-demo -o wide --context kind-kind
消えたファイルと残ったファイルを比べる
1 つ目の端末で Running になったのを確かめたら、2 つ目の端末で、さきほどと同じ 3 つのコマンドでファイルを読みます。
$ kubectl exec volume-demo --context kind-kind -- cat /container.txt
$ kubectl exec volume-demo --context kind-kind -- cat /cache/emptydir.txt
$ kubectl exec volume-demo --context kind-kind -- cat /host-data/hostpath.txt
結果は次のようになるはずです。
| コマンド | 結果 | 理由 |
|---|---|---|
cat /container.txt | ファイルが無いことを知らせるエラーの行 | 新しい Pod のコンテナは、イメージの最初の中身から始まる |
cat /cache/emptydir.txt | ファイルが無いことを知らせるエラーの行 | 前の Pod の emptyDir は Pod と一緒に消え、新しい空の emptyDir が作られた |
cat /host-data/hostpath.txt | hostPath | ノードの /var/local/volume-demo は Pod とは別にあり、同じノードの同じディレクトリをマウントした |
参考:Volumes | Kubernetes、Volumes | Kubernetes、Pod Lifecycle | Kubernetes
1 行目と 2 行目でファイルが無いことを知らせるエラーが出るのは、手順の失敗ではなく、この回で確かめたい結果です。エラーの行の文面は公式ドキュメントでは確かめられませんでした。No such file or directory のような、ファイルが無いことを表す文が含まれると考えられます。kubectl exec がコンテナの中のコマンドの失敗を知らせる行も、続けて出ることがあると考えられます。
作り直した Pod の UID を、削除する前の UID と見比べます。
$ kubectl get pod volume-demo -o jsonpath='{.metadata.uid}{"\n"}' --context kind-kind
$ cat uid-before.txt
名前は同じ volume-demo でも、UID は違います。公式ドキュメントによると、前の Pod と同じ名前で代わりの Pod を作っても、.metadata.uid は前の Pod と違うものになります。第8回で見たとおり、作り直した Pod は同じ名前の別の Pod です。emptyDir は「その UID の Pod」と同じ期間だけ存在するので、UID が変われば emptyDir も新しくなります。一方、hostPath のディレクトリはノードにあり、どの UID の Pod とも結び付いていないので、前の Pod が書いたファイルが読めたと考えられます。
参考:Object Names and IDs | Kubernetes、Pod Lifecycle | Kubernetes、Pod Lifecycle | Kubernetes
etcd の Pod の hostPath を見る
最後に、Kubernetes 自身のコンポーネントが使う hostPath を見ます。2 つ目の端末で、kube-system の Namespace の Pod を一覧します。
$ kubectl get pods -n kube-system --context kind-kind
第4回で見たとおり、etcd の static Pod の名前は etcd-<ノード名> です。このクラスタでは etcd-kind-control-plane になると考えられます。一覧に出た etcd- で始まる名前を <etcd の Pod の名前> に入れて、Pod のボリュームの一覧を取り出します。
$ kubectl get pod <etcd の Pod の名前> -n kube-system -o jsonpath='{.spec.volumes}{"\n"}' --context kind-kind
参考:kubectl get | Kubernetes、JSONPath Support | Kubernetes、Implementation details | Kubernetes
ボリュームの一覧が JSON の形で 1 行に表示され、その中に hostPath を含む項目があるはずです。API リファレンスの形のとおりなら、1 つの項目にはボリュームの名前(name)と、hostPath の中身(path と、指定されていれば type)が組になって入っているはずです。hostPath の中の path がノードのどのディレクトリをマウントしているかを表します。
kubeadm は etcd のデータのディレクトリを hostPath でマウントします。kubeadm の設定の API リファレンスによると、etcd のデータのディレクトリ(dataDir)の既定は /var/lib/etcd です。kind が既定を変えていなければ、path が /var/lib/etcd の項目があると考えられます。hostPath の項目が 1 つとは限らないので、path の値を見比べて探します。etcd はクラスタのデータを保存するので、etcd の Pod が作り直されても、データがノードに残るようにしていると考えられます。
参考:Pod | Kubernetes API Reference、Implementation details | Kubernetes、kubeadm Configuration (v1beta4) | Kubernetes
片付け
1 つ目の端末で Ctrl+C を押して、Pod の監視を止めます。
2 つ目の端末で、作った Pod を、マニフェストを指定して削除します。
$ kubectl delete -f volume-demo.yaml --context kind-kind
/var/local/volume-demo をマウントする Pod はもう無いので、ノードのディレクトリがどうなったかを見ます。
$ docker exec kind-control-plane ls /var/local/volume-demo
hostpath.txt が表示されるはずです。Pod を消しても、hostPath のディレクトリとファイルはノードに残っていると考えられます。公式のチュートリアルと同じく、ノードに残ったファイルは手で消します。rm -r は一般に、ディレクトリを中身ごと消すコマンドです。
$ docker exec kind-control-plane rm -r /var/local/volume-demo
参考:Configure a Pod to Use a PersistentVolume for Storage | Kubernetes、docker container exec | Docker Docs
書き出した UID のファイルも消します。
$ rm uid-before.txt
最後に、クラスタを削除し、消えたことを確かめます。
$ kind delete cluster
$ kind get clusters
kind delete cluster は存在しないクラスタを指定してもエラーを返しません。kind get clusters に kind が表示されなくなったことを確かめます。kubeconfig に kind-kind のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-kind で消します。クラスタを削除するとノードのコンテナも消えるので、ノードのディレクトリを手で消し忘れても、そこにあったファイルはノードのコンテナと一緒に消えると考えられます。
注意点
ボリュームでつまずきやすい点と、hostPath を使うときに気を付ける点、バージョンで変わる点を、次の表にまとめます。
| 項目 | 内容 |
|---|---|
| コンテナの中のファイルは再起動で消える | ボリュームではない場所に書いたファイルは、コンテナが再起動すると失われる |
emptyDir は Pod と同じ期間だけ | コンテナの再起動では消えないが、Pod がどんな理由でノードから消えても、データは完全に消える。同じ名前で作り直した Pod の emptyDir も新しい空のディレクトリ |
| Pod を超えて残すデータ | 永続的なボリュームは、個々の Pod の期間を超えて存在する(第27回) |
medium: Memory はメモリーを使う | tmpfs に置かれ、書いたファイルはコンテナのメモリーの上限に数えられる。sizeLimit もメモリーの上限も書かないと、ノードのメモリーをすべて使いかねない |
sizeLimit に届く前に尽きることがある | 既定の記憶装置のときの容量はノードのローカルの一時的なストレージから割り当てられ、ほかの用途で埋まると上限より前に尽きることがある |
hostPath は多くのセキュリティのリスクがある | 公式ドキュメントは、使わずに済むなら使わないよう警告している。代わりの例は local の PersistentVolume |
hostPath のデータはノードごと | 同じ設定の Pod でも、ノードごとにファイルが違うため、動きが変わることがある |
hostPath の使用量は一時的なストレージに数えられない | 使いすぎるとノードのディスクが逼迫するので、使用量を自分で見張る |
FileOrCreate は親のディレクトリを作らない | 親のディレクトリが無いと Pod は起動に失敗する。ディレクトリとファイルを別々にマウントする |
| Pod Security Standards の Baseline では禁止 | Baseline の水準では hostPath のボリュームを書けない(第39回) |
| v1.37 時点のアルファの機能 | emptyDir の mode と、メモリーの上の emptyDir の sizeLimit を動いたまま変える機能は、アルファで既定では無効 |
参考:Volumes | Kubernetes、Pod Lifecycle | Kubernetes、Resource Management for Pods and Containers | Kubernetes、Pod Security Standards | Kubernetes
hostPath の代わりの例に挙がっている local のボリュームについて、公式ドキュメントは hostPath と比べて、Pod を手でノードに割り当てなくても、長く使えて持ち運びやすい形で使える、と説明しています。Kubernetes は PersistentVolume に書いたノードの条件(ノードアフィニティ)を見て、ボリュームがどのノードにあるかを把握します。local のボリュームは前もって作った PersistentVolume としてだけ使えます。PersistentVolume の一覧のページでも、hostPath の PersistentVolume は、シングルノードのクラスタで試すためだけのもので、マルチノードのクラスタでは動かない、と書かれています。公式のチュートリアルも本番のクラスタでは hostPath を使わない、と説明しています。PersistentVolume は第27回、ノードアフィニティは第31回で扱います。
参考:Volumes | Kubernetes、Persistent Volumes | Kubernetes、Configure a Pod to Use a PersistentVolume for Storage | Kubernetes
関連記事
Pod を超えてデータを残す PersistentVolume と PersistentVolumeClaim(PVC)、PVC を作るとボリュームが用意される動的プロビジョニングについては、以下の記事で解説しています。
PersistentVolume と PVC の仕組みPod の期間を超えてデータを残す PersistentVolume、PVC、StorageClass の関係を図で説明します。静的と動的のプロビジョニング、1 対 1 のバインド、アクセスモード、回収ポリシー、volumeBindingMode を押さえ、kind のクラスタで PVC とそれを使う Pod を作り、ボリュームが自動で用意される流れを観察します。StatefulSet と PVC を組み合わせて、Pod を消しても同じボリュームにつなぎ直し、データを残す方法については、以下の記事で解説しています。
StatefulSet と PVC でデータを残すStatefulSet の volumeClaimTemplates が Pod ごとに PersistentVolumeClaim を作り、Pod を作り直しても同じボリュームにつなぎ直す仕組みを図で説明します。PVC の名前の付き方、消したり数を減らしたりしても PVC が残る既定の動き、persistentVolumeClaimRetentionPolicy を押さえ、kind のクラスタでデータが残ることを確かめる手順を示します。メモリーの上の emptyDir の大きさにかかわる、コンテナのメモリーの requests と limits については、以下の記事で解説しています。