在 K8S 上也能跑 VM!KubeVirt 簡介與建立(上)

Eddie Yen
Gemini Open Cloud 雙子星雲端
18 min readJun 30, 2022

KubeVirt 是一個 K8S 套件專案,隨著微服務與雲原生這幾年的發展,它被部分企業視為能夠促進雲原生更進一步的發展,以及讓傳統企業踏入容器化的第一步;另外在部分還使用傳統大型 VM 環境的企業,已漸漸考慮使用 KubeVirt 做為取代方案。

KubeVirt 起源於 2017 年,由 RedHat 所主導的「容器化 VM」解決方案,其構想是希望能夠在 K8S 環境上,建構一套可以透過容器來啟動 VM 的解決方案。之所以會有此構想,在於部分類型的服務與運作架構還不適合在容器下運作、或者是架構過於龐大或複雜,導致轉型成微服務相較困難,但又不希望同時具有兩種以上截然不同的運算叢集需要管理,所想出來的解決方案。

KubeVirt 除了具備傳統 VM 都有的功能外,還具有部分與容器接近的特性與功能,從而可以與 K8S 上的其他容器溝通互動。

目前已發展到 v0.53 版本,儘管主要還是以 RedHat 的 OpenShift 平台發展為開發目標,但目前也可以在其他已裝好 K8S 環境的 Linux 平台上獨立安裝,只要注意 KubeVirt 與 K8S 版本之間的配合即可,不論標準 K8S、miniKube、或是 MicroK8S 等都可以安裝。另除 OpenShift 外,部分廠商如Platform9 也加入了 KubeVirt 功能進自身平台中。

KubeVirt 運作架構

KubeVirt套件運作關係圖

KubeVirt 主要由以下套件組成:
- virt-controller:對整個 KubeVirt 環境做管控
- virt-api:接收與發送 K8S API 以及 virt-handler 的請求與狀態回報等
- virt-handler:存在於 K8S node 內,主要是管理該節點上 VM
- virt-launcher:存在於 K8S node 內,主要運行 VM 時的 Pod 單元

可以注意到,KubeVirt 本身僅在於管理 VM 的狀態與尋找需提供 VM 要的資源,實際上資源來源、Scheduling 功能等等,本質上還是由 K8S 本體負責,包括 PV/PVC、StorageClass、網路、VM 所分配到的節點位置等;而在Scheduling 所包含的指派節點、使用專屬資源以及 Affinity 設定等,一樣是透過 match 等 API 定義,讓 K8S Scheduler 去做分配。

我們再切入到細一點。可能有人會問,那麼 KubeVirt 的 VM 建立跟一些也同樣容器化、如 Openstack 這種開源大型雲端叢集架構的差異在哪呢?畢竟一些 Openstack 部署專案如 Kolla、Triple-O 也同樣把服務都容器化了,這之間有什麼不同?

就 VM 運作架構上來說,KubeVirt 與運作架構容器化的 Openstack 的差異,在於:

● Openstack 還是統一由單個容器集中管理所有 VM,包含 VMM 在內,都在同一個容器下運行,且因為純用 docker 去運行的環境大多數容器網路模式都是設定 host,因此在部署這類 Openstack 環境時,都要求實體系統上不得安裝 libvirt 等 VMM 套件,或者是在部署時自動偵測並移除。

● KubeVirt 則是在 VM 啟動時,會單獨建立一個包含 VMM 的 Pod,並由這個 Pod 單獨運行指定的 VM,在建立時也會先找好需求的網路與儲存位置;若之後有其他 VM 需要開機,則會再啟動相對應的 Pod。換句話說,每一個VM 都會由獨立的 Pod 去服務。除了能夠確保每個 VM 的運作獨立性,就算實體系統另外安裝了 VMM 套件,也不影響 KubeVirt VM 的運作。

KubeVirt 儲存類型

KubeVirt 在儲存類型的支援上,也與 K8S 一樣,分為非永久儲存、永久儲存和定義儲存三類,並且大部分的類型跟 K8S 的方式是差不多的。

非永久儲存類:
emptyDisk:建立一個空的 QCOW2 image,與 K8S 的 emptyDir 類似。
containerDisk:前身名為為 registryDisk。顧名思義,是將 VM 映像檔打包變成容器 image 後,再透過這類 image 掛載使用。

永久儲存類:
PersistentVolumeClaim:使用 K8S 的 PVC 來當作 VM 映像檔的儲存空間。
DataVolume:PVC 的自動化衍伸,會自動下載並轉換 VM 映像檔後,再自動放入到 PVC 中,需要配合 CDI 套件使用。
hostDisk:直接存取實體系統內已存在的 VM 映像檔。

