qemu-guest-agent调研

1.原理分析

 qga是一个运行在虚拟机内部的普通应用程序(可执行文件名称默认为qemu-ga服务名称默认为qemu-guest-agent),其目的是实现一种宿主机和虚拟机进行交互的方式,这种方式不依赖于网络,而是依赖于virtio-serial(默认首选方式)或者isa-serial,而QEMU则提供了串口设备的模拟及数据交换的通道,最终呈现出来的是一个串口设备(虚拟机内部)和一个unixsocket文件(宿主机上)

qga通过读写串口设备与宿主机上的socket通道进行交互,宿主机上可以使用普通的unixsocket读写方式对socket文件进行读写,最终实现与qga的交互,交互的协议与qmpQEMUMonitor Protocol)相同(简单来说就是使用JSON格式进行数据交换),串口设备的速率通常都较低,所以比较适合小数据量的交换。


QEMU virtio串口设备模拟参数:

/usr/bin/kvm(QEMU) \

……\

-device virtio-serial-pci,id=virtio-serial0,bus=pci.0,addr=0x6 \

-device isa-serial,chardev=charserial1,id=serial1 \

-chardevsocket,id=charchannel0,path=/var/lib/libvirt/qemu/test.agent,server,nowait\

-devicevirtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,\

name=com.163.spice.0

通过上面的参数就可以在宿主机上生成一个unixsocket文件,路径为:/var/lib/libvirt/qemu/test.agent,同时在虚拟机内部生成一个serial设备,名字为com.163.spice.0,设备路径为:/dev/vport0p1,映射出来的可读性比较好的路径为:/dev/virtio-ports/com.163.spice.0,可以在运行qga的时候通过-p参数指定读写这个设备。

Libvirt支持QEMU串口相关配置,所以上述参数已经可以通过libvirt进行配置,且更简单直观,配置方式如下:

<channeltype='unix'>

<sourcemode='bind' path='/var/lib/libvirt/qemu/test.agent'/>

<targettype='virtio' name='com.163.spice.0'/>

</channel>

需要注意的是libvirt-qemu:kvm用户要有权限读写'/var/lib/libvirt/qemu/test.agent'

2.实现程度

2.1已有功能

目前qga最新版本为1.5.50linux已经实现下面的所有功能,windows仅支持加*的那些功能:

  • guest-sync-delimited*:宿主机发送一个int数字给qgaqga返回这个数字,并且在后续返回字符串响应中加入ascii码为0xff的字符,其作用是检查宿主机与qga通信的同步状态,主要用在宿主机上多客户端与qga通信的情况下客户端间切换过程的状态同步检查,比如有两个客户端ABqga发送给A的响应,由于A已经退出,目前B连接到qgasocket,所以这个响应可能被B收到,如果B连接到socket之后,立即发送该请求给qga,响应中加入了这个同步码就能区分是A的响应还是B的响应;在qga返回宿主机客户端发送的int数字之前,qga返回的所有响应都要忽略;

  • guest-sync*:与上面相同,只是不在响应中加入0xff字符;

  • guest-ping*Pingthe guest agent, a non-error return implies success

  • guest-get-time*:获取虚拟机时间(返回值为相对于1970-01-01in UTCTimein nanoseconds.);

  • guest-set-time*:设置虚拟机时间(输入为相对于1970-01-01in UTCTimein nanoseconds.);

  • guest-info*:返回qga支持的所有命令;

  • guest-shutdown*:关闭虚拟机(支持haltpowerdownreboot,默认动作为powerdown);

  • guest-file-open:打开虚拟机内的某个文件(返回文件句柄);

  • guest-file-close:关闭打开的虚拟机内的文件;

  • guest-file-read:根据文件句柄读取虚拟机内的文件内容(返回base64格式的文件内容);

  • guest-file-write:根据文件句柄写入文件内容到虚拟机内的文件;

  • guest-file-seekSeekto a position in the file, as with fseek(), and return the currentfile position afterward. Also encapsulates ftell()'s functionality,just Set offset=0, whence=SEEK_CUR

  • guest-file-flushWritefile changes bufferred in userspace to disk/kernel buffers

  • guest-fsfreeze-statusGetguest fsfreeze state. error state indicates

  • guest-fsfreeze-freezeSyncand freeze all freezable, local guest filesystems

  • guest-fsfreeze-thawUnfreezeall frozen guest filesystems

  • guest-fstrimDiscard(or "trim") blocks which are not in use by the filesystem

  • guest-suspend-disk*Suspendguest to disk

  • guest-suspend-ram*Suspendguest to ram

  • guest-suspend-hybridSaveguest state to disk and suspend to ramThiscommand requires the pm-utils package to be installed in theguest.);

  • guest-network-get-interfacesGetlist of guest IP addresses, MAC addresses and netmasks

  • guest-get-vcpusRetrievethe list of the guest's logical processors

  • guest-set-vcpusAttemptto reconfigure (currently: enable/disable) logical processors insidethe guest

2.2功能扩展方式

