从镜像中 exclude 文件或目录, 缩减镜像大小

我的目标是从在基于镜像创建虚拟机的时候 exclude 部分文件或目录,使得创建虚机更快。本来我想如果 qemu-image convert 的时候支持 exclude,那就简单了,但是不支持,查了更多资料以后,我发现 guestfish 的 tar-out 支持 exclude,但是出来的是 tar 包,而不是 image 了(virt-tar-out 这个命令和 tar-out 类似,但是不支持 exclude)。


下面举个栗子,记录一下过程。

我的 domain 是 vm1, 系统盘名称叫 vm1,数据盘(/home)叫 vm1_data。

首先,对于系统盘的 vm1,我打算不使用 tar-out 打包出来,而是还是用 qemu-image convert 命令转换成 qcow2 格式,即使 vm1 已经是 qcow2,转换之后的 image 大小也可能减小,而且我使用 -c 选项启用压缩,转换之后的 image 大概 900M,大小我能接受。

下面是完整的转换命令:

qemu-img convert -c -O qcow2 vm1 /mfs/vm1

这种方法有个问题,如果系统盘不止 vm1,还有其他盘,会有问题,但是这种情况几乎没有,暂不考虑。


对于数据盘 vm1_data,不用 image ,使用 tar-out,而且我准备 exclude 出名字为 log 或者 logs 的目录,所以我先要获取 log 或者 logs 的目录路径。

在 vm1 关机情况下,对整个 domain 查找 /home 下 log 或 logs 的目录:

guestfish -d vm1 -i sh \
"find /home -type d -name log -o -name logs "

结果是:

/home/op/assets_agent_v2/logs
/home/ossec/ossec/logs

然后,用 tar-out 打包出来(启用压缩),格式如下:

guestfish -d vm1 \
run : mount /dev/datavg/home / : \
tar-out / home.tgz compress:gzip \
"excludes:op/assets_agent_v2/logs/* ossec/ossec/logs/*"

home.tgz 即是数据盘中去除日志的目录,当然在系统的实现中,我们支持自定义 exclude 目录。


在开机状态下,不能使用 -d vm1,只能用 add vm1_data 这种格式,此次 find 不支持 -type d 类似的参数,只能使用简单的 find 目录:

guestfish add vm1_data \
: run : mount /dev/datavg/home / : find /

这条命令会显示所有的文件和目录,如果想去重 log 或者 logs 目录,可以做聚合。

然后 tar-out 命令类似下面:

guestfish add vm1_data \
: run : mount /dev/datavg/home / : \
tar-out / home.tgz compress:gzip \
"excludes:op/assets_agent_v2/logs/* ossec/ossec/logs/*"

由于在开机状态下,我们指定了 /home 的 image,如果 /home 有多个 image,那么这种情况就需要关机了,系统中应该判断,如果有多个数据盘,需提示关机。


那么,对于 tar-out 出来的包,怎么「塞进」新的数据盘呢。

先创建 vm2_data:

virsh vol-create-as –pool vm_storage_pool \
–name vm2_data –capacity 10G –allocation 1G \
–format qcow2

然后创建 lvm 和文件系统,和 vm1_data 保持一致:

virt-format -a vm2_data –lvm=/dev/datavg/home \
–filesystem=ext4

查看 lvm group:

# virt-filesystems -a vm2_data –volume-groups
/dev/datavg

查看 physical volumes:

# virt-filesystems -a vm2_data –physical-volumes
/dev/sda1

查看文件系统:

# virt-filesystems -a vm2_data -l

Name Type VFS Label Size Parent
/dev/datavg/home filesystem ext4 – 10733223936 –

准备工作完成了,可以 tar-in 数据了:

guestfish add vm2_data \
: run : mount /dev/datavg/home / : \
tar-in home.tgz / compress:gzip

如果 home.tgz 通过 http 获取,可以这样:

curl http://nosa.me/vm2_home.tgz | \
guestfish add vm2_data : run : \
mount /dev/datavg/home / : \
tar-in – / compress:gzip

最后,借贵地记录一下 allocation 和 capacity 的说明。

allocation
Providing the total storage allocation for the volume. This may be smaller than the logical capacity if the volume is sparsely allocated. It may also be larger than the logical capacity if the volume has substantial metadata overhead. This value is in bytes. If omitted when creating a volume, the volume will be fully allocated at time of creation. If set to a value smaller than the capacity, the pool has the option of deciding to sparsely allocate a volume. It does not have to honour requests for sparse allocation though. Different types of pools may treat sparse volumes differently. For example, the logical pool will not automatically expand volume’s allocation when it gets full; the user is responsible for doing that or configuring dmeventd to do so automatically.By default this is specified in bytes, but an optional attribute unit can be specified to adjust the passed value. Values can be: ‘B’ or ‘bytes’ for bytes, ‘KB’ (kilobytes, 103 or 1000 bytes), ‘K’ or ‘KiB’ (kibibytes, 210 or 1024 bytes), ‘MB’ (megabytes, 106 or 1,000,000 bytes), ‘M’ or ‘MiB’ (mebibytes, 220 or 1,048,576 bytes), ‘GB’ (gigabytes, 109 or 1,000,000,000 bytes), ‘G’ or ‘GiB’ (gibibytes, 230 or 1,073,741,824 bytes), ‘TB’ (terabytes, 1012 or 1,000,000,000,000 bytes), ‘T’ or ‘TiB’ (tebibytes, 240 or 1,099,511,627,776 bytes), ‘PB’ (petabytes, 1015 or 1,000,000,000,000,000 bytes), ‘P’ or ‘PiB’ (pebibytes, 250 or 1,125,899,906,842,624 bytes), ‘EB’ (exabytes, 1018 or 1,000,000,000,000,000,000 bytes), or ‘E’ or ‘EiB’ (exbibytes, 260 or 1,152,921,504,606,846,976 bytes). Since 0.4.1, multi-character unit since 0.9.11
capacity
Providing the logical capacity for the volume. This value is in bytes by default, but a unit attribute can be specified with the same semantics as for allocationThis is compulsory when creating a volume. Since 0.4.1

把 raw 格式的虚拟机 转换成 qcow2 格式的虚拟机

在这篇文章,我打算把老的用 raw 格式的虚拟机 转换成 qcow2 的虚拟机,qcow2 镜像小,可以实现动态迁移的目的。

 

raw 格式的虚拟机系统盘 是 /dev/vm_storage_pool_vg/vm2, 数据盘是 /dev/vm_storage_pool_vg/vm2_data,下面把 raw 格式的镜像转换成 qcow2 。

[shell]qemu-img convert -f raw /dev/vm_storage_pool_vg/vm2 -O qcow2 /tmp/vmN.img[/shell]

[shell]qemu-img convert -f raw /dev/vm_storage_pool_vg/vm2_data -O qcow2 /tmp/vmN_data.img[/shell]

同时把 vm2 的虚拟机配置文件 dump 出来。

virsh dumpxml vm2 > /tmp/vmN.xml

然后 把 /tmp/vmN.img 、/tmp/vmN_data.img 和 /tmp/vmN.xml 都拷贝 到 「新宿主机」上。

 

假设新虚拟机名称叫 vm3 ,我们先新宿主机上 创建 和 原 虚拟机vm2 大小一样的镜像,如下:

[shell]virsh vol-create-as –pool vm_sp –name vm3.img –capacity 20G –allocation 1G –format qcow2[/shell]

[shell]virsh vol-create-as –pool vm_sp –name vm3_data.img –capacity 10G –allocation 1G –format qcow2[/shell]

然后把 vmN.img 覆盖 vm3.img ,把 vmN_data.img 覆盖 vm3_data.img ,vmN.xml 文件 拷贝到 /etc/libvirt/qemu/vm3.xml 。