定義儲存類:
cloudInitNoCloud:作為一個 configMap 用的儲存類型,主要是用來寫入 cloud-init 設定,使 VM 在建立時自動設定預設的登入帳密和設定 IP 等功能。

這些儲存類型要如何使用,之後會有篇幅介紹。這邊只先說明,針對 PVC/DV 的部分,KubeVirt 強烈建議搭配 StorageClass 來使用,因此使用 KubeVirt 時,也建議自身的 K8S 環境先裝好符合自身需求的 CSI 套件並建立好 StorageClass。

KubeVirt 網路

由於 KubeVirt 本身共用不少 K8S 的功能,因此在網路上,自然也會共用 K8S 本身的網路,這其中又分為前端與後端兩類。前端指的是 VM 在 virt-launcher、也就是 Libvirt 設定連接的網路,後端指的是 virt-launcher 與 K8S 網路的連接,包括 K8S 預設的 CNI 網路。

KubeVirt 主要支援的「後端」網路類型有這兩種:
● Pod 網路:顧名思義,直接使用 K8S 上的預設 CNI 做為網路連線的基礎,類似 Openstack 的 Private Network 和 Neutron 的概念。KubeVirt 本身並沒有限制 K8S 上使用的 CNI 類型,舉凡 Calico、Flannel 等這些都可以支援。同時在這狀況下,也可以直接把 Service、Ingress 等 K8S 的網路設定功能套用在 KubeVirt 的 VM 上。

Pod網路連接架構 (CNI=Calico)

● Multus 網路:Multus 為 Intel 所建立的 K8S 網路架構開源專案,其目的是讓 K8S 能夠同時支援多種網路架構或 CNI,達到單一 Pod 可以介接多個網路的使用需求。以 Openstack 的角度而言,Multus 就類似於 K8S 上的 Provider Network,可透過 Multus 先建立好預設的 CNI 設定後,再設定容器或 VM 的Pod 去介接使用 Multus 所產生的網路。缺點是這類網路沒辦法透過 K8S 物件去做細部控管,只能從外部去控制。此外,還要預先建立好對應的連接,比如要使用 Multus 去做 Bridge 網路介接,就必須要先在實體系統上建立好對應的 Bridge。

Multus網路連接架構 (Bridge模式)

在「前端」網路中,大多是使用 QEMU 及 Libvirt 提供的網路模式為主,種類如下:
Bridge:讓 Libvirt 直接設定與後端網路做介接
slirp:使用 QEMU 自己的 usermode 網路,QEMU 會建立一個 NAT 網路給 VM 使用。這種網路可讓 VM 上網,但不支援 ICMP 協定。
sriov:將實體機器上已設定好 SR-IOV 功能的網卡 Pass-through 到 VM 內使用。
masquerade:由 KubeVirt 自己透過 iptables 產生 NAT 網路,與 slirp 差異在可以限定哪些 Port 能做 Forwarding 連入,在建立 VM 時指定要開放的 Port 號碼,缺點是只支援 Pod 後端。

KubeVirt 官方建議,後端使用 Pod 模式的情況下,建議前端模式使用 masquerade;而後端使用 Multus 模式下,則依照需求使用適合的前端模式。

部署 KubeVirt

在部署 KubeVirt 之前,除了要先部署好 K8S 及預設的 CNI 套件外,還需先做以下事前安裝:
1. 安裝 KVM 套件
2. 確保實體機器上的 CPU 虛擬化技術相關設定是否已經啟用
3. 建立一個適合環境的 K8S CSI 以建立 StorageClass
這邊就不再概述這類的安裝與設定檢查。

之後,可直接到 KubeVirt 的 GitHub 找到適合當前 K8S 的版本,並直接套用兩個部署 YAML 檔,分別為 Operator 與 CR(Custom Role)。

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

另外下載其專屬的 Virtctl,這是 KubeVirt 專屬的 CLI 工具,下載完畢後重新命名並丟到 /usr/bin 或 /usr/sbin 底下,再賦予可執行權限即可。注意,Virtctl 的版本必須與當前部署的 KubeVirt 版本一致。

wget https://github.com/kubevirt/kubevirt/releases/download/v0.41.4/virtctl-v0.41.0-linux-amd64mv virtctl-v0.41.4-linux-amd64 /usr/sbin/virtctl && chmod +x /usr/sbin/virtctl

部署下去後,會在 K8S 環境下建立名為 kubevirt 的 namespace,並產生以下Pod:

部署 KubeVirt CDI

CDI 全名為 Containerized Data Importer,是讓各類 VM 或 ISO 映像檔轉換成能夠在 KubeVirt 環境下使用的狀態,並同時建立與管理 KubeVirt 的映像儲存空間。

