Kubernetns 容器與VM的編排與監控實戰
使用Kubernetes進行容器編排的好處是眾所周知的。但是,如果您的業務不適合容器化,那又該怎么辦?也許您有一個基于第三方VM的應用,該應用不太容易也不太合適進行容器化,又或者可能需要與Kubernetes平臺運行不同的內核或OS。
您真正想要的是讓Kubernetes與基于標準容器一起編排VM的方式,就像普通Pod一樣。最近,有兩個比較好的項目旨在使您做到這一點。分別是是KubeVirt和OpenShift CNV。
在此博客中,我將逐步介紹KubeVirt,您可以按照自己的步驟,使用Calico網絡將KubeVirt添加到群集中,然后使用Calico網絡策略來保護VM。
開始之前
我在開發集群中使用Ubuntu 20.04和兩個裸機服務器。盡管我在“第1步”中對如何創建類似的開發集群進行了解釋,但是如果您已經選擇了其他Kubernetes或OpenShift環境,則可以安全地跳過它。
要求:
至少一臺具有2個CPU,4GB Ram和20GB存儲空間的主機
kubectl命令行實用程序
SSH客戶端
KubeVirt安裝與管理
步驟1:建立集群
在開始創建集群之前,讓我們對主機進行初始化配置以適合Kubernetes。查看這個官方的教程(https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)并準備您的主機。
讓我們創建一個Kubernetes集群
- sudo kubeadm init --pod-network-cidr=192.168.0.0/16
執行以下命令來配置kubectl:
- mkdir -p $HOME/.kube
- sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
- sudo chown $(id -u):$(id -g) $HOME/.kube/config
移除master上的污點,以便您可以在其上調度pod。
- kubectl taint nodes --all node-role.kubernetes.io/master-
它應該返回以下內容:
- node/<your-hostname> untainted
步驟2:安裝Calico
使用清單安裝Calico
- kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
步驟3:安裝KubeVirt
使用namespace,我們可以將資源隔離到邏輯模塊中,并且可以更輕松地管理它們。
- kubectl create namespace kubevirt
建議使用支持硬件虛擬化的主機,以確保您的主機能夠使用virt-host-validate二進制文件。
- virt-host-validate qemu
- QEMU: Checking for hardware virtualization :PASS
如果主機缺少此命令,則可以使用發行版軟件包管理器進行安裝,也可以使用來檢查kvm文件夾是否可用 ls /dev/kvm。
默認情況下,KubeVirt嘗試利用硬件仿真。但是,此功能并非在所有環境中都可用,在這種情況下,您可以使用以下方式啟用軟件仿真:
- kubectl create configmap -n kubevirt kubevirt-config \
- --from-literal debug.useEmulation=true
應用這些清單并運行KubeVirt operator以自動安裝所有必需的資源。
- kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/v0.38.1/kubevirt-operator.yaml
- kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/v0.38.1/kubevirt-cr.yaml
啟用遷移功能。(該功能依賴于存儲)
- kubectl create configmap -n kubevirt kubevirt-config --from-literal feature-gates="LiveMigration"
啟用VNC代理功能組件。
- kubectl apply -f kubevirt/vnc.yaml
您可以使用此命令檢查KubeVirt的安裝進度。
- kubectl -n kubevirt wait kv kubevirt --for condition=Available
步驟4:創建一個簡單的VM
首先,我們創建一個namespace來隔離此演示的資源。
- kubectl create namespace kv-policy-demo
現在,使用虛擬機實例(VMI)定制資源,我們可以創建與Kubernetes完全集成的VM。
- kubectl create -f - <<EOF
- apiVersion: kubevirt.io/v1alpha3
- kind: VirtualMachineInstance
- metadata:
- name: vmi-cirros
- namespace: kv-policy-demo
- labels:
- special: l-vmi-cirros
- spec:
- domain:
- devices:
- disks:
- - disk:
- bus: virtio
- name: containerdisk
- resources:
- requests:
- memory: 64M
- volumes:
- - name: containerdisk
- containerDisk:
- image: kubevirt/cirros-registry-disk-demo:latest
- EOF
請注意,如果您使用的是軟件仿真,則啟動虛擬機可能會非常慢,并且完成IP地址分配可能需要5到6分鐘的時間。
- kubectl create -f - <<EOF
- apiVersion: v1
- kind: Service
- metadata:
- name: vmi-cirros-ssh-svc
- namespace: kv-policy-demo
- spec:
- ports:
- - name: crrios-ssh-svc
- nodePort: 30000
- port: 27017
- protocol: TCP
- targetPort: 22
- selector:
- special: l-vmi-cirros
- type: NodePort
- EOF
通過使用您節點的IP地址通過服務節點端口進行訪問,確認我們可以使用SSH訪問VM 。默認密碼是gocubsgo。
- ssh cirros@10.1.2.3 -p 30000
通過從新的虛擬機ping google來確認虛擬機可以訪問外界。
- ping www.google.com -c 5
步驟5:添加網絡安全性
應用以下策略在其namespace中隔離VM。這將僅將允許入向為SSH的協議,并且阻止所有出現的流量VM。(取決于您的VM,您將需要其他策略,但是此簡單策略對本教程很有用。)
- kubectl create -f - <<EOF
- apiVersion: networking.k8s.io/v1
- kind: NetworkPolicy
- metadata:
- name: only-allow-ingress-ssh-to-vm
- namespace: kv-policy-demo
- spec:
- podSelector:
- matchLabels:
- special: l-vmi-cirros
- policyTypes:
- - Ingress
- - Egress
- ingress:
- - from:
- ports:
- - port: 22
- EOF
SSH進入虛擬機,然后嘗試再次ping google。
您將無法執行此操作,因為該策略將阻止所有從Pod發起的與外界的通信。這非常強大–您可以使用與保護Pod相同的范例來保護VM!
步驟6:訪問虛擬機
現在我們已經為基于Kubernetes的VM管理設置了Kubevirt,現在讓我們訪問我們的VM。
- 使用NodePort服務和弱密碼在外部公開VM時要小心
- 串行控制臺僅可通過kubectl virt插件使用
- SSH以root用戶身份登錄
通過virt:
- SSH訪問:
- kubectl virt expose vmi test-vm --port=22 --name=test-vm-ssh --type=NodePort
- 串行控制臺:
- virtctl console test-vm
不通過virt:
- apiVersion: v1
- kind: Service
- metadata:
- name: test-vm-ssh
- namespace: default
- spec:
- ports:
- - name: test-vm-ssh
- protocol: TCP
- port: 22
- targetPort: 22
- selector:
- kubevirt.io/name: test-vm
- type: NodePort
使用VNC:
- 如果尚未啟用VNC
- kubectl apply -f kubevirt/vnc/vnc.yaml
- 查找VNC服務節點端口
- 在以下位置訪問VM
http://NODE_IP:NODEPORT/?namespace=VM_NAMESPACE
- 僅顯示namespace VM_NAMESPACE下的VM。選擇所需虛擬機所在的namespace。
步驟7:測試CDI
- 在應用之前,請確保填寫DataVolume/VM清單中的所有變量以適合您的環境
- 嘗試創建一個DataVolume
- kubectl apply -f datavolume/datavolume-cirros.yaml
- kubectl apply -f datavolume/datavolume-ubuntu.yaml
- kubectl get datavolumes
- 在VM清單中即時使用DataVolume
- kubectl apply -f vm/CDI-PVC.yaml
步驟8:清理
要清除本指南中使用的namespace和VM,可以運行以下命令
- kubectl delete namespace kv-policy-demo
其他:故障排除
- 確保Kubernetes有足夠的備用CPU/RAM來部署您請求的VM
- 確保硬件虛擬化受支持并且可用,或者ConfigMap中存在軟件虛擬化標志
- 更改標志需要重新啟動部署
- 確保服務選擇器正確定位到VM Pod
- 檢查Docker MTU和CNI插件MTU是否適合您的網絡
- 使用kubectl virt console $VM_NAME_HERE以確保虛擬機已啟動
從內部監控KubeVirt VM
部署Prometheus Operator
一旦準備好了k8s集群,就是部署Prometheus Operator。原因是KubeVirt CR安裝在群集上時將檢測ServiceMonitor CR是否已存在。如果是這樣,那么它將創建ServiceMonitors,這些ServiceMonitors被配置為可立即監控所有KubeVirt組件(virt-controller,virt-api和virt-handler)。
盡管本文中沒有介紹監控KubeVirt本身,但是還是在部署KubeVirt之前先部署Prometheus Operator。
要部署Prometheus Operator,您需要首先創建其namespace,例如monitoring:
- kubectl create ns monitoring
然后在新的namespace中部署operator:
- helm fetch stable/prometheus-operator
- tar xzf prometheus-operator*.tgz
- cd prometheus-operator/ && helm install -n monitoring -f values.yaml kubevirt-prometheus stable/prometheus-operator
部署完所有內容后,您可以刪除helm下載的所有內容:
- cd ..
- rm -rf prometheus-operator*
要記住的一件事是我們在此處添加的版本名稱:kubevirt-prometheus。ServiceMonitor稍后聲明我們時將使用版本名稱。
部署具有持久性存儲的VirtualMachine
現在,我們已經準備好所需要的一切。下面讓我們配置虛擬機。
我們將從CDI的DataVolume(https://github.com/kubevirt/containerized-data-importer/blob/master/doc/datavolumes.md)資源PersistenVolume開始。由于我沒有動態存儲提供程序,因此我將創建2個PV,引用將聲明它們的PVC。注意每個PV的claimRef。
- apiVersion: v1
- kind: PersistentVolume
- metadata:
- name: example-volume
- spec:
- storageClassName: ""
- claimRef:
- namespace: default
- name: cirros-dv
- accessModes:
- - ReadWriteOnce
- capacity:
- storage: 2Gi
- hostPath:
- path: /data/example-volume/
- ---
- apiVersion: v1
- kind: PersistentVolume
- metadata:
- name: example-volume-scratch
- spec:
- storageClassName: ""
- claimRef:
- namespace: default
- name: cirros-dv-scratch
- accessModes:
- - ReadWriteOnce
- capacity:
- storage: 2Gi
- hostPath:
- path: /data/example-volume-scratch/
有了永久性存儲后,我們可以使用以下清單創建虛擬機:
- apiVersion: kubevirt.io/v1alpha3
- kind: VirtualMachine
- metadata:
- name: monitorable-vm
- spec:
- running: true
- template:
- metadata:
- name: monitorable-vm
- labels:
- prometheus.kubevirt.io: "node-exporter"
- spec:
- domain:
- resources:
- requests:
- memory: 1024Mi
- devices:
- disks:
- - disk:
- bus: virtio
- name: my-data-volume
- volumes:
- - dataVolume:
- name: cirros-dv
- name: my-data-volume
- dataVolumeTemplates:
- - metadata:
- name: "cirros-dv"
- spec:
- source:
- http:
- url: "https://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img"
- pvc:
- storageClassName: ""
- accessModes:
- - ReadWriteOnce
- resources:
- requests:
- storage: "2Gi"
注意,KubeVirt的VirtualMachine資源有一個VirtualMachine模板和一個dataVolumeTemplate。在VirtualMachine模板上,重要的是要注意我們已將VM命名為VM monitorable-vm,以后將使用該名稱連接到其控制臺virtctl。我們添加的標簽prometheus.kubevirt.io: "node-exporter"也很重要,因為在VM內部安裝與配置node-exporter時將會使用到它。
在dataVolumeTemplate上,需要注意的是,我們將PVC命名為cirros-dv,DataVolume資源將用它創建2個PVC,cirros-dv和cirros-dv-scratch。注意,cirros-dv和cirros-dv-scratch是PersistentVolume清單上引用的名稱。名稱必須匹配才能工作。
在VM內安裝node-exporter
一旦VirtualMachineInstance運行,我們就可以使用virtctl console monitorable vm連接到它的控制臺。如果需要用戶和密碼,請提供相應的憑據。如果您使用的是本指南中的同一磁盤映像,則用戶和密碼分別為cirros和gocubsgo
以下腳本將安裝node-exporter并將虛擬機配置為在啟動時候自啟:
- curl -LO -k https://github.com/prometheus/node_exporter/releases/download/v1.0.1/node_exporter-1.0.1.linux-amd64.tar.gz
- gunzip -c node_exporter-1.0.1.linux-amd64.tar.gz | tar xopf -
- ./node_exporter-1.0.1.linux-amd64/node_exporter &
- sudo /bin/sh -c 'cat > /etc/rc.local <<EOF
- #!/bin/sh
- echo "Starting up node_exporter at :9100!"
- /home/cirros/node_exporter-1.0.1.linux-amd64/node_exporter 2>&1 > /dev/null &
- EOF'
- sudo chmod +x /etc/rc.local
PS:如果您使用其他基礎映像,請配置node-exporter以在啟動時相應地啟動
配置Prometheus抓取VM的node-exporter
配置Prometheus來抓取node-exporter非常簡單。我們需要做的就是創建一個新的Service和一個ServiceMonitor:
- apiVersion: v1
- kind: Service
- metadata:
- name: monitorable-vm-node-exporter
- labels:
- prometheus.kubevirt.io: "node-exporter"
- spec:
- ports:
- - name: metrics
- port: 9100
- targetPort: 9100
- protocol: TCP
- selector:
- prometheus.kubevirt.io: "node-exporter"
- ---
- apiVersion: monitoring.coreos.com/v1
- kind: ServiceMonitor
- metadata:
- name: kubevirt-node-exporters-servicemonitor
- namespace: monitoring
- labels:
- prometheus.kubevirt.io: "node-exporter"
- release: monitoring
- spec:
- namespaceSelector:
- any: true
- selector:
- matchLabels:
- prometheus.kubevirt.io: "node-exporter"
- endpoints:
- - port: metrics
- interval: 15s
讓我們分解一下,以確保我們正確設置了所有內容。從Service開始:
- spec:
- ports:
- - name: metrics
- port: 9100
- targetPort: 9100
- protocol: TCP
- selector:
- prometheus.kubevirt.io: "node-exporter"
在規范中,我們正在創建一個名為metrics的新端口,該端口將重定向到每個標記為prometheus.kubevirt.io: "node-exporter",此處為端口9100,這是node_exporter的默認端口號。
- apiVersion: v1
- kind: Service
- metadata:
- name: monitorable-vm-node-exporter
- labels:
- prometheus.kubevirt.io: "node-exporter"
我們還在為服務本身貼上標簽prometheus.kubevirt.io: "node-exporter",將由ServiceMonitor對象使用。現在讓我們看看我們的ServiceMonitor規范:
- spec:
- namespaceSelector:
- any: true
- selector:
- matchLabels:
- prometheus.kubevirt.io: "node-exporter"
- endpoints:
- - port: metrics
- interval: 15s
由于我們的ServiceMonitor將部署在monitoring namespace中,而我們的服務則在default namespace中,因此我們需要設置namespaceSelector.any=true。
我們還告訴ServiceMonitor,Prometheus需要從標記為prometheus.kubevirt.io: "node-exporter"以及哪些端口被命名為metrics。幸運的是,我們的service就是這么做的!
最后一件要注意的事。Prometheus配置可以設置為監視多個ServiceMonitors。我們可以通過以下命令查看prometheus正在監視的服務:
- # Look for Service Monitor Selector
- kubectl describe -n monitoring prometheuses.monitoring.coreos.com monitoring-prometheus-oper-prometheus
確保我們的ServiceMonitor具有Prometheus的Service Monitor Selector的所有標簽。一個常見的選擇器是我們在部署helm的prometheus時設置的版本名稱!
測試
您可以通過端口轉發Prometheus Web UI并執行一些PromQL來進行快速測試:
- kubectl port-forward -n monitoring prometheus-monitoring-prometheus-oper-prometheus-0 9090:9090
為確保一切正常,請訪問localhost:9090/graph并執行PromQL up{pod=~"virt-launcher.*"}。Prometheus應該返回從monitorable-vm的node-exporter收集的數據。
您可以試用virtctl,停止和啟動VM,以查看指標的行為。您會注意到,使用停止VM時virtctl stop monitorable-vm,VirtualMachineInstance被殺死,因此它也是Pod。這將導致我們的服務無法找到pod的端點,然后將其從Prometheus的目標中刪除。
由于這種行為,下面的告警規則將無法正常工作,因為我們的目標實際上已經消失了,而不是降級了。
- - alert: KubeVirtVMDown
- expr: up{pod=~"virt-launcher.*"} == 0
- for: 1m
- labels:
- severity: warning
- annotations:
- summary: KubeVirt VM {{ $labels.pod }} is down.
但是,如果VM在不停止的情況下連續崩潰,則pod不會被殺死,并且仍將監視目標。node-exporter永遠不會啟動或將與VM一起不斷關閉,因此這樣的警報可能會起作用:
- - alert: KubeVirtVMCrashing
- expr: up{pod=~"virt-launcher.*"} == 0
- for: 5m
- labels:
- severity: critical
- annotations:
- summary: KubeVirt VM {{ $labels.pod }} is constantly crashing before node-exporter starts at boot.
結論:
在此博客文章中,首先,我們簡單介紹以及快速安裝體驗了kubevirt, 然后我們使用node-exporter從KubeVirt VM中公開了監控指標。我們還配置了Prometheus Operator來收集這些指標。本文也是將Kubernetes監控實踐與KubeVirt VM運行的應用程序結合使用。