修改 /etc/libvirt/qemu/vm3.xml 的几项内容:
1. sed -i “s/vm2/vm3/g” /etc/libvirt/qemu/vm3.xml
2. 修改掉 uuid
3. 修改 mac 地址
4. 修改磁盘路径和类型等信息。
5. 把 disk 和 interface 的 <address 一行都删掉

然后重定义使之生效:
# virsh define /etc/libvirt/qemu/vm3.xml

 

然后修改 vm3 的IP 、主机名 和 Puppet 等一些信息,跑如下的脚本:

[shell]

#!/usr/bin/guestfish -f

domain vm3
run
list-filesystems
mount /dev/domovg/root /
mount /dev/datavg/home /home/
command "/bin/rm -f /etc/udev/rules.d/70-persistent-net.rules"
command "sed -i ‘s/IPADDR=.*/IPADDR=10.19.26.102/g’ /etc/sysconfig/network-scripts/ifcfg-em2"
command "sed -i ‘s/HOSTNAME=.*/HOSTNAME=test100.hlg01/g’ /etc/sysconfig/network"
command "/bin/rm -rf /var/lib/puppet/"

[/shell]

然后 virsh start vm3 起来,此时 还有一个问题,就是 /etc/udev/rules.d/70-persistent-net.rules 显示网卡 是 eth0 和 eth1 ,我们要把改成 em2 和 em1 。

所以,virsh destory vm3 ,然后修改:

[shell]

#!/usr/bin/guestfish -f

domain vm3
run
list-filesystems
mount /dev/domovg/root /
mount /dev/datavg/home /home/
command "sed -i ‘s/eth0/em2/g;s/eth1/em1/g’ /etc/udev/rules.d/70-persistent-net.rules"

[/shell]

最后,virsh start vm3 起来,OVER~~ 。

 

根据 qcow2 格式的虚拟机生成完全相同的虚拟机

其实这个才是我真实的需求,也就是根据任何一个虚拟机生成一个完全一样的虚拟机。

 

事实上,在新的宿主机先创建相同空间的 qcow2 镜像,然后把 原镜像文件 拷贝到 新宿主机,原镜像文件 覆盖 新创建镜像文件,然后再编辑  新虚拟机 的配置文件,就可以启动了,当然启动之后 新虚拟机的 IP 和主机名 等信息 和原虚拟机一样,这个我们之后再讨论如何修改。

 

这里举个例子,我们的目标是把 一台宿主机的 vm1 拷贝成 另一台宿主机 的 vm2 并启动,存储池都是 vm_sp ,基于「目录」。

 

原宿主机:

# virsh vol-info vm1.img –pool vm_sp
Name: vm1.img
Type: file
Capacity: 20.00 GiB
Allocation: 1.65 GiB

# virsh vol-info vm1_data.img –pool vm_sp
Name: vm1_data.img
Type: file
Capacity: 10.00 GiB
Allocation: 292.27 MiB

 

新宿主机:

# virsh vol-create-as –pool vm_sp –name vm2.img –capacity 20G –allocation 1G –format qcow2
Vol vm2.img created

# virsh vol-create-as –pool vm_sp –name vm2_data.img –capacity 10G –allocation 1G –format qcow2
Vol vm2_data.img created

 

原宿主机

把 vm1.img 和 vm1_data.img 拷贝到 新宿主机,并分别覆盖 vm2.img 和 vm2_data.img ;

然后 virsh dumpxml vm1 >/tmp/vm2.xml,把 /tmp/vm2.xml 拷贝到 新宿主机的 /etc/libvirt/qemu/vm2.xml

 

在 新宿主机:
修改 /etc/libvirt/qemu/vm2.xml 的几项内容:
1. sed -i “s/vm1/vm2/g” /etc/libvirt/qemu/vm2.xml
2. 修改掉 uuid
3. 修改 mac 地址
4. 把disk 和 interface 的 <address 一行都删掉

