仮想化通信

日本仮想化技術株式会社の公式エンジニアブログ

KubeadmでKubernetesクラスターを作ってKubeVirtでVMを動かす

要約

  • Kubernetes (API)で仮想マシンが扱える
  • 仮想マシンでアプリケーションを実行できる
  • 仮想マシン(QEMU-KVM)でできることはいろいろやれそう

各ソフトウェアの概要

KubeadmはKubernetesクラスターを作成するツールの一つです。Kubernetesは大規模なコンテナーをデプロイおよび管理するためのオープンソースソフトウェアです。基本的にアプリケーションはコンテナーで動作します。コンテナーは仮想マシンと比べて軽量なところが魅力ですが、仮想マシンと比べて分離されていないため一つのコンテナーの不具合が同じノード上の他のコンテナーに影響を与えたり、セキュリティ上の様々な問題が発生するという懸念があります。

もちろんそうならないために日頃から様々なセキュリティ対策(パッチを適用するなど)を施す必要があるわけですが、今回は数多くある対策の中から、KubeVirtを取り上げてみようと思います。

Kubernetes 1.21が4月8日にリリースされましたが、この内容はリリース前に行われたため、バージョン1.20.5で確認しています。

KubeVirtは名前からなんとなく想像つくかもしれませんが、Kubernetesで仮想マシンを使うためのものです。KubeVirtを使うとアプリケーションは仮想マシンの中で動作します。 文章でずらずらっと書くより、アーキテクチャー図を見てもらったほうが分かりやすいと思いますので、公式のリンクを貼ってみます。

github.com

ポイントとしてはKubeVirtが使えるKubernetesクラスターはアプリケーションの実行環境として、仮想マシンとコンテナーが使えるというところでしょうか。それらは同じネットワークで接続されているため、仮想マシン上のアプリケーションとコンテナー上のアプリケーションはネットワークアクセスできます。

今回はシングルノードのKubernetesクラスターをkubeadmで作成して、そのクラスターでKubeVirtを使えるような構成を取りたいと思います。

Kubeadmを使ったKubernetesクラスターの構築

検証に使ったマシン

めちゃくちゃ古いマシンで申し訳なさを感じますが、次のようなスペックのマシンを使いました。 この環境でも動かすことができるので、今の世代のマシンだったら大抵のマシンでも動くでしょう。

ハードウェア スペック
CPU Intel(R) Core(TM) i5 CPU 650 @ 3.20GHz
メモリー 16GB (PC3-10600E 4GB x4)
ストレージ 160GB SATA HDD

OSのセットアップ

まず試しに動かしてみようと思い、KubeVirt on Kubernetes with CRI-O from scratchを参考にしたため、以下の構成でセットアップしています。OSはアップデートして最新の状態にしてから試したので7.9を使いました。CNIは参考情報からアレンジしています。

オブジェクト 種類バージョン
OS CentOS 7.9
CRI CRI-O
CNI Calico

CentOS向けのCRI-Oは安定稼働は難しいため、containerdを推奨します。CRIを変えても同じように実行可能です。

2021/05/07追記

Ubuntu 20.04およびOracle Linux 8でもKubeVirtの動作を手元の環境で確認。

CRIのセットアップ

公式のドキュメントContainer runtimesに従って、CRI-Oをインストールしました。 細かい話は以下にまとめてあります。

tech.virtualtech.jp

クラスターの作成

Installing kubeadmに従ってツールをインストールしたあと、Creating a cluster with kubeadmに従ってクラスターを作成します。 細かい話は以下にまとめてあります。

tech.virtualtech.jp

CNIとしてCalicoの導入

参考にしたKubeVirt on Kubernetes with CRI-O from scratch - Installing Kubernetes はMultus CNI+FlannelをCNIとして追加しているのですが、今回はCalicoを使うことにしました。

導入方法についてはCalico公式のドキュメントに従います。

永続ストレージの準備

