OpenStackもいらなくなる?kubevirt がどんなものか触って見た。

kubevirt を利用すると kubernetes仮想マシンを管理することが可能になります。 Kubernetes で管理できるということでスケジューリングやセルフヒーリング、ロードバンランサなどのサービスが利用できるなど期待が高まります。

CNCF Cloud Native Interactive Landscape では Application Definition & Image Build のカテゴリに分類されていました。

f:id:komeiy:20190810181532p:plain:w400

はじめに

OpenStack もいらなくなる?と書いたのは、冒頭に書いた Kubernetes で得られる一般的なメリットを利用できる点に期待したからです。 OpenStack を作ることが自体がとても大変で、最近だとドキュメント通りでうまく起動はできるとは思うのですが、やはり手間であることには変わりないと思います。 OpenStack をインストールして使える形にすること自体が本質ではないからだと思います。EC2、Route53、EBS、ELB 相当の必要なコンポーネントを作り上げるだけで一苦労です。

VM を提供するために OpenStack 新しく作るとしたら何で作るのが楽かなと考えた時に、Kubernetes 上にコンテナで作る?とかも考えたのですが、 結局、OpenStack を ansible で積み立てるのが dockerfile と yaml になったくらいで労力は変わらないんですよね。。

ということで、kubevirt に OpenStack もいらなくなる?という淡い期待を抱いたわけです。 Kubernetes という一枚抽象レイヤーを挟むことで、仮想マシンを立ち上げるためのサービスの可搬性があがります。

(もちろん、OpenStack は単なる VM を提供するだけのものではないのですが)

kubevirt のコンポーネント

ここからは kubevirt がどんなものか理解するために、実際に動作させて見るわけですが、まずはコンポーネントを軽く見てみます。 代表的なものを紹介しておきます。

  • virt-controller
    • VM を起動させるための pod を作成します。pod が特定のノードでスケジュールされた場合に、virt-handler と連携します。
  • virt-hnadller
    • daemonset として動作します。 api や virt-launcher と連携し、 例えば VM リソースの削除などを virt-launcher へ指示します。
  • virt-launcher
    • libvirtd を介してドメインの作成や削除を制御します。cgroups や namespace を使って VMプロセスをホストします。

f:id:komeiy:20190809004415p:plain

kubevirt をインストール

minikube インストール

今回、構築した環境をまとめておきます。

$ cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)

まずはホスト側で kvm の nested を on にしておきます。パフォーマンス気にしないようであれば必須ではないです。 kvm を使わずに、qemu だけで起動もできます。

$ cat /sys/module/kvm_intel/parameters/nested
N
$ modprobe -r kvm_intel
$ modprobe kvm_intel nested=1
$ vi /etc/modprobe.d/kvm.conf
$ sudo vi /etc/modprobe.d/kvm.conf
$ cat /sys/module/kvm_intel/parameters/nested
Y

必要なパッケージをインストール

$ sudo yum -y install libguestfs libvirt libvirt-client python-virtinst qemu-kvm virt-manager virt-top virt-viewer virt-who virt-install bridge-utils

インストールは以下を参考にします。 github.com{:target="_blank"}

$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 \
>   && chmod +x minikube
$ ls
minikube
$ sudo install minikube /usr/local/bin
$ minikube

こちら{:target="_blank"} を参考に以下も実施しておきましょう

curl -LO https://storage.googleapis.com/minikube/releases/latest/docker-machine-driver-kvm2 \
  && sudo install docker-machine-driver-kvm2 /usr/local/bin/

以下の通り kubevirt というプロファイル名であげておきます。

$ minikube config -p kubevirt set memory 4096
$ minikube config -p kubevirt set vm-driver kvm2
$ minikube start -p kubevirt

kubectl をインストールします。minikube 入れたときに ~/.kube/config を設定しておいてくれます。

$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
$ chmod +x ./kubectl
$ sudo mv ./kubectl /usr/local/bin/kubectl

一応確認してみます。

$ kubectl get nodes
NAME       STATUS   ROLES    AGE     VERSION
minikube   Ready    master   5m52s   v1.15.0

以下も対応しておきましょう。

