KVM虚拟机和Cockpit自动化失效踩坑记

注意,这是一篇由AI生成的问题分析报告。

背景

在一台Fedora 44的KVM宿主机(192.168.1.64, 使用cockpit管理)上创建了一个Fedora 44 Cloud版本的虚拟机master-0001。创建完成后发现:

  1. 不知道虚拟机的root密码,无法登录。
  2. 多次在Cockpit创建界面的"自动化"区域里指定root密码和SSH公钥,全部不生效

虚拟机的IP为192.168.122.208(libvirt默认NAT网段192.168.122.0/24)。

排查过程

第一步:通过 guest agent 应急登录

virsh dumpxml master-0001 里发现virtio-serial通道的QEMU guest agent是state='connected',说明VM内agent正常运行,可以通过它直接在虚拟机内执行命令并重置密码

验证agent在线:

[root@x-cluster ~]# virsh qemu-agent-command master-0001 '{"execute":"guest-ping"}'
{"return":{}}

guest-exec 执行任意命令(注意:agent运行在受限SELinux域中,读/etc/shadow、写/root/.ssh会被拒绝):

[root@x-cluster ~]# virsh qemu-agent-command master-0001 '{"execute":"guest-exec",
  "arguments":{"path":"/bin/sh","arg":["-c","cat /etc/os-release"],"capture-output":true}}'
{"return":{"pid":1083}}

# 稍等后用 guest-exec-status 取回输出
NAME="Fedora Linux"
VERSION="44 (Cloud Edition)"
RELEASE_TYPE=stable

重置root密码(密码参数需要base64编码):

[root@x-cluster ~]# PW=$(echo -n 'vmpass1234' | base64)
[root@x-cluster ~]# virsh qemu-agent-command master-0001 '{"execute":"guest-set-user-password",
  "arguments":{"username":"root","password":"'"$PW"'","crypted":false}}'
{"return":{}}

第二步:控制台自动登录注入SSH公钥

只设密码还不够:Fedora默认 PermitRootLogin prohibit-password,root不能通过SSH密码登录,密钥也不能直接写入(SELinux限制agent写/root/.ssh)。因此通过控制台登录获得完整root shell,再注入公钥:

  1. 用QEMU monitor截屏控制台画面:
[root@x-cluster ~]# virsh qemu-monitor-command master-0001 '{"execute":"human-monitor-command",
  "arguments":{"command-line":"screendump /tmp/screen.ppm"}}'
  1. 把PPM拿到本机转PNG后用tesseract OCR,确认是tty1文本登录界面:
Fedora Linux 44 (Cloud Edition)
Kernel 6.19.10-300.fc44.x86_64 on x86_64 (tty1)

ens3: 192.168.122.208 ...

fedora login: _
  1. virsh send-key 逐个按键输入(要放慢速度、一次一个key,否则会丢键),登录后得到 [root@fedora ~]#
[root@x-cluster ~]# virsh send-key master-0001 KEY_R; sleep 0.5
[root@x-cluster ~]# virsh send-key master-0001 KEY_O; sleep 0.5
... 
[root@x-cluster ~]# virsh send-key master-0001 KEY_ENTER
  1. 宿主机生成密钥对,用guest agent把公钥写到VM的/tmp/pubkey,再在控制台shell里安装:
[root@x-cluster ~]# ssh-keygen -t ed25519 -f /root/.ssh/vm_key -N '' -C 'vm-master-0001'
# 在控制台shell中执行(通过send-key单字符键入)
[root@fedora ~]# cat /tmp/pubkey > /root/.ssh/authorized_keys
[root@fedora ~]# echo 'PermitRootLogin yes' > /etc/ssh/sshd_config.d/99-root.conf
[root@fedora ~]# systemctl restart sshd

至此三种登录方式全部可用:

方式 命令
SSH密钥 ssh -i /root/.ssh/vm_key [email protected]
SSH密码 sshpass -p 'vmpass1234' ssh [email protected]
控制台 vmpass1234 登录

第三步:定位Cockpit自动化失效的根因

静态线索

  • virsh dumpxml master-0001没有任何cloud-init种子盘,只有一个vda系统盘;metadata标记为install_source_type=cloud
  • VM内 cloud-init status 显示 disabled/var/lib/cloud/data 不存在,无任何运行日志;连镜像默认的fedora用户(uid 1000)都没有创建 → 种子从未被注入
  • Cockpit日志出现了一批报错:
内部错误:子进程报告(status=125):无法在 trusted.libvirt.security.ref_dac 上
获取 XATTR /var/lib/libvirt/boot/virtinst-hso34tm0-cloudinit.iso: 没有那个文件或目录
Unable to remove disk metadata on vm master-0001
  from /var/lib/libvirt/boot/virtinst-hso34tm0-cloudinit.iso (disk target sda)

挖出Cockpit的调用方式

cockpit-machines的后端嵌入在/usr/share/cockpit/machines/index.js.gz里(内嵌一段python),cloud源+启动时实际执行的是:

virt-install --cloud-init user-data=/tmp/cockpit-machines-XXXX-user-data ...

种子ISO由virt-install生成在/var/lib/libvirt/boot/virtinst-<随机>-cloudinit.iso