qga功能扩展十分方便,只需要在qapi-schema.json文件中定义好功能名称、输入输出数据类型,然后在commands-posix.c里面增加对应的功能函数即可,下面的补丁即在qga中增加一个通过statvfs获取虚拟机磁盘空间信息的功能:

diff--git a/qga/commands-posix.c b/qga/commands-posix.c

indexe199738..2f42a2f 100644

---a/qga/commands-posix.c

+++b/qga/commands-posix.c

@@-21,6 +21,7 @@

#include<stdio.h>

#include<string.h>

#include<sys/stat.h>

+#include<sys/statvfs.h>

#include<inttypes.h>

#include"qga/guest-agent-core.h"

#include"qga-qmp-commands.h"

@@-1467,6 +1468,36 @@ void qmp_guest_fstrim(bool has_minimum, int64_tminimum, Error **err)

}

#endif

+GuestFileSystemStatistics*qmp_guest_get_statvfs(const char *path, Error **errp)

+{

+ int ret;

+ GuestFileSystemStatistics *fs_stat;

+ struct statvfs *buf;

+ buf = g_malloc0(sizeof(struct statvfs));

+

+

+ ret = statvfs(path, buf);

+ if (ret < 0) {

+ error_setg_errno(errp, errno, "Failed to get statvfs");

+ return NULL;

+ }

+

+ fs_stat = g_malloc0(sizeof(GuestFileSystemStatistics));

+ fs_stat->f_bsize = buf->f_bsize;

+ fs_stat->f_frsize = buf->f_frsize;

+ fs_stat->f_blocks = buf->f_blocks;

+ fs_stat->f_bfree = buf->f_bfree;

+ fs_stat->f_bavail = buf->f_bavail;

+ fs_stat->f_files = buf->f_files;

+ fs_stat->f_ffree = buf->f_ffree;

+ fs_stat->f_favail = buf->f_favail;

+ fs_stat->f_fsid = buf->f_fsid;

+ fs_stat->f_flag = buf->f_flag;

+ fs_stat->f_namemax = buf->f_namemax;

+

+ return fs_stat;

+}

+

/*register init/cleanup routines for stateful command groups */

voidga_command_state_init(GAState *s, GACommandState *cs)

