この記事の内容
k8sクラスタで、KubeVirtを利用することでVMを作り、Pod(コンテナ)と同様に、PVCをつなげて使ったり、Probeで死活管理したりできます。
こちらの記事で、Kubevirtをインストールし、テスト用VM(cirros)を作りました。
この続きで、RockyLinuxのマシンイメージから、VMを作ります。
以下に従って実施します。
環境
- AlmaLinux 9.5の3VMで、Masterノードx1、Workerノードx2でk8sクラスタを構成。
- ホストOS=RHEL/AlmaLinux、各ノードとするVMのゲストOS=AlmaLinuxです。
- k8sクラスタに対し、以下をインストール済みです。
-- KubeVirt(VM作成用)
-- MetalLB(ロードバランサ用)
-- kubernetes-dashboard(k8sクラスタの各リソース表示確認など)
1. NFSのStorageClassの作成
まず、CDI(Containerized Data Importer)のインストール、ですが、その前提に、StorageClassがあると良い(なくても、hostpathでノードのローカルディスクでPVは作れるが、VMがそのノードに縛られる)とありますので、以下を利用し、NFSのStorageClassを作って使うことにします。
どこかにNFSサーバを用意してディレクトリをNFS共有し、k8sクラスタの各ノードからNFSマウントできるネットワーク構成にし、「Kubernetes NFS Subdir External Provisioner」をインストールして設定すると、「nfs-client」というStorageClassが利用できるようになります。
ということで、このようなNFSサーバを用意します。
- al9host(192.168.11.103)をNFSサーバにする(k8sクラスタからアクセス可)。
- al9hostに、/root/sc-nfs/を作り、NFSエクスポート(共有)する。
(1) NFSサーバの作成
al9hostで、nfs-utilsが入っていることを確認します。無ければ、以下で入れます。
dnf install nfs-utils
共有するディレクトリを作ります。
[root@al9host ~]# mkdir /root/sc-nfs
[root@al9host ~]#
各ノードからアクセスできるよう設定し、nfs-serverサービスを有効化/起動します。
[root@al9host ~]# vim /etc/exports
[root@al9host ~]#
[root@al9host ~]# cat /etc/exports
/root/sc-nfs 192.168.11.0/24(rw,sync,no_root_squash)
[root@al9host ~]#
[root@al9host ~]# systemctl enable --now nfs-server
Created symlink /etc/systemd/system/multi-user.target.wants/nfs-server.service → /usr/lib/systemd/system/nfs-server.service.
[root@al9host ~]#
ファイアウォールのNFSを許可します。
[root@al9host ~]# firewall-cmd --permanent --add-service=nfs
success
[root@al9host ~]#
[root@al9host ~]# firewall-cmd --reload
success
[root@al9host ~]#
[root@al9host ~]# firewall-cmd --list-services
cockpit dhcpv6-client nfs ssh
[root@al9host ~]#
試しに、MasterノードでNFSマウントできることを確認します(確認後、アンマウント)。
[root@master1 kubevirt]# mount -t nfs 192.168.11.103:/root/sc-nfs /mnt/
[root@master1 kubevirt]#
[root@master1 kubevirt]# mount | grep sc-nfs
192.168.11.103:/root/sc-nfs on /mnt type nfs4 (rw,relatime,vers=4.2,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=192.168.11.131,local_lock=none,addr=192.168.11.103)
[root@master1 kubevirt]#
[root@master1 kubevirt]# umount /mnt/
[root@master1 kubevirt]#
(2) Kubernetes NFS Subdir External Provisionerのインストール
helmコマンドが必要です。Helmは、kubernetes-dashboardをインストールするときに使っており、このk8sクラスタでは、master1にインストール済みです。helmコマンドを使えるようにする手順は以下にあります。
helmコマンドを使い、リポジトリを追加します。
[root@master1 kubevirt]# helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
"nfs-subdir-external-provisioner" has been added to your repositories
[root@master1 kubevirt]#
インストールします。ここでは、引数に
- --set nfs.server=192.168.11.103
- --set nfs.path=/root/sc-nfs
を指定しており、これによって、al9hostのNFSサーバを指定しています。
[root@master1 kubevirt]# helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --set nfs.server=192.168.11.103 --set nfs.path=/root/sc-nfs
NAME: nfs-subdir-external-provisioner
LAST DEPLOYED: Tue Feb 11 11:53:57 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
[root@master1 kubevirt]#
インストール後、「nfs-client」のStorageClassが使えるようになったことを確認します。
[root@master1 sc-nfs]# kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-client cluster.local/nfs-subdir-external-provisioner Delete Immediate true 100m
[root@master1 sc-nfs]#
こちらは、kubernetes-dashboardで見たものです。
(3) PVCの作成確認
試しに、nfs-clientから永続ボリュームを切り出してみます(StorageClassは、PVを切り出して、PVCでPVに対応付けて、をまとめてやってくれる便利な機能)。
以下の、PVCのマニフェストファイルを作ります。これは、nfs-clientから、20GiBのPVを切り出して、test-claimという名前のPVCとして使えるようにする、というものです。
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteMany
resources:
requests:
storage: 20Gi
PVCを作成します。
[root@master1 sc-nfs]# kubectl apply -f ./test_pvc.yaml
persistentvolumeclaim/test-claim created
[root@master1 sc-nfs]#
PVCが作成されたことを確認します。
[root@master1 sc-nfs]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
test-claim Bound pvc-d9d41a0f-3ab1-4d54-a7cd-f4d49698bb24 20Gi RWX nfs-client <unset> 30s
[root@master1 sc-nfs]#
また、PVはNFSサーバの共有されたボリュームに、20GiB分のディスク領域として確保し、PodやVMが使用できるようにしたものですので、PVに書かれたデータは、実際は、NFS上に置かれます。
この、NFSサーバ側をのぞいてみると、以下のように、
- namespace名がdefaultの
- PVC名がtest-claimの
- PVであるpvc-d9d41a0f-3ab1-4d54-a7cd-f4d49698bb24
↑PVなのに、名前がpvc-なの、直感的じゃないですよね
のディレクトリが1つ、作られていました。
[root@al9host ~]# cd sc-nfs/
[root@al9host sc-nfs]#
[root@al9host sc-nfs]# ls
default-test-claim-pvc-d9d41a0f-3ab1-4d54-a7cd-f4d49698bb24
[root@al9host sc-nfs]#
中には何もデータを置いておらず、サイズは0です。
[root@al9host sc-nfs]# du -h
0 ./default-test-claim-pvc-d9d41a0f-3ab1-4d54-a7cd-f4d49698bb24
0 .
[root@al9host sc-nfs]#
2. CDIのインストール
ここまでの話の流れは、
- VMをマシンイメージから作りたい。KubeVirtのサンプル手順( https://kubevirt.io/labs/kubernetes/lab2.html )に従って実施する。
- VM作成前に、まず、CDI(Containerized Data Importer)をインストールする手順になっている。ただ、その前に、StorageClassを使えるようにした方がいいとある。
- nfs-clientのStorageClassを作った。
でした。ということで、次に、CDIをインストールします。
(1) CDIの必要性について整理
CDIとは
CDIは、KubeVirtのコミュニティで開発されている、Kubernetesで永続ストレージを管理するためのアドオンです。主目的は、KubeVirtのVMの仮想ディスクを、PVC上に宣言的に提供できるようにすることです。
「KubeVirtのVMの仮想ディスクを、PVC上に宣言的に提供できるようにすること」がどういうことか理解するには、そもそも、k8s環境におけるVMと仮想ディスクの関係がどういうものか、明確にイメージする必要があると思います。
VMの仮想ディスクについて
一般的に、VMを作る場合、例えば、20GiBの空の仮想ディスク(KVMでいえばqcow2ファイルやrawファイル)を作成し、VMに接続し、VMは、インストーラISOファイルから起動します。

これとは別の、便利なVMの作り方として、マシンイメージとcloud-initの仕組みを使ってVMを作る方法があります。KubeVirtのVMの作り方ガイドでは、こちらの方法が例示されています。
マシンイメージとcloud-initによるVM作成

この方法では、
- OSディストリビュータが、インストーラISOファイルのとなりで提供している、マシンイメージファイル(qcow2形式。呼び方はまちまち)をダウンロードする。
- マシンイメージは、作成するVMのゲストOSの「/」領域を含むディスクとして使う(=ルートディスク)。もし例えば、ルートディスクを16GiBや40GiBにしたい、などのときは、仮想サイズ(=virtual size)をqemu-imgコマンドなどで変えてから使う。
- マシンイメージは、cloud-initによるゲストOSの初期設定(ユーザ作成やパスワード設定など)に対応しており、その初期設定を記述した短いテキストファイルをisoファイル化し、VMに接続する。
を行った上でVMを起動すると、cloud-initのisoファイルの内容に沿ってマシンイメージが初期設定され、VM起動から数十秒でセットアップが完了し、OSにログインして使用できるようになります。
cloud-initは、AWSのインスタンスを作るときの仕組みで、これを、他のパブリッククラウドや、KVMやKubeVirtでVMを作るときにも使うことができます。
KVMで、マシンイメージからAlmaLinux9のVMを作る方法については、以下にあります。
仮想ディスクの格納場所について
さて、KVM環境の場合、VMの仮想ディスクファイルは、通常、defaultストレージプール(=/var/lib/libvirt/images/)に格納します。

一方、KubeVirt環境の場合、VMは、k8sクラスタのどのWorkerノードでも動けるように、各Workerノードからアクセスできる共有ディスク(NFSなど)に仮想ディスクファイルが格納されていると、可用性が高まるため望ましい、となります。

k8sの永続ボリュームについて
ただし、KubeVirt環境=k8s環境であり、k8s環境では、単純に共有ディスクをどこかのWorkerノードにマウントして、Podが再起動しても残したいデータ(例えば、MariaDBコンテナの/var/lib/mysql/など)を置くのに使わせる、ということはできません。それをしたい場合は、
- PVリソースを作る。PVは、NFS上のディレクトリに対応付けされる。
- PVCリソースを作る。PVCは、どのPVを使うか対応付けされる。
- Podは、PVCを接続して使う。これにより、PVのデータに対応付けされる。
という使い方が必要です。

StorageClassについて
この、PV作成、PVC作成、PodとPVの対応付け、を簡易化したのが、StorageClassです。あらかじめ、Storageclassリソースをk8sクラスタ上に作り、nfs-clientという名前でNFSサーバを抽象的に指定できるようにしておくことで、Pod作成時に、nfs-clientから100GiBの永続ボリュームを切り出して使いたい、と宣言して使えるようになります。個別にPVを作り、PVCを作り、対応付ける、を、自動的にやってくれます。

KubeVirtのVMのPVについて
PodではなくVMの場合も、StorageClass経由でPVを利用し、PVに仮想ディスクを永続的に格納します。これで、VMが動いているWorkerノードで障害が起きた場合でも、別のWorkerノードで起動して、継続して利用できます。

CDIによる仮想ディスクの宣言的提供
ここまでを踏まえて、あらためて最初の、
「CDIの主目的は、KubeVirtのVMの仮想ディスクを、PVC上に宣言的に提供できるようにすること」
の意味を考えると、VMの仮想ディスクを、宣言的に提供するとは、以下の図のように、PVCに対応付けられたPVに、VMが使用する仮想ディスクが格納されるように宣言をする(PVCリソースのマニフェストファイルを作り適用する)と、そこに実際にマシンイメージのファイルを入手して配置することをCDIがやってくれる、という関係となります。

(2) CDIのインストール
以下に従って、CDIをインストールします。
現在(2025/2)のCDIの最新は、v1.61.1です。
[root@master1 kubevirt]# export VERSION=$(basename $(curl -s -w %{redirect_url} https://github.com/kubevirt/containerized-data-importer/releases/latest))
[root@master1 kubevirt]#
[root@master1 kubevirt]# echo $VERSION
v1.61.1
[root@master1 kubevirt]#
Operatorと、CR(カスタムリソース)のマニフェストファイルを取得します。
[root@master1 kubevirt]# curl -L -O https://github.com/kubevirt/containerized-data-importer/releases/download/$VERSION/cdi-operator.yaml
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 346k 100 346k 0 0 521k 0 --:--:-- --:--:-- --:--:-- 521k
[root@master1 kubevirt]#
[root@master1 kubevirt]# curl -L -O https://github.com/kubevirt/containerized-data-importer/releases/download/$VERSION/cdi-cr.yaml
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 353 100 353 0 0 460 0 --:--:-- --:--:-- --:--:-- 0
[root@master1 kubevirt]#
[root@master1 kubevirt]# ls -l | grep cdi
-rw-r--r--. 1 root root 353 Feb 11 14:05 cdi-cr.yaml
-rw-r--r--. 1 root root 354827 Feb 11 14:05 cdi-operator.yaml
[root@master1 kubevirt]#
各リソースを作成します。
[root@master1 kubevirt]# kubectl create -f ./cdi-operator.yaml
namespace/cdi created
customresourcedefinition.apiextensions.k8s.io/cdis.cdi.kubevirt.io created
clusterrole.rbac.authorization.k8s.io/cdi-operator-cluster created
clusterrolebinding.rbac.authorization.k8s.io/cdi-operator created
serviceaccount/cdi-operator created
role.rbac.authorization.k8s.io/cdi-operator created
rolebinding.rbac.authorization.k8s.io/cdi-operator created
deployment.apps/cdi-operator created
[root@master1 kubevirt]#
[root@master1 kubevirt]# kubectl create -f ./cdi-cr.yaml
cdi.cdi.kubevirt.io/cdi created
[root@master1 kubevirt]#
これで、namespace=cdiに、Cdiリソース(名前はcdi)が作られています。
[root@master1 kubevirt]# kubectl get cdi -n cdi
NAME AGE PHASE
cdi 2m34s Deployed
[root@master1 kubevirt]#
namespace=cdiに作られた各リソースは以下です。
[root@master1 kubevirt]# kubectl get all -n cdi
Warning: kubevirt.io/v1 VirtualMachineInstancePresets is now deprecated and will be removed in v2.
NAME READY STATUS RESTARTS AGE
pod/cdi-apiserver-cb54d6865-85vv4 1/1 Running 0 76s
pod/cdi-deployment-788b9948f8-w2nzk 1/1 Running 0 76s
pod/cdi-operator-7f6b8ddb4f-mjmpj 1/1 Running 0 96s
pod/cdi-uploadproxy-6cf85fc475-sbfnk 1/1 Running 0 76s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/cdi-api ClusterIP 10.110.82.60 <none> 443/TCP 76s
service/cdi-prometheus-metrics ClusterIP 10.105.9.213 <none> 8080/TCP 76s
service/cdi-uploadproxy ClusterIP 10.104.14.2 <none> 443/TCP 76s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/cdi-apiserver 1/1 1 1 76s
deployment.apps/cdi-deployment 1/1 1 1 76s
deployment.apps/cdi-operator 1/1 1 1 96s
deployment.apps/cdi-uploadproxy 1/1 1 1 76s
NAME DESIRED CURRENT READY AGE
replicaset.apps/cdi-apiserver-cb54d6865 1 1 1 76s
replicaset.apps/cdi-deployment-788b9948f8 1 1 1 76s
replicaset.apps/cdi-operator-7f6b8ddb4f 1 1 1 96s
replicaset.apps/cdi-uploadproxy-6cf85fc475 1 1 1 76s
[root@master1 kubevirt]#
3. CDIによるマシンイメージのインポート
ここまでで、VMの仮想ディスクを永続ボリュームに置いて利用する準備ができましたので、VMを作成します。ここでは、RockyLinux9のマシンイメージを使い、VMを作ります。
RockyLinux9のマシンイメージは、ここで提供されています。
Index of /pub/rocky/9/images/x86_64/
../
CHECKSUM 19-Nov-2024 17:15 5492
~
Rocky-9-GenericCloud-Base-9.5-20241118.0.x86_64..> 19-Nov-2024 03:53 609812480
Rocky-9-GenericCloud-Base-9.5-20241118.0.x86_64..> 19-Nov-2024 17:13 203
Rocky-9-GenericCloud-Base.latest.x86_64.qcow2 19-Nov-2024 03:53 609812480
Rocky-9-GenericCloud-Base.latest.x86_64.qcow2.C..> 19-Nov-2024 17:13 187
Rocky-9-GenericCloud-LVM-9.5-20241118.0.x86_64...> 19-Nov-2024 03:53 619839488
Rocky-9-GenericCloud-LVM-9.5-20241118.0.x86_64...> 19-Nov-2024 17:14 201
Rocky-9-GenericCloud-LVM.latest.x86_64.qcow2 19-Nov-2024 03:53 619839488
Rocky-9-GenericCloud-LVM.latest.x86_64.qcow2.CH..> 19-Nov-2024 17:14 185
Rocky-9-GenericCloud.latest.x86_64.qcow2 19-Nov-2024 03:53 609812480
Rocky-9-GenericCloud.latest.x86_64.qcow2.CHECKSUM 19-Nov-2024 17:13 177
~
色々と種類がありますが、ここでは、
https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base-9.5-20241118.0.x86_64.qcow2
を使用することにします。これは、恐らく、RockyLinux 9.5のインストールISOファイルから、パッケージ:最小限を選択してインストールしたのと同等のゲストOSが得られるものと思われます。
また、上記「Labs - Experiment with CDI」のページにある、dv_fedora.yamlは、DataVolume(PV/PVC/StorageClassをさらに簡易化するリソース)の例なので、ここでは、以下のCDIを使うPVCリソースのサンプル(golden-pvc.yaml)をベースに、マニフェストファイルを作ります。
(1) PVC(+PV)の作成
以下を作りました。PVサイズは16GiBを指定しています。仮想ディスクは、16GiBのPVに収まる程度の、適当なサイズにリサイズされて格納されることになります。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: "rocky-9.5-20241118"
labels:
app: containerized-data-importer
annotations:
cdi.kubevirt.io/storage.import.endpoint: "https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base-9.5-20241118.0.x86_64.qcow2"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 16Gi
storageClassName: nfs-client
適用します。
[root@master1 kubevirt]# kubectl apply -f ./pvc_rocky-9.5-20241118.yaml
persistentvolumeclaim/rocky-9.5-20241118 created
[root@master1 kubevirt]#
直後に、NFSサーバのファイルを確認すると、ディレクトリが2つ作られていました。scratchの方は、中間ファイルです。
[root@al9host sc-nfs]# ls -l
合計 0
drwxrwxrwx. 2 root root 6 2月 16 08:07 default-rocky-9.5-20241118-pvc-f5fcfd71-0604-4476-aebe-8f93f007b1b3
drwxrwxrwx. 2 root root 22 2月 16 08:07 default-rocky-9.5-20241118-scratch-pvc-7fa7cdf2-176d-4739-a2bb-32709c864ce8
[root@al9host sc-nfs]#
[root@al9host sc-nfs]# du -h
0 ./default-rocky-9.5-20241118-pvc-f5fcfd71-0604-4476-aebe-8f93f007b1b3
64M ./default-rocky-9.5-20241118-scratch-pvc-7fa7cdf2-176d-4739-a2bb-32709c864ce8
64M .
[root@al9host sc-nfs]#
また、importerのPodが作られ、PVに仮想ディスクを作る処理が行われます。
[root@master1 kubevirt]# kubectl get pod
NAME READY STATUS RESTARTS AGE
importer-rocky-9.5-20241118 1/1 Running 0 70s
nfs-subdir-external-provisioner-7868d5c9d8-w9zfx 1/1 Running 0 23h
[root@master1 kubevirt]#
このPodのログを見ると、以下のように、進捗が見られます。
[root@master1 kubevirt]# kubectl logs importer-rocky-9.5-20241118
I0215 23:07:37.425648 1 importer.go:107] Starting importer
I0215 23:07:37.425988 1 importer.go:182] begin import process
I0215 23:07:37.944795 1 data-processor.go:348] Calculating available size
I0215 23:07:37.947123 1 data-processor.go:360] Checking out file system volume size.
I0215 23:07:37.947962 1 data-processor.go:367] Request image size not empty.
I0215 23:07:37.948108 1 data-processor.go:373] Target size 16Gi.
I0215 23:07:37.961087 1 data-processor.go:247] New phase: TransferScratch
I0215 23:07:37.995332 1 util.go:96] Writing data...
I0215 23:07:38.963870 1 prometheus.go:78] 0.71
I0215 23:07:39.963953 1 prometheus.go:78] 2.27
I0215 23:07:40.964020 1 prometheus.go:78] 4.71
I0215 23:07:41.964746 1 prometheus.go:78] 6.36
I0215 23:07:42.964914 1 prometheus.go:78] 7.96
I0215 23:07:43.965364 1 prometheus.go:78] 8.94
I0215 23:07:44.966232 1 prometheus.go:78] 10.83
まず、ダウンロードが行われて、100%になると、変換が行われています。
I0215 23:08:30.986380 1 prometheus.go:78] 89.41
I0215 23:08:31.987503 1 prometheus.go:78] 91.48
I0215 23:08:32.988479 1 prometheus.go:78] 93.03
I0215 23:08:33.989357 1 prometheus.go:78] 94.55
I0215 23:08:34.989496 1 prometheus.go:78] 97.04
I0215 23:08:35.990730 1 prometheus.go:78] 98.87
I0215 23:08:36.784653 1 data-processor.go:247] New phase: Convert
I0215 23:08:36.784717 1 data-processor.go:253] Validating image
I0215 23:08:36.991303 1 prometheus.go:78] 100.00
E0215 23:08:37.039726 1 prlimit.go:156] failed to kill the process; os: process already finished
I0215 23:08:37.040309 1 qemu.go:115] Running qemu-img with args: [convert -t writeback -p -O raw /scratch/tmpimage /data/disk.img]
I0215 23:08:37.059908 1 qemu.go:273] 0.00
I0215 23:08:37.375001 1 qemu.go:273] 1.00
I0215 23:08:37.575827 1 qemu.go:273] 2.01
I0215 23:08:37.763935 1 qemu.go:273] 3.04
I0215 23:08:37.877032 1 qemu.go:273] 4.21
I0215 23:08:37.964994 1 qemu.go:273] 5.23
I0215 23:08:38.259331 1 qemu.go:273] 6.27
終わると、importerのPodも消えます。
I0215 23:09:18.905379 1 qemu.go:273] 91.87
I0215 23:09:19.165375 1 qemu.go:273] 92.87
I0215 23:09:19.663791 1 qemu.go:273] 93.88
I0215 23:09:20.077426 1 qemu.go:273] 94.88
I0215 23:09:20.565438 1 qemu.go:273] 95.88
I0215 23:09:20.971847 1 qemu.go:273] 96.89
I0215 23:09:21.468741 1 qemu.go:273] 97.89
I0215 23:09:21.584420 1 qemu.go:273] 98.90
I0215 23:09:21.769701 1 qemu.go:273] 99.90
E0215 23:09:23.919311 1 prlimit.go:156] failed to kill the process; os: process already finished
I0215 23:09:23.919334 1 data-processor.go:247] New phase: Resize
E0215 23:09:23.925683 1 prlimit.go:156] failed to kill the process; os: process already finished
W0215 23:09:23.925720 1 data-processor.go:330] Available space less than requested size, resizing image to available space 16234053632.
I0215 23:09:23.925725 1 data-processor.go:341] Expanding image size to: 16234053632
E0215 23:09:23.933723 1 prlimit.go:156] failed to kill the process; os: process already finished
I0215 23:09:23.933740 1 data-processor.go:253] Validating image
E0215 23:09:23.937732 1 prlimit.go:156] failed to kill the process; os: process already finished
I0215 23:09:23.941377 1 data-processor.go:247] New phase: Complete
I0215 23:09:23.941500 1 importer.go:231] {"scratchSpaceRequired":false,"preallocationApplied":false,"message":"Import Complete"}
[root@master1 kubevirt]#
[root@master1 kubevirt]#
[root@master1 kubevirt]# kubectl logs importer-rocky-9.5-20241118
error: error from server (NotFound): pods "importer-rocky-9.5-20241118" not found in namespace "default"
[root@master1 kubevirt]#
[root@master1 kubevirt]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-subdir-external-provisioner-7868d5c9d8-w9zfx 1/1 Running 0 23h
[root@master1 kubevirt]#
(2) PVC/PVの確認
これで、PVC(名前:rocky-9.5-20241118)と、そのPVCに対応したPV(名前:pvc-f5fcfd71-0604-4476-aebe-8f93f007b1b3)が作られています。
[root@master1 kubevirt]# kubectl get pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
persistentvolume/pvc-f5fcfd71-0604-4476-aebe-8f93f007b1b3 16Gi RWO Delete Bound default/rocky-9.5-20241118 nfs-client <unset> 5m22s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/rocky-9.5-20241118 Bound pvc-f5fcfd71-0604-4476-aebe-8f93f007b1b3 16Gi RWO nfs-client <unset> 5m22s
[root@master1 kubevirt]#
NFSサーバを見ると、scratchのディレクトリは先頭にarchived-の付いた名前に代わっていました。
[root@al9host sc-nfs]# ls -l
合計 0
drwxrwxrwx. 2 root root 22 2月 16 08:07 archived-default-rocky-9.5-20241118-scratch-pvc-7fa7cdf2-176d-4739-a2bb-32709c864ce8
drwxrwxrwx. 2 root root 22 2月 16 08:08 default-rocky-9.5-20241118-pvc-f5fcfd71-0604-4476-aebe-8f93f007b1b3
[root@al9host sc-nfs]#
PVのディレクトリには、仮想ディスクファイルdisk.imgが1つだけ置かれています。これが、VMの仮想ディスク(ルートディスク)となります。
[root@al9host sc-nfs]# ls -lhs ./default-rocky-9.5-20241118-pvc-f5fcfd71-0604-4476-aebe-8f93f007b1b3/
合計 1.2G
1.2G -rw-rw----. 1 qemu root 16G 2月 16 08:09 disk.img
[root@al9host sc-nfs]#
qemu-img infoで、仮想ディスクファイルの情報を見ると、
- raw形式
- virtual sizeが15.1GiB
であることがわかります。PVを16GiBとする、と指定した場合、VMの仮想ディスクは、15.1GiB(シンプロビジョニング)が作られた、となります。
[root@al9host sc-nfs]# qemu-img info ./default-rocky-9.5-20241118-pvc-f5fcfd71-0604-4476-aebe-8f93f007b1b3/disk.i
mg
image: ./default-rocky-9.5-20241118-pvc-f5fcfd71-0604-4476-aebe-8f93f007b1b3/disk.img
file format: raw
virtual size: 15.1 GiB (16234053632 bytes)
disk size: 1.13 GiB
Child node '/file':
filename: ./default-rocky-9.5-20241118-pvc-f5fcfd71-0604-4476-aebe-8f93f007b1b3/disk.img
protocol type: file
file length: 15.1 GiB (16234053632 bytes)
disk size: 1.13 GiB
[root@al9host sc-nfs]#
archived-の方には、tmpimageというファイルが置いてありました。
[root@al9host sc-nfs]# ls -lhs ./archived-default-rocky-9.5-20241118-scratch-pvc-7fa7cdf2-176d-4739-a2bb-32709c864ce8/
合計 582M
582M -rwxr-xr-x. 1 qemu root 582M 2月 16 08:08 tmpimage
[root@al9host sc-nfs]#
qemu-img infoで見ると、実サイズ1.2GiB、仮想サイズ16GiBとあり、CDIにImportを指示した、Rocky-9-GenericCloud-Base-9.5-20241118.0.x86_64.qcow2と同じに見えます。
[root@al9host sc-nfs]# qemu-img info ./archived-default-rocky-9.5-20241118-scratch-pvc-7fa7cdf2-176d-4739-a2bb-32
709c864ce8/tmpimage
image: ./archived-default-rocky-9.5-20241118-scratch-pvc-7fa7cdf2-176d-4739-a2bb-32709c864ce8/tmpimage
file format: qcow2
virtual size: 10 GiB (10737418240 bytes)
disk size: 582 MiB
cluster_size: 65536
Format specific information:
compat: 1.1
compression type: zlib
lazy refcounts: false
refcount bits: 16
corrupt: false
extended l2: false
Child node '/file':
filename: ./archived-default-rocky-9.5-20241118-scratch-pvc-7fa7cdf2-176d-4739-a2bb-32709c864ce8/tmpimage
protocol type: file
file length: 582 MiB (609812480 bytes)
disk size: 582 MiB
[root@al9host sc-nfs]#
別途、ダウンロードしたものとdiffすると、一致していました。
[root@al9host sc-nfs]# diff archived-default-rocky-9.5-20241118-scratch-pvc-7fa7cdf2-176d-4739-a2bb-32709c864ce8/tmpimage /tmp/Rocky-9-GenericCloud-Base-9.5-20241118.0.x86_64.qcow2
[root@al9host sc-nfs]#
最初試したとき、PVCのspec.resources.requests.storageを10Giにしていました。その時は何度もエラーになり、やり直しされ、最終的に7回ほどで成功しましたが、そもそも、元のマシンイメージが10Giですが、PVサイズを10Giにすると変換後に置かれるファイルは10Gi未満になります。qemu-img resizeで変換する場合、10Gi未満を指定するとエラーになるため、PVサイズは、それなりに適切なサイズを指定する必要があります。例えば、40Giを指定した場合、40GiのPVが作られ、そこに置かれる仮想ディスクは38GiBとなり、ゲストOSの/領域が37GiB程度として見える、という関係になります。
単に、URLで指定したqcow2ファイル(580MBとか)をダウンロードして保存できる空きサイズを指定すればいいわけではないのでご注意を(最初、そう勘違いして700Miとかにしていました)。

