1 Volumeとは
KubernetesのVolumeとは、Pod内のコンテナがデータを保存・共有するための仕組みです。通常、コンテナのルートファイルシステムは一時的であり、コンテナが再生成されるとデータは失われます。Volumeを利用することで、次のようなことが可能になります。
- コンテナの再起動後もデータを保持できる(Volumeの種類による)
- 同一Pod内の複数コンテナ間でデータを共有できる
- ノード上のディスクや、NFS、クラウドストレージなどの外部ストレージをコンテナから利用できる
VolumeはPod単位で定義され、各コンテナにマウントして使用します。Volumeにはさまざまな種類があり、emptyDir や hostPath のようにPodに直接定義するものと、PersistentVolumeClaim(PVC) を介して PersistentVolume(PV) を利用するものがあります。Volumeの種類によっては、Podが削除・再作成された後もデータを保持できます。
(1) Volumeタイプ(Podから直接指定するもの)
| 種類 |
概要 |
用途 |
| emptyDir |
Pod起動時に作成され、Pod削除時に消える一時領域 |
一時ファイル |
| hostPath |
ノードのローカルディレクトリを直接マウントする。ストレージ変更時はPodの定義修正が必要 |
デバッグや単一ノードでの検証用。Podが別ノードに移動するとデータが引き継げないため、本番環境での利用は非推奨 |
| nfs |
NFSサーバのディレクトリをマウント |
複数Pod間での共有ストレージ |
(2) 永続ストレージを抽象化する仕組み(PV/PVC)
PodはPVCを指定するだけで、背後でどのストレージ(hostPathなのかNFSなのか等)が使われているかを意識せずにVolumeを利用できます。
- PV(PersistentVolume):ストレージ実体(例:NFS, クラウドディスクなど)をKubernetesリソースとして定義したもの
- PVC(PersistentVolumeClaim):利用者が必要な容量・アクセスモードを要求し、PodからPVを利用するための窓口
2 検証環境
2.1 ネットワーク構成
検証環境は3台の仮想マシンでKubernetesクラスタを構成しています。
+--- control ---+ +--- worker1 ---+ +--- worker2 ---+
| | | | | |
|AlmaLinux 10.2 | |AlmaLinux 10.2 | |AlmaLinux 10.2 |
| | | | | |
+-------+-------+ +-------+-------+ +-------+-------+
|.2 |.139 |.171
| | |
| | |
| 192.168.1.0/24 | |
+--------------------------------------------------------+
| KVM |
+--------------------------------------------------------+それぞれの役割は以下のとおりです。
1台をコントロールノード、2台をワーカーノードとして使用します。
| ホスト名 |
名称 |
役割 |
| control |
コントロールノード |
クラスタ(control、worker1、worker2)の状態を管理し、Pod をどのノードで実行するかを決定するノード |
| worker1 |
ワーカーノード |
Pod を実行するノード |
| worker2 |
ワーカーノード |
Pod を実行するノード |
2.2 ソフトウェアのバージョン
各ノードのAlmaLinuxバージョンは以下のとおりです。
[root@control ~]# cat /etc/redhat-release
AlmaLinux release 10.2 (Lavender Lion)
各ノードのカーネルバージョンは以下のとおりです。
[root@control ~]# uname -r
6.12.0-211.7.3.el10_2.x86_64
Kubernetesのバージョンは以下のとおりです。
[root@control ~]# kubectl version
Client Version: v1.35.3
Kustomize Version: v5.7.1
Server Version: v1.35.3
2.3 ノードのリソース
各ノードには4GBのメモリを割り当てています。
[root@control ~]# free -h
total used free shared buff/cache available
Mem: 3.6Gi 1.3Gi 1.0Gi 5.8Mi 1.5Gi 2.3Gi
Swap: 0B 0B 0B各ノードは 4コアのCPU(4 vCPU) を搭載しています。
[root@control ~]# lscpu -xe
CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE
0 0 0 0 0:0:0:0 yes
1 0 1 1 1:1:1:1 yes
2 0 2 2 2:2:2:2 yes
3 0 3 3 3:3:3:3 yes
lscpuコマンドの詳しい使い方は、以下のページをご覧ください。
hana-shin.hatenablog.com
3 emptyDirの使い方
emptyDirは、Podが起動したときに作成され、Podが削除されると消える一時的なVolumeです。ここでは、以下について確認します。
- Pod内のコンテナ間でファイル共有ができること
- Podが削除されるとデータも削除されること
まず、YAMLファイルを作成します。このPodにはcontainer1(nginx)とcontainer2(busybox)という2つのコンテナが定義されており、両方が同じemptyDirボリュームshared-volumeを/dataにマウントしています。
[root@control ~]# vi emptydir.yaml
[root@control ~]# cat emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
name: emptydir-test
spec:
containers:
- name: container1
image: nginx
volumeMounts:
- name: shared-volume
mountPath: /data
- name: container2
image: busybox
command: ["sleep", "3600"]
volumeMounts:
- name: shared-volume
mountPath: /data
volumes:
- name: shared-volume
emptyDir: {}
[root@control ~]#YAMLファイルの内容をKubernetesに適用し、Podを作成します。
[root@control ~]# kubectl apply -f emptydir.yaml
pod/emptydir-test created
Podの状態を確認します。READY が「2/2」、STATUS が「Running」となっていることから、Pod内の2つのコンテナが両方とも正常に動作していることがわかります。
[root@control ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
emptydir-test 2/2 Running 0 3m17s 10.244.189.65 worker2 <none> <none>
container1(nginxコンテナ)にログインします。
[root@control ~]# kubectl exec -it emptydir-test -c container1 -- /bin/bash
ログインしたら、emptyDirにマウントされた /data にファイルを作成します。
root@emptydir-test:/# echo "hello" > /data/test.txt
root@emptydir-test:/# cat /data/test.txt
hello
root@emptydir-test:/#
container1から抜けます。
root@emptydir-test:/# exit
exit
次に、container2(busyboxコンテナ)にログインします。
[root@control ~]# kubectl exec -it emptydir-test -c container2 -- /bin/sh
ログインしたら、container1で作成したファイルを確認します。container1で作成したファイルと同じ内容のファイルが確認できるので、emptyDirでデータ共有できていることがわかります。
/ # cat /data/test.txt
hello
/ #
container2から抜けます。
/ # exit
[root@control ~]#
次に、コンテナで作成したファイルがノード上のどこに保存されているのかを確認します。
まず、Podの詳細情報を取得し、その中からUIDを抽出します。このUIDは、ノード上のディレクトリ名として使用されます。
[root@control ~]# kubectl get pod emptydir-test -o yaml | grep uid
uid: 8d8f6017-c297-4fe7-ba80-3c0c21a75c6d
uid: 0
uid: 0次に、kubeletが管理しているディレクトリ配下に移動し、emptyDirの実体を確認します。
[root@worker2 shared-volume]# pwd
/var/lib/kubelet/pods/8d8f6017-c297-4fe7-ba80-3c0c21a75c6d/volumes/kubernetes.io~empty-dir/shared-volume
このディレクトリ内に、コンテナ内で作成したファイルが存在するか確認します。
[root@worker2 shared-volume]# ls
test.txt
ファイルの内容を確認すると、コンテナ内で作成した内容と一致していることがわかります。このことから、コンテナ内の /data に作成したファイルは、実際にはノード上の /var/lib/kubelet/ 配下に保存されていることがわかります。
[root@worker2 shared-volume]# cat test.txt
hello
最後に、Podを削除します。
[root@control ~]# kubectl delete -f emptydir.yaml
Podを削除したあとに /var/lib/kubelet/pods 配下を確認すると、作成した emptyDir のデータも削除されていることがわかります。このことから、emptyDir は Pod のライフサイクルに紐づいた一時的な領域であり、Pod が起動している間だけ有効であることが確認できます。なお、コンテナが再起動した場合は Pod は存続しているため、emptyDir のデータは保持されます。一方、Pod が削除されると、emptyDir のデータも削除されます。
4 hostPathの使い方
hostPath確認用のYAMLファイルを作成します。
このYAMLでは、ワーカーノード上の /tmp/hostpath-data 配下のディレクトリをコンテナ内の /data にマウントします。hostPath はノード上のローカルディレクトリをマウントするため、データは「Pod」ではなく「ノード」に紐づきます。このため、同じノードに再スケジュールされた場合は、以前保存したファイルをそのまま参照できますが、異なるノードに Pod が移動した場合は、移動前に別ノード上で hostPath に保存したデータを参照できません。なお、type: DirectoryOrCreate を指定することで、対象のディレクトリが存在しない場合は自動的に作成されます。
[root@control ~]# vi hostpath.yaml
[root@control ~]# cat hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: hostpath-test
spec:
containers:
- name: container1
image: nginx
volumeMounts:
- name: shared-volume
mountPath: /data
- name: container2
image: busybox
command: ["sleep", "3600"]
volumeMounts:
- name: shared-volume
mountPath: /data
volumes:
- name: shared-volume
hostPath:
path: /tmp/hostpath-data
type: DirectoryOrCreateちなみに、「3 emptyDirの使い方」で作成したYAMLファイルとの差分は以下のとおりです。
[root@control ~]# diff -Nur emptydir.yaml hostpath.yaml
--- emptydir.yaml 2026-03-23 14:52:36.262665552 +0900
+++ hostpath.yaml 2026-03-23 15:07:49.462094598 +0900
@@ -1,7 +1,7 @@
apiVersion: v1
kind: Pod
metadata:
- name: emptydir-test
+ name: hostpath-test
spec:
containers:
- name: container1
@@ -19,4 +19,6 @@
volumes:
- name: shared-volume
- emptyDir: {}
+ hostPath:
+ path: /tmp/hostpath-data
+ type: DirectoryOrCreateYAMLファイルを適用し、hostPathを利用するPodを作成します。
[root@control ~]# kubectl apply -f hostpath.yaml
pod/hostpath-test created
Podの状態や配置されたノードを確認します。READY が「2/2」、STATUS が「Running」となっていることから、2つのコンテナが正常に動作していることがわかります。
[root@control ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hostpath-test 2/2 Running 0 75s 10.244.189.67 worker2 <none> <none>
container1(nginxコンテナ)にログインします。
[root@control ~]# kubectl exec -it hostpath-test -c container1 -- /bin/bash
hostPathでマウントされた /data 配下にファイルを作成し、内容を確認します。
root@hostpath-test:/# echo "hello" > /data/test.txt
root@hostpath-test:/# cat /data/test.txt
hello
root@hostpath-test:/# exit
exit
container2(busyboxコンテナ)にログインします。
[root@control ~]# kubectl exec -it hostpath-test -c container2 -- /bin/sh
container1で作成したファイルが参照できることを確認します。
/ # cat /data/test.txt
hello
/ # exit
[root@control ~]#
ワーカーノード上のディレクトリに、同じファイルが作成されていることを確認します。
[root@worker2 ~]# cat /tmp/hostpath-data/test.txt
hello
作成したPodを削除します。
[root@control ~]# kubectl delete -f hostpath.yaml
pod "hostpath-test" deleted from default namespace
Podが削除されていることを確認します。
[root@control ~]# kubectl get pods -o wide
No resources found in default namespace.
ノード上にファイルが残っていることを確認します。
[root@worker2 ~]# cat /tmp/hostpath-data/test.txt
hello
次の検証のため、作成したPodを削除します。
[root@control ~]# kubectl delete -f hostpath.yaml
pod "hostpath-test" deleted from default namespace
5 PV(PersistentVolume)/PVC(PersistentVolumeClaim)の使い方
PVおよびPVCは、永続ストレージを管理するための仕組みです。前章で説明したhostPathはストレージのパスが変更されるとすべてのPodの定義を修正する必要があるなど、運用面での課題があります。これに対し、PV/PVCを利用することでストレージを抽象化し、Podからストレージの詳細を切り離すことができます。PodはPVCのみを参照するため、ストレージの変更はPV側で吸収でき、Podの定義を変更する必要がありません。本章では、PV/PVCの基本的な動きを理解するため、あえてバックエンドにhostPathを使った構成で検証を行います。
| 種類 |
概要 |
作成者 |
| PV |
ストレージ本体(データの保存領域) |
管理者 |
| PVC |
PVを使うための申請。PodとPVをつなぐ役割 |
利用者 |
(1) 管理者側(PVの作成)
PV(PersistentVolume)を定義するためのYAMLファイルを作成します。このYAMLでは、ノード上の /tmp/pv-data をストレージとして使用するPVを定義しています。
[root@control ~]# vi pv.yaml
[root@control ~]# cat pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-test
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /tmp/pv-dataYAMLファイルをKubernetesに適用し、PersistentVolumeを作成します。
[root@control ~]# kubectl apply -f pv.yaml
persistentvolume/pv-test created
作成したPVの状態を確認します。STATUS が Available となっており、まだどのPVCにもバインドされていないことがわかります。
[root@control ~]# kubectl get pv -o wide
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE VOLUMEMODE
pv-test 1Gi RWO Retain Available <unset> 59s Filesystem
(2) 利用者側(PVCの作成)
次に、先ほど作成したPVを利用するためのPVC(PersistentVolumeClaim)を作成します。PVCでは、必要な容量やアクセスモードを指定します。
[root@control ~]# vi pvc.yaml
[root@control ~]# cat pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-test
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1GiYAMLファイルを適用してPVCを作成します。
[root@control ~]# kubectl apply -f pvc.yaml
persistentvolumeclaim/pvc-test created
PVCを作成すると、条件に合うPVが自動的に割り当てられます。PVとPVCの状態を確認し、Bound になっていることを確認します。
[root@control ~]# kubectl get pv,pvc -o wide
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE VOLUMEMODE
persistentvolume/pv-test 1Gi RWO Retain Bound default/pvc-test <unset> 44m Filesystem
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE VOLUMEMODE
persistentvolumeclaim/pvc-test Bound pv-test 1Gi RWO <unset> 114s Filesystem
(3) 利用者側(Podの作成)
PVにhostPathを使っているため、実際のデータ保存場所は /tmp/pv-data になります。挙動としてはhostPathと同様ですが、PV/PVCによりストレージの場所をPodから隠蔽できる点が異なります。これにより、Podを変更せずにストレージを差し替えることが可能となります。
PVCを利用するPodを定義するYAMLファイルを作成します。このPodでは、pvc-test を /data にマウントして使用します。
[root@control ~]# vi pod.yaml
[root@control ~]# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: pvc-test-pod
spec:
containers:
- name: busybox
image: busybox
command: ["sleep", "3600"]
volumeMounts:
- name: storage
mountPath: /data
volumes:
- name: storage
persistentVolumeClaim:
claimName: pvc-testYAMLファイルを適用し、PVCを利用するPodを作成します。
[root@control ~]# kubectl apply -f pod.yaml
pod/pvc-test-pod created
Podの状態を確認します。STATUS が Running となり、正常に起動していることがわかります。
[root@control ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pvc-test-pod 1/1 Running 0 19s 10.244.235.145 worker1 <none> <none>
作成したPodにログインし、/data にファイルを作成します。
[root@control ~]# kubectl exec -it pvc-test-pod -- /bin/sh
/ # echo "hello world" > /data/test.txt
/ # cat /data/test.txt
hello world
Pod から抜けます。
/ # exit
ノード上の /tmp/pv-data を確認し、コンテナ内で作成したファイルが保存されていることを確認します。
[root@worker1 ~]# cat /tmp/pv-data/test.txt
hello world
次に、Podを削除してもデータが残ることを確認します。まずPodを削除します。
[root@control ~]# kubectl delete -f pod.yaml
pod "pvc-test-pod" deleted from default namespace
Podが削除されていることを確認します。
[root@control ~]# kubectl get pods
No resources found in default namespace.
同じ定義のPodを再度作成します。
[root@control ~]# kubectl apply -f pod.yaml
pod/pvc-test-pod created
再作成したPodが Running になっていることを確認します。
[root@control ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pvc-test-pod 1/1 Running 0 28s 10.244.235.146 worker1 <none> <none>
再度Podにログインし、以前作成したファイルが残っていることを確認します。
[root@control ~]# kubectl exec -it pvc-test-pod -- /bin/sh
/ # cat /data/test.txt
hello world
今回の検証では、Podが再度同じ worker1 にスケジュールされたためデータを引き継げています。hostPathを使用したPVの場合、別ノードにスケジュールされるとデータは参照できなくなるため、実運用ではネットワークストレージ(NFSやクラウドの永続ディスクなど)や、NodeAffinityを設定したLocal Volumeが使われます。
最後に、検証で使用したPod、PVC、PVを削除してクリーンアップします。
[root@control ~]# kubectl delete -f pod.yaml
pod "pvc-test-pod" deleted from default namespace
[root@control ~]# kubectl delete -f pvc.yaml
persistentvolumeclaim "pvc-test" deleted from default namespace
[root@control ~]# kubectl delete -f pv.yaml
persistentvolume "pv-test" deleted
Z 参考図書
今回の記事執筆にあたり参考にした図書は以下のものです。