KubeVirtでマシンイメージからRockyLinuxのVMを作る

# この記事の内容
k8sクラスタで、KubeVirtを利用することでVMを作り、Pod（コンテナ）と同様に、PVCをつなげて使ったり、Probeで死活管理したりできます。

こちらの記事で、Kubevirtをインストールし、テスト用VM（cirros）を作りました。

https://qiita.com/nonbiri-yama/items/1c7af659c58fe8d0235e

この続きで、RockyLinuxのマシンイメージから、VMを作ります。
以下に従って実施します。

https://kubevirt.io/labs/kubernetes/lab2.html

# 環境
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/572be7d9-1171-4ad0-9fd0-c29739fb4e48.png)

- 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を作って使うことにします。

https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner

どこかにNFSサーバを用意してディレクトリをNFS共有し、k8sクラスタの各ノードからNFSマウントできるネットワーク構成にし、「Kubernetes NFS Subdir External Provisioner」をインストールして設定すると、「nfs-client」というStorageClassが利用できるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/a5c316dc-4c16-47ec-9158-0107af80c391.png)

ということで、このようなNFSサーバを用意します。
- al9host（192.168.11.103）をNFSサーバにする（k8sクラスタからアクセス可）。
- al9hostに、/root/sc-nfs/を作り、NFSエクスポート（共有）する。

### (1) NFSサーバの作成

al9hostで、nfs-utilsが入っていることを確認します。無ければ、以下で入れます。
```
dnf install nfs-utils
```

共有するディレクトリを作ります。
```bash
[root@al9host ~]# mkdir /root/sc-nfs
[root@al9host ~]#
```

