經常使用vmWare的同學都知道有vmware-tools這個工具,這個安裝在vm內部的工具,可以實現宿主機與虛擬機的通訊,大大增強了虛擬機的性能與功能,
如vmware現在的Unity mode下可以讓應用程序無縫地與宿主機交互,更不用提直接復制粘帖文件及內容的小功能了。
對於KVM而言,其實也有一款這樣的工具叫做 Qemu Guest Agent(以下稱qga).
原理分析:
qga是一個運行在虛擬機內部的普通應用程序(可執行文件名稱默認為qemu-ga,服務名稱默認為qemu-guest-agent),其目的是實現一種宿主機和虛擬機進行交互的方式,這種方式不依賴於網絡,而是依賴於virtio-serial(默認首選方式)或者isa-serial,而QEMU則提供了串口設備的模擬及數據交換的通道,最終呈現出來的是一個串口設備(虛擬機內部)和一個unix socket文件(宿主機上)。
qga通過讀寫串口設備與宿主機上的socket通道進行交互,宿主機上可以使用普通的unix socket讀寫方式對socket文件進行讀寫,最終實現與qga的交互,交互的協議與qmp(QEMU Monitor 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 \ -chardev socket,id=charchannel0,path=/var/lib/libvirt/qemu/test.agent,server,nowait \ -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,\ name=com.163.spice.0
通過上面的參數就可以在宿主機上生成一個unix socket文件,路徑為:/var/lib/libvirt/qemu/test.agent,同時在虛擬機內部生成一個serial設備,名字為com.163.spice.0,設備路徑為:/dev/vport0p1,映射出來的可讀性比較好的路徑為:/dev/virtio-ports/com.163.spice.0,可以在運行qga的時候通過-p參數指定讀寫這個設備。
也可以通的XML文件來配置這個串口設備:
<channel type='unix'> <source mode='bind' path='/var/lib/libvirt/qemu/test.agent'/> <target type='virtio' name='com.163.spice.0'/> </channel>
注意: libvirt-qemu:kvm用戶要有權限讀寫'/var/lib/libvirt/qemu/test.agent'
已有功能
目前qga最新版本為1.5.50,linux已經實現下面的所有功能,windows僅支持加*的那些功能:
Ø guest-sync-delimited*:宿主機發送一個int數字給qga,qga返回這個數字,並且在后續返回字符串響應中加入ascii碼為0xff的字符,其作用是檢查宿主機與qga通信的同步狀態,主要用在宿主機上多客戶端與qga通信的情況下客戶端間切換過程的狀態同步檢查,比如有兩個客戶端A、B,qga發送給A的響應,由於A已經退出,目前B連接到qga的socket,所以這個響應可能被B收到,如果B連接到socket之后,立即發送該請求給qga,響應中加入了這個同步碼就能區分是A的響應還是B的響應;在qga返回宿主機客戶端發送的int數字之前,qga返回的所有響應都要忽略;
Ø guest-sync*:與上面相同,只是不在響應中加入0xff字符;
Ø guest-ping*:Ping the guest agent, a non-error return implies success;
Ø guest-get-time*:獲取虛擬機時間(返回值為相對於1970-01-01 in UTC,Time in nanoseconds.);
Ø guest-set-time*:設置虛擬機時間(輸入為相對於1970-01-01 in UTC,Time in nanoseconds.);
Ø guest-info*:返回qga支持的所有命令;
Ø guest-shutdown*:關閉虛擬機(支持halt、powerdown、reboot,默認動作為powerdown);
Ø guest-file-open:打開虛擬機內的某個文件(返回文件句柄);
Ø guest-file-close:關閉打開的虛擬機內的文件;
Ø guest-file-read:根據文件句柄讀取虛擬機內的文件內容(返回base64格式的文件內容);
Ø guest-file-write:根據文件句柄寫入文件內容到虛擬機內的文件;
Ø guest-file-seek:Seek to a position in the file, as with fseek(), and return the current file position afterward. Also encapsulates ftell()'s functionality, just Set offset=0, whence=SEEK_CUR;
Ø guest-file-flush:Write file changes bufferred in userspace to disk/kernel buffers;
Ø guest-fsfreeze-status:Get guest fsfreeze state. error state indicates;
Ø guest-fsfreeze-freeze:Sync and freeze all freezable, local guest filesystems;
Ø guest-fsfreeze-thaw:Unfreeze all frozen guest filesystems;
Ø guest-fstrim:Discard (or "trim") blocks which are not in use by the filesystem;
Ø guest-suspend-disk*:Suspend guest to disk;
Ø guest-suspend-ram*:Suspend guest to ram;
Ø guest-suspend-hybrid:Save guest state to disk and suspend to ram(This command requires the pm-utils package to be installed in the guest.);
Ø guest-network-get-interfaces:Get list of guest IP addresses, MAC addresses and netmasks;
Ø guest-get-vcpus:Retrieve the list of the guest's logical processors;
guest-set-vcpus:Attempt to reconfigure (currently: enable/disable) logical processors inside the guest。
功能擴展方式
qga功能擴展十分方便,只需要在qapi-schema.json文件中定義好功能名稱、輸入輸出數據類型,然后在commands-posix.c里面增加對應的功能函數即可,下面的補丁即在qga中增加一個通過statvfs獲取虛擬機磁盤空間信息的功能:
diff --git a/qga/commands-posix.c b/qga/commands-posix.c index e199738..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_t minimum, 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 */ void ga_command_state_init(GAState *s, GACommandState *cs) { diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index 7155b7a..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腳本在編譯的時候動態生成出來,這對開發人員來說是非常方便的,開發人員在擴展功能的時候只需要關注輸入、輸出的數據類型,以及功能的函數內容即可。
寫到這里,有經驗的開發人員已經知道如何用qga開發監控平台,下面是網友的實現思路,供參考:
社區活躍度
QEMU社區從2011年7月20號開始在QEMU代碼倉庫中增加qga功能,最近一次提交在2013年5月18號,總共有100多次提交記錄,代碼維護人員主要來自redhat和IBM,社區的活躍度不高,但是QEMU本身的提交記錄從2003年至今已有27200多條,還是比較活躍的,qga的功能及代碼都比較簡單,也是活躍度不高的一個重要原因。
QEMU代碼倉庫地址:git clone git://git.qemu-project.org/qemu.git
qga代碼位於QEMU代碼的根目錄下的qga目錄中。
監控方案現狀
目前普遍雲主機監控的實現方法是,在創建雲主機的過程中,增加監控腳本及其配置文件、定時任務及監控信息推送配置文件的注入過程,包括四個文件,其中監控信息推送配置文件(/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,其中有一條任務為:root su -c 'python /etc/vm_monitor/send_monitor_data.py' > /dev/null 2>&1,也即每60s收集並推送一次監控信息給監控平台。
當前監控方案的問題
- 依賴雲主機內部的python解釋器
- 雲主機必須存在root賬戶
- 依賴NVS文件注入功能;並且為了注入這些監控文件對nova的改動也比較大,也無法與社區同步;windows鏡像也會注入這些無用的文件,可能導致一些意想不到的問題;另外如果有的鏡像的操作系統不在第一個分區上,則注入的監控文件會失效
- 已經運行的雲主機內部的監控相關文件更新困難,導致新監控項的添加、推送周期、推送地址等的修改也比較困難,靈活性較差
- Nova中base64編碼的注入腳本的代碼可讀性很差,代碼更新及維護困難
- 定位問題一般都需要登錄到雲主機內部進行,對於采用密鑰對登錄的雲主機來說定位問題比較困難
采用qga方式的監控方案
首先為每個雲主機增加virtio-serial的配置,這個只需要修改生成libvirt配置文件的代碼即可,並且應該可以提交給社區;其次需要在虛擬機內部安裝qga服務;最后需要在宿主機上新增一個服務進程(這里暫定為monitor服務),用來通過與qga交互從雲主機內部獲取監控信息;總的模塊交互流程如下:
雲主機創建流程中的監控相關操作:
monitor服務單次監控信息獲取及推送流程如下:
參考資料:
libvirt:http://wiki.libvirt.org/page/Qemu_guest_agent
RedHat ovirt: http://www.ovirt.org/Category:Ovirt_guest_agent
qemu: http://wiki.qemu.org/Features/QAPI/GuestAgent