一、简介
libvirt是一套免费、开源的支持Linux下主流虚拟化工具的C函数库,其旨在为包括Xen在内的各种虚拟化工具提供一套方便、可靠的编程接口,支持与C,C++,Ruby,Python等多种主流开发语言的绑定。当前主流Linux平台上默认的虚拟化管理工具virt-manager(图形化),virt-install(命令行模式)等均基于libvirt开发而成。
本文基于libvirt,使用它的C函数库进行虚拟机的相应操作。
二、详解
1、创建虚拟机
(1)C语言代码
/*************************************************************************** * create_kvm.c * create kvm machine(domain) based on conf.xml * compile command: 'gcc -g -Wall create_kvm.c -o create -lvirt' * run shell command:'qemu-img create -f qcow2 newlinux.img 15G' * running command: './create ./create_kvm.xml' ***************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include<assert.h> #include <libvirt/libvirt.h> #include <libvirt/virterror.h> static int create_kvm(); static int get_status(); static int open_file(char *file_name); static virConnectPtr conn = NULL; static virDomainPtr dom = NULL; char *buff = NULL; int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "parametes are wrong,please checkout!\n"); return -1; } if (open_file(argv[1]) != 0) { fprintf(stderr, "open_file failed!\n"); return -1; } conn = virConnectOpen("qemu:///system"); if (conn == NULL) { fprintf(stderr, "Failed to open connection to qemu:///system\n"); return -1; } if (create_kvm() != 0) { fprintf(stderr, "create_kvm failed!\n"); virConnectClose(conn); return -1; } if (get_status() != 0) { fprintf(stderr, "create_kvm failed!\n"); virDomainFree(dom); virConnectClose(conn); return -1; } if (dom != NULL) virDomainFree(dom); if (conn != NULL) virConnectClose(conn); return 0; } int open_file(char *file_name) { FILE *fp = fopen(file_name, "r+"); assert(fp); int flag = fseek(fp, 0, SEEK_END); assert(flag == 0); int len = ftell(fp); buff = (char *)malloc(sizeof(char) * (len + 1)); flag = fseek(fp, 0, SEEK_SET); assert(flag == 0); int num = fread(buff, 1, len + 1, fp); assert(num == len); return 0; } int create_kvm() { dom = virDomainDefineXML(conn, buff); if (!dom) { virErrorPtr error = virGetLastError(); fprintf(stderr, "virDomainDefineXML failed:%s!\n", error->message); virFreeError(error); return -1; } if (virDomainCreate(dom) < 0) { virErrorPtr error = virGetLastError(); fprintf(stderr, "virDomainCreate failed:%s!\n", error->message); virDomainUndefine(dom); virFreeError(error); //virDomainFree(dom); return -1; } return 0; } int get_status() { char *status = NULL; virErrorPtr error = NULL; int vcpus = 0; unsigned long long node_free_memory = 0; int id = 0; const char *name = NULL; virNodeInfo nodeinfo; virDomainInfo info; // 获取虚拟机状态 fprintf(stdout, "****************************************************\n"); /* get the capabilities of conn*/ status = virConnectGetCapabilities(conn); if (status == NULL) { error = virGetLastError(); fprintf(stderr, "virConnectGetCapabilities failed: %s\n", error->message); virFreeError(error); return -1; } fprintf(stdout, "Capabilities of connection:\t%s\n", status); free(status); status = NULL; /* get the hostname reported from conn*/ status = virConnectGetHostname(conn); if (status == NULL) { error = virGetLastError(); fprintf(stderr, "virConnectGetHostname failed: %s\n", error->message); virFreeError(error); return -1; } fprintf(stdout, "Connection hostname:\t%s\n", status); free(status); status = NULL; /* get the maximum number of vcpus supported by conn3 */ vcpus = virConnectGetMaxVcpus(conn, NULL); if (vcpus < 0) { error = virGetLastError(); fprintf(stderr, "virConnectGetMaxVcpus failed: %s\n", error->message); virFreeError(error); return -1; } fprintf(stdout, "Maximum number of cpus supported on connection:\t%d\n", vcpus); /* get the amount of free memory available on the node from conn4 */ node_free_memory = virNodeGetFreeMemory(conn); if (node_free_memory == 0) { error = virGetLastError(); fprintf(stderr, "virNodeGetFreeMemory failed: %s\n", error->message); virFreeError(error); return -1; } fprintf(stdout, "Node free memory:\t%llu\n", node_free_memory); /* get the node information from conn*/ if (virNodeGetInfo(conn, &nodeinfo) < 0) { error = virGetLastError(); fprintf(stderr, "virNodeGetInfo failed: %s\n", error->message); virFreeError(error); return -1; } fprintf(stdout, "Node information from connection\n"); fprintf(stdout, "Model:\t%s\n", nodeinfo.model); fprintf(stdout, "Memory size:\t%lukb\n", nodeinfo.memory); fprintf(stdout, "Number of CPUs:\t%u\n", nodeinfo.cpus); fprintf(stdout, "MHz of CPUs:\t%u\n", nodeinfo.mhz); fprintf(stdout, "Number of NUMA nodes:\t%u\n", nodeinfo.nodes); fprintf(stdout, "Number of CPU sockets:\t%u\n", nodeinfo.sockets); fprintf(stdout, "Number of CPU cores per socket:\t%u\n", nodeinfo.cores); fprintf(stdout, "Number of CPU threads per core:\t%u\n", nodeinfo.threads); fprintf(stdout, "****************************************************\n"); fprintf(stdout, "id\t名称\t\t状态\n"); fprintf(stdout, "------------------------------------------\n"); id = virDomainGetID(dom); name = virDomainGetName(dom); if (virDomainGetInfo(dom, &info) < 0) { error = virGetLastError(); fprintf(stderr, "virDomainGetInfo failed: %s\n", error->message); virFreeError(error); return -1; } fprintf(stdout, "%d\t%s\t\t%d\n", id, name, info.state); fprintf(stdout, "****************************************************\n"); return 0; }(2)创建所需要的启动配置xml文件 首先需要准备好centos6.3.iso系统镜像文件,在xml中<source file = ‘/home/taiyang/centos6.3.iso‘/>设置其全路径。
然后创建img虚拟机镜像文件#qemu-img create -f qcow2 newlinux.img 15G,在 <source file = ‘/var/lib/libvirt/images/newlinux.img‘/>设置其路径。
最后#vim create_kvm.xml,输入一下内容:
<domain type = 'kvm'> //虚拟机类型,kvm <name>newlinux</name> //虚拟机名称 <memory>1048576</memory> //分配内存,单位kb <vcpu>1</vcpu> //分配vcpu,单位个数 <os> <type arch = 'x86_64' machine = 'pc'>hvm</type> <boot dev = 'cdrom'/> //cd 启动 <boot dev = 'hd'/> //硬盘启动 </os> <features> <acpi/> <apic/> <pae/> </features> <clock offset = 'localtime'/> <on_poweroff>destroy</on_poweroff> <on_reboot>restart</on_reboot> <on_crash>destroy</on_crash> <devices> <emulator>/usr/libexec/qemu-kvm</emulator> <disk type = 'file' device = 'disk'> //对应的镜像,就是之前使用qemu-img命令新建的img文件,注意路径要正确 <driver name = 'qemu' type = 'qcow2'/> <source file = '/var/lib/libvirt/images/newlinux.img'/> <target dev = 'hda' bus = 'ide'/> </disk> <disk type = 'file' device = 'cdrom'> //可选项,iso通常是操作系统的安装光盘 <source file = '/home/taiyang/centos6.3.iso'/> <target dev = 'hdb' bus = 'ide'/> </disk> <interface type = 'bridge'> //libvirt默认虚拟机的网络配置是NAT模式,就是虚拟机与宿主机的网络拓扑是NAT形式。实际中,许多开发者更希望使用网桥模式。 <source bridge = 'virbr0'/> </interface> <input type ='tablet' bus='usb'/> <input type = 'mouse' bus = 'ps2'/> <graphics type = 'vnc' port = '-1' listen = '0.0.0.0' keymap = 'en-us'/> //vnc端口系统自动配置 </devices> </domain>注意:<memory>1048576</memory>单位kb,分配内存过大无法启动;文件路径不正确无法启动。
(3)编译运行
#gcc -g -Wall create_kvm.c -o create -lvirt
#./create create_kvm.xml
可以通过virsh查看创建的虚拟机:
也可以通过virt-manager查看创建的虚拟机:
2、操作虚拟机(开机、关机、查询状态)
(1)C语言代码
/* gcc -g -Wall virtctl.c -o virtctl -lvirt */ /* virtctl guest-name start/shutdown/status */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <libvirt/libvirt.h> #include <libvirt/virterror.h> static int set_start(char *guestname); static int set_shutdown(char *guestname); static int get_status(char *guestname); static virConnectPtr conn = NULL; static virDomainPtr dom = NULL; int main(int argc, char *argv[]) { if (argc != 3) { fprintf(stderr, "Usage:./virtctl guest-name start/shutdown/status\n"); return -1; } conn = virConnectOpen("qemu:///system"); if (conn == NULL) { fprintf(stderr, "Failed to open connection to qemu:///system\n"); return -1; } dom = virDomainLookupByName(conn, argv[1]); if (dom == NULL) { fprintf(stderr, "virDomainLookupByName failed!\n"); virConnectClose(conn); return -1; } if (strcmp(argv[2], "start") == 0 || strcmp(argv[2], "START") == 0) { if (set_start(argv[1]) != 0) { fprintf(stderr, "start failed!\n"); virDomainFree(dom); //virConnectClose(conn); return -1; } } if (strcmp(argv[2], "shutdown") == 0 || strcmp(argv[2], "SHUTDOWN") == 0) { if (set_shutdown(argv[1]) != 0) { fprintf(stderr, "shutdown failed!\n"); virConnectClose(conn); virDomainFree(dom); return -1; } } if (strcmp(argv[2], "status") == 0 || strcmp(argv[2], "STATUS") == 0) { if (get_status(argv[1]) != 0) { fprintf(stderr, "get status failed!\n"); virDomainFree(dom); virConnectClose(conn); return -1; } } if (dom != NULL) virDomainFree(dom); if (conn != NULL) virConnectClose(conn); return 0; } int set_start(char *guestname) { int flag = -1; // 启动已定义虚拟机 flag = virDomainCreate(dom); if (flag != 0) { virErrorPtr error = virGetLastError(); fprintf(stderr, "virDomainCreate failed:%s!\n", error->message); virFreeError(error); return -1; } return 0; } int set_shutdown(char *guestname) { int flag = -1; // 关闭已定义虚拟机 flag = virDomainShutdown(dom); if(flag != 0) { virErrorPtr error = virGetLastError(); fprintf(stderr, "virDomainShutdown failed:%s!\n", error->message); virFreeError(error); return -1; } return 0; } int get_status(char *guestname) { char *status = NULL; virErrorPtr error = NULL; int vcpus = 0; unsigned long long node_free_memory = 0; int id = 0; const char *name = NULL; virNodeInfo nodeinfo; virDomainInfo info; // 获取虚拟机状态 fprintf(stdout, "****************************************************\n"); /* get the capabilities of conn*/ status = virConnectGetCapabilities(conn); if (status == NULL) { error = virGetLastError(); fprintf(stderr, "virConnectGetCapabilities failed: %s\n", error->message); virFreeError(error); return -1; } //fprintf(stdout, "Capabilities of connection:\n%s\n", status); free(status); status = NULL; /* get the hostname reported from conn*/ status = virConnectGetHostname(conn); if (status == NULL) { error = virGetLastError(); fprintf(stderr, "virConnectGetHostname failed: %s\n", error->message); virFreeError(error); return -1; } //fprintf(stdout, "------------------------------------------\n"); fprintf(stdout, "Connection hostname:\t%s\n", status); free(status); status = NULL; /* get the maximum number of vcpus supported by conn3 */ vcpus = virConnectGetMaxVcpus(conn, NULL); if (vcpus < 0) { error = virGetLastError(); fprintf(stderr, "virConnectGetMaxVcpus failed: %s\n", error->message); virFreeError(error); return -1; } fprintf(stdout, "Maximum number of cpus supported on connection:\t%d\n", vcpus); /* get the amount of free memory available on the node from conn4 */ node_free_memory = virNodeGetFreeMemory(conn); if (node_free_memory == 0) { error = virGetLastError(); fprintf(stderr, "virNodeGetFreeMemory failed: %s\n", error->message); virFreeError(error); return -1; } fprintf(stdout, "Node free memory:\t%llu\n", node_free_memory); /* get the node information from conn*/ if (virNodeGetInfo(conn, &nodeinfo) < 0) { error = virGetLastError(); fprintf(stderr, "virNodeGetInfo failed: %s\n", error->message); virFreeError(error); return -1; } fprintf(stdout, "------------------------------------------\n"); fprintf(stdout, "Node information from connection\n"); fprintf(stdout, "Model:\t%s\n", nodeinfo.model); fprintf(stdout, "Memory size:\t%lukb\n", nodeinfo.memory); fprintf(stdout, "Number of CPUs:\t%u\n", nodeinfo.cpus); fprintf(stdout, "MHz of CPUs:\t%u\n", nodeinfo.mhz); fprintf(stdout, "Number of NUMA nodes:\t%u\n", nodeinfo.nodes); fprintf(stdout, "Number of CPU sockets:\t%u\n", nodeinfo.sockets); fprintf(stdout, "Number of CPU cores per socket:\t%u\n", nodeinfo.cores); fprintf(stdout, "Number of CPU threads per core:\t%u\n", nodeinfo.threads); fprintf(stdout, "****************************************************\n"); fprintf(stdout, "id\t名称\t\t状态\n"); fprintf(stdout, "------------------------------------------\n"); id = virDomainGetID(dom); name = virDomainGetName(dom); if (virDomainGetInfo(dom, &info) < 0) { error = virGetLastError(); fprintf(stderr, "virDomainGetInfo failed: %s\n", error->message); virFreeError(error); return -1; } fprintf(stdout, "%d\t%s\t\t%d\n", id, name, info.state); fprintf(stdout, "****************************************************\n"); return 0; }
(2)编译运行
#gcc -g -Wall virtctl.c -o virtctl -lvirt
#./virtctl newlinux status
**************************************************** Connection hostname: localhost.localdomain Maximum number of cpus supported on connection: 16 Node free memory: 1381175296 ------------------------------------------ Node information from connection Model: x86_64 Memory size: 3823996kb Number of CPUs: 4 MHz of CPUs: 3200 Number of NUMA nodes: 1 Number of CPU sockets: 1 Number of CPU cores per socket: 2 Number of CPU threads per core: 2 **************************************************** id 名称 状态 ------------------------------------------ 8 newlinux 1 ****************************************************
3、virsh命令行管理工具
Libvirt有两种控制方式,命令行和图形界面
(1)图形界面:通过执行名virt-manager,启动libvirt的图形界面,在图形界面下可以一步一步的创建虚拟机,管理虚拟机,还可以直接控制虚拟机的桌面。
(2)virsh所有命令行:
help 打印帮助
attach-device 从一个XML文件附加装置
attach-disk 附加磁盘设备
attach-interface 获得网络界面
autostart 自动开始一个域
capabilities 性能
cd change the current directory
connect 连接(重新连接)到 hypervisor
console 连接到客户会话
cpu-baseline compute baseline CPU
cpu-compare compare host CPU with a CPU described by an XML file
create 从一个 XML 文件创建一个域
start 开始一个(以前定义的)非活跃的域
destroy 删除一个域
detach-device 从一个 XML 文件分离设备
detach-disk 分离磁盘设备
detach-interface 分离网络界面
define 从一个 XML 文件定义(但不开始)一个域
domid 把一个域名或 UUID 转换为域 id
domuuid 把一个域名或 id 转换为域 UUID
dominfo 域信息
domjobinfo domain job information
domjobabort abort active domain job
domname 将域 id 或 UUID 转换为域名
domstate 域状态
domblkstat 获得域设备块状态
domifstat 获得域网络接口状态
dommemstat get memory statistics for a domain
domblkinfo domain block device size information
domxml-from-native Convert native config to domain XML
domxml-to-native Convert domain XML to native config
dumpxml XML 中的域信息
edit 编辑某个域的 XML 配置
find-storage-pool-sources 发现潜在存储池源
find-storage-pool-sources-as 找到潜在存储池源
freecell NUMA可用内存
hostname 打印管理程序主机名
list 列出域
migrate 将域迁移到另一个主机中
migrate-setmaxdowntime set maximum tolerable downtime
net-autostart 自动开始网络
net-create 从一个 XML 文件创建一个网络
net-define 从一个 XML 文件定义(但不开始)一个网络
net-destroy 删除一个网络
net-dumpxml XML 中的网络信息
net-edit 为网络编辑 XML 配置
net-list 列出网络
net-name 把一个网络UUID 转换为网络名
net-start 开始一个(以前定义的)不活跃的网络
net-undefine 取消定义一个非活跃的网络
net-uuid 把一个网络名转换为网络UUID
iface-list list physical host interfaces
iface-name convert an interface MAC address to interface name
iface-mac convert an interface name to interface MAC address
iface-dumpxml interface information in XML
iface-define define (but don‘t start) a physical host interface from an XML file
iface-undefine undefine a physical host interface (remove it from configuration)
iface-edit edit XML configuration for a physical host interface
iface-start start a physical host interface (enable it / "if-up")
iface-destroy destroy a physical host interface (disable it / "if-down")
managedsave managed save of a domain state
nodeinfo 节点信息
nodedev-list 这台主机中中的枚举设备
nodedev-dumpxml XML 中的节点设备详情
nodedev-dettach dettach node device from its device driver
nodedev-reattach reattach node device to its device driver
nodedev-reset 重置节点设备
nodedev-create create a device defined by an XML file on the node
nodedev-destroy destroy a device on the node
nwfilter-define define or update a network filter from an XML file
nwfilter-undefine undefine a network filter
nwfilter-dumpxml network filter information in XML
nwfilter-list list network filters
nwfilter-edit edit XML configuration for a network filter
pool-autostart 自动启动某个池
pool-build 建立池
pool-create 从一个 XML 文件中创建一个池
pool-create-as 从一组变量中创建一个池
pool-define 在一个 XML 文件中定义(但不启动)一个池
pool-define-as 在一组变量中定义池
pool-destroy 销毁池
pool-delete 删除池
pool-dumpxml XML 中的池信息
pool-edit 为存储池编辑 XML 配置
pool-info 存储池信息
pool-list 列出池
pool-name 将池 UUID 转换为池名称
pool-refresh 刷新池
pool-start 启动一个(以前定义的)非活跃的池
pool-undefine 取消定义一个不活跃的池
pool-uuid 把一个池名称转换为池 UUID
secret-define define or modify a secret from an XML file
secret-dumpxml secret attributes in XML
secret-set-value set a secret value
secret-get-value Output a secret value
secret-undefine undefine a secret
secret-list list secrets
pwd print the current directory
quit 退出这个非交互式终端
exit 退出这个非交互式终端
reboot 重新启动一个域
restore 从一个存在一个文件中的状态恢复一个域
resume 重新恢复一个域
save 把一个域的状态保存到一个文件
schedinfo 显示/设置日程安排变量
dump 把一个域的内核 dump 到一个文件中以方便分析
shutdown 关闭一个域
setmem 改变内存的分配
setmaxmem 改变最大内存限制值
setvcpus 改变虚拟 CPU 的号
suspend 挂起一个域
ttyconsole tty 控制台
undefine 取消定义一个非活跃的域
update-device update device from an XML file
uri 打印管理程序典型的URI
vol-create 从一个 XML 文件创建一个卷
vol-create-from create a vol, using another volume as input
vol-create-as 从一组变量中创建卷
vol-clone clone a volume.
vol-delete 删除卷
vol-wipe wipe a vol
vol-dumpxml XML 中的卷信息
vol-info 存储卷信息
vol-list 列出卷
vol-pool returns the storage pool for a given volume key or path
vol-path returns the volume path for a given volume name or key
vol-name returns the volume name for a given volume key or path
vol-key returns the volume key for a given volume name or path
vcpuinfo 域 vcpu 的信息
vcpupin 控制域 vcpu affinity
version 显示版本
vncdisplay vnc 显示
snapshot-create Create a snapshot
snapshot-current Get the current snapshot
snapshot-delete Delete a domain snapshot
snapshot-dumpxml Dump XML for a domain snapshot
snapshot-list List snapshots for a domain
snapshot-revert Revert a domain to a snapshot
virsh -c qemu:///system #连接本机
virsh -c qemu+ssh://root@192.168.40.125 #连接远程libvirt,也可通过connect命令
virsh list #显示本地活动虚拟机
virsh list --all #显示本地所有的虚拟机(活动的+不活动的)
virsh define create_kvm.xml #通过配置文件定义一个虚拟机(这个虚拟机还不是活动的)
virsh start newlinux #启动名字为virtual的非活动虚拟机
virsh create create_kvm.xml # 创建虚拟机(创建后,虚拟机立即执行,成为活动主机)
virsh suspend newlinux # 暂停虚拟机
virsh resume newlinux # 启动暂停的虚拟机
virsh shutdown newlinux # 正常关闭虚拟机
virsh destroy newlinux # 强制关闭虚拟机
virsh dominfo newlinux #显示虚拟机的基本信息
virsh domname 2 # 显示id号为2的虚拟机名
virsh domid newlinux # 显示虚拟机id号
virsh domuuid newlinux # 显示虚拟机的uuid
virsh domstate newlinux # 显示虚拟机的当前状态
virsh dumpxml newlinux # 显示虚拟机的当前配置文件(可能和定义虚拟机时的配置不同,因为当虚拟机启动时,需要给虚拟机分配id号、uuid、vnc端口号等等)
virsh setmem newlinux 512000 #给不活动虚拟机设置内存大小
virsh setvcpus newlinux 4 # 给不活动虚拟机设置cpu个数
virsh edit newlinux # 编辑配置文件(一般是在刚定义完虚拟机之后)
例1:virsh创建kvm虚拟机#qemu-img create -f qcow2 newlinux.qcow2 15G //制作虚拟机镜像 #vim create_kvm.xml //创建配置文件 #virsh define create_kvm.xml //创建虚拟机 #virsh start newlinux //启动虚拟机 #virsh vncdisplay newlinux //查看虚拟机的vnc端口,然后就可以通过vnc登录 #vncviewer :0来完成虚拟机的安装例2:Add CDROM
#virsh attach-disk guest01 /root/disc1.iso hdc --driver file --type cdrom --mode readonly
例3:Change CDROM
#virsh attach-disk guest01 /root/disc2.iso hdc --driver file --type cdrom --mode readonly
例4:Remove CDROM
#virsh attach-disk guest01 " " hdc --driver file --type cdrom --mode readonly
三、参考
(1)libvirt官方的APIs列表:http://libvirt.org/html/index.html(2)网络博客:http://blog.csdn.net/qq250941970/article/details/5989940
(3)libvirt架构及源码分析:http://blog.chinaunix.net/uid-20940095-id-3813601.html
四、总结
(1)Libvirt涉及内容众多,可以先了解qemu-img、virsh、virt-manager、virt-install等工具。
(2)有时间的可以下载开源的Libvirt源码分析,这样对自己的帮助更大。