问题重现
环境
-
在支持Intel VT-x虚拟化的PC上安装Windows7 SP1 64位;
-
在VMware Workstation 12.1.0中安装CentOS 7 x64,同时开启Intel VT-x支持;
-
在虚拟机内部编译安装Qemu 2.5.0(编译参数:--target-list=x86_64-softmmu --enable-debug-tcg --enable-debug-info --enable-sdl --enable-curses --enable-vnc --enable-kvm)和Libvirt 1.3.4(编译参数:--disable-nls --enable-debug)。
步骤
- 在LVM分区上直接创建qcow2的外部镜像:
- 创建测试磁盘
$ mkdir -pv /opt/vm/$ dd if=/dev/zero of=/opt/vm/disk.img bs=1024 count=4M$ losetup /dev/loop0 /opt/vm/disk.img
- 创建LVM卷组和分区
$ vgcreate qemu /dev/loop0$ lvcreate -L 256M -n image01 qemu$ lvcreate -L 256M -n image02 qemu$ lvcreate -L 256M -n image03 qemu
- 格式化
$ qemu-img create -f qcow2 /dev/qemu/image01 128M$ qemu-img create -f qcow2 -b /dev/qemu/image01 /dev/qemu/image02 128M$ qemu-img create -f qcow2 -b /dev/qemu/image02 /dev/qemu/image03 128M
- 配置虚拟机,配置文件test.xml 的内容如下:
test 262144 1 hvm destroy restart destroy /opt/vm/qemu/build-v2.5.0/x86_64-softmmu/qemu-system-x86_64
- 启动虚拟机,进入Libvirt的编译目录,依次执行如下命令:
$ ./src/virtlockd -d$ ./daemon/libvirtd -d$ ./tools/virsh define test.xml$ ./tools/virsh start testerror: Failed to start domain testerror: internal error: process exited while connecting to monitor: 2016-05-16T02:43:42.812996Z qemu-system-x86_64: -drive file=/dev/qemu/image03,format=qcow2,if=none,id=drive-scsi0-0-0: Could not open backing file: Could not open backing file: Could not open '/dev/qemu/image01': Operation not permitted
原因分析
-
这个问题仅仅在使用LVM分区直接创建qcow2多级快照,且使用Libvirt进行启动时出现权限问题。修改文件的GID、UID和权限,libvirtd的执行用户组和用户,关闭或配置AppArmor均无法解决此问题。
-
使用相同的命令行参数直接使用Qemu命令行运行正常,但使用Libvirt启动时就会出现上述问题。通过对Qemu的源码分析和调试,发现最终问题出在打开镜像文件的open函数上,均会以errno为1的方式返回错误。且在两种运行方式中,open时的Linux权限相关设置完全相同。
-
而这两种启动方式的主要差别在于,libvirtd启动时使用了QMP与Qemu进行通信,以及fork时设置了不同的进程运行环境。通过测试和源码分析发现open函数失败在Qemu和libvirtd建立QMP通信之前,所以可以排除是QMP导致的问题。
因此问题可能就出现在Libvirt启动Qemu时对其进程运行环境进行的设置,最终通过测试和源码分析发现问题就在这里。
过程分析
-
libvirtd在启动Qemu时,会分析Qemu的磁盘配置,根据其中设置的路径和类型分析它的Backing File,并生成一个链表。上述配置<source file='/dev/qemu/image03'/>则会生成类似这两的结构“image03-> image02-> image01”。
-
然而在这个分析过程中,如果imageXX中并未包含Backing File的格式定义,比如image03中并未说明image02的格式,image02中并未说明image01的格式;同时在libvirtd的qemu.conf中并未设置“allow_disk_format_probing = 1”来探测image02和image01的格式。则libvirtd只会生成“image03-> image02”这一级的链表,而不会进行更深度的遍历。
-
在libvirtd创建(fork)出Qemu进程,在执行(exec)之前会对这个子进程进行一系列的设置,其中包括对子进程cgroup的设置。在对进程的设备访问权限进行设置时使用的是白名单,只会允许Qemu子进程对之前分析出来的磁盘链表(如image03和 image02,却没有image01的访问权限,所以最终在打开image01时,会因为权限不足而打开失败。
相关源码
-
src/util/virstoragefile.c中virStorageFileGetMetadataInternal()、qcow2GetBackingStore()和qcowXGetBackingStore()函数,启动Qemu进程前分析配置文件中设置的磁盘镜像的Backing File和Format。
-
src/storage/storage_driver.c中virStorageFileGetMetadataRecurse()函数,迭代调用virStorageFileGetMetadataInternal()遍历所有磁盘镜像和他的Backing File。
-
src/qemu/qemu_process.c中qemuProcessLaunch()函数,fork新进程,设置新进程,exec新Qemu进程。
-
src/qemu/qemu_cgroup.c中qemuSetupDevicesCgroup()函数,设置进程的设备cgroup配额。
解决办法
- 修改/etc/libvirt/qemu.conf中的cgroup设置,默认不禁止对设备文件(devices)的访问,或者将要访问的设备文件加入允许列表。
把
cgroup_controllers = [ "cpu", "devices", "memory", "blkio", "cpuset", "cpuacct" ]
改为
cgroup_controllers = [ "cpu", "memory", "blkio", "cpuset", "cpuacct" ]
或者把image01加入默认允许访问列表
cgroup_device_acl = [ "/dev/null", "/dev/full", "/dev/zero", "/dev/random", "/dev/urandom", "/dev/ptmx", "/dev/kvm", "/dev/kqemu", "/dev/rtc","/dev/hpet", "/dev/vfio/vfio", "/dev/qemu/image01"]
- 修改/etc/libvirt/qemu.conf,允许镜像文件的格式探测(有安全风险,不建议使用)。
allow_disk_format_probing = 1
- 在创建新的qcow2镜像时指定Backing File的格式。
$ qemu-img create -f qcow2 /dev/qemu/image01 128M$ qemu-img create -f qcow2 -b /dev/qemu/image01 -o backing_fmt=qcow2 /dev/qemu/image02 128M$ qemu-img create -f qcow2 -b /dev/qemu/image02 -o backing_fmt=qcow2 /dev/qemu/image03 128M
- 使用rebase命令修改已存在的镜像的Backing File格式。
$ qemu-img rebase -f qcow2 -b /dev/qemu/image02 -F qcow2 /dev/qemu/image03$ qemu-img rebase -f qcow2 -b /dev/qemu/image01 -F qcow2 /dev/qemu/image02
优先推荐第3种或者第4种方法。