cockpit管理qemu虚拟机
十月 09, 2025 更新: 八月 10, 2026 [虚拟化与容器] #qemu #cockpit #debug高效登录虚拟机GUI
使用virt-viewer客户端spice协议登录
apt install virt-viewer
virt-viewer --reconnect win11
virt-viewer --wait win11
virsh domdisplay win11
连接局域网的虚拟机
#1. 如果远程宿主机也在跑 libvirtd 并允许远程连接,你还可以直接用:
# 这样 virt-viewer 会先通过 libvirt 查询到 SPICE 端口,然后自动帮你连上去(不用手动写端口)。
# 通常推荐这种方式,安全性更好(走 SSH)
virt-viewer -c qemu+ssh://user@<宿主机IP>/system <VM名称或UUID>
virt-viewer -c qemu+ssh://peter@192.168.7.99/system win11
#2. 开放spice监听外部地址
# 修改listen='0.0.0.0' 表示允许外部访问(默认可能是 127.0.0.1,那就只能本机连)。
# port 可以固定(如 5901)或自动分配
sudo virsh list
sudo virsh dominfo win11
sudo grep -C 0 --group-separator="======" listen /etc/libvirt/qemu/win11.xml
sudo sed -i'' 's/127.0.0.1/0.0.0.0/' /etc/libvirt/qemu/win11.xml
# 一般需要配置密码,否则太不安全
## <graphics type='spice' port='5901' listen='0.0.0.0'>
## <passwd>123</passwd>
## </graphics>
virt-viewer --spice-uri spice://192.168.7.99:5901 --spice-password 123
cat <<EOF > win11.vv
[virt-viewer]
type=spice
host=192.168.7.99
port=5901
password=123
delete-this-file=1
fullscreen=0
EOF
remote-viewer vm.vv
# 或者直接双击执行
# 可能的问题
# 音频/USB 不工作 SPICE 的辅助通道(如 5902)可能被防火墙阻断,需开放连续端口范围
启动问题排查
- 从debian12 升级到debian13系统后, cockpit的web页面登录失败
sudo systemctl status cockpit.service
# 2月 03 14:00:57 xiao99 cockpit-tls[2028483]: cockpit-tls: gnutls_handshake failed: A TLS fatal alert has been received.
# 2月 03 14:01:06 xiao99 cockpit-tls[2028483]: cockpit-tls: gnutls_handshake failed: A TLS fatal alert has been received.
# 另外网页会自动刷行页面,后续的cockpit.socket 会崩溃,出现另一个报错信息, 这个应该不是根因
# Dependency failed for cockpit.service - Cockpit Web Service.
gnutls-cli --insecure -p 9090 localhost
# Processed 0 CA certificate(s).
# Resolving 'localhost:9090'...
# Connecting to '127.0.0.1:9090'...
# *** Fatal error: Error in the pull function.
echo -e "GET / HTTP/1.0\r\n\r\n" | socat - OPENSSL:127.0.0.1:9090,verify=0
# 2026/02/03 14:18:00 socat[2106042] W refusing to set empty SNI host name
# 2026/02/03 14:18:00 socat[2106042] W SSL_connect(): Connection reset by peer
证书问题解决
sudo systemd-run \
--unit=cockpit-cert-debug \
--pty \
-p RuntimeDirectory=cockpit/tls \
-p WorkingDirectory=/ \
/usr/lib/cockpit/cockpit-certificate-ensure --for-cockpit-tls
sudo systemctl reset-failed cockpit-debug.service
sudo systemd-run \
--unit=cockpit-debug \
--pty \
-p WorkingDirectory=/run \
-p RuntimeDirectory=cockpit/tls \
-p DynamicUser=yes \
-p User=cockpit-systemd-service \
-p Group=cockpit-wsinstance-socket \
-p NoNewPrivileges=yes \
-p ProtectSystem=strict \
-p PrivateDevices=yes \
-p ProtectKernelTunables=yes \
-p MemoryDenyWriteExecute=yes \
-p PrivateIPC=yes \
-p PrivateNetwork=yes \
/usr/lib/cockpit/cockpit-tls
sudo systemctl enable --now \
cockpit.socket \
cockpit-wsinstance-http.socket \
cockpit-wsinstance-https-factory.socket
虚拟机标签不显示
现象:登录 Cockpit 后左侧导航只有 概览/日志/存储/网络/用户账户/服务,没有「虚拟机」标签。
原因:未安装 cockpit-machines 包(虚拟机页面的功能由它提供,与 libvirt 本体无关)。
# 查看已装 cockpit 组件
dpkg -l | grep cockpit
sudo apt install cockpit-machines
安装后强制刷新浏览器(Ctrl+Shift+R)即可出现「虚拟机器」标签。 注意:Ubuntu 24.04 若 cockpit 核心来自 backports(如 362),建议一并安装 backports 版对齐版本,避免旧版页面与新版 bridge 不兼容:
sudo apt install cockpit-machines/noble-backports
登录断开:自签证书过期自动重新生成
现象:前一天还能打开 Cockpit,今天突然「已断开连接 / 服务器已关闭了连接」。服务器日志里大量:
cockpit-tls: gnutls_handshake failed: A TLS fatal alert has been received.
排查发现 /etc/cockpit/ws-certs.d/ 下的自签证书在当天被重新生成了(旧证书过期):
ls -la /etc/cockpit/ws-certs.d/
openssl x509 -in /etc/cockpit/ws-certs.d/0-self-signed.cert -noout -dates
浏览器缓存/信任的是旧证书,服务器换了新证书后 TLS 握手被浏览器直接掐断,表现就是「已断开连接」。这与上面 Debian 升级导致的证书问题根因不同(那次是升级破坏了证书文件,这次是正常到期轮换)。
解决(浏览器侧操作):
- 隐身窗口打开
https://<ip>:9090,出现"您的连接不是私密连接"时点 高级 → 继续前往 - 若曾把旧证书导入系统/浏览器信任库,删除旧记录
- 清除该站点的缓存/站点数据
服务端验证连通性:
curl -sk -o /dev/null -w 'HTTP %{http_code}\n' https://localhost:9090/
虚拟机页面「服务器已关闭了连接」:pybridge 单例缓存 bug
现象:其他标签都正常,唯独点「虚拟机」后整个会话断开,页面显示「已断开连接 / 服务器已关闭了连接」+ 重新连接按钮。每次点都崩,可稳定复现。
排查过程
- 服务器日志(journalctl)看到 bridge 崩溃:
journalctl -u cockpit-session@ -n 50
# cockpit.protocol.CockpitProtocolError: channel is already open
# File ".../cockpit/router.py", line 281, in channel_control_received
# OSError: [Errno 22] sd_bus_attach_event: Invalid argument
- 检查运行中 bridge 的环境,发现缺少
XDG_RUNTIME_DIR和DBUS_SESSION_BUS_ADDRESS:
tr '\0' '\n' < /proc/<bridge-pid>/environ
# 只有 XDG_SESSION_CLASS/XDG_SESSION_TYPE/HOME/PATH
-
干净环境复现:
sd_bus_default_user报No medium found(ENOMEDIUM)——web 会话没有 session bus。/etc/pam.d/cockpit只有pam_unix,没有pam_systemd,所以 bridge 拿不到用户会话总线地址。 -
给 bridge 打临时日志补丁(router.py 记录重复打开的 channel id,dbus.py 记录打开的 bus/name),再触发一次崩溃,抓到决定性证据:
# DBG dbus-open channel=1:6!6 bus=session name=org.libvirt superuser=None
# → attach_event EINVAL(异步异常,channel 残留在 open_channels,无 close 响应)
# DBG channel=1:6!6 already-open message={'bus': 'session', 'payload': 'dbus-json3', 'name': 'org.libvirt', ...}
# → 页面超时后重试同一 channel id → CockpitProtocolError → bridge 死亡
根因
崩溃链完整还原:
- 会话内第一个 session bus 通道(如日志页的
org.freedesktop.systemd1)调用sd_bus_default_user失败(ENOMEDIUM),但systemd_ctypes/bus.py的default_user()先把空壳 Bus() 写入了单例缓存,失败后也不清除:if Bus._default_user_instance is None: Bus._default_user_instance = Bus() # 先赋值(空壳!) Bus._default_user(byref(...)) # 失败时异常,但空壳已缓存 - 虚拟机页打开
org.libvirtsession 通道(第二个 session 通道),拿到缓存的空 bus →attach_event(NULL)返回 EINVAL → channel 半开残留在路由表 - 页面收不到 ready/close 响应,超时重试同一 channel id → router 抛
CockpitProtocolError('channel is already open')→ 整个 bridge 进程崩溃 → 前端「服务器已关闭了连接」
修复
只把成功创建的连接写入缓存(default_user/default_system 同样处理):
@staticmethod
def default_user(attach_event: bool = True) -> 'Bus':
if Bus._default_user_instance is None:
instance = Bus()
Bus._default_user(byref(instance))
if attach_event:
instance.attach_event(None, 0)
Bus._default_user_instance = instance
return Bus._default_user_instance
修复后 session 通道干净地返回 ChannelError,虚拟机页按设计回落到 system bus(superuser: "try"),虚拟机列表正常加载。
经验
- 判定顺序:先看服务器日志定位崩溃点,再看 bridge 进程环境找缺失依赖,最后用临时日志补丁抓决定性证据
systemd_ctypes的单例模式「先赋值后初始化」是隐患,任何初始化失败都会污染缓存——排查时可先怀疑这类模式- 复现脚本(模拟 bridge 协议驱动 channel 打开)能验证修复,但要记得模拟浏览器的「重试」行为,否则不会触发崩溃