$ systemctl stop firewalld
$ systemctl disable firewalld
$ sudo sysctl -w net.bridge.bridge-nf-call-iptables=1

sysctl: cannot stat /proc/sys/net/bridge/bridge-nf-call-iptables: そのようなファイルやディレクトリはありません こんな感じで not found になったら以下を試して見てください。br_netfilter モジュールに含まれるようになっているはずです。

$ sudo modprobe br_netfilter

kubevirt の環境セットアップ

まずは virt-operator を作ります。 Kubevirt の core components を作成してくれます。crd と必要な role や deployment などができます。

$ export KUBEVIRT_VERSION=$(curl -s https://api.github.com/repos/kubevirt/kubevirt/releases|grep tag_name|sort -V | tail -1 | awk -F':' '{print $2}' | sed 's/,//' | xargs)
$ echo $KUBEVIRT_VERSION
v0.19.0-rc.0
$ kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml
namespace/kubevirt created
customresourcedefinition.apiextensions.k8s.io/kubevirts.kubevirt.io created
clusterrole.rbac.authorization.k8s.io/kubevirt.io:operator created
serviceaccount/kubevirt-operator created
clusterrole.rbac.authorization.k8s.io/kubevirt-operator created
clusterrolebinding.rbac.authorization.k8s.io/kubevirt-operator created
deployment.apps/virt-operator created

kubevirt の crd ができてますね。

$ kubectl get crds
NAME                    CREATED AT
kubevirts.kubevirt.io   2019-07-21T04:21:34Z

kubevirt の namespace を確認してみます。

$ kubectl get deployment -n kubevirt
NAME            READY   UP-TO-DATE   AVAILABLE   AGE
virt-operator   2/2     2            2           27m
$ kubectl get pod -n kubevirt
NAME                             READY   STATUS    RESTARTS   AGE
virt-operator-76d85fb55b-2b9l6   1/1     Running   0          27m
virt-operator-76d85fb55b-d494d   1/1     Running   0          27m

virt-api , controller を作ります。 node ごとに handler も立ち上がります。

$ kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml
$ kubectl get deployment -n kubevirt
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
virt-api          2/2     2            2           3m16s
virt-controller   2/2     2            2           2m49s
virt-operator     2/2     2            2           32m

以下のように pod が立ち上がります。

$ kubectl get pod -n kubevirt
NAME                             READY   STATUS    RESTARTS   AGE
virt-api-65f4879dc7-ltmb8        1/1     Running   0          106m
virt-api-65f4879dc7-nwmzf        1/1     Running   0          106m
virt-controller-5ccf9d47-bwkcw   1/1     Running   0          106m
virt-controller-5ccf9d47-jcg5q   1/1     Running   0          106m
virt-handler-jddl7               1/1     Running   0          106m
virt-operator-76d85fb55b-2b9l6   1/1     Running   0          136m
virt-operator-76d85fb55b-d494d   1/1     Running   0          136m

test vm を立ち上げてみる

$ kubectl apply -f https://raw.githubusercontent.com/kubevirt/kubevirt.github.io/master/labs/manifests/vm.yaml
virtualmachine.kubevirt.io/testvm created

先ほど入れた virtctl を使ってみましょう。つい型は以下の通りです。

$  virtctl help
virtctl controls virtual machine related operations on your kubernetes cluster.

Available Commands:
  console      Connect to a console of a virtual machine instance.
  expose       Expose a virtual machine instance, virtual machine, or virtual machine instance replica set as a new service.
  help         Help about any command
  image-upload Upload a VM image to a PersistentVolumeClaim.
  restart      Restart a virtual machine.
  start        Start a virtual machine.
  stop         Stop a virtual machine.
  version      Print the client and server version information.
  vnc          Open a vnc connection to a virtual machine instance.

Use "virtctl <command> --help" for more information about a given command.
Use "virtctl options" for a list of global command-line options (applies to all commands).
$ virtctl start testvm
$ kubectl get vms
NAME     AGE    RUNNING   VOLUME
testvm   114s   true
$ kubectl get vmis
NAME     AGE   PHASE        IP    NODENAME
testvm   28s   Scheduling

ちょっと待つと vm が起動します。

$ kubectl get vmis
NAME     AGE   PHASE     IP            NODENAME
testvm   67s   Running   172.17.0.11   minikube

起動しない場合は virt-launcher がちゃんとできているか kubectl get pod / kubectl describe pod virt-launcher--xxxxx で確認してみてください。

ちなみにホストのインタフェースを見ると以下のように docker bridge が同じサブネットにいました。

5: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:dc:39:98:2b brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:dcff:fe39:982b/64 scope link
       valid_lft forever preferred_lft forever

コンソールを叩いています。

$ virtctl console testvm
Successfully connected to testvm console. The escape sequence is ^]