{

diff--git a/qga/qapi-schema.json b/qga/qapi-schema.json

index7155b7a..a071c3f 100644

---a/qga/qapi-schema.json

+++b/qga/qapi-schema.json

@@-638,3 +638,52 @@

{'command': 'guest-set-vcpus',

'data': {'vcpus': ['GuestLogicalProcessor'] },

'returns':'int' }

+

+##

+#@GuestFileSystemStatistics:

+#

+#Information about guest file system statistics.

+#

+#@f_bsize: file system block size.

+#

+#@f_frsize: fragment size.

+#

+#@f_blocks: size of fs in f_frsize units.

+#

+#@f_bfree: free blocks.

+#

+#@f_bavail: free blocks for non-root.

+#

+#@f_files: inodes.

+#

+#@f_ffree: free inodes.

+#

+#@f_favail: free inodes for non-root.

+#

+#@f_fsid: file system id.

+#

+#@f_flag: mount flags

+#

+#@f_namemax: maximum filename length.

+#

+#Since 1.5.10(NetEase)

+##

+{'type': 'GuestFileSystemStatistics',

+ 'data': { 'f_bsize': 'int', 'f_frsize': 'int', 'f_blocks': 'int',

+ 'f_bfree': 'int', 'f_bavail': 'int', 'f_files': 'int',

+ 'f_ffree': 'int', 'f_favail': 'int', 'f_fsid': 'int',

+ 'f_flag': 'int', 'f_namemax': 'int'} }

+

+##

+#@guest-get-statvfs:

+#

+#Get the information about guest file system statistics by statvfs.

+#

+#Returns: @GuestFileSystemStatistics.

+#

+#Since 1.5.10(NetEase)

+##

+{'command': 'guest-get-statvfs',

+ 'data': { 'path': 'str' },

+ 'returns': 'GuestFileSystemStatistics' }

+

中间复杂的类型定义代码,以及头文件包含关系处理都由一个python脚本在编译的时候动态生成出来,这对开发人员来说是非常方便的,开发人员在扩展功能的时候只需要关注输入、输出的数据类型,以及功能的函数内容即可。



3.社区活跃度

QEMU社区从2011720号开始在QEMU代码仓库中增加qga功能,代码维护人员主要来自redhatIBM,社区的活跃度不高,但是QEMU本身的提交记录从2003年至今已有27200多条,还是比较活跃的,qga的功能及代码都比较简单,也是活跃度不高的一个重要原因。

QEMU代码仓库地址:gitclone git://git.qemu-project.org/qemu.git

qga代码位于QEMU代码的根目录下的qga目录中。

4.利用qemu-guest-agent实现监控方案

4.1监控方案现状

目前云主机监控的实现方法是,在创建云主机的过程中,增加监控脚本及其配置文件、定时任务及监控信息推送配置文件的注入过程,包括四个文件,其中监控信息推送配置文件(/etc/vm_monitor/info)由管理平台根据云主机所属用户的注册信息以及监控平台相关配置生成,并传入创建云主机的API来实现文件的注入,监控脚本(/etc/vm_monitor/send_monitor_data.py)及其配置文件(/etc/vm_monitor/monitor_settings.xml)、定时任务文件(/etc/cron.d/inject_cron_job)是包含在NVS经过base64编码后的监控脚本文件inject_files.json中。

工作模式为,在root账户增加定时任务inject_cron_job,其中有一条任务为:rootsu -c 'python /etc/vm_monitor/send_monitor_data.py' > /dev/null2>&1,也即每60s收集并推送一次监控信息给监控平台。

4.2当前方案存在的问题

  • 依赖云主机内部的python解释器

  • 云主机必须存在root账户

  • 依赖NVS文件注入功能;并且为了注入这些监控文件对nova的改动也比较大,也无法与社区同步;windows镜像也会注入这些无用的文件,可能导致一些意想不到的问题;另外如果有的镜像的操作系统不在第一个分区上,则注入的监控文件会失效

  • 已经运行的云主机内部的监控相关文件更新困难,导致新监控项的添加、推送周期、推送地址等的修改也比较困难,灵活性较差

  • Novabase64编码的注入脚本的代码可读性很差,代码更新及维护困难

  • 定位问题一般都需要登录到云主机内部进行,对于采用密钥对登录的云主机来说定位问题比较困难

4.3采用qga方式的监控方案

首先为每个云主机增加virtio-serial的配置,这个只需要修改生成libvirt配置文件的代码即可,并且应该可以提交给社区;其次需要在虚拟机内部安装qga服务;最后需要在宿主机上新增一个服务进程(这里暂定为monitor服务),用来通过与qga交互从云主机内部获取监控信息;总的模块交互流程如下:

云主机创建流程中的监控相关操作流程图

monitor服务单次监控信息获取及推送流程如下:

单次监控数据获取及推送流程图

4.4采用qga方式需要做的工作

  • 需要扩展qga的功能,增加获取文件系统信息的功能

  • qga打包的相关工作

  • 需要重构监控信息获取脚本,把读本地/proc目录相关文件改为通过qga读文件

  • 计算节点新增monitor服务

  • nova代码修改,包括新增virtio-serial配置生成流程以及去掉原有的监控文件注入流程

  • 已有镜像需要更新,安装qga

4.5qga方式存在的问题

  • 与当前方案的兼容性问题:已创建的云主机仍然采用原有的监控方式进行数据推送,新创建的云主机则采用qga方式进行监控数据的推送(可以通过检查宿主机上没有socket文件来区分新创建还是已有云主机);监控项保持不变;

  • qga升级问题:除非需要增加读取云主机内部相关文件不能获取到相关信息的监控项,否则不需要特别对qga进行升级;如果确实需要升级,可以在云主机内部配置包含新版本qgaapt相关源,通过apt方式进行升级;

  • 监控项扩展问题:在不需要对qga进行升级的情况下,只需要升级monitor服务即可(已有云主机也不需要改动);如果需要同时升级qga(这种情况比较麻烦,如果不升级qga则新的监控项无法推送);

  • 安全问题:主要包括暴露在计算节点的socket文件以及暴露在云主机内部的virtio-serial,这两个文件或设备的安全性问题;外部的socket文件与云主机镜像文件类似,都是有权限控制的,普通用户无法访问;云主机内部的virtio-serial设备可能会被用户攻击,但由于其速率较低,所以对宿主机影响不大,主要的问题可能是用户会伪造监控文件,然后通过virtio-serial设备返回给monitor服务,导致报警异常,之前的监控文件注入方式比qga方式在这一问题上更严重,因为监控完全是通过注入的脚本在云主机内部获取并推送的,用户可以做任何修改,所以这个问题也应该不是问题;

  • 用户隐私保护问题:用户可能会担心我们使用qga获取云主机内部的某些敏感文件,但这个问题其实在云计算环境是无法完全避免的,即使不通过qga,我们也可以通过拷贝云主机镜像并重新创建新的云主机的方式获取云主机内部的任意文件,所以这应该也不是问题;

4.6类似agent比较

ovirt-guest-agent

agent的原理与qga完全相同,是redhat公司为自己的OVirt虚拟化管理平台开发的与虚拟机交互的方案,与qga不同之处在于redhat采用了python作为其guestagent的开发语言,其支持的协议也是基于JSON格式的,并且它支持部分windows系统。其支持的功能列表可以在其主页查看到:http://www.ovirt.org/Guest_Agent


参考:http://git.qemu.org/?p=qemu.git;a=blob;f=qga/qapi-schema.json;h=7155b7ab55fc4ef5336fd771ca06905c485fad62;hb=refs/heads/master

http://git.qemu.org/?p=qemu.git;a=commitdiff;h=6912e6a94cb0a1d650271103efbc3ac2299e4fd0

http://wiki.qemu.org/Features/QAPI/GuestAgent

http://wiki.libvirt.org/page/Qemu_guest_agent

http://www.cnblogs.com/biangbiang/p/3222458.html

你可能感兴趣的:(qemu-guest-agent调研)