# virsh define /etc/libvirt/qemu/vm2.xml

然后我们要修改 虚拟机的网络、主机名、Puppet等配置,下篇文章中继续介绍。

 

在基于「目录」的存储池上 创建 qcow2 格式的虚拟机

我为了使用 「对一个虚拟机创建镜像,并恢复成另一个虚拟机」的功能,准备使用 qcow2 格式的镜像,同时使用 基于「目录」的存储池。


宿主机上的 KVM 等相关工具都已经装好了,而且配置了桥接网卡,br1 对应外网, br2 对应内网。

安装 qemu-img ,用于创建 qcow2 格式的镜像文件

yum -y install qemu-img

下面命令使用一个 lvm 的卷 来作为 存储池 的存储,使用基于 「目录」的存储池,如果想增加存储池的大小,增加卷的大小即可。

lvcreate -n vm_sp -L 100G vm_storage_pool_vg
mkfs.ext4 /dev/vm_storage_pool_vg/vm_sp
mkdir /vm_sp
chown root:root /vm_sp
chmod 700 /vm_sp
mount -t ext4 /dev/vm_storage_pool_vg/vm_sp /vm_sp/
virsh pool-define-as vm_sp --type dir --target /vm_sp
virsh pool-info vm_sp

查看存储池信息,开启存储池子,并设置为自动启动。

# virsh pool-list --all
Name State Autostart
-----------------------------------------
vm_sp inactive no

# virsh pool-start vm_sp
Pool vm_sp started

# virsh pool-list
Name State Autostart
-----------------------------------------
vm_sp active no

# virsh pool-autostart vm_sp
Pool vm_sp marked as autostarted

# virsh pool-list
Name State Autostart
-----------------------------------------
vm_sp active yes

# cat /etc/libvirt/storage/vm_sp.xml
<!–
WARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE
OVERWRITTEN AND LOST. Changes to this xml configuration should be made using:
virsh pool-edit vm_sp
or other application using the libvirt API.
–>

<pool type=’dir’>
<name>vm_sp</name>
<uuid>784ffb8d-3269-3f92-6fe2-4f706c0c576b</uuid>
<capacity unit=’bytes’>0</capacity>
<allocation unit=’bytes’>0</allocation>
<available unit=’bytes’>0</available>
<source>
</source>
<target>
<path>/vm_sp</path>
<permissions>
<mode>0755</mode>
<owner>-1</owner>
<group>-1</group>
</permissions>
</target>
</pool>

另外,下面两个命令:
virsh pool-destroy 用于销毁存储池
virsh pool-undefine 用于取消存储池的定义


创建 vm1.img ,使用 qcow2 格式,初始分配 1G

# virsh vol-create-as --pool vm_sp --name vm1.img --capacity 20G --allocation 1G --format qcow2

# file /vm_sp/vm1.img
/vm_sp/vm1.img: Qemu Image, Format: Qcow , Version: 2

# virsh vol-info /vm_sp/vm1.img
Name: vm1.img
Type: file
Capacity: 20.00 GiB
Allocation: 136.00 KiB

再创建一个 数据盘

virsh vol-create-as --pool vm_sp --name vm1_data.img --capacity 10G --allocation 1G --format qcow2

安装虚拟机:

svirt-install --name vm1 --ram 16000 --vcpus=4 --disk path=/vm_sp/vm1.img -localtime --accelerate --location=/tmp/CentOS-6.3-x86_64_Multi_Choice_20120814.iso -x "ip=10.19.26.100 netmask=255.255.255.0 gateway=10.19.26.1 dns=10.0.12.234 dnsdomain=wandoujia.com ks=http://10.0.11.12/ks/centos_6.3_x64_kvm_guest.cfg console=tty0 console=ttyS0,115200n8" --nographics --network bridge=br2