login as 'cirros' user. default password: 'gocubsgo'. use 'sudo' for root.
testvm login: root
Password:
Login incorrect
testvm login: cirros
Password:
$
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
    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 1500 qdisc pfifo_fast qlen 1000
    link/ether 02:42:ac:11:00:0b brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.11/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe11:b/64 scope link
       valid_lft forever preferred_lft forever
$ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=50 time=2.187 ms
64 bytes from 8.8.8.8: seq=1 ttl=50 time=1.833 ms
64 bytes from 8.8.8.8: seq=2 ttl=50 time=1.852 ms

kubevirt で persistent volume を使った vm を立ち上げてみる

VM 用の PV を作成する

やっぱり永続的ボリューム使わないと困りますよね。 pod 立ち上げるたびにデータが消える VM は使いたくないですよね

$ kubectl create -f https://raw.githubusercontent.com/kubevirt/kubevirt.github.io/master/labs/manifests/storage-setup.yml
$ kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/v1.9.0/cdi-controller.yaml

storage-setup.yml の中で storageclass を作成していますが、 storageclass.kubernetes.io/is-default-class: "true" が入っているので、以下のように default が複数になってしまう点注意してください。また今回は minikube で 1 Node になるので persistent volume の type は hostpath が利用されています。以下、マニュアルの記載です。

HostPath (Single node testing only – local storage is not supported in any way and WILL NOT WORK in a multi-node cluster)

$ kubectl get sc
NAME                 PROVISIONER                AGE
hostpath (default)   hostpath                   11s
standard (default)   k8s.io/minikube-hostpath   4h56m

cdi (containerized-data-importer) の pod ができていることが確認できます。

$ kubectl get pods -n cdi
NAME                               READY   STATUS    RESTARTS   AGE
cdi-apiserver-775cdcc9b7-2qthb     1/1     Running   0          47s
cdi-deployment-75f8755479-5zgd2    1/1     Running   0          47s
cdi-uploadproxy-7d9c96fb4f-2g2w8   1/1     Running   0          47s

ここで PersistentVolumeClaim を作成します。イメージは qcow2 です。 以下の通り失敗したら先ほどのストレージタイプのデフォルトを確認してください。

$ kubectl create -f https://raw.githubusercontent.com/kubevirt/kubevirt.github.io/master/labs/manifests/pvc_fedora.yml
Error from server (Forbidden): error when creating "https://raw.githubusercontent.com/kubevirt/kubevirt.github.io/master/labs/manifests/pvc_fedora.yml": persistentvolumeclaims "fedora" is forbidden: Internal error occurred: 2 default StorageClasses were found

一応、デフォルトが二つある場合の対処法を書いておきます

