PVE三部曲之二:QEMU Guest Agent的用法

接上回。试想一下,当你美滋滋地玩着游戏,突然给你来一个“你已断开连接”,是不是很恼火?

yeah

为什么会这样?

经过简单的排查,可以发现是由于Proxmox 节能面板导致的。

yeah

PVE 节能面板 的设计初衷是 降低电费成本 ——当没有玩家在线时,让游戏服务器所在的 VM 自动进入休眠,从而减少资源消耗。(此部分功能已经通过N8N实现。)

但这带来了一个新的问题:VM/CT 在休眠后重新启动时,虚拟机内部的系统时间不会立即与真实时间同步。

虽然此时玩家仍然可以正常连接到游戏服务端,但当虚拟机的时间同步服务稍后自动矫正时间时,服务器会检测到 “客户端时间与服务器时间不一致”,并因此强制中断所有会话,要求重新连接。

对于玩家来说,体验就变成了—— “你已断开连接”。

目录

今天的主角登场: QEMU Guest Agent

什么是Qemu Guest Agent

我不得不佩服设计QGA的人,这简直是一个天才的设计!用职场黑话来说就是:

  • 全面打通宿主机与虚拟机的协同链路,实现双端通信颗粒度高维对齐;
  • 针对虚拟机在不同状态下的各类“痛点”提前处理,让系统运行更加稳定可控;
  • 强化虚拟机的“肖像”数据,让状态更透明,最终实现从监控到运维的完整“闭环”。

说人话就是:

qemu-guest-agent 是一个辅助守护进程,安装在虚拟机中。它用于在主机和虚拟机之间交换信息,并在虚拟机中执行命令。

那么我的需求就是:当虚拟机从睡眠中恢复的时候,我们需要通过QGA更新虚拟机的时间。

安装和配置Qemu Guest Agent

我将以Windows系统为例进行安装介绍。

🟦1.下载VirtIO 驱动包

进入官网下载ISO包: https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/latest-virtio/

下载文件(例如):

virtio-win-0.1.xx.iso

并把 ISO 挂载到你的 Windows 虚拟机。

🟦2.安装QEMU Guest Agent

在 Windows 虚拟机内打开挂载好的 VirtIO ISO

进入目录:

guest-agent

找到:

qemu-guest-agent-x64.msi (64 位)

双击安装,按默认步骤即可。

安装完成后,Windows 会自动增加一个服务。

🟦3.启动并配置服务

打开 Windows 服务管理器:

Win + R → 输入 services.msc → 回车

找到 QEMU Guest Agent:

✔ 设置为自动启动

  1. 双击该服务

  2. “启动类型” → 选择 Automatic(自动)

  3. 点击 Start(启动)

🟦4.在PVE中启用QGA

在 PVE(Proxmox)中,选中虚拟机:

  1. 点击 Options
  2. 找到 QEMU Guest Agent
  3. 设置为 Enabled(启用)
  4. 如果需要自动时间同步,还可以额外启用:

Use Qemu Guest Agent: Yes

yeah

🟦5.验证是否安装成功

在 PVE 后台(或宿主机)运行:

qm agent vmid ping

如果返回为空:

{}

代表 QGA 已正常通信。

yeah

更新节能面板逻辑

我们现在已经成功安装了Qemu Guest Agent,但是目前还不能实现我们的需求。因为现在只是宿主和虚拟机之间能够通信,还没有定义通信的内容。

所以我们修改启动虚拟机按钮的逻辑:

javascript
async function manageVM(vmId, action) {
    const numericVmId = parseInt(vmId, 10);
    const cleanedAction = action.trim();

    // 1. 先执行原有 VM 操作,也就是恢复虚拟机
    const response = await fetch(`${PVE_HOST}/api2/json/nodes/pve/qemu/${numericVmId}/status/${cleanedAction}`, {
        method: 'POST',
        headers: {
            'Authorization': AUTH_HEADER
        }
    });
    const data = await response.json();


    // 2. 对指定的虚拟机执行时间同步
    if (numericVmId === vmId && cleanedAction === 'resume') {
        // 格式化本地时间为 "YYYY-MM-DD HH:mm:ss"
        const pad = (n) => n.toString().padStart(2, '0');
        const now = new Date();
        const currentTime = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
        
        console.log(currentTime);

        const execCommand = [
            "powershell.exe",
            "-Command",
            `Set-Date -Date '${currentTime}'`
        ];

        try {
            const execRes = await fetch(
                `${PVE_HOST}/api2/json/nodes/pve/qemu/${numericVmId}/agent/exec`,
                {
                    method: 'POST',
                    headers: {
                        'Authorization': AUTH_HEADER,
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ command: execCommand })
                }
            );

            const execData = await execRes.json();
            console.log("[TIME-SYNC] Set-Date exec response:", execData);
        } catch (err) {
            console.error("[TIME-SYNC] Error executing Set-Date:", err);
        }
    }

    return true;
}

关键的代码就是下面这段,通过PVE的API发送一段powershell脚本给到虚拟机,让它立即更新时间。

javascript
        const execCommand = [
            "powershell.exe",
            "-Command",
            `Set-Date -Date '${currentTime}'`
        ];

        try {
            const execRes = await fetch(
                `${PVE_HOST}/api2/json/nodes/pve/qemu/${numericVmId}/agent/exec`,
                {
                    method: 'POST',
                    headers: {
                        'Authorization': AUTH_HEADER,
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ command: execCommand })
                }
            );

💖后记

对齐协同链路颗粒度,
前置化解决痛点,
强化虚拟机肖像,
构建运维管理闭环。

yeah

PVE三部曲之三:备份和还原
PVE三部曲之一:在概要中显示温度的方法