ここまでで、普通のKubernetesのセットアップは出来上がっています。 後々仮想マシンには永続ストレージを提供する必要があるため、Rancherが開発しているLocal Path Provisionerを使っています(複数のノードを構成する場合は、永続データの提供方法の検討が必要なので注意)。

github.com

SELinux有効でも動作するようにするため、以下の作業を事前に行ってから上記のInstallationを実行しました。

$ sudo mkdir /opt/local-path-provisioner
$ sudo chmod 777 /opt/local-path-provisioner
$ sudo chcon -R -t svirt_sandbox_file_t /opt/local-path-provisioner

KubeVirtプロジェクトもほぼ同様のものを用意しているのでこっちを使うのもありです。

github.com

こちらの場合は事前に次のように実行してから導入します。

$ sudo mkdir  /var/hpvolumes
$ sudo chmod 777 /var/hpvolumes
$ sudo chcon -t container_file_t -R /var/hpvolumes

KubeVirtをクラスターに追加

KubeVirtのインストール

KubeVirtのインストールについては基本的にはKubeVirt on Kubernetes with CRI-O from scratch - Installing KubeVirtを参考にセットアップしました。セットアップしたKubeVirtは導入時時点で最新だったバージョンを使いました。virtctlツールも同様のバージョンを/usr/local/bin/のパスにインストールしています。

[4/21/2021 追記]

v0.39.0にはいくつか問題があり、その修正が行われたv0.40.0がリリースされたため、バージョンを変更しました。

$ kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/v0.40.0/kubevirt-operator.yaml
$ kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/v0.40.0/kubevirt-cr.yaml

$ curl -L -o virtctl https://github.com/kubevirt/kubevirt/releases/download/v0.40.0/virtctl-v0.40.0-linux-amd64
$ chmod +x virtctl
$ sudo cp virtctl /usr/local/bin

KubeVirtを試す

引き続き、KubeVirt on Kubernetes with CRI-O from scratch - Installing KubeVirtの「Installing the first Virtual Machine in KubeVirt」に従って、Kubernetesで仮想マシンを使ってみたいと思います。

とは言いつつ、コマンドを実行するだけです。

$ kubectl apply -f https://raw.githubusercontent.com/kubevirt/kubevirt.github.io/master/labs/manifests/vm.yaml
$ kubectl get vms
NAME        AGE   RUNNING   VOLUME
testvm   13s   false

vm.yamlという名前のYAMLをブラウザーなどで見てみると、testvmという名前のCirrOSというテスト用のOSイメージを使った仮想マシンを作成するマニフェストであることがわかります。メモリーは64MBを割り当てているようです。

仮想マシンを作ったら、このマシンをKubernetesから利用するために仮想マシンを起動する必要があります。次のように実行します。

$ virtctl start testvm
VM testvm was scheduled to start

コマンドを実行して仮想マシンが起動したこと、IPアドレスが割り当てられたことを確認します。このIPアドレスはKubernetesに追加したCNIによって提供されます。

$ kubectl get vms
NAME        AGE     RUNNING   VOLUME
testvm   7m11s   true

$ kubectl get vmis
kubectl get vmis
NAME        AGE    PHASE        IP    NODENAME
testvm    14s   Scheduling

$ kubectl get vmis
NAME     AGE   PHASE     IP             NODENAME
testvm   63s   Running   10.244.16.151  k8smaster

あとはいつものように仮想マシンにsshでアクセスするだけです。 ssh接続後の環境でコマンドを実行することで、ホストのカーネルを利用していないことがわかります。

$ uname -a
Linux k8smaster 3.10.0-1160.21.1.el7.x86_64 #1 SMP Tue Mar 16 18:28:22 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

$ ssh cirros@10.244.16.151
cirros@10.244.16.151's password: gocubsgo
$ uname -a
Linux testvm 4.4.0-28-generic #47-Ubuntu SMP Fri Jun 24 10:09:13 UTC 2016 x86_64 GNU/Linux
$ exit

これでKubernetesで仮想マシンが使える環境ができました。

OSイメージを追加する