$ kubectl patch storageclass standard -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
storageclass.storage.k8s.io/standard patched
$ kubectl get storageclass standard
NAME       PROVISIONER                AGE
standard   k8s.io/minikube-hostpath   5h2m
$
$ kubectl get storageclass
NAME                 PROVISIONER                AGE
hostpath (default)             hostpath                   112m
standard   k8s.io/minikube-hostpath   6h48m```

問題なく作成されていれば以下のようになるはずです。

$  kubectl get pvc
NAME     STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
fedora   Bound    pvc-a5633c44-7b33-460d-8a51-4c4706528979   10Gi       RWO            hostpath       91s

importer pod が立ち上がって、指定したイメージを kubevirt で立ち上げられるように pv に書き込んでくれます。

This will create the PVC with a proper annotation so that CDI controller detects it and launches an importer pod to gather the image specified in the cdi.kubevirt.io/storage.import.endpoint annotation.

$  kubectl get pod
NAME                    READY   STATUS    RESTARTS   AGE
importer-fedora-zshdd   1/1     Running   1          118s
$  kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM            STORAGECLASS   REASON   AGE
pvc-a5633c44-7b33-460d-8a51-4c4706528979   10Gi       RWO            Delete           Bound    default/fedora   hostpath                90s

importer が起動中に log を見ると以下のように処理が見えます。

I0722 09:27:38.023484       1 importer.go:58] Starting importer
I0722 09:27:38.024785       1 importer.go:100] begin import process
I0722 09:27:38.358624       1 data-processor.go:237] Calculating available size
I0722 09:27:38.362428       1 data-processor.go:245] Checking out file system volume size.
I0722 09:27:38.362459       1 data-processor.go:249] Request image size not empty.
I0722 09:27:38.362474       1 data-processor.go:254] Target size 10Gi.
I0722 09:27:38.362567       1 data-processor.go:167] New phase: Convert
I0722 09:27:38.362774       1 data-processor.go:173] Validating image
I0722 09:27:39.772281       1 qemu.go:205] 0.00
I0722 09:27:44.024407       1 qemu.go:205] 1.00

この pod は処理が終わると削除されます。

I0722 09:35:49.844195       1 data-processor.go:167] New phase: Resize
I0722 09:35:49.863375       1 data-processor.go:230] Expanding image size to: 10Gi
I0722 09:35:49.997196       1 data-processor.go:167] New phase: Complete
I0722 09:35:49.997274       1 importer.go:132] import complete
$
$  kubectl get pod
No resources found.

PV にデータが入ったのでここで VM を立ち上げます。 ちなみに CDI の機能として、upload 用の PV を作ったり、 golden image から clone したりして PV を作成することも可能です。参考になるドキュメントはこちら{:target="_blank"} 。

PV を使って VM を作成する

以下のマニュフェストを使います。kind: VirtualMachine になっています。以下のマニュフェストでは label がついていないので、何かつけておいてください。

$ wget https://raw.githubusercontent.com/kubevirt/kubevirt.github.io/master/labs/manifests/vm1_pvc.yml
$ ssh-keygen
$ PUBKEY=`cat ~/.ssh/id_rsa.pub`
$ sed -i "s%ssh-rsa.*%$PUBKEY%" vm1_pvc.yml
$ kubectl create -f vm1_pvc.yml

testvm の時と同様 pod / vm / vmi を確認して問題ないことを確認します。 今回は、ssh のアクセスをしてみます。virtctl で node port を作成します。 ここでは virtctl を使っていますが、テストではない場合、結局は vm の label をみて service を作っているだけなのでマニュフェストファイルを作って作成しましょう。

$ virtctl expose vmi vm1 --name vm1s --type NodePort --port 27017 --target-port 22
Service vm1s successfully exposed for vmi vm1
$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)           AGE
kubernetes   ClusterIP   10.10.0.1       <none>        443/TCP           9h
vm1s         NodePort    10.111.13.242   <none>        27017:31677/TCP   5s

ポートを確認できましたので、ssh してみます。

$ ssh 192.168.122.214 -p 31677 -l root
Last login: Sun Jul 21 12:18:02 2019 from 172.21.0.110
[root@vm1 ~]#
[root@vm1 ~]# cat /etc/redhat-release
Fedora release 30 (Thirty)

無事に ssh もできました。

missing label information for vm: vm1 が出てしまった人は vm (pod) に lable がついてないことが原因です。 Kubernetes の service は label をみて対象を決めているので lable がないと転送できません。

virtclt 側のコード{:target="_blank"} をみると以下の通りの記載があって virtctl 側でエラーを出すようになっています。

serviceSelector = vmi.ObjectMeta.Labels

if len(serviceSelector) == 0 {
        return fmt.Errorf("missing label information for %s: %s", vmType, vmName)
}

動作確認

launcher にログインしてみます。

$ kubectl exec -it virt-launcher-vm3-729lq /bin/bash
[root@vm3 /]#
[root@vm3 /]#
[root@vm3 /]#
[root@vm3 /]# ls
augconf  boot  etc   lib    libvirtd.sh  media  opt   root  sbin  sys  usr
bin      dev   home  lib64  lost+found   mnt    proc  run   srv   tmp  var

ここで libvirtd や qemu が動いています。 ps aux | grep libvirtd など叩いてみましょう。

念のため、PV の確認もしましょう。適当なファイルを配置します。

[root@vm1 ~]# touch test
[root@vm1 ~]# echo "test dayo" > test
[root@vm1 ~]#
[root@vm1 ~]#
[root@vm1 ~]# cat test
test dayo

vm1 を削除してみます。 virtctl 側に delete がないのかなと思ったら bugzilla{:target="_blank"} に記載がありました。

$ kubectl delete vm vm1
virtualmachine.kubevirt.io "vm1" deleted

$ kubectl get vm
No resources found.

virt-launcher-vm1 も消えています。 もう一度起動してみて確認します。

# ssh 192.168.122.214 -p 31677 -l root
Last login: Mon Jul 21 12:53:57 2019 from 172.21.0.110
[root@vm1 ~]# ls
test
[root@vm1 ~]# cat test
test dayo

無事に確認できました。Kubernetes の基本機能部分なので当たり前っちゃ当たり前なのですが。 一応、コンテナを強制停止や minikube を再起動してみたところ、Terminating->ContainerCreating > Running になりました。

実態として Kubernetes の pod として存在しているので、当然これらの Kubernetes の恩恵を享受できるのですが、それが嬉しいですね。 そうなると、 vm を作成するときの VirtualMachine リソースでどんなことが記述できるのかが気になるところですね。先ほどつけた label は pod (launcher) にも反映されているはずです。

explain してみれば楽かなと思いますが、 FIEDS がなかったです。残念。

$ kubectl explain VirtualMachine
KIND:     VirtualMachine
VERSION:  kubevirt.io/v1alpha3

DESCRIPTION:
     <empty>

その他

VirtualMachine ですが、nodeSelectorAffinity や Anti-Affinity が pod と同様に利用できます。Taints と Tolerations も利用できますので VM の配置は Kubernetes の pod と同様の考え方で配置の制御ができます。

SR-IOV とか VirtualMachineInstanceReplicaSet リソースを試してみたいです。VirtualMachineInstanceReplicaSet がちゃんと動けば update がしやすいですし、HPA と合わせると AutoScale も容易 に実現できますね。Kubernetes ありがたやです。

feature-gates を有効すれば、Live-migration もできるようです。Node 1台なので試せていないのですが、VM だとやりたくなりますよね。

まとめ

冒頭に OpenStackもいらなくなる?と書いたのですが、 kubevirt を使えば 割といい線 いってるのかなと思いました。 ただ、現時点では プロダクション向けにバリバリ使うという印象ではない です。 実際、検証していて起動時に VMDHCP から IP 取得できないケースに何回か遭遇したりしました。

そもそも、従来の VMKubernetes のようなコンテナを扱うオーケストレータに乗せて共存させることで移行を加速するものと理解した方が良さそうです。

とはいえ、GCPAWS には相応のコンピュートサービスがあると思いますが、オンプレでそういったサービスをどう提供する?と考えた時の 選択肢の一つに近い将来なりうるのかな という印象です。

Kubernetes の機能が使えるので、この手の開発物は Kubernetes さえあれば準備するものが少なく済む のは嬉しいですね。Kubevirt の GUI があると嬉しいかな・・・と思います。

結論としては、 今は厳しいが将来に期待 ってところでしょうか。今回で理解が深まりました。 とはいえ、将来はマイクロサービス化がもっと進んでそう。

Kubernetes完全ガイド (impress top gear)

Kubernetes完全ガイド (impress top gear)

Kubernetes実践ガイド クラウドネイティブアプリケーションを支える技術 (impress top gear)

Kubernetes実践ガイド クラウドネイティブアプリケーションを支える技術 (impress top gear)


シェアして頂けると嬉しいです。
参考になったという方がいれば是非お願いしますm(_ _ )m
モチベーション維持の観点で非常に励みになります。