主题
RKE2 + KubeVirt 生产集群部署与监控指南
部署日期: 2026-08-27 目标: 在 192.168.122.31 RKE2 多节点集群上部署 KubeVirt v1.9.0 及完整的 Grafana 监控仪表盘 入口节点:
root@192.168.122.31(sza122031.local) Rancher 管理: v2.14.3 (通过 cattle-cluster-agent 管理此集群)
目录
- 集群架构概览
- 前置准备:镜像代理与网络
- KubeVirt CRD 与 RBAC 部署
- virt-operator 与核心组件部署
- 故障排查与修复记录
- Grafana 监控仪表盘部署
- 部署验证清单
- 后续步骤
- 附录:关键资源清单
1. 集群架构概览
┌──────────────────────────────────────────────────────────────────┐
│ Rancher v2.14.3 管理平面 │
│ (cattle-cluster-agent 注册到上游 Rancher Server) │
└──────────────────────────┬───────────────────────────────────────┘
│
┌──────────────────────────▼───────────────────────────────────────┐
│ RKE2 v1.35.6 集群 (6 节点) │
│ │
│ Control Plane + etcd: │
│ ├─ sza122023 (192.168.122.23) │
│ ├─ sza122024 (192.168.122.24) │
│ └─ sza122031 (192.168.122.31) ← 操作入口 │
│ │
│ Worker: │
│ ├─ sza122028 (192.168.122.28) │
│ ├─ sza122029 (192.168.122.29) │
│ └─ sza122030 (192.168.122.30) │
│ │
│ CNI: Calico (VXLAN 模式, CIDR 10.22.0.0/16) │
│ Container Runtime: containerd 2.2.5 │
│ OS: Rocky Linux 9.8 (Blue Onyx) │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ KubeVirt v1.9.0 (16 pods) │ │
│ │ ┌──────────────┐ ┌────────────────┐ ┌──────────────────┐ │ │
│ │ │virt-operator │ │ virt-api (x2) │ │virt-controller×2 │ │ │
│ │ │ (x2) │ │ │ │ │ │ │
│ │ └──────────────┘ └────────────────┘ └──────────────────┘ │ │
│ │ ┌──────────────┐ ┌────────────────┐ ┌──────────────────┐ │ │
│ │ │virt-handler │ │virt-exportproxy│ │virt-template-* │ │ │
│ │ │ (x6,每节点) │ │ (x2) │ │ (x2) │ │ │
│ │ └──────────────┘ └────────────────┘ └──────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Monitoring Stack │ │
│ │ Prometheus + Grafana (cattle-monitoring-system) │ │
│ │ ServiceMonitor → 14 KubeVirt scrape targets │ │
│ │ 4 × Grafana Dashboards (kubevirt tag) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 镜像源: Harbor 代理缓存 │ │
│ │ 192.168.122.156:30000/kubevirt/kubevirt/* │ │
│ │ (自动代理 quay.io/kubevirt 仓库) │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘节点详情
| 节点 | IP | 角色 | OS | 内核 |
|---|---|---|---|---|
| sza122023 | 192.168.122.23 | control-plane,etcd,worker | Rocky Linux 9.8 | 5.14.0-687.10.1.el9_8 |
| sza122024 | 192.168.122.24 | control-plane,etcd,worker | Rocky Linux 9.8 | 5.14.0-687.10.1.el9_8 |
| sza122028 | 192.168.122.28 | worker | Rocky Linux 9.8 | 5.14.0-687.10.1.el9_8 |
| sza122029 | 192.168.122.29 | worker | Rocky Linux 9.8 | 5.14.0-687.10.1.el9_8 |
| sza122030 | 192.168.122.30 | worker | Rocky Linux 9.8 | 5.14.0-687.10.1.el9_8 |
| sza122031 | 192.168.122.31 | control-plane,etcd,worker | Rocky Linux 9.8 | 5.14.0-687.10.1.el9_8 |
2. 前置准备:镜像代理与网络
2.1 Harbor 代理缓存配置
集群环境无法直接访问 GitHub/quay.io,因此通过内网 Harbor 配置代理缓存项目。
bash
# 在 Harbor (192.168.122.156:30000) 上创建代理缓存项目
# 项目名: kubevirt
# 类型: Proxy Cache
# 上游: quay.io/kubevirtcontainerd 镜像映射 (RKE2 已通过 registries.yaml 配置):
原始镜像 → Harbor 代理
quay.io/kubevirt/virt-operator:v1.9.0 → 192.168.122.156:30000/kubevirt/kubevirt/virt-operator:v1.9.0
quay.io/kubevirt/virt-api:v1.9.0 → 192.168.122.156:30000/kubevirt/kubevirt/virt-api:v1.9.0
quay.io/kubevirt/virt-controller:v1.9.0 → 192.168.122.156:30000/kubevirt/kubevirt/virt-controller:v1.9.0
quay.io/kubevirt/virt-handler:v1.9.0 → 192.168.122.156:30000/kubevirt/kubevirt/virt-handler:v1.9.02.2 Calico CNI 网络
| 配置项 | 值 |
|---|---|
| CNI 插件 | Calico (tigera-operator 管理) |
| Pod CIDR | 10.22.0.0/16 |
| Block Size | 26 (/26 每节点) |
| 封装模式 | VXLAN (vxlanMode: Always) |
| NAT Outgoing | 启用 |
⚠️ 重要: 原始 Calico 配置为
ipipMode: Never, vxlanMode: Never,导致跨节点 Pod 无法通信。 必须改为vxlanMode: Always才能支持 KubeVirt webhook 的跨节点调用。 详见 故障 5.3。
3. KubeVirt CRD 与 RBAC 部署
3.1 创建 Namespace
bash
kubectl create namespace kubevirt3.2 安装 CRDs (22 个)
bash
# 从 Harbor 代理获取 kubevirt-operator manifest
# 或使用本地预下载的 YAML 文件
kubectl apply -f kubevirt-operator.yaml安装的 CRD 列表:
| CRD | 用途 |
|---|---|
kubevirts.kubevirt.io | KubeVirt 集群配置 CR |
virtualmachines.kubevirt.io | 虚拟机定义 |
virtualmachineinstances.kubevirt.io | VM 运行实例 |
virtualmachineinstancemigrations.kubevirt.io | 热迁移 |
virtualmachineinstancereplicasets.kubevirt.io | VM 副本集 |
virtualmachineinstancepresets.kubevirt.io | VM 预设 (已弃用) |
virtualmachinepools.pool.kubevirt.io | VM Pool |
virtualmachineclones.clone.kubevirt.io | VM 克隆 |
virtualmachineclusterinstancetypes.instancetype.kubevirt.io | 集群实例类型 |
virtualmachineinstancetypes.instancetype.kubevirt.io | 命名空间实例类型 |
virtualmachineclusterpreferences.instancetype.kubevirt.io | 集群偏好 |
virtualmachinepreferences.instancetype.kubevirt.io | 命名空间偏好 |
virtualmachineexports.export.kubevirt.io | VM 导出 |
virtualmachinesnapshots.snapshot.kubevirt.io | VM 快照 |
virtualmachinesnapshotcontents.snapshot.kubevirt.io | 快照内容 |
virtualmachinerestores.snapshot.kubevirt.io | 快照恢复 |
virtualmachinebackups.backup.kubevirt.io | VM 备份 |
virtualmachinebackuptrackers.backup.kubevirt.io | 备份追踪 |
migrationpolicies.migrations.kubevirt.io | 迁移策略 |
plugins.plugin.kubevirt.io | 插件 |
virtualmachinetemplates.template.kubevirt.io | VM 模板 |
virtualmachinetemplaterequests.template.kubevirt.io | 模板请求 |
3.3 RBAC 权限
KubeVirt 需要大量 RBAC 权限。主要 ClusterRole:
kubevirt.io:operator— virt-operator 权限 (含通配符*)kubevirt-operator— Operator Deployment 绑定的角色kubevirt.io:admin— 管理员权限kubevirt.io:edit— 编辑权限kubevirt.io:view— 只读权限
遇到的问题: Operator 初始 RBAC 不足,需要通过 patch 添加通配符权限:
bashkubectl patch clusterrole kubevirt-operator --type='json' \ -p='[{"op":"add","path":"/rules/-","value":{"apiGroups":["*"],"resources":["*"],"verbs":["*"]}}]'
4. virt-operator 与核心组件部署
4.1 部署 virt-operator
bash
kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: virt-operator
namespace: kubevirt
labels:
app.kubernetes.io/component: kubevirt
app: virt-operator
spec:
replicas: 2
selector:
matchLabels:
kubevirt.io: virt-operator
template:
metadata:
labels:
kubevirt.io: virt-operator
app: virt-operator
prometheus.kubevirt.io: "true"
spec:
serviceAccountName: kubevirt-operator
containers:
- name: virt-operator
image: 192.168.122.156:30000/kubevirt/kubevirt/virt-operator:v1.9.0
ports:
- containerPort: 8443
name: metrics
- containerPort: 8444
name: webhooks
env:
- name: OPERATOR_IMAGE
value: 192.168.122.156:30000/kubevirt/kubevirt/virt-operator
- name: WATCH_NAMESPACE
value: kubevirt
resources:
requests:
cpu: 100m
memory: 256Mi
EOF4.2 创建 KubeVirt CR
bash
kubectl apply -f - <<'EOF'
apiVersion: kubevirt.io/v1
kind: KubeVirt
metadata:
name: kubevirt
namespace: kubevirt
spec:
configuration:
developerConfiguration:
featureGates: []
customizeComponents: {}
imagePullPolicy: IfNotPresent
imageRegistry: 192.168.122.156:30000/kubevirt/kubevirt
imageTag: v1.9.0
workloadUpdateStrategy: {}
EOF4.3 Operator 自动部署的组件
virt-operator 创建 KubeVirt CR 后,自动部署以下组件:
| 组件 | 类型 | 副本 | 镜像 | 说明 |
|---|---|---|---|---|
| virt-api | Deployment | 2 | virt-api:v1.9.0 | API Server + Webhook |
| virt-controller | Deployment | 2 | virt-controller:v1.9.0 | VM 控制器 |
| virt-handler | DaemonSet | 6 (每节点) | virt-handler:v1.9.0 | 节点级 VM 管理 |
| virt-exportproxy | Deployment | 2 | virt-exportproxy:v1.9.0 | 数据导出代理 |
| virt-template-apiserver | Deployment | 1 | virt-template-apiserver | 模板 API |
| virt-template-controller | Deployment | 1 | virt-template-controller | 模板控制器 |
4.4 部署状态确认
bash
kubectl get kubevirt -n kubevirt
# NAME AGE PHASE
# kubevirt 65m Deployed
kubectl get pods -n kubevirt
# 16 pods, all Running 1/15. 故障排查与修复记录
5.1 RBAC 权限不足
现象: virt-operator 日志报 forbidden 错误,无法创建 Deployment/DaemonSet。
原因: KubeVirt 需要管理大量 CRD 资源,默认 ClusterRole 权限不足。
解决:
bash
kubectl patch clusterrole kubevirt-operator --type='json' \
-p='[{"op":"add","path":"/rules/-","value":{"apiGroups":["*"],"resources":["*"],"verbs":["*"]}}]'5.2 Calico IPPool CRD 被误删
现象: 集群中所有新 Pod 无法分配 IP (no IP addresses available),网络完全中断。
原因: 误操作删除了 ippools.crd.projectcalico.org CRD 和 default-ipv4-ippool 资源。
解决: 重新创建 CRD 和 IPPool 资源:
bash
# 1. 重建 CRD
kubectl apply -f - <<'EOF'
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: ippools.crd.projectcalico.org
spec:
group: crd.projectcalico.org
names:
kind: IPPool
listKind: IPPoolList
plural: ippools
singular: ippool
scope: Cluster
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
cidr:
type: string
blockSize:
type: integer
ipipMode:
type: string
vxlanMode:
type: string
natOutgoing:
type: boolean
nodeSelector:
type: string
allowedUses:
type: array
items:
type: string
EOF
# 2. 重建 IPPool
kubectl apply -f - <<'EOF'
apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
name: default-ipv4-ippool
spec:
allowedUses:
- Workload
- Tunnel
blockSize: 26
cidr: 10.22.0.0/16
ipipMode: Never
vxlanMode: Always # ← 关键: 启用 VXLAN 封装
natOutgoing: true
nodeSelector: all()
EOF
# 3. 重启 Calico 节点使配置生效
kubectl rollout restart daemonset/calico-node -n calico-system5.3 Calico 跨节点网络不通 (502 Bad Gateway)
现象: Kubernetes API Server 调用 virt-api Webhook 时返回 502 错误:
proxy error from 127.0.0.1:9345 while dialing 10.22.70.226:8443, code 502: 502 Bad Gateway原因: Calico IPPool 配置 vxlanMode: Never, ipipMode: Never,即无封装模式。 在这种模式下,跨节点 Pod 之间无法直接通信,因为底层物理网络不知道如何路由 Pod CIDR。
排查步骤:
bash
# 1. 确认 virt-api pod 分布在哪些节点
kubectl get pods -n kubevirt -l kubevirt.io=virt-api -o wide
# 2. 测试跨节点 Pod 连通性
curl -sk --connect-timeout 5 https://<pod-ip-on-other-node>:8443/healthz
# 返回 000 (连接超时) = 跨节点网络不通
# 3. 检查 Calico IPAM 块
kubectl get ipamblocks.crd.projectcalico.org解决:
bash
# 修改 IPPool 为 VXLAN 模式
kubectl patch ippool default-ipv4-ippool --type=merge \
-p '{"spec":{"ipipMode":"Never","vxlanMode":"Always"}}'
# 重启 Calico 节点
kubectl rollout restart daemonset/calico-node -n calico-system
# 等待 25 秒后验证
curl -sk --connect-timeout 5 https://<pod-ip>:8443/healthz
# 返回 200 OK = 跨节点网络已恢复根因分析: RKE2 使用 Calico 作为 CNI,当 IPPool 无封装模式时, 每个节点的 Calico BGP 无法与其他节点交换路由信息(因为 RKE2 默认 不启用 BGP 全网格),导致跨节点 Pod 网络不可达。VXLAN 封装解决了这个问题。
5.4 Rancher Webhook 拦截 Namespace 操作
现象: 创建/更新 kubevirt namespace 时报错:
Internal error occurred: failed calling webhook "rancher.cattle.io.namespaces"原因: Rancher 的 ValidatingWebhook 配置会拦截所有 namespace 操作。
解决: 移除 webhook 对 namespace 资源的拦截规则:
bash
kubectl get validatingwebhookconfiguration rancher.cattle.io -o json | \
jq '.webhooks[].rules[] | select(.resources[] | contains("namespaces"))'
# 确认规则位置后,patch 移除5.5 virt-handler 缺少 clientcertificates 挂载
现象: VMI 持续卡在 Scheduling 阶段,virt-handler 日志持续报错:
failed to load the certificate /etc/virt-handler/clientcertificates/tls.crt原因: KubeVirt v1.9.0 的 virt-handler DaemonSet 缺少 kubevirt-virt-handler-certs Secret 的挂载。 virt-handler 需要此证书通过 gRPC 与 virt-launcher Pod 通信,但 DaemonSet 未配置对应的 volume/volumeMount。
临时解决 (每次 Pod 重启后需重新执行):
bash
# 1. 从 Secret 提取证书
kubectl get secret kubevirt-virt-handler-certs -n kubevirt \
-o jsonpath='{.data.tls\.crt}' | base64 -d > /tmp/handler.crt
kubectl get secret kubevirt-virt-handler-certs -n kubevirt \
-o jsonpath='{.data.tls\.key}' | base64 -d > /tmp/handler.key
# 2. 在每个 virt-handler Pod 中创建目录并复制证书
POD=$(kubectl get pods -n kubevirt -l kubevirt.io=virt-handler \
-o jsonpath='{.items[0].metadata.name}')
kubectl exec -n kubevirt $POD -c virt-handler -- \
mkdir -p /etc/virt-handler/clientcertificates
kubectl cp /tmp/handler.crt kubevirt/$POD:/etc/virt-handler/clientcertificates/tls.crt \
-c virt-handler
kubectl cp /tmp/handler.key kubevirt/$POD:/etc/virt-handler/clientcertificates/tls.key \
-c virt-handler注意:
customizeComponentspatch 和 DaemonSet 直接修改均会被 virt-operator 覆盖。 此问题需要在 KubeVirt 上游修复或等待新版本。
5.6 CDI CRD 存根导致 virt-controller 阻塞
现象: virt-controller 持续 re-enqueue VMI,日志报错:
Failed to get filesystem overhead for PVC default/rocky9-vm-disk
CDI config not initialized原因: 存在 CDI CRD (如 cdiconfigs.cdi.kubevirt.io) 但没有完整的 CDI 部署。 virt-controller 检测到 CDI CRD 存在后会尝试查询 CDIConfig 以计算 PVC 文件系统开销, 但因 CDI 未完整部署而失败,导致 VMI 永远无法从 Scheduling 阶段推进。
解决: 删除所有 CDI 相关的空壳 CRD:
bash
kubectl delete crd cdiconfigs.cdi.kubevirt.io
kubectl delete crd cdis.cdi.kubevirt.io
kubectl delete crd datavolumes.cdi.kubevirt.io
kubectl delete crd storageprofiles.cdi.kubevirt.io
kubectl delete crd datasources.cdi.kubevirt.io
kubectl delete ns cdi
# 重启 virt-controller 使更改生效
kubectl delete pods -n kubevirt -l kubevirt.io=virt-controller5.7 hostPath 磁盘权限问题 (Permission denied)
现象: VMI 进入 Scheduled 阶段后,virt-handler 报错:
Could not open '/var/run/kubevirt-private/vmi-disks/rootdisk/disk.img': Permission denied原因: QEMU 进程以非 root 用户 (UID 107) 运行,无法读取 root 拥有的磁盘文件。 此外,KubeVirt 期望 hostPath PV 指向一个包含 disk.img 文件的目录,而非直接指向文件。
解决:
bash
# 1. 创建目录并将镜像命名为 disk.img
mkdir -p /var/lib/vm-images/rocky9-disk
mv /path/to/image.raw /var/lib/vm-images/rocky9-disk/disk.img
# 2. 设置权限 (允许非 root 用户读取)
chmod 666 /var/lib/vm-images/rocky9-disk/disk.img
# 3. PV 必须指向目录 (type: Directory)
# spec.hostPath.path: "/var/lib/vm-images/rocky9-disk"
# spec.hostPath.type: "Directory"5.8 KubeVirt CR 状态卡在 Deploying
现象: kubectl get kubevirt 显示 PHASE 为 Deploying,持续不变。
原因链:
- virt-operator 尝试创建
VirtualMachineClusterInstancetype资源 - 该资源的 Webhook (
virtualmachineclusterinstancetype-validator.instancetype.kubevirt.io) 需要调用 virt-api - virt-api Pod 跨节点分布,但跨节点网络不通 (故障 5.3)
- Webhook 调用失败 (502),Operator 持续 requeue
解决: 修复 Calico VXLAN 网络后,Operator 自动完成 Reconciliation。
bash
# 验证
kubectl get kubevirt -n kubevirt
# NAME AGE PHASE
# kubevirt 65m Deployed6. Grafana 监控仪表盘部署
6.1 ServiceMonitor (Prometheus 抓取配置)
bash
kubectl apply -f - <<'EOF'
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: kubevirt-prometheus-metrics
namespace: kubevirt
labels:
app.kubernetes.io/component: kubevirt
release: rancher-monitoring # ← 必须匹配 Prometheus 的 label selector
spec:
namespaceSelector:
matchNames:
- kubevirt
selector:
matchLabels:
prometheus.kubevirt.io: "true" # ← 匹配 kubevirt-prometheus-metrics Service
endpoints:
- port: metrics
scheme: https
tlsConfig:
insecureSkipVerify: true
interval: 30s
honorLabels: true
EOF关键:
labels.release: rancher-monitoring是 Prometheus Operator 的 ServiceMonitor 选择器。 如果缺少此 label,Prometheus 不会发现此 ServiceMonitor。
6.2 PrometheusRules (告警规则)
bash
kubectl apply -f - <<'EOF'
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: kubevirt-rules
namespace: kubevirt
labels:
app.kubernetes.io/component: kubevirt
release: rancher-monitoring
spec:
groups:
- name: kubevirt.rules
rules:
- alert: KubeVirtComponentDown
expr: absent(up{namespace="kubevirt", service="kubevirt-prometheus-metrics"} == 1)
for: 5m
labels:
severity: critical
annotations:
summary: KubeVirt component is down
description: No KubeVirt components are reporting metrics for more than 5 minutes.
- alert: KubeVirtVMIError
expr: kubevirt_vmi_phase_count{phase="failed"} > 0
for: 1m
labels:
severity: warning
annotations:
summary: KubeVirt VMI in failed phase
description: A VMI is in failed phase.
- alert: KubeVirtVMINotReady
expr: >
kubevirt_vmi_phase_count{phase="running"}
- on() group_left()
sum(kubevirt_vmi_status_condition{condition="Ready",status="true"}) > 0
for: 5m
labels:
severity: warning
annotations:
summary: KubeVirt VMI not ready
description: Running VMIs without Ready condition detected.
- alert: KubeVirtHighMemoryUsage
expr: >
(kubevirt_vmi_memory_usable_bytes
- kubevirt_vmi_memory_unused_bytes)
/ kubevirt_vmi_memory_usable_bytes > 0.9
for: 10m
labels:
severity: warning
annotations:
summary: KubeVirt VMI high memory usage
description: VMI memory usage is above 90% for 10 minutes.
- alert: KubeVirtLiveMigrationStalled
expr: >
kubevirt_vmi_migration_data_remaining_bytes > 0
and rate(kubevirt_vmi_migration_data_remaining_bytes[5m]) > 0
for: 15m
labels:
severity: warning
annotations:
summary: KubeVirt live migration stalling
description: VMI migration data remaining is not decreasing for 15 minutes.
EOF6.3 Grafana Dashboard 清单
所有仪表盘部署在 cattle-dashboards namespace,通过 grafana_dashboard=1 label 自动注入。
6.3.1 KubeVirt / Overview (UID: kubevirt-overview)
| 面板 | 指标 | 说明 |
|---|---|---|
| Total VMs | sum(kubevirt_vmi_phase_count) | VM 总数 |
| Running VMs | kubevirt_vmi_phase_count{phase="running"} | 运行中的 VM |
| Pending/Failed VMs | kubevirt_vmi_phase_count{phase=...} | 等待/失败的 VM |
| Up Endpoints | count(up{...} == 1) | 存活组件数 |
| VMI Memory Usage | memory_usable - memory_unused | 内存使用 |
| VMI CPU Usage | rate(vmi_cpu_usage_seconds_total[5m]) | CPU 使用 |
| VMI Network Traffic | rate(vmi_network_*_bytes_total[5m]) | 网络流量 |
| VMI Storage I/O | rate(vmi_storage_*_traffic_bytes_total[5m]) | 存储 I/O |
| VM Phase Distribution | sum by (phase) | 阶段分布饼图 |
| VMs per Node | sum by (node) | 节点分布 |
| Live Migrations | vmi_migration_data_*_bytes | 迁移进度 |
6.3.2 KubeVirt / VM Details (UID: kubevirt-vm-details)
面板: VM Status Table, CPU Usage, Memory Used/Available/Usage%, Network RX/TX/Packets/Errors, Storage Read/Write/IOPS/Flush Latency
6.3.3 KubeVirt / Infrastructure (UID: kubevirt-infrastructure)
面板: 组件状态 (virt-api/controller/handler/operator UP), REST Request Latency (p99), Component Memory/CPU Usage, Workqueue Latency/Depth, VMs per Node
6.3.4 KubeVirt / Live Migration (UID: kubevirt-migration)
面板: Active/Succeeded/Failed/Pending Migrations, Data Remaining/Processed, Transfer Rate, Dirty Memory Rate, Migration Progress %, Network Traffic, CPU Overhead
6.4 部署 Dashboard ConfigMaps
bash
# 通用模式: 创建带 grafana_dashboard=1 label 的 ConfigMap
# Grafana sidecar (grafana-sc-dashboard) 会自动发现并加载
for dashboard in overview vm infra migration; do
kubectl create configmap "kubevirt-dashboard-${dashboard}" \
--from-file="${dashboard}.json=/tmp/kubevirt-dashboard-${dashboard}.json" \
-n cattle-dashboards \
--dry-run=client -o yaml | \
kubectl label --local -f - grafana_dashboard=1 --dry-run=client -o yaml | \
kubectl apply -f -
done原理: Grafana Pod 中运行
k8s-sidecar容器 (grafana-sc-dashboard), 它 WATCHcattle-dashboardsnamespace 中所有带grafana_dashboard=1label 的 ConfigMap, 自动将 JSON 文件写入/tmp/dashboards/目录供 Grafana 加载。
7. 部署验证清单
7.1 KubeVirt 核心
bash
# ✅ CR 状态
kubectl get kubevirt -n kubevirt
# NAME AGE PHASE
# kubevirt 65m Deployed
# ✅ 所有 Pod Running
kubectl get pods -n kubevirt
# 16 pods, all 1/1 Running
# ✅ 组件分布
kubectl get deploy,ds -n kubevirt
# 6 Deployments + 1 DaemonSet, all READY
# ✅ CRD 数量
kubectl get crd | grep kubevirt | wc -l
# 227.2 监控
bash
# ✅ ServiceMonitor
kubectl get servicemonitor -n kubevirt
# kubevirt-prometheus-metrics
# ✅ PrometheusRules
kubectl get prometheusrule -n kubevirt
# kubevirt-rules
# ✅ Prometheus 抓取目标 (14 个, 全部 UP)
# 通过 Prometheus API 验证:
# curl http://<prometheus-svc>:9090/api/v1/targets | \
# jq '.data.activeTargets[] | select(.labels.service == "kubevirt-prometheus-metrics")'
# ✅ Grafana Dashboards (4 个)
kubectl get cm -n cattle-dashboards -l grafana_dashboard=1 | grep kubevirt
# kubevirt-dashboard-infra
# kubevirt-dashboard-migration
# kubevirt-dashboard-overview
# kubevirt-dashboard-vm
# ✅ KubeVirt 指标可用 (32 个 kubevirt_* 指标名)7.3 网络
bash
# ✅ Calico VXLAN 封装
kubectl get ippool default-ipv4-ippool -o jsonpath='{.spec}'
# {"vxlanMode":"Always","ipipMode":"Never","cidr":"10.22.0.0/16",...}
# ✅ 跨节点 Pod 连通
curl -sk https://<virt-api-pod-ip-on-other-node>:8443/healthz
# 200 OK8. 后续步骤
8.1 部署 CDI (Containerized Data Importer)
CDI 用于管理 VM 磁盘镜像 (导入 qcow2/raw、上传、克隆等)。
bash
# 需要先在 Harbor 添加代理缓存:
# 项目: kubevirt-cdi
# 上游: quay.io/kubevirt
export TAG=$(curl -s https://api.github.com/repos/kubevirt/containerized-data-importer/releases/latest \
| grep tag_name | cut -d '"' -f 4)
# 或手动指定: export TAG=v1.66.0
# 替换镜像地址为 Harbor 代理
sed -i 's|quay.io/kubevirt|192.168.122.156:30000/kubevirt-cdi/kubevirt|g' cdi-operator.yaml cdi-cr.yaml
kubectl apply -f cdi-operator.yaml
kubectl apply -f cdi-cr.yaml8.2 安装 virtctl
bash
# 从 Harbor 代理下载或从内网镜像获取
export VERSION=v1.9.0
wget https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/virtctl-${VERSION}-linux-amd64
chmod +x virtctl-${VERSION}-linux-amd64
sudo mv virtctl-${VERSION}-linux-amd64 /usr/local/bin/virtctl
# 验证
virtctl version8.3 创建测试 VM
yaml
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: test-vm
namespace: default
spec:
running: true
template:
metadata:
labels:
app: test-vm
spec:
domain:
cpu:
cores: 2
memory:
guest: 4Gi
devices:
disks:
- name: rootdisk
disk:
bus: virtio
interfaces:
- name: default
masquerade: {}
networks:
- name: default
pod: {}
volumes:
- name: rootdisk
containerDisk:
image: 192.168.122.156:30000/kubevirt-local/containerdisks/ubuntu:22.04
- name: cloudinit
cloudInitNoCloud:
userData: |
#cloud-config
password: xxx
chpasswd: { expire: False }
ssh_pwauth: truebash
kubectl apply -f test-vm.yaml
# 等待 VMI 启动
kubectl get vmi -w
# 通过 virtctl 访问控制台
virtctl console test-vm
# 获取 VMI IP
kubectl get vmi test-vm -o jsonpath='{.status.interfaces[0].ipAddress}'8.4 VM 磁盘导入 (CDI DataVolume)
yaml
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
name: rocky9-base
namespace: default
spec:
source:
http:
url: "http://<file-server>/Rocky-9-GenericCloud.qcow2"
pvc:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 50Gi8.5 确认 Rancher UI 插件需求
当前已实现 Grafana 监控 Dashboard。若需在 Rancher UI 界面中直接管理 VM (如 Harvester/KubeVirt UI Extension),需进一步调研并安装对应的 Rancher UI 插件。
9. 附录:关键资源清单
9.1 KubeVirt Services
| Service | ClusterIP | Port | 说明 |
|---|---|---|---|
| virt-api | 10.23.130.160 | 443 | API Server + Webhook |
| virt-exportproxy | 10.23.25.237 | 443 | 数据导出代理 |
| kubevirt-prometheus-metrics | None (Headless) | 443 | Prometheus 指标端点 |
| kubevirt-operator-webhook | 10.23.98.100 | 443 | Operator Webhook |
| virt-template-api-service | 10.23.19.66 | 443 | 模板 API |
| virt-template-controller-mgr | 10.23.220.125 | 8443 | 模板控制器指标 |
| virt-template-webhook-service | 10.23.213.15 | 443 | 模板 Webhook |
9.2 Prometheus 指标 (kubevirt_* 前缀, 共 32 个)
| 指标名 | 类型 | 说明 |
|---|---|---|
kubevirt_info | Gauge | KubeVirt 版本信息 |
kubevirt_configuration_emulation_enabled | Gauge | 是否启用软件模拟 |
kubevirt_vmi_phase_count | Gauge | VMI 各阶段计数 |
kubevirt_vmi_memory_usable_bytes | Gauge | VMI 可用内存 |
kubevirt_vmi_memory_unused_bytes | Gauge | VMI 未用内存 |
kubevirt_vmi_memory_available_bytes | Gauge | VMI 可用内存总量 |
kubevirt_vmi_cpu_usage_seconds_total | Counter | VMI CPU 使用秒数 |
kubevirt_vmi_network_receive_bytes_total | Counter | 网络接收字节 |
kubevirt_vmi_network_transmit_bytes_total | Counter | 网络发送字节 |
kubevirt_vmi_network_receive_packets_total | Counter | 网络接收包数 |
kubevirt_vmi_network_transmit_packets_total | Counter | 网络发送包数 |
kubevirt_vmi_network_receive_errors_total | Counter | 网络接收错误 |
kubevirt_vmi_network_transmit_errors_total | Counter | 网络发送错误 |
kubevirt_vmi_storage_read_traffic_bytes_total | Counter | 存储读字节 |
kubevirt_vmi_storage_write_traffic_bytes_total | Counter | 存储写字节 |
kubevirt_vmi_migration_data_remaining_bytes | Gauge | 迁移剩余数据 |
kubevirt_vmi_migration_data_processed_bytes | Counter | 迁移已处理数据 |
kubevirt_vmi_migration_dirty_memory_rate_bytes | Gauge | 迁移脏内存速率 |
kubevirt_vmi_migrations_in_pending_phase | Gauge | 等待中迁移数 |
kubevirt_vmi_migrations_in_running_phase | Gauge | 运行中迁移数 |
kubevirt_vmi_migrations_in_scheduling_phase | Gauge | 调度中迁移数 |
kubevirt_vmi_status_condition | Gauge | VMI 状态条件 |
kubevirt_rest_client_requests_total | Counter | REST 请求计数 |
kubevirt_rest_client_request_duration_seconds_bucket | Histogram | REST 请求延迟 |
kubevirt_rest_client_request_duration_seconds_count | Counter | REST 请求延迟计数 |
kubevirt_rest_client_request_duration_seconds_sum | Counter | REST 请求延迟总和 |
kubevirt_rest_client_request_size_bytes_bucket | Histogram | REST 请求大小 |
kubevirt_rest_client_response_size_bytes_bucket | Histogram | REST 响应大小 |
kubevirt_virt_api_ready_status | Gauge | virt-api 就绪状态 |
kubevirt_virt_controller_ready_status | Gauge | virt-controller 就绪状态 |
kubevirt_virt_handler_ready_status | Gauge | virt-handler 就绪状态 |
kubevirt_virt_operator_ready_status | Gauge | virt-operator 就绪状态 |
kubevirt_virt_controller_leading_status | Gauge | Leader 选举状态 |
kubevirt_node_deprecated_machine_types | Gauge | 弃用的机器类型 |
9.3 告警规则
| 告警名 | 严重级别 | 触发条件 |
|---|---|---|
| KubeVirtComponentDown | critical | 无 KubeVirt 组件上报指标 >5m |
| KubeVirtVMIError | warning | 存在 failed 状态的 VMI >1m |
| KubeVirtVMINotReady | warning | 运行中 VMI 无 Ready 条件 >5m |
| KubeVirtHighMemoryUsage | warning | VMI 内存使用 >90% >10m |
| KubeVirtLiveMigrationStalled | warning | 迁移数据不减少 >15m |
9.4 访问地址
| 服务 | 访问方式 |
|---|---|
| Grafana | Rancher UI → 集群 → Monitoring → Grafana |
| Grafana (内网) | http://<grafana-clusterip>:3000 |
| Grafana 密码 | xxx / (从 Secret rancher-monitoring-grafana 获取) |
| Prometheus | Rancher UI → 集群 → Monitoring → Prometheus |
| KubeVirt Dashboard | Grafana → Dashboards → 搜索 tag kubevirt |
| KubeVirt Dashboard | https://ai-ear.cn:36002 (Basic Auth: admin / xxx) |
10. KubeVirt Dashboard 部署 (jimyag/kubevirt-dashboard)
由于 Rancher 官方 Extension 目录中暂无 KubeVirt 专用插件(仅有 Harvester 扩展),我们部署了社区版 jimyag/kubevirt-dashboard 作为 K8s 原生 Dashboard,并通过 Rancher NavLink 和 frpc 隧道集成到外部访问。
双 UI 方案: 同时保留 Python Web UI (systemd +
/opt/kubevirt-web-ui/,ai-ear.cn:36003) 作为轻量级管理界面,支持 NodeIP 网段自定义、qcow2 镜像下载等功能。
10.1 架构
External Browser
│ https://ai-ear.cn:36002 (TLS: ai-ear.cn 证书)
▼
公网机 8.153.84.140
Traefik entryPoint ep36002 — TLS 终结
│ frp 隧道 19030 (frps :7000)
▼
192.168.122.9 (frpc, systemd 用户服务 frpc-kubevirt-dashboard)
│ localIP=192.168.122.31, localPort=8080
▼
192.168.122.31 Auth Proxy (:8080, HTTP Basic Auth)
│ NodePort :51362
▼
kubevirt-dashboard Pod (Go binary :8080, ServiceAccount ──▶ K8s API)网络路径: https://ai-ear.cn:36002 → 公网机 Traefik (ep36002, TLS 终结) → frp 隧道端口 19030 (frps 8.153.84.140:7000) → frpc (192.168.122.9) → 192.168.122.31:8080 (Auth Proxy) → :51362 (NodePort) → Pod :8080
10.2 K8s 资源
| 资源 | 命名空间 | 名称 | 说明 |
|---|---|---|---|
| Namespace | - | kubevirt-dashboard | 独立命名空间 |
| ServiceAccount | kubevirt-dashboard | kubevirt-dashboard | Pod 使用的 SA |
| ClusterRole | - | kubevirt-dashboard | KubeVirt + Core API 权限 |
| ClusterRoleBinding | - | kubevirt-dashboard | SA → ClusterRole 绑定 |
| Deployment | kubevirt-dashboard | kubevirt-dashboard | 1 副本, Go binary |
| Service | kubevirt-dashboard | kubevirt-dashboard | NodePort 51362 → Pod :8080 |
10.3 镜像
| 项目 | 值 |
|---|---|
| 上游镜像 | ghcr.io/jimyag/kubevirt-dashboard:latest |
| Harbor 镜像 | 192.168.122.156:30000/kubevirt-local/kubevirt-dashboard:latest |
| 拉取策略 | IfNotPresent |
| Harbor 项目 | kubevirt-local (非代理缓存,手动推送) |
注意: 直接拉取
ghcr.io镜像在国内网络环境下经常超时。解决方案: 通过南京大学镜像 (ghcr.nju.edu.cn) 下载 tarball,再用skopeo推送到内部 Harbor。
bash
# 推送镜像到 Harbor 的命令 (参考)
skopeo copy --dest-tls-verify=false \
--dest-creds admin:xxx \
docker-archive:/tmp/kubevirt-dashboard.tar \
docker://192.168.122.156:30000/kubevirt-local/kubevirt-dashboard:latest10.4 RBAC
bash
kubectl get sa kubevirt-dashboard -n kubevirt-dashboard
kubectl get clusterrole kubevirt-dashboard
kubectl get clusterrolebinding kubevirt-dashboard权限范围 (ClusterRole):
kubevirt.io- VM/VMI 的 CRUD 操作subresources.kubevirt.io- VM 子资源操作 (start/stop/restart/console)cdi.kubevirt.io- DataVolume 管理instancetype.kubevirt.io- 实例类型查询snapshot.kubevirt.io- 快照管理migrations.kubevirt.io- 迁移管理- Core API - Namespace/Node/Pod/Event/ConfigMap/Secret 读写
storage.k8s.io- StorageClass 查询
10.5 功能清单
| 功能 | 说明 | 状态 |
|---|---|---|
| VM 列表 | 多命名空间查看, 搜索过滤 | ✅ |
| VM 详情 | CPU/内存/IP/节点/事件 | ✅ |
| 创建 VM | YAML 编辑器方式 | ✅ |
| 启动/停止/重启 | VM 生命周期管理 | ✅ |
| 删除 VM | 确认操作 | ✅ |
| 控制台 | VNC/Serial 控制台 | ✅ |
| 多集群 | 支持多 kubeconfig context | ✅ (当前单集群) |
10.6 Rancher NavLink 集成
yaml
apiVersion: ui.cattle.io/v1
kind: NavLink
metadata:
name: kubevirt-dashboard
spec:
label: KubeVirt VMs
description: KubeVirt Virtual Machine Dashboard
group: Virtualization
target: _blank
toURL: http://ai-ear.cn:36002效果: Rancher UI 左侧导航栏出现 Virtualization → KubeVirt VMs 链接,点击在新标签页打开 Dashboard (外网地址)。
10.7 管理命令
bash
# 查看 Pod 状态
kubectl get pods -n kubevirt-dashboard -o wide
# 查看日志
kubectl logs -n kubevirt-dashboard -l app=kubevirt-dashboard -f
# 重启 (滚动更新)
kubectl rollout restart deployment/kubevirt-dashboard -n kubevirt-dashboard
# 查看 Service
kubectl get svc kubevirt-dashboard -n kubevirt-dashboard
# 查看 Deployment YAML
kubectl get deployment kubevirt-dashboard -n kubevirt-dashboard -o yaml10.8 登录认证 (HTTP Basic Auth Proxy)
kubevirt-dashboard 无内置登录认证,直接使用 Pod ServiceAccount 的 K8s API 权限。我们在节点上部署了一个轻量级 Python 认证代理 (HTTP Basic Auth),拦截所有公网和内网请求。
认证参数
| 参数 | 值 |
|---|---|
| 用户名 | admin |
| 密码 | xxx |
| 认证方式 | HTTP Basic Auth (RFC 7617) |
| Realm | KubeVirt Dashboard |
架构
Browser
│
├── https://ai-ear.cn:36002/ ──────────────────────────┐
│ ▼
│ ┌────────────────────────┐
│ │ Auth Proxy (:8080) │
│ │ Basic Auth Check │
│ │ 401 if no/wrong auth │
│ │ proxy to :51362 │
│ └──────────┬─────────────┘
│ │
│ ┌──────────▼─────────────┐
│ │ K8s NodePort :51362 │
│ │ → kubevirt-dashboard │
│ │ Pod :8080 │
│ └────────────────────────┘部署文件
| 文件 | 路径 | 主机 | 说明 |
|---|---|---|---|
| Auth Proxy 脚本 | /opt/kubevirt-web-ui/auth_proxy.py | 192.168.122.31 | Python HTTP Basic Auth 反向代理 |
| Systemd 服务 | kubevirt-dashboard-auth-proxy.service | 192.168.122.31 | 自动启动, 监听 8080 |
管理命令
bash
# 在 192.168.122.31 上
systemctl status kubevirt-dashboard-auth-proxy
journalctl -u kubevirt-dashboard-auth-proxy -f
# 修改凭据
systemctl edit kubevirt-dashboard-auth-proxy
# [Service]
# Environment="AUTH_USER=newuser"
# Environment="AUTH_PASS=newpass"
systemctl restart kubevirt-dashboard-auth-proxy
# 测试
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/ # → 401
curl -s -o /dev/null -w "%{http_code}" -u admin:xxx http://127.0.0.1:8080/ # → 20010.9 外网访问 (https://ai-ear.cn:36002)
公网走 公网机 Traefik TLS 终结 + frp 隧道 (沿用 GitLab/Jenkins 惯例),内网通过 iptables DNAT (192.168.122.22)。两条路径均指向 Auth Proxy (:8080)。
完整网络路径
公网: https://ai-ear.cn:36002 → 公网机 8.153.84.140 Traefik (ep36002, TLS 终结, 证书 ai-ear.cn)
→ 127.0.0.1:19030 (frps 隧道端口) → frpc (192.168.122.9) → 192.168.122.31:8080 (Auth Proxy) → :51362
内网: 192.168.122.22:36002 → iptables DNAT → 192.168.122.31:8080 (Auth Proxy) → :51362 (HTTP)2026-09-03 由「frp TCP 直出 36002 (HTTP)」改为 Traefik TLS 惯例:frpc remotePort 由 36002 改为 19030, 公网端口 36002 让给 Traefik 监听。TLS 在公网机终结,证书
/etc/pki/nginx/ai-ear.cn.crt(Traefik default TLS store),隧道与后端保持明文 HTTP。
frpc 配置 (192.168.122.9)
toml
# /home/xxx/config/frp/frpc-kubevirt-dashboard.toml
serverAddr = "8.153.84.140"
serverPort = 7000
transport.tcpMux = true
auth.method = "token"
auth.token = "xxx"
# 公网 HTTPS 入口 36002 → 公网机 Traefik → 本隧道端口 19030
[[proxies]]
name = "kubevirt-dashboard-36002"
type = "tcp"
localIP = "192.168.122.31"
localPort = 8080
remotePort = 19030bash
# systemd 用户服务 (xxx 用户)
systemctl --user restart frpc-kubevirt-dashboard
systemctl --user status frpc-kubevirt-dashboard公网机 Traefik 配置 (8.153.84.140)
静态配置 /etc/traefik/traefik.yaml entryPoint (新增需重启 traefik,秒级中断):
yaml
entryPoints:
ep36002:
address: ":36002"动态配置 /etc/traefik/dynamic.yaml router + service (watch 热加载):
yaml
http:
routers:
# KubeVirt Dashboard(K8s 集群,经 frp 隧道 19030,2026-09-02)
svc-36002:
entryPoints: [ep36002]
rule: "Host(`ai-ear.cn`)"
service: svc-19030
tls: {}
services:
svc-19030:
loadBalancer:
servers:
- url: "http://127.0.0.1:19030"⚠️ Traefik v3 字段陷阱: HTTP 服务 servers 用
url: "http://host:port",TCP 服务 (passthrough 等) servers 用address: "host:port"。两者混用会报field not found, node: url/address,且整个 file provider 配置加载失败 (所有路由失效)。 svc-19030 必须放在http.services段,勿追加到文件末尾的tcp.services段。
bash
systemctl restart traefik
ss -tlnp | grep -E ':36002|:19030' # 期望: traefik 持 36002, frps 持 19030验证
bash
curl -sk -o /dev/null -w "%{http_code}\n" https://ai-ear.cn:36002/ # → 401 (未带 Basic Auth)
curl -sk -u 'admin:xxx' -o /dev/null -w "%{http_code}\n" https://ai-ear.cn:36002/ # → 200
echo | openssl s_client -connect ai-ear.cn:36002 -servername ai-ear.cn 2>/dev/null \
| openssl x509 -noout -subject -dates # subject=CN = ai-ear.cniptables DNAT (192.168.122.22)
bash
iptables -t nat -A PREROUTING -p tcp --dport 36002 \
-j DNAT --to-destination 192.168.122.31:8080
iptables -t nat -A POSTROUTING -p tcp -d 192.168.122.31 --dport 8080 \
-j MASQUERADE
iptables -t nat -A OUTPUT -p tcp --dport 36002 \
-j DNAT --to-destination 192.168.122.31:8080持久化通过 kubevirt-web-ui-forward.service (systemd oneshot) 实现。
10.10 Python Web UI (ai-ear.cn:36003)
Python Web UI 与 kubevirt-dashboard 并行运行,监听在不同端口:
| UI | 公网地址 | 认证方式 | 后端 |
|---|---|---|---|
| kubevirt-dashboard (Go) | https://ai-ear.cn:36002 | HTTP Basic Auth | K8s Pod + Auth Proxy :8080 (公网经 Traefik TLS) |
| Python Web UI | http://ai-ear.cn:36003 | Session Login | systemd :8082 |
功能特性
| 功能 | 说明 |
|---|---|
| VM 列表/详情 | 多命名空间查看, 搜索过滤, 实时状态 |
| 创建 VM | 表单方式, 支持 containerDisk 和 qcow2 两种镜像类型 |
| NodeIP 网段配置 | 支持 Bridge (Multus) 网络模式, 可自定义 VM IP/子网/网关/DNS |
| qcow2 镜像下载 | 通过 init container 下载 qcow2 到 PVC, 支持自定义 URL |
| 启动/停止/重启/删除 | VM 生命周期管理 |
| 事件查看 | VM 关联的 K8s Events |
部署文件
| 文件 | 路径/名称 | 说明 |
|---|---|---|
| Python 后端 | /opt/kubevirt-web-ui/server.py | HTTP 服务器, 配置 API, 代理 K8s API |
| HTML 前端 | /opt/kubevirt-web-ui/index.html | SPA, 支持网络/镜像自定义 |
| Systemd 服务 | kubevirt-web-ui.service | 监听 :8082 |
| KubeConfig | /root/.kube/config | K8s API 认证 |
认证
| 参数 | 值 |
|---|---|
| 用户名 | root |
| 密码 | xxx |
| 方式 | Session Cookie (24h 有效) |
可配置参数 (systemd Environment)
所有参数均通过 systemd Environment 配置,修改后 systemctl daemon-reload && systemctl restart kubevirt-web-ui:
| 变量 | 默认值 | 说明 |
|---|---|---|
AUTH_USER | root | 登录用户名 |
AUTH_PASS | xxx | 登录密码 |
PORT | 8082 | 监听端口 |
NODE_IP_SUBNET | 192.168.122.0/24 | NodeIP 网段 (Bridge 模式默认子网) |
NODE_GATEWAY | 192.168.122.1 | Bridge 模式默认网关 |
NODE_DNS | 8.8.8.8,223.5.5.5 | Bridge 模式默认 DNS |
NODE_NET_MODE | masquerade | 默认网络模式 (masquerade 或 bridge) |
DEFAULT_STORAGE_CLASS | nfs | qcow2 PVC 默认 StorageClass |
DEFAULT_DISK_SIZE | 20Gi | qcow2 PVC 默认磁盘大小 |
QCOW2_IMAGES | JSON 数组 | qcow2 镜像列表 (name + url) |
CONTAINER_IMAGES | JSON 数组 | containerDisk 镜像列表 (name + image) |
配置 API
前端通过 GET /api/config 获取默认配置,无需认证:
json
{
"network": {
"subnet": "192.168.122.0/24",
"gateway": "192.168.122.1",
"dns": "8.8.8.8,223.5.5.5",
"defaultMode": "masquerade"
},
"images": {
"qcow2": [
{"name": "Rocky 9.4 Minimal", "url": "http://192.168.122.31:9001/repo/images/Rocky-9.4-x86_64-minimal.qcow2"},
{"name": "Ubuntu 22.04 Cloud", "url": "http://192.168.122.31:9001/repo/images/ubuntu-22.04-server-cloudimg-amd64.qcow2"},
{"name": "CentOS Stream 9", "url": "http://192.168.122.31:9001/repo/images/CentOS-Stream-GenericCloud-9-latest.x86_64.qcow2"}
],
"containerDisks": [...]
},
"storage": {"defaultClass": "nfs", "defaultDiskSize": "20Gi"}
}创建 VM 网络模式说明
Pod Network (Masquerade): VM 获得 Pod 内部 IP (10.22.x.x),通过 NAT 访问外部网络。适用于无需独立 IP 的场景。
Bridge Network (Multus): VM 获得 NodeIP 网段 IP (如 192.168.122.x),与物理网络直接通信。需要:
- 在目标节点创建
NetworkAttachmentDefinition(bridge CNI) - cloud-init 中自动注入静态 IP/网关/DNS 网络配置
管理命令
bash
# 在 192.168.122.31 上
systemctl status kubevirt-web-ui
journalctl -u kubevirt-web-ui -f
systemctl restart kubevirt-web-ui
# 修改配置 (示例: 更改 NodeIP 网段)
systemctl edit kubevirt-web-ui
# [Service]
# Environment="NODE_IP_SUBNET=10.0.0.0/24"
# Environment="NODE_GATEWAY=10.0.0.1"
systemctl daemon-reload && systemctl restart kubevirt-web-ui
# 查看当前配置
curl -s http://127.0.0.1:8082/api/config | python3 -m json.toolfrpc 配置 (192.168.122.9)
toml
# /home/xxx/config/frp/frpc-kubevirt-webui.toml
serverAddr = "8.153.84.140"
serverPort = 7000
transport.tcpMux = true
auth.method = "token"
auth.token = "xxx"
[[proxies]]
name = "kubevirt-webui-36003"
type = "tcp"
localIP = "192.168.122.31"
localPort = 8082
remotePort = 36003iptables DNAT (192.168.122.22)
bash
iptables -t nat -A PREROUTING -p tcp --dport 36003 \
-j DNAT --to-destination 192.168.122.31:8082
iptables -t nat -A POSTROUTING -p tcp -d 192.168.122.31 --dport 8082 \
-j MASQUERADE
iptables -t nat -A OUTPUT -p tcp --dport 36003 \
-j DNAT --to-destination 192.168.122.31:808210.11 Rocky 9 VM 部署 (hostPath PV 方式)
由于 CDI 尚未完整部署,我们使用 hostPath PV 方式直接挂载 qcow2 磁盘镜像。
10.11.1 准备磁盘镜像
bash
# 从内网 HTTP 服务器下载 qcow2 镜像
curl -Lo /tmp/Rocky-9-GenericCloud.qcow2 \
http://192.168.122.9:9001/repo/osiso/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2
# 安装 qemu-img (如果没有)
dnf install -y qemu-img
# 转换为 raw 格式
qemu-img convert -f qcow2 -O raw /tmp/Rocky-9-GenericCloud.qcow2 /tmp/Rocky-9-GenericCloud.raw
# 创建目录并移动 (KubeVirt 要求 PV 指向包含 disk.img 的目录)
mkdir -p /var/lib/vm-images/rocky9-disk
mv /tmp/Rocky-9-GenericCloud.raw /var/lib/vm-images/rocky9-disk/disk.img
# 设置权限 (QEMU 以 UID 107 运行)
chmod 666 /var/lib/vm-images/rocky9-disk/disk.img10.11.2 创建 PV/PVC
bash
kubectl apply -f - <<'EOF'
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: rocky9-vm-disk
labels:
type: rocky9-disk
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/var/lib/vm-images/rocky9-disk"
type: Directory
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- sza122031.local
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: rocky9-vm-disk
namespace: default
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
selector:
matchLabels:
type: rocky9-disk
EOF10.11.3 创建 VM
yaml
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: rocky9-test
namespace: default
labels:
app: rocky9-test
kubevirt.io/vm: rocky9-test
spec:
running: true
template:
metadata:
labels:
app: rocky9-test
kubevirt.io/vm: rocky9-test
spec:
nodeSelector:
kubernetes.io/hostname: sza122031.local
evictionStrategy: None # ← 必须设为 None (hostPath 不支持 LiveMigrate)
domain:
cpu:
cores: 2
memory:
guest: 2Gi
devices:
disks:
- name: rootdisk
disk:
bus: virtio
- name: cloudinit
disk:
bus: virtio
interfaces:
- name: default
masquerade: {}
networks:
- name: default
pod: {}
volumes:
- name: rootdisk
persistentVolumeClaim:
claimName: rocky9-vm-disk
- name: cloudinit
cloudInitNoCloud:
userData: |
#cloud-config
hostname: rocky9-test
password: xxx
chpasswd:
expire: false
ssh_pwauth: true
disable_root: false10.11.4 验证
bash
# 检查 VM 状态
kubectl get vm,vmi -n default
# NAME STATUS READY
# rocky9-test Running True
# 获取 VMI IP
kubectl get vmi rocky9-test -n default -o jsonpath='{.status.interfaces[0].ipAddress}'
# 10.22.183.219
# SSH 到 VM
sshpass -p rocky ssh -o StrictHostKeyChecking=no rocky@<VMI_IP>
# rocky9-test$ hostname
# rocky9-test
# 停止 VM
kubectl patch vm rocky9-test -n default --type merge -p '{"spec":{"running":false}}'
# 启动 VM
kubectl patch vm rocky9-test -n default --type merge -p '{"spec":{"running":true}}'已知限制:
- VM 只能在指定节点 (sza122031.local) 上运行 (hostPath 限制)
- 不支持热迁移 (evictionStrategy 必须设为 None)
- 磁盘是共享的,多个 VM 实例会写入同一文件
- virt-handler Pod 重启后需重新手动复制证书 (见故障 5.5)
10.12 containerDisk 镜像 (已解决 ✅)
已将所有 containerDisk 镜像推送到 Harbor 私有仓库 kubevirt-local 项目:
| 镜像 | 私有仓库地址 |
|---|---|
| Ubuntu 22.04 | 192.168.122.156:30000/kubevirt-local/containerdisks/ubuntu:22.04 |
| Ubuntu 24.04 | 192.168.122.156:30000/kubevirt-local/containerdisks/ubuntu:24.04 |
| Fedora Latest | 192.168.122.156:30000/kubevirt-local/containerdisks/fedora:latest |
| CentOS Stream 9 | 192.168.122.156:30000/kubevirt-local/containerdisks/centos-stream:9 |
推送命令参考:
bash
skopeo copy \
--src-tls-verify=false --dest-tls-verify=false \
--dest-creds admin:PASSWORD \
--override-os linux --override-arch amd64 \
--image-parallel-copies 2 \
docker://quay.io/containerdisks/ubuntu:22.04 \
docker://192.168.122.156:30000/kubevirt-local/containerdisks/ubuntu:22.04Web UI 已更新为使用私有仓库地址,创建 VM 时将自动从内网拉取镜像。
变更记录
| 日期 | 操作 | 说明 |
|---|---|---|
| 2026-08-27 | 环境评估 | 确认 RKE2 v1.35.6, 6 节点, Rocky Linux 9.8 |
| 2026-08-27 | Harbor 代理缓存 | 创建 kubevirt 项目代理 quay.io |
| 2026-08-27 | 部署 22 个 CRD | KubeVirt 所有自定义资源定义 |
| 2026-08-27 | 部署 RBAC | ClusterRole + ClusterRoleBinding |
| 2026-08-27 | 修复 RBAC 权限 | Patch 添加通配符权限 |
| 2026-08-27 | 部署 virt-operator | 2 副本, 从 Harbor 拉取镜像 |
| 2026-08-27 | 创建 KubeVirt CR | imageRegistry 指向 Harbor |
| 2026-08-27 | 恢复 Calico CRD/IPPool | 修复误删导致的网络中断 |
| 2026-08-27 | 修复 Rancher Webhook | 移除对 namespace 操作的拦截 |
| 2026-08-27 | 修复 Calico VXLAN | 从 Never → Always, 解决跨节点 502 |
| 2026-08-27 | KubeVirt Deployed ✅ | 16 pods 全部 Running |
| 2026-08-27 | 部署 ServiceMonitor | 14 个 scrape targets, 全部 UP |
| 2026-08-27 | 部署 PrometheusRules | 5 条告警规则 |
| 2026-08-27 | 部署 Grafana Dashboards | 4 个仪表盘 (Overview/VM/Infra/Migration) |
| 2026-08-27 | 调研 Rancher Extension | 确认官方目录无 KubeVirt 插件 (仅 Harvester) |
| 2026-08-27 | 部署 KubeVirt Web UI | Python 后端 + SPA 前端, systemd 服务 |
| 2026-08-27 | 创建 Rancher NavLink | Virtualization → KubeVirt VMs 侧边栏集成 |
| 2026-08-27 | 验证 Web UI API | 创建/启动/停止/删除 VM 全流程通过 |
| 2026-08-27 | 下载 Rocky 9 qcow2 | 从 192.168.122.9:9001 下载并转换为 raw (10GB) |
| 2026-08-27 | 排查 virt-handler 证书问题 | 手动复制 clientcertificates 到 Pod (临时方案) |
| 2026-08-27 | 排查 CDI CRD 干扰 | 删除空壳 CDI CRD,解除 virt-controller 阻塞 |
| 2026-08-27 | 修复磁盘权限 | chmod 666 + PV 指向 Directory (非 File) |
| 2026-08-27 | Rocky 9 VM 启动成功 ✅ | VM Running, SSH 可达, Rocky Linux 9.8 |
| 2026-08-27 | Web UI 登录认证 | session-based 认证 (admin), 登录页 + 401 拦截 + Logout |
| 2026-08-27 | 外网端口转发 | iptables DNAT: ai-ear.cn:36002 → 192.168.122.31:8080 |
| 2026-08-27 | NavLink 更新 | Rancher 侧边栏链接指向 ai-ear.cn:36002 |
| 2026-08-27 | 替换 Web UI 为 kubevirt-dashboard | jimyag/kubevirt-dashboard (Go), Harbor 镜像部署 |
| 2026-08-27 | 停用旧 Python Web UI | systemctl stop/disable kubevirt-web-ui |
| 2026-08-27 | 部署 Auth Proxy | Python Basic Auth 代理 :8080 → NodePort :51362 |
| 2026-08-27 | 配置 frpc 隧道 | 192.168.122.9 → ai-ear.cn:36002 → 192.168.122.31:8080 |
| 2026-08-27 | 恢复 Python Web UI | 重新启用自建 Web UI, 支持 Bridge 网络 + qcow2 PVC |
| 2026-08-27 | 更新默认凭据 | root/ai-ear.cn, systemd 环境变量驱动 |
| 2026-08-27 | 修复 JS 语法错误 | renderVMs 使用 ' 替代 ' 转义, 解决 Unexpected identifier 报错 |
| 2026-08-27 | containerDisk 镜像推送 ✅ | 4 个镜像推送至 Harbor kubevirt-local 项目 (skopeo) |
| 2026-09-03 | 公网访问改 HTTPS | ai-ear.cn:36002 由 frp TCP 直出改为公网机 Traefik TLS 终结 (ep36002 + frp 隧道 19030),沿用 GitLab/Jenkins 惯例;frpc remotePort 36002→19030;NavLink toURL 改 https;修正文档中 Basic Auth 密码笔误 (xxx) |