这个命令仅供参考。

这里我修改 /etc/libvirt/qemu/vm1.xml ,增加第二块盘做数据盘,第二块网卡做公网网卡。

<disk type=’file’ device=’disk’>
    <driver name=’qemu’ type=’qcow2′ cache=’none’/>
    <source file=’/vm_sp/vm1_data.img’/>
    <target dev=’vdb’ bus=’virtio’/>
</disk>

<interface type=’bridge’>
    <mac address=’52:54:00:6a:e8:25’/>
    <source bridge=’br1’/>
    <model type=’virtio’/>
</interface>

然后重定义配置文件,使之生效。

virsh define /etc/libvirt/qemu/vm1.xml

然后重启 vm1 。

virsh destroy vm1
virsh start vm1

然后 virsh console 进入系统配置网卡和主机名等,使系统可用。


删除虚拟机:

name="vm1"
virsh destory $name
virsh undefine $name
virsh vol-delete --pool vm_sp ${name}.img
virsh vol-delete --pool vm_sp ${name}_data.img

那么现在有个需求,我们要对 qcow2 镜像增加大小,怎么做呢,看下面。

# qemu-img info vm1_data.img
image: vm1_data.img
file format: qcow2
virtual size: <span style="color: #0000ff;">10G</span> (10737418240 bytes)
disk size: 292M
cluster_size: 65536

# qemu-img resize vm1_data.img +10G
Image resized.

# qemu-img info vm1_data.img
image: vm1_data.img
file format: qcow2
virtual size: <span style="color: #0000ff;">20G</span> (21474836480 bytes)
disk size: 292M
cluster_size: 65536

# qemu-img resize vm1_data.img -- 10G

This image format does not support resize

可以看到,qcow2 只能增大,不能减小;但是注意,如果用存储池的话,这么加 在存储池里用下面命令是看不到的:

virsh vol-info vm1_data --pool vm_storage_pool

此时,可以用这个命令:

virsh vol-resize vm1_data --capacity 20G --pool vm_storage_pool

那么,增加的大小如何在虚拟机中生效呢,我们以 数据盘 vm1_data 举例,它在虚拟机中是 /dev/vdb,会被做成一个pv ,VG 名称是 datavg 。

通过 virsh destory vm1 和 virsh start vm1 的方式把虚拟机重启了(在虚拟机上 reboot 没用),可用看到 /dev/vdb 空间增加了10G,然后用 pvresize 命令使 pv 空间生效 。

# pvresize /dev/vdb
Physical volume "/dev/vdb" changed
1 physical volume(s) resized / 0 physical volume(s) not resized

# pvs
PV VG Fmt Attr PSize PFree
/dev/vdb datavg lvm2 a– 20.00g 10.00g

另外,如果要增加 vm1.img 这种系统盘大小,同样重启机器之后也会看到 /dev/vda 变大了,但是 pvresize 不管用了,因为我们把 /dev/vda 分了几个区,有一个还做了 swap,此时我们可以用「新建」分区然后加入 VG 的方式,这种方式比较稳妥。

挂载qcow2 镜像文件中的的 lvm 分区

先用qemu-img 生成一个qcow2的镜像

# qemu-img convert /dev/vm_storage_pool_vg/vm07 /dev/vm_storage_pool_vg/vm07_data -O qcow2 vm07.qcow2

/dev/vm_storage_pool_vg/vm07 /dev/vm_storage_pool_vg/vm07_data 分别是系统盘和数据盘,都是raw 格式的,里面有LVM分区,所以转换之后 vm07.qcow2 是含有LVM的qcow2镜像。
先挂载qcow2镜像。

# modprobe nbd max_part=16

# qemu-nbd -c /dev/nbd0 vm07.qcow2

# partprobe /dev/nbd0

# ls /dev/nbd0*
/dev/nbd0 /dev/nbd0p1 /dev/nbd0p2 /dev/nbd0p3