4. VMの作成
VMのマニフェストファイルを作り、上で作成したPVCの先の仮想ディスクを使うように、VMを作成します。
(1) VMのマニフェストファイルの作成
KubeVirtのVM作成のサンプルページの、「Let’s create a Virtual Machine making use of it. Review the file vm1_pvc.yml.」のあたりに、VMのマニフェストファイルのひな形があります。
まずは、このひな形を取得します。
[root@master1 kubevirt]# curl -LO https://kubevirt.io/labs/manifests/vm1_pvc.yml
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1018 100 1018 0 0 13945 0 --:--:-- --:--:-- --:--:-- 13945
[root@master1 kubevirt]#
中身は以下でした。VMの仮想HWの設定と、cloud-initの設定、があります。
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
creationTimestamp: 2018-07-04T15:03:08Z
generation: 1
labels:
kubevirt.io/os: linux
name: vm1
spec:
runStrategy: Always
template:
metadata:
creationTimestamp: null
labels:
kubevirt.io/domain: vm1
spec:
domain:
cpu:
cores: 2
devices:
disks:
- disk:
bus: virtio
name: disk0
- cdrom:
bus: sata
readonly: true
name: cloudinitdisk
machine:
type: q35
resources:
requests:
memory: 1024M
volumes:
- name: disk0
persistentVolumeClaim:
claimName: fedora
- cloudInitNoCloud:
userData: |
#cloud-config
hostname: vm1
ssh_pwauth: True
disable_root: false
ssh_authorized_keys:
- ssh-rsa YOUR_SSH_PUB_KEY_HERE
name: cloudinitdisk
これをベースに、作成するVMの設定を作ります。まず、複製します。
[root@master1 kubevirt]# cp vm1_pvc.yml rl95vm-1.yaml
[root@master1 kubevirt]#
設定を適当に作ります。ベースとの差分を以下としました。
[root@master1 kubevirt]# diff -U1 vm1_pvc.yml rl95vm-1.yaml
--- vm1_pvc.yml 2025-02-16 15:52:52.432177821 +0900
+++ rl95vm-1.yaml 2025-02-16 16:39:41.726584128 +0900
@@ -7,3 +7,3 @@
kubevirt.io/os: linux
- name: vm1
+ name: rl95vm-1
spec:
@@ -14,3 +14,3 @@
labels:
- kubevirt.io/domain: vm1
+ kubevirt.io/domain: rl95vm-1
spec:
@@ -18,3 +18,3 @@
cpu:
- cores: 2
+ cores: 4
devices:
@@ -32,3 +32,3 @@
requests:
- memory: 1024M
+ memory: 4096M
volumes:
@@ -36,3 +36,3 @@
persistentVolumeClaim:
- claimName: fedora
+ claimName: rocky-9.5-20241118
- cloudInitNoCloud:
@@ -40,7 +40,11 @@
#cloud-config
- hostname: vm1
- ssh_pwauth: True
- disable_root: false
- ssh_authorized_keys:
- - ssh-rsa YOUR_SSH_PUB_KEY_HERE
+ hostname: rl95vm-1
+ users:
+ - name: myuser
+ ssh_pwauth: true
+ chpasswd:
+ list: |
+ root:al9host!
+ myuser:al9host!
+ expire: False
name: cloudinitdisk
[root@master1 kubevirt]#
(2) VMの作成
適用します。
[root@master1 kubevirt]# kubectl apply -f ./rl95vm-1.yaml
virtualmachine.kubevirt.io/rl95vm-1 created
[root@master1 kubevirt]#
Vmリソース、Vmiリソースを確認します。
[root@master1 kubevirt]# kubectl get vm,vmi
NAME AGE STATUS READY
virtualmachine.kubevirt.io/rl95vm-1 65s Running True
NAME AGE PHASE IP NODENAME READY
virtualmachineinstance.kubevirt.io/rl95vm-1 65s Running 10.244.1.74 worker1.internal True
[root@master1 kubevirt]#
IPアドレスが10.244.1.74であることがわかります。sshで、cloud-initで作成したmyuserユーザでログインできます。
[root@master1 kubevirt]# ssh myuser@10.244.1.74
The authenticity of host '10.244.1.74 (10.244.1.74)' can't be established.
ED25519 key fingerprint is SHA256:9ZlNBWeY2LfJEnAKHWokC6wrZp8VywpkOPwNlbW6jqw.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.244.1.74' (ED25519) to the list of known hosts.
myuser@10.244.1.74's password:
[myuser@rl95vm-1 ~]$
rootにもなれます。
[myuser@rl95vm-1 ~]$ su - root
Password:
[root@rl95vm-1 ~]#
このとき、cloud-initの書き方は結構クセがあり、Webで見つけたUbuntuの書き方では、RockyLinuxは設定できないことがあるようでした(詳細不明)。しかも、うまく設定できなくてもエラーが出ないので、VMは作成できてもログインができない、でかなり試行錯誤しました。上記は、cloud-initを使用して、CockpitでVMを作成したときに、自動作成されたcloud-init.isoを参考に作ったものです。
AlmaLinux9のホストOS上で、以下の設定で、Cockpit上でVM作成した場合に作られる、cloud-init.isoの中身は、以下でした。
- rootパスワード「al9host!」
- myuserユーザを作成し、パスワード「al9host!」
- ssh公開鍵を追加(myuserユーザの~/.ssh/authorized_keysに登録される)
[root@localhost ~]# more /mnt/*
::::::::::::::
/mnt/meta-data
::::::::::::::
::::::::::::::
/mnt/user-data
::::::::::::::
#cloud-config
users:
- name: myuser
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDdWhVr/7Yq37UUPHXcGoUc~(略)~DWXSbtfp8l1M8K9cJDF10sKI5gxEM= root@al9host
ssh_pwauth: true
chpasswd:
list: |
root:$5$gHVy7gxS.zvitDxk$h1HOJ3EnCwgrmW8myTvl.bBBcmucjXeJCU4ZFeq6IrA
myuser:$5$25ixkhGjMTuMVzEG$xx9dJqad50exo9l68TjLXPbs4wCkK2ghL2HTA2enUtD
expire: False
[root@localhost ~]#
(3) ゲストOSの確認
CPU。
[root@rl95vm-1 ~]# cat /proc/cpuinfo | grep -A4 processor
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 94
model name : Intel Core Processor (Skylake, IBRS)
--
processor : 1
vendor_id : GenuineIntel
cpu family : 6
model : 94
model name : Intel Core Processor (Skylake, IBRS)
--
processor : 2
vendor_id : GenuineIntel
cpu family : 6
model : 94
model name : Intel Core Processor (Skylake, IBRS)
--
processor : 3
vendor_id : GenuineIntel
cpu family : 6
model : 94
model name : Intel Core Processor (Skylake, IBRS)
[root@rl95vm-1 ~]#
メモリ。
[root@rl95vm-1 ~]# free -m
total used free shared buff/cache available
Mem: 3534 376 3108 0 270 3157
Swap: 0 0 0
[root@rl95vm-1 ~]#
4096Giを指定しましたが、3534Miになっていました。
[root@rl95vm-1 ~]# free -h
total used free shared buff/cache available
Mem: 3.5Gi 376Mi 3.0Gi 0.0Ki 270Mi 3.1Gi
Swap: 0B 0B 0B
[root@rl95vm-1 ~]#
ディスク。
[root@rl95vm-1 ~]# fdisk -l | grep vd
Disk /dev/vda: 15.12 GiB, 16234053632 bytes, 31707136 sectors
/dev/vda1 2048 6143 4096 2M BIOS boot
/dev/vda2 6144 210943 204800 100M EFI System
/dev/vda3 210944 2258943 2048000 1000M Linux extended boot
/dev/vda4 2258944 31707102 29448159 14G Linux root (x86-64)
[root@rl95vm-1 ~]#
PVサイズを16Giとした場合、仮想ディスク(disk.img)のvirtual sizeは15.1GiBでした。ゲストOSで、「/」領域として使えるサイズは、14GiB(空は13GiB)でした。
[root@rl95vm-1 ~]# df -h | grep -e Size -e vda
Filesystem Size Used Avail Use% Mounted on
/dev/vda4 14G 1.1G 13G 8% /
/dev/vda3 936M 257M 680M 28% /boot
/dev/vda2 100M 7.0M 93M 8% /boot/efi
[root@rl95vm-1 ~]#
IPアドレスは、kubectl get vmiで得られるものと、同じ値でした。
[root@rl95vm-1 ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc fq_codel state UP group default qlen 1000
link/ether ca:e9:b7:f1:b8:52 brd ff:ff:ff:ff:ff:ff
altname enp1s0
inet 10.244.1.74/24 brd 10.244.1.255 scope global dynamic noprefixroute eth0
valid_lft 86311869sec preferred_lft 86311869sec
inet6 fe80::c8e9:b7ff:fef1:b852/64 scope link noprefixroute
valid_lft forever preferred_lft forever
[root@rl95vm-1 ~]#
[root@rl95vm-1 ~]# ip route
default via 10.244.1.1 dev eth0 proto dhcp src 10.244.1.74 metric 100
10.244.0.0/16 via 10.244.1.1 dev eth0 proto dhcp src 10.244.1.74 metric 100
10.244.1.0/24 dev eth0 proto kernel scope link src 10.244.1.74 metric 100
[root@rl95vm-1 ~]#
ネットワーク的には、Webにつながります。
[root@rl95vm-1 ~]# dnf install net-tools
Last metadata expiration check: 0:14:06 ago on Sun 16 Feb 2025 08:33:13 AM UTC.
Dependencies resolved.
========================================================================================================================================
Package Architecture Version Repository Size
========================================================================================================================================
Installing:
net-tools x86_64 2.0-0.64.20160912git.el9 baseos 294 k
Transaction Summary
========================================================================================================================================
Install 1 Package
DNSサーバとして、k8sクラスタのCoreDNSのIPアドレスを指していました。
[root@rl95vm-1 ~]# nmcli dev show | grep DNS
IP4.DNS[1]: 10.96.0.10
[root@rl95vm-1 ~]#
[root@master1 ~]# kubectl get service -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 7d2h
[root@master1 ~]#
[root@master1 ~]# kubectl get endpoints -n kube-system
NAME ENDPOINTS AGE
kube-dns 10.244.0.2:53,10.244.0.6:53,10.244.0.2:53 + 3 more... 7d2h
[root@master1 ~]#
kube-dnsのServiceのエンドポイントが10.244.0.2:53、10.244.0.6:53で、それらは、以下のcorednsのPodです。2個いるのは冗長化のためだと思いますが、どちらもmaster1で動いているので、実際は冗長化できていませんね(Masterノードが1台のため)。
[root@master1 ~]# kubectl get pod -n kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-668d6bf9bc-ck8r6 1/1 Running 5 7d2h 10.244.0.2 master1.internal <none> <none>
coredns-668d6bf9bc-pc55x 1/1 Running 5 7d2h 10.244.0.6 master1.internal <none> <none>
etcd-master1.internal 1/1 Running 5 7d2h 192.168.11.131 master1.internal <none> <none>
kube-apiserver-master1.internal 1/1 Running 5 7d2h 192.168.11.131 master1.internal <none> <none>
kube-controller-manager-master1.internal 1/1 Running 5 7d2h 192.168.11.131 master1.internal <none> <none>
kube-proxy-984bt 1/1 Running 5 7d2h 192.168.11.131 master1.internal <none> <none>
kube-proxy-kz2tl 1/1 Running 6 7d2h 192.168.11.133 worker2.internal <none> <none>
kube-proxy-pgf79 1/1 Running 6 7d2h 192.168.11.132 worker1.internal <none> <none>
kube-scheduler-master1.internal 1/1 Running 5 7d2h 192.168.11.131 master1.internal <none> <none>
[root@master1 ~]#
corednsの話は、ちょっと、本筋から外れました。(ゲストOSがどうやってWebにつながるか、つい、気になり...)
図を描くとこうなります。
5. k8sクラスタ外からVMへのアクセス
最後に、VMにk8sクラスタ外からネットワークアクセスできるようにします。MetalLBがいるので、「type: LoadBalancer」のServiceリソースを作ると、k8sクラスタ外に対し、rl95vm-1のVMにアクセスするIPアドレス/ポートを公開できます。
(1) Serviceの作成
rl95vm-1のVMのIPアドレスの22/TCPポートをエンドポイントとするServiceのマニフェストファイルを作ります。
apiVersion: v1
kind: Service
metadata:
name: rl95vm-1-ssh-service
spec:
selector:
kubevirt.io/domain: rl95vm-1
ports:
- protocol: TCP
port: 22
targetPort: 22
type: LoadBalancer
適用します。
[root@master1 ~]# kubectl apply -f ./rl95vm-1-ssh-service.yaml
service/rl95vm-1-ssh-service created
[root@master1 ~]#
rl95vm-1-ssh-serviceのServiceリソースができており、MetalLBにより、EXTERNAL-IPのIPアドレスが付与されていることを確認します。192.168.11.139が割り当たっています。
[root@master1 ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d3h
rl95vm-1-ssh-service LoadBalancer 10.97.119.79 192.168.11.139 22:30560/TCP 26s
[root@master1 ~]#
エンドポイントが、rl95vm-1のIPアドレスの22番ポートを指しています。
[root@master1 ~]# kubectl get endpoints
NAME ENDPOINTS AGE
cluster.local-nfs-subdir-external-provisioner <none> 5d6h
kubernetes 192.168.11.131:6443 7d3h
rl95vm-1-ssh-service 10.244.1.74:22 3m48s
[root@master1 ~]#
(2) k8sクラスタ外からのssh接続確認
k8sクラスタ外のal9hostから、rl95vm-1にログインできました。
[root@al9host ~]# ssh myuser@192.168.11.139
The authenticity of host '192.168.11.139 (192.168.11.139)' can't be established.
ED25519 key fingerprint is SHA256:9ZlNBWeY2LfJEnAKHWokC6wrZp8VywpkOPwNlbW6jqw.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.11.139' (ED25519) to the list of known hosts.
myuser@192.168.11.139's password:
Last login: Sun Feb 16 08:38:26 2025 from 10.244.0.0
[myuser@rl95vm-1 ~]$
まとめ
KubeVirtを使い、k8sクラスタ内に、RockyLinux9.5のマシンイメージからVMを作り、Serviceを作り、k8sクラスタ外からアクセスできることを確認しました。
参考にさせていただいた情報
ありがとうございます。