部署的方式與 KubeVirt 一致,套用 CDI 的 Operator 與 CR 兩個 YAML 即可。

kubectl apply -f https://github.com/kubevirt/containerized-data-importer/releases/download/v1.21.0/cdi-operator.yamlkubectl apply -f https://github.com/kubevirt/containerized-data-importer/releases/download/v1.21.0/cdi-cr.yaml

套用後,會建立名為 cdi 的 namespace,並產生以下 Pod:

匯入 ISO 映像檔

這邊會以建立 Windows VM 做為範例。由於 KubeVirt 本身通常支援 PVC/DV 以及 containerDisk 為多數,為了要讓 KubeVirt 建立的 VM 能夠使用 ISO 安裝檔,必須先將 ISO 匯入進 PVC 內才行,可以透過 Virtctl 來自動建立 PVC 並上傳到 CDI 內作轉換與置放作業。

要透過 Virtctl 匯入,必須要先知道 CDI 的 upload proxy 的位址,先透過kubectl get svc -n cdi的方式,得知 proxy 的位址。下圖所顯示的cdi-uploadproxy所顯示的 IP 即是我們需要得到的資訊。

接著,就可以使用 Virtctl 進行上傳。範例的情況是已經先建立一個以 NFS-CSI 為基底的 StorageClass,會先在這 StorageClass 上建立 PVC 後,再把 ISO 轉換放到此 PVC 內。

virtctl image-upload pvc <要建立的名稱> \
--size=<建立的PVC大小> \
--storage-class=<建立PVC所使用的StorageClass> \
--image-path=<要匯入的映像檔路徑> \
--uploadproxy-url=<cdi-uploadproxy的網址> \
--insecure <- 允許不安全的HTTPS連線
輸入範例

等待 PVC 建立、上傳並轉換後,便可使用此 PVC 進行 Windows 安裝

匯入成功畫面

建立KubeVirt VM

接著我們就可以來建立第一個 VM 了。與在 K8S 建立 Pod 一樣,透過匯入 YAML 的方式來建立。以下是我們會使用到的 YAML 檔案內容:

上面的部分參數或格式,對於有接觸過 KVM 的使用者來說會帶有一點熟悉感,只是編寫架構上仍有些差異,我們就具體看看這些設定上的意義。

KubeVirt 的 VM 物件主要有 VirtualMachine、VirtualMachineInstance 這兩種,前者是 VM 的範本,後者則是 VM 的執行 Profile。可以注意到在 spec內,主要是由一個 template 來撰寫整個內容,也就是底下是在定義VirtualMachineInstance 的參數;在 template 內的 metadata,則是定義真正建立 VM 時的名稱。

在主要的 spec 內,常規來說主要會使用到以下幾個必要類別:domain、network 和 volumes。

先提 domain,主要會使用到的有這幾個類別:
● CPU:定義 CPU 的數量與規格設定等。
● devices:定義 VM 內會有哪些虛擬裝置。常用的主要是 disks 與 interfaces。
● resources:定義 VM 要使用的資源,而 VM 使用的 RAM 大小可在此設定。

範例中另外出現的選擇性使用類別:
● features:定義 VM 需要啟用哪些虛擬硬體功能,包括啟用提高 Windows 相容性的 Hyper-V 功能都在這邊設定。
● devices/input:加入部分輸入裝置。範例中加入的 tablet 是 KVM 預設常加入的裝置,用於提高滑鼠游標在 VM 畫面內的準確度。
● machine:定義 VM 要使用的模擬平台類型,有 q35 與 i440fx 可選,沒有設定的話預設為使用 i440fx,但需留意兩個模擬平台之間的 I/O 差異,其中 i440fx 不支援 SATA。

devices/disks
我們就挑幾個重點來講。在 disks 部分,每一個 YAML Array 就是一個掛載的儲存裝置,以此範例來說,我們掛載了 3 個 disk 裝置,其中有兩個光碟機與一個硬碟。
disk/cdrom:定義此儲存裝置屬於哪種類型,一般常用的是 disk 與 cdrom 兩種。
disk類型/bus:定義這個 disk 要使用哪個虛擬介面去介接。這邊的硬碟是使用 virtio 作為它的連接介面,此為 KVM 自帶的獨特介面,效能較好,缺點是安裝 Windows 時需要載入其驅動程式才可以找得到硬碟。
name:定義此裝置的命名,與 K8S 建立 Pod 時一樣,跟 volumes 有對應關係,因此需記住所設定的名稱。
bootOrder:定義 disk 的開機順序,順序從 1 開始排先後,需注意要安裝作業系統的硬碟一定要設定此參數,否則可能會被安裝程式判定硬碟不具有開機功能而拒絕安裝。

