kubectl 进入 virt-launcher pod 之后如何进入虚拟机内部
结论
kubectl exec 进入 virt-launcher pod ≠ 进入虚拟机内部。 virt-launcher pod 只是承载 QEMU 进程的容器,guest 系统运行在 QEMU 进程内部,既不是容器进程,也不在容器的命名空间里,无法用 nsenter/chroot 进入。
进入虚拟机内部的几种方式:
| 方式 | 命令 | 说明 |
|---|---|---|
| 串行控制台 | virtctl console <vm> |
文本终端,Linux 排障最常用,官方推荐 |
| SSH | virtctl ssh <user>@<vm> |
需在创建 VM 时注入 SSH 公钥 |
| 图形桌面 | virtctl vnc <vm> |
需本机装 virt-viewer / VNC 客户端 |
| Pod 内应急 | pod 内执行 sock-connector 连接串口 socket |
等价于 virtctl console,仅 virtctl 不可用时使用 |
核心原理:virtctl console 的底层实现,就是 virt-handler 通过 Kubernetes API 在 compute 容器内执行一个名为 sock-connector 的辅助程序,让它连接 QEMU 暴露的串口 unix socket(/var/run/kubevirt-private/<vmi-dir>/virt-serial0)。所以只要在 pod 里找到这个 socket,手动执行同样的连接,就能进入虚拟机内部。
分析版本
- 软件:KubeVirt(virtctl / virt-api / virt-handler / virt-launcher)
- 版本:v1.x(以 v1.3+ 及同源的 OpenShift Virtualization 4.x 为准;串口 socket 机制与
sock-connector自早期版本沿用至今) - 关键路径:
/var/run/kubevirt-private/<vmi-dir>/virt-serial0(串口)、virt-vnc(图形)、virtqemusock(QMP) - 关键二进制:
/usr/share/kubevirt/virt-launcher/sock-connector(compute 容器内自带)
详细分析
1. 为什么 kubectl exec 进 pod 不等于进入虚拟机
virt-launcher pod 内通常有多个容器,核心是 compute 容器,里面运行:
virt-launcher进程:管理 libvirtd 和 QEMU 的生命周期,对外提供 gRPC 服务供 virt-handler 调用;libvirtd:根据 VMI 定义生成 domain XML;qemu-system-*:真正的虚拟机进程,guest 操作系统运行在 QEMU 内部。
guest 内的进程对容器来说不可见,guest 的文件系统、内核都不在容器的挂载/进程命名空间中,因此 kubectl exec -it <pod> -c compute -- bash 只能拿到 QEMU 所在容器的 shell,拿不到虚拟机内部。
2. virtctl console 的底层原理(调用链)
virtctl console 表面是一条命令,实际是一条完整链路:
virtctl(客户端)
→ virt-api(控制面,WebSocket 代理)
→ virt-handler(节点 DaemonSet)
→ 经 Kubernetes API exec 进入 virt-launcher pod 的 compute 容器,执行 sock-connector
→ sock-connector 连接 unix socket virt-serial0
→ QEMU 串口 → guest 的 ttyS0
也就是说,virtctl 本身并没有"直连虚拟机"的魔法,它在 pod 里干的事和我们手动做的一模一样:连 pod 内的串口 socket。知道这一点后,在 pod 内手动进入 guest 就顺理成章了。
3. 方法一:在 pod 内直连串口 socket(等价 virtctl console)
# 1. 进入 compute 容器(注意指定 -c compute)
kubectl exec -it virt-launcher-<vm>-xxxxx -c compute -- bash
# 2. 查看 VMI 的 socket 目录(目录名可能是 VMI UID 或 namespace-name,以实际为准)
ls -la /var/run/kubevirt-private/*/
# 3. 连接串口 socket,直接进入 guest 控制台
/usr/share/kubevirt/virt-launcher/sock-connector \
/var/run/kubevirt-private/<vmi-dir>/virt-serial0
连接成功后会直接看到 guest 的 login: 提示,输入账号密码即可进入虚拟机内部。
- 断开方式:在 guest 里
logout/exit,或另开一个终端杀掉 sock-connector 进程。注意Ctrl+]是 virtctl 客户端本地处理的转义键,在 pod 里裸连 socket 时不受支持。 - 容器里没有 sock-connector 怎么办:可改用 socat(如果容器内有):
socat - UNIX-CONNECT:/var/run/kubevirt-private/<vmi-dir>/virt-serial0 - 两个工具都没有:可在 pod 外执行同一条转发命令(效果等同免装 virtctl):
kubectl exec -i <pod> -c compute -- \ /usr/share/kubevirt/virt-launcher/sock-connector \ /var/run/kubevirt-private/<vmi-dir>/virt-serial0
4. 方法二:virsh + QEMU monitor 控制 guest(排障用)
compute 容器自带 virsh(libvirt 客户端),域名为 default_<vm-name>,可以对 guest 发按键、查状态、看事件,适合排障:
# 确认域名
virsh list
# 模拟按键(如向 guest 发送 Ctrl+Alt+Del)
virsh qemu-monitor-command default_<vm-name> --hmp "sendkey ctrl-alt-del"
# 查询运行状态
virsh qemu-monitor-command default_<vm-name> query-status --pretty
# 持续观察 QEMU 事件(另开终端配合 virtctl pause/unpause 观察)
virsh qemu-monitor-event --pretty --loop
注意:virsh 的 console 命令在 KubeVirt 里通常不可用,因为 KubeVirt 把串口配置成了 unix socket(而非 libvirt 分配的 pty),virsh console 会失败或连错对象;串口请走方法一。
5. 方法三:转发 VNC socket 获取图形界面
pod 内同样暴露了 VNC socket(<vmi-dir>/virt-vnc)。用 socat 把它转发到本机,再用 VNC 客户端连接即可看到图形桌面:
# 在能访问集群的机器上执行,把 pod 内的 VNC socket 转发到本机 5901
kubectl exec -i <pod> -c compute -- \
socat TCP-LISTEN:5901,reuseaddr,fork \
UNIX-CONNECT:/var/run/kubevirt-private/<vmi-dir>/virt-vnc
然后本机用 VNC 客户端连接 127.0.0.1:5901(需确保网络可达,或结合端口转发)。
6. 方法四:正规做法——退出 pod 用 virtctl
除非 virtctl 完全不可用(离线、网络隔离、只允许在节点上操作)的极端场景,否则正规做法还是退出 pod,直接用 virtctl:
virtctl console <vm> # 文本控制台,Ctrl+] 退出
virtctl ssh <user>@<vm> # SSH 登录(需预置公钥)
virtctl vnc <vm> # 图形桌面(需 virt-viewer)
pod 内连 socket 本质是"曲线救国",只适合应急排障。
7. 常见问题
7.1 virtctl console 是 VNC 登录吗?
不是。两者完全不同:
| 命令 | 类型 | 看到什么 | 底层通道 |
|---|---|---|---|
virtctl console <vm> |
串行控制台 | 纯文本终端(login: 命令行),无图形界面 | pod 内的 virt-serial0 socket |
virtctl vnc <vm> |
图形控制台 | 虚拟机桌面(GUI),类似远程桌面 | pod 内的 virt-vnc socket |
virtctl ssh <vm> |
SSH 登录 | 命令行(网络 SSH 协议) | 走虚拟机网络 |
virtctl console 相当于把一根"串口线"接到虚拟机上(ttyS0),Linux 虚拟机排障最常用,Windows 虚拟机基本无法使用(没有串口登录)。virtctl vnc 才相当于"看显示器",是图形界面,Windows/Linux 桌面都能用。
7.2 socket 目录名是 VMI UID 还是 name?
新旧版本有差异:早期版本 /var/run/kubevirt-private/<vmi-UID>/,较新版本可能为 <namespace>-<name> 或 <namespace>/<name>。不要背路径,先 ls /var/run/kubevirt-private/*/ 看实际目录,再找其中的 virt-serial0。
7.3 容器里没有 sock-connector / socat 怎么办?
- 先确认是否在 compute 容器内(
kubectl exec ... -c compute); - 用
ls /usr/share/kubevirt/virt-launcher/确认 sock-connector 是否存在; - 都没有时,可用
kubectl debug起一个带 socat 的临时容器共享 pod 环境,或在 pod 外经kubectl exec转发(见方法一)。
7.4 进入后提示 login 但不知道密码?
KubeVirt 不保存虚拟机内部密码。创建 VM 时需要通过 cloud-init 的 userData 注入账号密码,或通过 accessCredentials(sshPublicKey)注入 SSH 公钥;没有注入过凭据,控制台会停在 login: 让你输入一个不存在的账号。
参考链接
- KubeVirt 官方文档:Accessing Virtual Machines — https://kubevirt.io/user-guide/user_workloads/accessing_virtual_machines/
- KubeVirt 官方文档:Execute virsh commands in virt-launcher pod — https://kubevirt.io/user-guide/debug_virt_stack/virsh-commands/
- KubeVirt 官方文档:Privileged debugging on the node — https://kubevirt.io/user-guide/debug_virt_stack/privileged-node-debugging/
- Red Hat OpenShift Virtualization:Managing VMs — https://docs.redhat.com/en/documentation/openshift_container_platform/4.21/html/virtualization/managing-vms
- sock-connector 经 kubectl exec 直连串口 socket 的实践 — https://blog.csdn.net/signmem/article/details/100780114
浙公网安备 33010602011771号