前の手順でKubernetesで仮想マシンが使える環境ができましたが、テスト用のOSであるCirrOSが動いただけでは特に何もできないと思うので、別のOSを動かしてみます。 その方法は公式のドキュメントとしてまとまっているため、Experiment with the Containerized Data Importer (CDI)を参考にします。

永続ストレージはRancherが開発しているLocal Path Provisionerを事前に準備しましたので、 storage-setup.ymlというマニフェストは実行しないことにします。

$ export VERSION=$(curl -s https://github.com/kubevirt/containerized-data-importer/releases/latest | grep -o "v[0-9]\.[0-9]*\.[0-9]*")
$ kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/$VERSION/cdi-operator.yaml
$ kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/$VERSION/cdi-cr.yaml

上記実行後コマンドを実行して、全てのプロセスがRunningになったことを確認します。

$ kubectl get pods -n cdi
NAME                               READY   STATUS    RESTARTS   AGE
cdi-apiserver-847d4bc7dc-2z976     1/1     Running   0          138m
cdi-deployment-66d7555b79-hcr59    1/1     Running   0          138m
cdi-operator-895bb5c74-pdpl8       1/1     Running   0          138m
cdi-uploadproxy-6c8698cd8b-t9q6w   1/1     Running   0          138m

OSイメージを使ってKubernetesでVMを使う

まず、次のようなマニフェストを作成してクラウドイメージをベースとしてストレージボリュームに登録します。

今回は公式の手順と異なりRancherのLocal Path Provisionerを使っているため、storageClassNameをlocal-pathに書き換えます。

$ kubectl get sc
NAME         PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  127m

$ cat pvc_fedora.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: "fedora"
  labels:
    app: containerized-data-importer
  annotations:
    cdi.kubevirt.io/storage.import.endpoint: "https://download.fedoraproject.org/pub/fedora/linux/releases/33/Cloud/x86_64/images/Fedora-Cloud-Base-33-1.2.x86_64.raw.xz"
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi
  storageClassName: local-path

もし kubevirtのhostpath-provisionerを使った場合は、storageClassNameで適切な名前を指定する他、リポジトリーの例のようにannotationsにノード名をkubevirt.io/provisionOnNode: kubevirt-demoのように記述を追加する。

カンが良いかたは気がついたかと思いますが、イメージ、名前、ストレージサイズを適切に指定すれば、その他のOSも動くはずです(QEMU用のクラウドイメージさえあればいけると思います)。

あとは編集したマニフェストを使って次のように実行します。これにより、ボリュームにOSイメージが書き込まれます。

$ kubectl create -f pvc_fedora.yml 

実行後、いくつかのコマンドを実行して、仮想マシンの作成状況を確認します。

$ kubectl get pvc fedora
$ kubectl get pod # Make note of the pod name assigned to the import process

イメージインポートの進捗はkubectl logsで確認できるようです。

$ kubectl logs -f importer-fedora   # Substitute your importer-fedora pod name here.
I0409 01:36:11.712534       1 importer.go:52] Starting importer
I0409 01:36:11.712737       1 importer.go:134] begin import process
I0409 01:36:13.325912       1 data-processor.go:356] Calculating available size
I0409 01:36:13.326597       1 data-processor.go:368] Checking out file system volume size.
I0409 01:36:13.326741       1 data-processor.go:376] Request image size not empty.
I0409 01:36:13.326787       1 data-processor.go:381] Target size 5Gi.
I0409 01:36:13.402364       1 nbdkit.go:240] Waiting for nbdkit PID.
I0409 01:36:13.903065       1 nbdkit.go:261] nbdkit ready.
I0409 01:36:13.903109       1 data-processor.go:238] New phase: Convert
I0409 01:36:13.903153       1 data-processor.go:244] Validating image
I0409 01:36:17.103228       1 qemu.go:259] 0.00
I0409 01:36:21.507712       1 qemu.go:259] 1.03
I0409 01:36:22.573000       1 qemu.go:259] 2.05
...
I0409 01:41:09.399546       1 qemu.go:259] 99.46
I0409 01:41:10.027139       1 data-processor.go:238] New phase: Resize
W0409 01:41:10.061203       1 data-processor.go:342] Available space less than requested size, resizing image to available space 5073430016.
I0409 01:41:10.061349       1 data-processor.go:348] Expanding image size to: 5073430016
I0409 01:41:10.092411       1 data-processor.go:244] Validating image
I0409 01:41:10.109496       1 data-processor.go:238] New phase: Complete
I0409 01:41:10.109634       1 importer.go:212] Import Complete