devices/interfaces
此為設定 VM 的網路卡,同樣一個 YAML Array 代表一個連接埠的設定值。
name:定義此網卡的命名,跟 networks 有對應關係。
model:設定網卡的類型。範例的 virtio 一樣也是使用 KVM 自己的虛擬網卡。
bridge:設定此網卡與上層網路的連接模式,也就是前面所提到的「前端」網路連接模式。

networks
主要設定 VM 的後端連接方式。
name:後端的連接名稱,對應上面的 interfaces,前後端名稱相同者就會相互連接。
pod:設定要使用的後端連接。

volumes
主要設定 disks 後端的連接方式
name:volume 連接名稱,對應上面的 disks,兩邊名稱相同者就會相互連接。
persistentVolumeClaim/containerDisk:設定 volume 來源。可用 PVC、containerDisk 與 hostDisk 三種,每個底下都有不同的來源定義。

以上大略說明,在寫好後就可以把 YAML 匯入進 KubeVirt 內

kubectl apply -f windows_kubevirt.yaml

之後需要為 VM Disk 建立一個空的 PVC

接著就可以透過 virtctl 指令讓 VM 開機,注意開機的名稱要是 metadata 內所填入的 label 名稱。

virtctl start win10-test

開機後,便可透過 kubectl 相關指令確認 VM 是否有成功啟動
啟動時會自動開一個 Pod,確定都是 Ready 與 Running 及表示 VM 正常運行。

VNC連線

那麼我們要怎麼連線到 VM 畫面呢?畢竟 KubeVirt 不像傳統 KVM 具有 virt-manager 可以直接連到 VM 的 VNC Server 去看畫面。這部分可以透過 virtctl 功能來建立 VNC 連線

virtctl vnc <VM名稱> [--proxy-only]

輸入後就會在執行的主機畫面上出現 VM 畫面,如果帶入 — proxy-only 則只會給連入的 Port 號碼,再使用第三方連線軟體連入。

但這邊有個問題,透過 virtctl 或者其他方式所建立的 VNC 連線,都限制了外部連入,必須要在運行 VM 的本機上才可以連線。這會限制到實體機系統必須要安裝帶桌面的版本,以及衍生出的遠端管理問題等。

這邊分享一個工具,virtvnc 是 KubeVirt 社群上的參與者所開發的 VNC Proxy 工具,主要用途是在平台上建立一個 VNC 代理,會自動偵測 KubeVirt 所建立的 VMI 並且獲取這些的連入 API 位址,並將當前運行的 VM 列出來並附上 VNC 連線連結,點入即可透過 noVNC 方式看到 VM 畫面。

首先先到該 GitHub 位址,照說明套用 YAML 檔案,之後獲取 virtvnc 的 NodePort。

連入後,可以在頁面上看到目前運行的 VM,點擊旁邊的 VNC 即可連到 VM 畫面操作。

遠端連入VM

前面我們提到網路模式時,特別提到 KubeVirt 網路的連線方式與一般的 Pod 可以相容,假設 VM 系統裝好了,要如何透過 RDP、SSH 等方式連入到 VM 內?

在 Pod 模式下,可以直接透過 Service 建立開放連入的 Port,可以獨立寫 SVC 的 YAML,或者是與 VM 的 YAML 寫在一起,只要 selector 有寫對即可。

匯入後的SVC Edit範本

如果嫌寫 YAML 太麻煩且不需要一些額外的設定,也可以透過 virtctl 來達成。

virtctl expose vm <VM名稱> --name=<要建立的SVC名稱> --port=<要開放的Port號碼> --type=<開放的類型>

之後就可以再透過 get svc 的方式取得開放的資訊

小結

本篇我們簡單介紹了 KubeVirt、其運作架構、部署方式,另外還提到如何在上面建立第一個 VM、畫面與遠端連入等。可以說,KubeVirt 本身的操作方式稍微複雜,但部署難度與建立等方式,對於已有接觸 K8S 一段時間的使用者而言,上手難度不高,整體架構相對簡單。之後我們將進一步探討KubeVirt 的額外功能,包括 Multus、containerDisk、GPU Passthrough 等。

雙子星雲端為 CNCF 會員,是 CNCF 所認證的 Kubernetes 服務提供商,在雲端技術擁有十多年以上的經驗,為台灣雲端技術早期領先者。目前為國家級 AI 雲的軟體及 Kubernetes 技術與服務提供商,更是諸多企業與單位導入容器與管理平台的最佳夥伴。

雙子星雲端除了既有的產品 AI Console 與 Gemini API gateway 之外,也提供企業諮詢與導入雲原生相關技術服務,協助企業擁抱 Cloud Natvive,達到數位轉型的目標。

--

--