原文鏈接:https://fuckcloudnative.io/posts/use-kubevirt-to-manage-windows-on-kubernetes/
最近我發現我的 Kubernetes
集群資源實在是太多了,有點浪費,不信你看:
既然閑置資源那么多,那我何不想辦法利用一下。怎么用,用來干什么又是一個問題,想到我手中只有 MacBook,缺少 Windows 操作系統,那就先想辦法用 Kubernetes 創建個 Windows 虛擬機用用吧,畢竟很多場景只能用 Windows(比如突破某盤的限速、Xshell 一把梭連接所有服務器)。於是我將目光轉向了 Kubevirt。
Kubevirt
是 Red Hat 開源的以容器方式運行虛擬機的項目,通過 CRD
的方式來管理虛擬機實例,它的所有概念都和一般的 Kubernetes 容器應用差不多,不需要增加學習成本,對於咱玩爛了容器的 YAML 工程師來說沒有任何壓力,我們可以直接用它來創建虛擬機啊。
1. Kubevirt 架構設計
Kubevirt 主要實現了下面幾種資源,以實現對虛擬機的管理:
VirtualMachineInstance(VMI)
: 類似於 kubernetes Pod,是管理虛擬機的最小資源。一個VirtualMachineInstance
對象即表示一台正在運行的虛擬機實例,包含一個虛擬機所需要的各種配置。VirtualMachine(VM)
: 為群集內的VirtualMachineInstance
提供管理功能,例如開機/關機/重啟虛擬機,確保虛擬機實例的啟動狀態,與虛擬機實例是 1:1 的關系,類似與spec.replica
為 1 的 StatefulSet。VirtualMachineInstanceReplicaSet
: 類似ReplicaSet
,可以啟動指定數量的VirtualMachineInstance
,並且保證指定數量的VirtualMachineInstance
運行,可以配置 HPA。
Kubevirt 的整體架構如圖:
- virt-api : 負責提供一些 KubeVirt 特有的 api,像是
console, vnc, startvm, stopvm
等。 - virt-controller : 管理和監控 VMI 對象及其關聯的 Pod,對其狀態進行更新。
- virt-handler : 以 DaemonSet 運行在每一個節點上,監聽 VMI 的狀態向上匯報,管理 VMI 的生命周期。
- virt-launcher : 以 Pod 方式運行,每個 VMI Object 都會對應一個 virt-launcher Pod,容器內有單獨的
libvirtd
,用於啟動和管理虛擬機。
如果你嫌上面的架構圖太繁瑣,這里還有一個簡化版:
這個圖里的 Agent 其實就是 virt-handler。
2. 磁盤和卷
虛擬機鏡像(磁盤)是啟動虛擬機必不可少的部分,KubeVirt 中提供多種方式的虛擬機磁盤,虛擬機鏡像(磁盤)使用方式非常靈活。這里列出幾種比較常用的:
- PersistentVolumeClaim : 使用 PVC 做為后端存儲,適用於數據持久化,即在虛擬機重啟或者重建后數據依舊存在。使用的 PV 類型可以是 block 和 filesystem,使用 filesystem 時,會使用 PVC 上的 /disk.img,格式為 RAW 格式的文件作為硬盤。block 模式時,使用 block volume 直接作為原始塊設備提供給虛擬機。
- ephemeral : 基於后端存儲在本地做一個寫時復制(COW)鏡像層,所有的寫入都在本地存儲的鏡像中,VM 實例停止時寫入層就被刪除,后端存儲上的鏡像不變化。
- containerDisk : 基於 scratch 構建的一個 docker image,鏡像中包含虛擬機啟動所需要的虛擬機鏡像,可以將該 docker image push 到 registry,使用時從 registry 拉取鏡像,直接使用 containerDisk 作為 VMI 磁盤,數據是無法持久化的。
- hostDisk : 使用節點上的磁盤鏡像,類似於
hostpath
,也可以在初始化時創建空的鏡像。 - dataVolume : 提供在虛擬機啟動流程中自動將虛擬機磁盤導入 pvc 的功能,在不使用 DataVolume 的情況下,用戶必須先准備帶有磁盤映像的 pvc,然后再將其分配給 VM 或 VMI。dataVolume 拉取鏡像的來源可以時 http,對象存儲,另一塊 PVC 等。
3. 准備工作
在安裝 Kubevirt 之前,需要做一些准備工作。先安裝 libvrt 和 qemu 軟件包:
# Ubuntu
$ apt install -y qemu-kvm libvirt-bin bridge-utils virt-manager
# CentOS
$ yum install -y qemu-kvm libvirt virt-install bridge-utils
查看節點是否支持 kvm 硬件輔助虛擬化
$ virt-host-validate qemu
QEMU: Checking for hardware virtualization : PASS
QEMU: Checking if device /dev/kvm exists : PASS
QEMU: Checking if device /dev/kvm is accessible : PASS
QEMU: Checking if device /dev/vhost-net exists : PASS
QEMU: Checking if device /dev/net/tun exists : PASS
QEMU: Checking for cgroup 'memory' controller support : PASS
QEMU: Checking for cgroup 'memory' controller mount-point : PASS
QEMU: Checking for cgroup 'cpu' controller support : PASS
QEMU: Checking for cgroup 'cpu' controller mount-point : PASS
QEMU: Checking for cgroup 'cpuacct' controller support : PASS
QEMU: Checking for cgroup 'cpuacct' controller mount-point : PASS
QEMU: Checking for cgroup 'cpuset' controller support : PASS
QEMU: Checking for cgroup 'cpuset' controller mount-point : PASS
QEMU: Checking for cgroup 'devices' controller support : PASS
QEMU: Checking for cgroup 'devices' controller mount-point : PASS
QEMU: Checking for cgroup 'blkio' controller support : PASS
QEMU: Checking for cgroup 'blkio' controller mount-point : PASS
QEMU: Checking for device assignment IOMMU support : PASS
QEMU: Checking if IOMMU is enabled by kernel : PASS
如果不支持,則先生成讓 Kubevirt 使用軟件虛擬化的配置:
$ kubectl create namespace kubevirt
$ kubectl create configmap -n kubevirt kubevirt-config \
--from-literal debug.useEmulation=true
4. 安裝 Kubevirt
部署最新版本的 Kubevirt
$ export VERSION=$(curl -s https://api.github.com/repos/kubevirt/kubevirt/releases | grep tag_name | grep -v -- '-rc' | head -1 | awk -F': ' '{print $2}' | sed 's/,//' | xargs)
$ kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/kubevirt-operator.yaml
$ kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/kubevirt-cr.yaml
查看部署結果:
$ kubectl -n kubevirt get pod
NAME READY STATUS RESTARTS AGE
virt-api-64999f7bf5-n9kcl 1/1 Running 0 6d
virt-api-64999f7bf5-st5qv 1/1 Running 0 6d8h
virt-controller-8696ccdf44-v5wnq 1/1 Running 0 6d
virt-controller-8696ccdf44-vjvsw 1/1 Running 0 6d8h
virt-handler-85rdn 1/1 Running 3 7d19h
virt-handler-bpgzp 1/1 Running 21 7d19h
virt-handler-d55c7 1/1 Running 1 7d19h
virt-operator-78fbcdfdf4-sf5dv 1/1 Running 0 6d8h
virt-operator-78fbcdfdf4-zf9qr 1/1 Running 0 6d
部署 CDI
Containerized Data Importer
(CDI)項目提供了用於使 PVC 作為 KubeVirt VM 磁盤的功能。建議同時部署 CDI:
$ 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
5. 客戶端准備
Kubevirt 提供了一個命令行工具 virtctl
,可以直接下載:
$ export VERSION=$(curl -s https://api.github.com/repos/kubevirt/kubevirt/releases | grep tag_name | grep -v -- '-rc' | head -1 | awk -F': ' '{print $2}' | sed 's/,//' | xargs)
$ curl -L -o /usr/local/bin/virtctl https://github.com/kubevirt/kubevirt/releases/download/$VERSION/virtctl-$VERSION-linux-amd64
$ chmod +x /usr/local/bin/virtctl
也可以通過 krew
安裝為 kubectl 的插件:
$ kubectl krew install virt
6. 虛擬機鏡像准備
Windows 鏡像下載
這里推薦兩個 Windows 鏡像下載站:
① MSDN I Tell You。該網站提供的鏈接是 ed2k
格式,需要通過特殊下載工具進行下載,比如百度網盤離線下載、迅雷、eMule 等,其中百度網盤離線下載最好使,但下載限速又是個大問題,開了超級會員的當我沒說。
② TechBench by WZT。該網站提供的是直鏈下載方式,可以用任意下載工具進行下載,比上面的網站方便多了,不過資源沒有上面的網站豐富。
我推薦通過第二個網站來下載 Windows 鏡像。
上傳鏡像
KubeVirt 可以使用 PVC 作為后端磁盤,使用 filesystem
類型的 PVC 時,默認使用的時 /disk.img
這個鏡像,用戶可以將鏡像上傳到 PVC,在創建 VMI 時使用此 PVC。使用這種方式需要注意下面幾點:
- 一個 PVC 只允許存在一個鏡像,只允許一個 VMI 使用,要創建多個 VMI,需要上傳多次
/disk.img
的格式必須是 RAW 格式
CDI 提供了使用使用 PVC 作為虛擬機磁盤的方案,在虛擬機啟動前通過下面方式填充 PVC:
- 通過 URL 導入虛擬機鏡像到 PVC,URL 可以是 http 鏈接,s3 鏈接
- Clone 一個已經存在的 PVC
- 通過 container registry 導入虛擬機磁盤到 PVC,需要結合
ContainerDisk
使用 - 通過客戶端上傳本地鏡像到 PVC
通過命令行 virtctl
,結合 CDI 項目,可以上傳本地鏡像到 PVC 上,支持的鏡像格式有:
- .img
- .qcow2
- .iso
- 壓縮為 .tar,.gz,.xz 格式的上述鏡像
我們的目標是安裝 Windows 10 虛擬機,所以需要將上面下載好的 Windows 鏡像上傳到 PVC:
$ virtctl image-upload \
--image-path='Win10_20H2_Chinese(Simplified)_x64.iso' \
--storage-class csi-rbd-sc \
--pvc-name=iso-win10 \
--pvc-size=7G \
--uploadproxy-url=https://<cdi-uploadproxy_svc_ip> \
--insecure \
--wait-secs=240
PersistentVolumeClaim default/iso-win10 created
Waiting for PVC iso-win10 upload pod to be ready...
Pod now ready
Uploading data to https://10.111.29.156
5.63 GiB / 5.63 GiB [======================================================================================================================================================] 100.00% 27s
Uploading data completed successfully, waiting for processing to complete, you can hit ctrl-c without interrupting the progress
Processing completed successfully
Uploading Win10_20H2_Chinese(Simplified)_x64.iso completed successfully
參數解釋:
- --image-path : 操作系統鏡像地址。
- --pvc-name : 指定存儲操作系統鏡像的 PVC,這個 PVC 不需要提前准備好,鏡像上傳過程中會自動創建。
- --pvc-size : PVC 大小,根據操作系統鏡像大小來設定,一般略大一個 G 就行。
- --uploadproxy-url : cdi-uploadproxy 的 Service IP,可以通過命令
kubectl -n cdi get svc -l cdi.kubevirt.io=cdi-uploadproxy
來查看。
7. 增加 hostDisk 支持
Kubevirt 默認沒有開啟對 hostDisk
的支持,需要手動開啟。步驟也很簡單,只需新建個 ConfigMap,增加 hostDisk
的特性:
kubevet-config.yaml
apiVersion: v1
data:
feature-gates: LiveMigration,DataVolumes,HostDisk
kind: ConfigMap
metadata:
labels:
kubevirt.io: ""
name: kubevirt-config
namespace: kubevirt
7. 創建虛擬機
創建 Windows 虛擬機的模板文件如下:
win10.yaml
apiVersion: kubevirt.io/v1alpha3
kind: VirtualMachine
metadata:
name: win10
spec:
running: false
template:
metadata:
labels:
kubevirt.io/domain: win10
spec:
domain:
cpu:
cores: 4
devices:
disks:
- bootOrder: 1
cdrom:
bus: sata
name: cdromiso
- disk:
bus: virtio
name: harddrive
- cdrom:
bus: sata
name: virtiocontainerdisk
interfaces:
- masquerade: {}
model: e1000
name: default
machine:
type: q35
resources:
requests:
memory: 16G
networks:
- name: default
pod: {}
volumes:
- name: cdromiso
persistentVolumeClaim:
claimName: iso-win10
- name: harddrive
hostDisk:
capacity: 50Gi
path: /data/disk.img
type: DiskOrCreate
- containerDisk:
image: kubevirt/virtio-container-disk
name: virtiocontainerdisk
這里用到了 3 個 Volume:
- cdromiso : 提供操作系統安裝鏡像,即上文上傳鏡像后生成的 PVC
iso-win10
。 - harddrive : 虛擬機使用的磁盤,即操作系統就會安裝在該磁盤上。這里選擇
hostDisk
直接掛載到宿主機以提升性能,如果使用分布式存儲則體驗非常不好。 - containerDisk : 由於 Windows 默認無法識別 raw 格式的磁盤,所以需要安裝 virtio 驅動。 containerDisk 可以將打包好 virtio 驅動的容器鏡像掛載到虛擬機中。
關於網絡部分,spec.template.spec.networks
定義了一個網絡叫 default
,這里表示使用 Kubernetes 默認的 CNI。spec.template.spec.domain.devices.interfaces
選擇定義的網絡 default,並開啟 masquerade
,以使用網絡地址轉換 (NAT) 來通過 Linux 網橋將虛擬機連接至 Pod 網絡后端。
使用模板文件創建虛擬機:
$ kubectl apply -f win10.yaml
啟動虛擬機實例:
$ virtctl start win10
# 如果 virtctl 安裝為 kubectl 的插件,命令格式如下:
$ kubectl virt start win10
查看實例運行狀態:
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
virt-launcher-win10-s742j 2/2 Running 0 15s
然后就可以通過 VNC 工具來訪問 Windows 虛擬機了。首先需要在本地安裝一個 VNC 客戶端,對於 macOS 來說,可以安裝 Tiger VNC 或者 Real VNC。我選擇安裝 Real VNC:
$ brew cask install vnc-viewer
連接到 Windows 虛擬機:
$ virtctl vnc win10
# 如果 virtctl 安裝為 kubectl 的插件,命令格式如下:
$ kubectl virt vnc win10
執行完上面的命令后,就會打開本地的 VNC 客戶端連接到虛擬機:
下面就是安裝正常的安裝步驟往下進行,到選擇硬盤那一步的時候,你會發現沒有一個硬盤可供使用,這時就需要安裝 virtio 驅動了。
不過不用擔心,virtio 驅動已經被掛載進來了,直接點擊加載驅動程序就可以安裝驅動了:
安裝好驅動后,硬盤就能正確顯示了:
下面就可以繼續安裝了。
安裝成功后會自動重啟進行初始化設置,那個熟悉的“海內存知己,天涯若比鄰”又回來了:
設置完成后,進入系統,打開設備管理器,可以看到有幾個未配置的設備。選擇其中一個右鍵單擊,然后選擇“更新驅動程序”。
選擇“瀏覽我的電腦以查找驅動程序”。
選擇“CD 驅動器(E:)virtio-win-0.1.1”,然后點擊確定。
設備管理器將自動找到正確的驅動程序,不需要指定驅動程序的路徑。
在提示符下,單擊“安裝”。
其他的設備驅動可以復制上面的步驟一一安裝。
8. CNI 插件問題解決
如果你的 Kubernetes 集群 CNI 插件用的是 Calico,這里會遇到虛擬機無法聯網的問題。因為 Calico 默認禁用了容器的 ip forward 功能,而 masquerade
需要開啟這個功能才能生效。
我們只需要修改 Calico 的 ConfigMap 就可以啟用容器的 ip forward 功能了,執行以下命令打開 configmap calico-config
:
$ kubectl -n kube-system edit cm calico-config
在 CNI 配置文件中加上以下的內容:
"container_settings": {
"allow_ip_forwarding": true
},
修改完的配置文件內容:
cni_network_config: |-
{
"name": "k8s-pod-network",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "calico",
"log_level": "info",
"log_file_path": "/var/log/calico/cni/cni.log",
"etcd_endpoints": "__ETCD_ENDPOINTS__",
"etcd_key_file": "__ETCD_KEY_FILE__",
"etcd_cert_file": "__ETCD_CERT_FILE__",
"etcd_ca_cert_file": "__ETCD_CA_CERT_FILE__",
"mtu": __CNI_MTU__,
"ipam": {
"type": "calico-ipam"
},
"container_settings": {
"allow_ip_forwarding": true
},
"policy": {
"type": "k8s"
},
"kubernetes": {
"kubeconfig": "__KUBECONFIG_FILEPATH__"
}
},
{
"type": "portmap",
"snat": true,
"capabilities": {"portMappings": true}
},
{
"type": "bandwidth",
"capabilities": {"bandwidth": true}
}
]
}
然后重啟 calico-node 容器:
$ kubectl -n kube-system delete pod -l k8s-app=calico-node
8. 遠程連接
在系統未安裝好之前,只能用 VNC 來遠程控制,但 VNC 的體驗實在讓人難受。現在系統裝好了,就可以使用 Windows 的遠程連接協議 RDP(Remote Desktop Protocol) 了。選擇開始 >設置 >系統>遠程桌面,打開啟用遠程桌面就好了。
現在可以通過 telnet 來測試一下 RDP 端口(3389
)的連通性:
$ kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
virt-launcher-win10-s742j 2/2 Running 0 139m 100.92.235.131 k8s03 <none> <none>
$ telnet 100.92.235.131 3389
Trying 100.92.235.131...
Connected to 100.92.235.131.
Escape character is '^]'.
如果你的本地電腦能夠直連 Pod IP
和 SVC IP
,現在就可以直接通過 RDP 客戶端來遠程連接 Windows 了。如果你的本地電腦不能直連 Pod IP
和 SVC IP
,但可以直連 Kubernetes 集群的 Node IP
,可以通過 NodePort
來暴露 RDP 端口。具體操作是創建一個 Service,類型為 NodePort:
$ kubectl virt expose vm win10 --name win10-rdp --port 3389 --target-port 3389 --type NodePort
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 17d
win10-rdp NodePort 10.98.20.203 <none> 3389:31192/TCP 20m
然后就可以通過 Node IP
來遠程連接 Windows 了。
如果你的本地操作系統是 Windows 10,可以在任務欄的搜索框中,鍵入“遠程桌面連接”,然后選擇“遠程桌面連接”。在“遠程桌面連接”中,鍵入你想要連接的電腦的名稱(從步驟 1),然后選擇“連接”。
如果你的本地操作系統是 macOS
,需要在 App Store 中安裝 Microsoft Remote Desktop
。
安裝完之后打開應用,選擇 Add PC:
在 PC name 一欄中輸入 NodeIP+NodePort
,然后點擊 Add。
然后右擊創建好的配置,選擇 Connect:
輸入賬號密碼后就可以連接到 Windows 了。
全屏之后就可以獲得完美的遠程桌面體驗了,盡情玩耍吧!
9. 參考
Kubernetes 1.18.2 1.17.5 1.16.9 1.15.12離線安裝包發布地址http://store.lameleg.com ,歡迎體驗。 使用了最新的sealos v3.3.6版本。 作了主機名解析配置優化,lvscare 掛載/lib/module解決開機啟動ipvs加載問題, 修復lvscare社區netlink與3.10內核不兼容問題,sealos生成百年證書等特性。更多特性 https://github.com/fanux/sealos 。歡迎掃描下方的二維碼加入釘釘群 ,釘釘群已經集成sealos的機器人實時可以看到sealos的動態。