用inotify实测复现真相

[root@x-cluster ~]# inotifywait -m -e create,delete /var/lib/libvirt/boot

观测结果(同一个repro VM)揭示了完整生命周期:

23:40:23 virtinst-dv_rozuk-cloudinit.iso CREATE     # ISO被创建
23:40:23 virtinst-dv_rozuk-cloudinit.iso OPEN        # libvirt处理(定义+启动域)
23:40:24 virtinst-dv_rozuk-cloudinit.iso DELETE      # 1秒后被virt-install删除!
23:40:24 virtinst-*-meta-data DELETE
23:40:24 virtinst-*-user-data DELETE

而域XML仍然引用这个已被删除的ISO路径:

<disk type='file' device='cdrom'>
  <source file='/var/lib/libvirt/boot/virtinst-dv_rozuk-cloudinit.iso'/>
  <target dev='sda' bus='sata'/>
</disk>

根因链条

  1. virt-install生成种子ISO并定义/启动域(此时ISO存在)。
  2. virt-install随即把种子ISO连同meta-data、user-data一并删除--cloud-init种子是"用完即删"),留下悬空引用。
  3. 此后任何对该域的处理(重新定义、重启、审计),libvirt的DAC安全驱动都会按路径恢复trusted.libvirt.security.ref_dac XATTR → 文件不存在 → internal error status=125,Cockpit的创建/重配流程在起步阶段就失败。
  4. 于是种子盘从未在虚拟机首次启动时正式生效,密码和公钥自然全部无效。

在另一个repro VM上重启,完整复现了用户遇到的同款错误日志

机制本身是好的

只要ISO在首次启动时真实存在,cloud-init就能正常消费:repro VM成功创建了fedora用户(uid 1000)并注入SSH公钥,ssh -i /root/.ssh/vm_key [email protected] 登录成功。

隐藏的坑

云镜像放在了/root/下(权限700),qemu用户(uid 107)没有读权限,作为backing_store时virt-install直接报权限不够失败。镜像应放置到qemu可读的目录(如/var/lib/libvirt/images/)。

解决方案

应急手段(本次已生效)

  • guest agent重置密码 + 控制台注入SSH公钥 + 放开 PermitRootLogin yes,三种登录方式均可用。

根因修复(方案B:关闭DAC安全驱动)

XATTR报错由libvirt的DAC安全驱动产生,单机VPS上将其关闭即可消除整类问题:

[root@x-cluster ~]# cp -a /etc/libvirt/qemu.conf /etc/libvirt/qemu.conf.bak.$(date +%Y%m%d%H%M%S)
[root@x-cluster ~]# sed -i 's|^#security_driver = "selinux"|security_driver = "none"|' /etc/libvirt/qemu.conf
[root@x-cluster ~]# systemctl daemon-reload && systemctl restart virtqemud

验证:

  • master-0001运行不受影响,SSH正常。
  • 悬空ISO域不再报XATTR/status=125,只剩清晰直接的 无法访问存储文件 ...: 没有那个文件或目录
  • 带旧dac标签的域在none下仍可正常定义、启动,未来重启安全。

权衡:security_driver="none" 会关闭libvirt对磁盘的动态所有权/DAC标签隔离。单机VPS可接受;多租户/高安全场景建议恢复驱动,改用下面推荐的持久种子方案。

长期建议(方案A:持久种子ISO)

不依赖Cockpit的自动化,手动生成一个不会被清理的种子ISO固定挂载:

[root@x-cluster ~]# dnf install -y cloud-utils
[root@x-cluster ~]# cloud-localds /opt/data/cloud-seed/master-ci.iso \
    --ssh-key /root/.ssh/vm_key.pub seed.yaml   # seed.yaml内含用户/密码等
[root@x-cluster ~]# virsh attach-disk master-0001 /opt/data/cloud-seed/master-ci.iso \
    sdb --type cdrom --config

关键命令速查

用途 命令
Guest agent在线检查 virsh qemu-agent-command <域> '{"execute":"guest-ping"}'
重置密码(密码需base64) virsh qemu-agent-command <域> '{"execute":"guest-set-user-password",...}'
转储域XML virsh dumpxml [--inactive] <域>
控制台截屏 virsh qemu-monitor-command <域> '{"execute":"human-monitor-command","arguments":{"command-line":"screendump /tmp/x.ppm"}}'
控制台按键 virsh send-key <域> KEY_R(逐个发送+延时,避免丢键)
监控种子ISO生命周期 inotifywait -m -e create,delete /var/lib/libvirt/boot

总结

  • 原因不在cloud镜像,也不是简单的"密码设置失败",而是virt-install对cloud-init种子ISO"用完即删"+ libvirt DAC安全驱动在缺失文件上恢复XATTR,导致Cockpit的自动化流程每次都在定义阶段失败,status=125的错误掩盖了真实情况。
  • 排查思路的关键是:先用guest agent拿到VM内的执行权和root密码,再用screendump+OCR+send-key打通控制台,最后用源码分析和inotify事件复现按下葫芦浮起瓢的完整生命周期。
  • 修复上,单机直接关掉DAC安全驱动一劳永逸;生产环境建议改用持久种子ISO方案,并把云镜像放到qemu可读的目录。