各ノードからアクセスできるよう設定し、nfs-serverサービスを有効化/起動します。
```bash
[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を許可します。
```bash
[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マウントできることを確認します（確認後、アンマウント）。
```bash
[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コマンドを使えるようにする手順は以下にあります。

https://qiita.com/nonbiri-yama/items/8239aa53c83f7ecbb184

helmコマンドを使い、リポジトリを追加します。
```bash
[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サーバを指定しています。
```bash
[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が使えるようになったことを確認します。

```bash
[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で見たものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/25f8be7c-4201-4d5c-a5bc-a92c06586ba6.png)


### (3) PVCの作成確認

試しに、nfs-clientから永続ボリュームを切り出してみます（StorageClassは、PVを切り出して、PVCでPVに対応付けて、をまとめてやってくれる便利な機能）。

以下の、PVCのマニフェストファイルを作ります。これは、nfs-clientから、20GiBのPVを切り出して、test-claimという名前のPVCとして使えるようにする、というものです。
```yaml:test_pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim
spec:
  storageClassName: nfs-client
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 20Gi
```

PVCを作成します。
```bash
[root@master1 sc-nfs]# kubectl apply -f ./test_pvc.yaml
persistentvolumeclaim/test-claim created
[root@master1 sc-nfs]#
```

PVCが作成されたことを確認します。
```bash
[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]#
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/b9ff6670-cd7a-429e-a42b-2249c73c8b2e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/2afd8d57-c949-4613-baf5-f32d9427fcce.png)

また、PVはNFSサーバの共有されたボリュームに、20GiB分のディスク領域として確保し、PodやVMが使用できるようにしたものですので、PVに書かれたデータは、実際は、NFS上に置かれます。

この、NFSサーバ側をのぞいてみると、以下のように、
- namespace名がdefaultの
- PVC名がtest-claimの
- PVであるpvc-d9d41a0f-3ab1-4d54-a7cd-f4d49698bb24
　　　　　↑PVなのに、名前がpvc-なの、直感的じゃないですよね

のディレクトリが1つ、作られていました。
```bash
[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です。
```bash
[root@al9host sc-nfs]# du -h
0       ./default-test-claim-pvc-d9d41a0f-3ab1-4d54-a7cd-f4d49698bb24
0       .
[root@al9host sc-nfs]#
```

# 2. CDIのインストール

ここまでの話の流れは、

1. VMをマシンイメージから作りたい。KubeVirtのサンプル手順（ https://kubevirt.io/labs/kubernetes/lab2.html ）に従って実施する。
2. VM作成前に、まず、CDI（Containerized Data Importer）をインストールする手順になっている。ただ、その前に、StorageClassを使えるようにした方がいいとある。
3. nfs-clientのStorageClassを作った。

でした。ということで、次に、CDIをインストールします。

### (1) CDIの必要性について整理

#### CDIとは

CDIは、KubeVirtのコミュニティで開発されている、Kubernetesで永続ストレージを管理するためのアドオンです。主目的は、KubeVirtのVMの仮想ディスクを、PVC上に宣言的に提供できるようにすることです。

https://github.com/kubevirt/containerized-data-importer

「KubeVirtのVMの仮想ディスクを、PVC上に宣言的に提供できるようにすること」がどういうことか理解するには、そもそも、k8s環境におけるVMと仮想ディスクの関係がどういうものか、明確にイメージする必要があると思います。

#### VMの仮想ディスクについて

一般的に、VMを作る場合、例えば、20GiBの空の仮想ディスク（KVMでいえばqcow2ファイルやrawファイル）を作成し、VMに接続し、VMは、インストーラISOファイルから起動します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/f37b9bd1-afb3-4ba6-89aa-e69ec1bd5a1d.png" alt="Description" width="50%">

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

#### マシンイメージとcloud-initによるVM作成

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/98dfdad8-2e3c-42b0-a8aa-174ab552a158.png" alt="Description" width="100%">

この方法では、

- 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を作る方法については、以下にあります。

https://qiita.com/nonbiri-yama/items/0fa631c8f11c68d16b34

#### 仮想ディスクの格納場所について

さて、KVM環境の場合、VMの仮想ディスクファイルは、通常、defaultストレージプール（=/var/lib/libvirt/images/）に格納します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/d2290131-b795-4244-a5ee-9ea02ed30867.png" alt="Description" width="100%">

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/5767320e-18d6-460b-9653-1c3e54f61c4e.png" alt="Description" width="100%">

#### k8sの永続ボリュームについて

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

という使い方が必要です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/997d1715-efa4-4c55-856f-f6c32f0df96c.png" alt="Description" width="100%">

#### StorageClassについて

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/2c19669a-0491-4e5a-864a-cca8da7921e2.png" alt="Description" width="100%">

#### KubeVirtのVMのPVについて

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/8f784dad-b3fb-4bb4-b672-8b7518f20a0f.png" alt="Description" width="100%">

#### CDIによる仮想ディスクの宣言的提供

ここまでを踏まえて、あらためて最初の、

「CDIの主目的は、KubeVirtのVMの仮想ディスクを、PVC上に宣言的に提供できるようにすること」

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/ebcd0b63-9091-424c-ad91-6d6f9a5f3f06.png" alt="Description" width="100%">


### (2) CDIのインストール

以下に従って、CDIをインストールします。

https://kubevirt.io/labs/kubernetes/lab2.html

現在（2025/2）のCDIの最新は、v1.61.1です。
```bash
[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（カスタムリソース）のマニフェストファイルを取得します。
```bash
[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]#
```

```bash
[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]#
```

```bash
[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]#
```

各リソースを作成します。
```bash
[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]#
```

```bash
[root@master1 kubevirt]# kubectl create -f ./cdi-cr.yaml
cdi.cdi.kubevirt.io/cdi created
[root@master1 kubevirt]#
```

これで、namespace=cdiに、Cdiリソース（名前はcdi）が作られています。
```bash
[root@master1 kubevirt]# kubectl get cdi -n cdi
NAME   AGE     PHASE
cdi    2m34s   Deployed
[root@master1 kubevirt]#
```

namespace=cdiに作られた各リソースは以下です。
```bash
[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のマシンイメージは、ここで提供されています。

https://dl.rockylinux.org/pub/rocky/9/images/x86_64/

```
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）をベースに、マニフェストファイルを作ります。

https://kubevirt.io/2018/containerized-data-importer.html

### (1) PVC(+PV)の作成

以下を作りました。PVサイズは16GiBを指定しています。仮想ディスクは、16GiBのPVに収まる程度の、適当なサイズにリサイズされて格納されることになります。
```yaml:pvc_rocky-9.5-20241118.yaml
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
```

適用します。
```bash
[root@master1 kubevirt]# kubectl apply -f ./pvc_rocky-9.5-20241118.yaml
persistentvolumeclaim/rocky-9.5-20241118 created
[root@master1 kubevirt]#
```

直後に、NFSサーバのファイルを確認すると、ディレクトリが2つ作られていました。scratchの方は、中間ファイルです。

```bash
[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に仮想ディスクを作る処理が行われます。
```bash
[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のログを見ると、以下のように、進捗が見られます。
```bash
[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%になると、変換が行われています。
```bash
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も消えます。

```bash
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）が作られています。
```bash
[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-の付いた名前に代わっていました。
```bash
[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の仮想ディスク（ルートディスク）となります。
```bash
[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（シンプロビジョニング）が作られた、となります。

```bash
[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というファイルが置いてありました。
```bash
[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と同じに見えます。
```bash
[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すると、一致していました。
```bash
[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とかにしていました）。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/3ca65ea5-7608-4607-8820-e333edb9f85e.png" alt="Description" width="100%">



# 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のマニフェストファイルのひな形があります。

https://kubevirt.io/labs/manifests/vm1_pvc.yml

まずは、このひな形を取得します。
```bash
[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の設定、があります。
```yaml:vm1_pvc.yml
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の設定を作ります。まず、複製します。
```bash
[root@master1 kubevirt]# cp vm1_pvc.yml rl95vm-1.yaml
[root@master1 kubevirt]#
```

設定を適当に作ります。ベースとの差分を以下としました。
```bash
[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の作成

適用します。
```bash
[root@master1 kubevirt]# kubectl apply -f ./rl95vm-1.yaml
virtualmachine.kubevirt.io/rl95vm-1 created
[root@master1 kubevirt]#
```

Vmリソース、Vmiリソースを確認します。
```bash
[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ユーザでログインできます。
```bash
[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にもなれます。
```bash
[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に登録される）

```bash
[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。
```bash
[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 ~]#
```

メモリ。
```bash
[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になっていました。
```bash
[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 ~]#
```

ディスク。
```bash
[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）でした。
```bash
[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で得られるものと、同じ値でした。
```bash
[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 ~]#
```

```bash
[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につながります。
```bash
[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アドレスを指していました。
```bash
[root@rl95vm-1 ~]# nmcli dev show | grep DNS
IP4.DNS[1]:                             10.96.0.10
[root@rl95vm-1 ~]#
```

```bash
[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につながるか、つい、気になり...）

図を描くとこうなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/ed454fa0-457b-4121-9249-f14d0f043cc3.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/38531fbd-56a4-469c-a6f8-26893ba25d5a.png)


# 5. k8sクラスタ外からVMへのアクセス

最後に、VMにk8sクラスタ外からネットワークアクセスできるようにします。MetalLBがいるので、「type: LoadBalancer」のServiceリソースを作ると、k8sクラスタ外に対し、rl95vm-1のVMにアクセスするIPアドレス/ポートを公開できます。

### (1) Serviceの作成

rl95vm-1のVMのIPアドレスの22/TCPポートをエンドポイントとするServiceのマニフェストファイルを作ります。
```yaml:rl95vm-1-ssh-service.yaml
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
```

適用します。

```bash
[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が割り当たっています。
```bash
[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番ポートを指しています。
```bash
[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にログインできました。
```bash
[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 ~]$
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468232/148d2adb-ba13-4cd3-90c9-a1cfae6c19b3.png)


# まとめ

KubeVirtを使い、k8sクラスタ内に、RockyLinux9.5のマシンイメージからVMを作り、Serviceを作り、k8sクラスタ外からアクセスできることを確認しました。


# 参考にさせていただいた情報

ありがとうございます。

https://zenn.dev/vampire_yuta/articles/a9806c655a3114

https://www.fraction.jp/log/archives/2022/09/18/kubevirt