Fedoraイメージのインポートが終わったら、このイメージを使って仮想マシンを作ってみます。 サンプルのマニフェストを使って、仮想マシンを作ってみます。

$ curl -LO https://raw.githubusercontent.com/kubevirt/kubevirt.github.io/master/labs/manifests/vm1_pvc.yml
$ cat vm1_pvc.yml

vm1_pvc.ymlをエディターで開いて、ssh_authorized_keysにSSHアクセス用の公開鍵を貼ってください(改行などに注意)。

            #cloud-config
            hostname: vm1
            ssh_pwauth: True
            disable_root: false
            ssh_authorized_keys:
            - ssh-rsa AAAAB3Y3v....zurqCPYEf ytooyama@k8smaster

編集したマニフェストを使って、仮想マシンを作成します。

$ kubectl create -f vm1_pvc.yml
virtualmachine.kubevirt.io/vm1 created

$ kubectl get -f vm1_pvc.yml
NAME   AGE   VOLUME
vm1    5s    

$ kubectl get vmi
NAME     AGE    PHASE     IP              NODENAME
testvm   35m    Running   10.244.16.151   k8smaster
vm1      2m9s   Running   10.244.16.152   k8smaster

vm1にsshアクセスしてみます。Fedoraのクラウドイメージのユーザーはfedoraです。

$ ssh fedora@10.244.16.152
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.244.16.152' (ECDSA) to the list of known hosts.
Last login: Thu Apr  8 07:36:17 2021 from 192.168.0.120
[fedora@vm1 ~]$ 

うまく行きました。VMから外部への疎通も問題なさそうです。

[fedora@vm1 ~]$ ping -c1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=113 time=13.5 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 13.455/13.455/13.455/0.000 ms
[fedora@vm1 ~]$ ping -c1 virtualtech.jp
PING virtualtech.jp (124.35.85.83) 56(84) bytes of data.
64 bytes from main.begi.net (124.35.85.83): icmp_seq=1 ttl=49 time=16.9 ms

--- virtualtech.jp ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 16.902/16.902/16.902/0.000 ms

長くなりましたが、KubernetesでKubeVirtが使えるようになりました。 今回は基本的なKubeVirtの利用方法について実証実験してみましたが、KubeVirtはMultus CNIを使ったり)、Host DevicesでPCI Passthroughして仮想マシンに引き込んだりいろいろできる(vGPUとかも使えるようです)らしいです。

去年の年末、頑張ったけどうまくいかなかったことができそうかも? 今回は「味見」をした程度なので、今後とも引き続き調査したいと思っています。いずれホストOSをUbuntu Serverにして実験などもしたいですね。

参考にした情報

付録

CentOS Stream 8も試して、動作を確認しました。Fedoraイメージと比べてイメージの圧縮率が高いこともあり、インポートには相応の時間がかかりました。ストレージ10GBでは不足したため、12GBを指定しています。app nameとラベルを書き換えています。ログインユーザーはcentosでした。

$ cat pvc_centos-stream8.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: "centost8"
  labels:
    app: containerized-data-importer2
  annotations:
    cdi.kubevirt.io/storage.import.endpoint: "http://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-20210210.0.x86_64.qcow2"
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 12Gi
  storageClassName: local-path

$ ssh centos@10.244.16.150
Activate the web console with: systemctl enable --now cockpit.socket
[centos@vm2 ~]$ cat /etc/centos-release 
CentOS Stream release 8