原文連結: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的動態。