Kubernetes 使用 Kubevirt 執行管理 Windows 10 作業系統

米開朗基楊發表於2020-12-03

原文連結: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 IPSVC IP,現在就可以直接通過 RDP 客戶端來遠端連線 Windows 了。如果你的本地電腦不能直連 Pod IPSVC 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的動態。

相關文章