# fdisk -ul /dev/nbd0

Disk /dev/nbd0: 180.4 GB, 180388626432 bytes
255 heads, 63 sectors/track, 21931 cylinders, total 352321536 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x0007e2d5

Device Boot Start End Blocks Id System
/dev/nbd0p1 * 2048 1050623 524288 83 Linux
Partition 1 does not end on cylinder boundary.
/dev/nbd0p2 1050624 9439231 4194304 82 Linux swap / Solaris
Partition 2 does not end on cylinder boundary.
/dev/nbd0p3 9439232 37748735 14154752 8e Linux LVM

可以看到LVM分区是/dev/nbd0p3,下面我们想办法挂载它。

可以算出偏移量 9439232 * 512 = 4832886784

删除映射
# qemu-nbd -d /dev/nbd0

重启以偏移量做映射
# qemu-nbd -c /dev/nbd0 vm07.qcow2 -o 4832886784

这样我们就可以看到LVM设备了- 第二个domovg 。
# vgs -v
Finding all volume groups
Finding volume group “domovg”
Archiving volume group “domovg” metadata (seqno 6).
Archiving volume group “domovg” metadata (seqno 7).
Creating volume group backup “/etc/lvm/backup/domovg” (seqno 7).
Finding volume group “domovg”
Archiving volume group “domovg” metadata (seqno 7).
Archiving volume group “domovg” metadata (seqno 6).
Creating volume group backup “/etc/lvm/backup/domovg” (seqno 6).
VG Attr Ext #PV #LV #SN VSize VFree VG UUID
domovg wz–n- 32.00m 1 6 0 835.09g 27.09g j6085A-ldRg-8gOV-F9pH-rnl9-EfvF-DwdA05
domovg wz–n- 32.00m 1 5 0 13.47g 4.47g xIRE6j-eOhA-lwkI-yhci-WCY1-Noaa-XvaB9D

看到有两个domovg,我把本机的domovg 改掉(本机是测试环境),保持我们要挂载的LVM设备不变
# vgrename j6085A-ldRg-8gOV-F9pH-rnl9-EfvF-DwdA05 domovg2
Volume group “domovg” successfully renamed to “domovg2”

# vgs -v
Finding all volume groups
Finding volume group “domovg2”
Finding volume group “domovg”
VG Attr Ext #PV #LV #SN VSize VFree VG UUID
domovg wz–n- 32.00m 1 5 0 13.47g 4.47g xIRE6j-eOhA-lwkI-yhci-WCY1-Noaa-XvaB9D
domovg2 wz–n- 32.00m 1 6 0 835.09g 27.09g j6085A-ldRg-8gOV-F9pH-rnl9-EfvF-DwdA05

激活所有LVM设备
# lvm vgchange -ay
6 logical volume(s) in volume group “domovg2” now active
5 logical volume(s) in volume group “domovg” now active

# ls /dev/mapper/
control domovg2-opt domovg2-tmp domovg2-var domovg-root domovg-usr
domovg2-home domovg2-root domovg2-usr domovg-opt domovg-tmp domovg-var

# mount /dev/mapper/domovg-root /mnt/

# ls /mnt/
bin dev home lib64 media opt root selinux sys usr
boot etc lib lost+found mnt proc sbin srv tmp var

如果要修改的話,注意下面几个文件:
在 /mnt/etc/sysconfig/network 里面修改主机名
在 /mnt/etc/sysconfig/network-scripts/ifcfg-em2 修改IP地址。
这个文件也要修改下 /mnt/etc/udev/rules.d/70-persistent-net.rules ,否则MAC地址会有问题。

 

如果系统里没有nbd模块或者qemu-nbd工具,请看:

http://nosa.me/2014/11/09/%E5%AE%89%E8%A3%85-nbd-%E6%A8%A1%E5%9D%97-%E5%92%8C-qemu-nbd-%E5%B7%A5%E5%85%B7/