把 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~~ 。

 

用 guestfish 修改虚拟机镜像里的文件

接上文,在新宿主机上生成的新虚拟机为 vm2 ,镜像文件分别为 /vm_sp/vm2.img (/ 分区)和 /vm_sp/vm2_data.img (/home分区,做数据盘),格式都是 qcow2,/vm_sp/ 是基于 目录的存储池。

 

首先先把 vm2 关机,否则无法修改 虚拟机内容。

 

安装 guestfish 命令
# yum -y install guestfish

 

我们先来看看单条命令,比如你只想修改主机名,可以这样:

[shell]
guestfish -d vm2 -i sh \
"sed -i ‘s/HOSTNAME.*/HOSTNAME=test0.hlg01/g’ /etc/sysconfig/network"
[/shell]

-d 表示 指定 虚拟机 域,-i 会自动 挂载 域 的磁盘,当然也可以不用 -d 而用 -a ,表示直接指定一个 img 文件。

另外,修改文件也可以用 virt-edt 命令,但是没上条命令那么通用,上条命令所有的 shell 都能执行。
# virt-edit -d $name /etc/sysconfig/network -e “s/HOSTNAME=.*/HOSTNAME=test0.hlg01/g

 

再来看看交互情况下的用法。
# guestfish -d vm2

Welcome to guestfish, the libguestfs filesystem interactive shell for
editing virtual machine filesystems.

Type: ‘help’ for help on commands
‘man’ to read the manual
‘quit’ to quit the shell

><fs> run
><fs> list-filesystems
/dev/sda1: ext3
/dev/sda2: swap
/dev/datavg/home: ext4
/dev/domovg/root: ext4
><fs> mount /dev/domovg/root /
><fs> mount /dev/datavg/home /home/
><fs> cat /etc/fstab

/dev/mapper/domovg-root / ext4 defaults,data=ordered 1 1
UUID=7415a5d3-bd20-4a08-9288-66c49ab20b8d /boot ext3 defaults,data=ordered,nodev,nosuid,noexec 1 2
UUID=df3f6ed1-40e7-4da4-b481-465c7ac5fc17 swap swap defaults 0 0
tmpfs /dev/shm tmpfs defaults 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0
sysfs /sys sysfs defaults 0 0
proc /proc proc defaults 0 0
/dev/datavg/home /home ext4 defaults,data=ordered,nodev,nosuid 1 2

><fs> cat /etc/udev/rules.d/70-persistent-net.rules
# This file was automatically generated by the /lib/udev/write_net_rules
# program, run by the persistent-net-generator.rules rules file.
#
# You can modify it, as long as you keep each rule on a single
# line, and change only the value of the NAME= key.
# PCI device 0x1af4:0x1000 (virtio-pci)
SUBSYSTEM==”net”, ACTION==”add”, DRIVERS==”?*”, ATTR{address}==”52:54:00:b0:4a:a1″, ATTR{type}==”1″, KERNEL==”eth*”, NAME=”em2″

# PCI device 0x1af4:0x1000 (virtio-pci)
SUBSYSTEM==”net”, ACTION==”add”, DRIVERS==”?*”, ATTR{address}==”52:54:00:6a:e8:21″, ATTR{type}==”1″, KERNEL==”eth*”, NAME=”em1″

><fs> cat /etc/sysconfig/network
NETWORKING=yes
NETWORKING_IPV6=no
HOSTNAME=test0.hlg01

><fs> cat /etc/sysconfig/network-scripts/ifcfg-em2
DEVICE=”em2″
BOOTPROTO=”static”
DNS1=”10.0.12.234″
GATEWAY=”10.19.26.1″
IPADDR=”10.19.26.101″
NETMASK=”255.255.255.0″
ONBOOT=”yes”
TYPE=”Ethernet”

><fs> ls /home/
lost+found

 

上面的交互不易于自动化,这里我们可以编写脚本,如下:

[shell]

#!/bin/bash

name=$1
ip=$2
hostname=$3
hwaddr_em2=$4
hwaddr_em1=$5

netmask=255.255.255.0

x=`echo $ip | awk -F. ‘{print $1"."$2"."$3}’`
gateway="$x.1"

# 定义虚拟机, 否则无法根据域修改镜像.
virsh define /etc/libvirt/qemu/$name.xml || exit 1

# 修改系统镜像, 包括:
# 1. 修改主机名;
# 2. 修改网卡设置(增加HWADDR的原因是系统可以把eth变成em);
# 3. 修改路由信息;
# 4. 删除/etc/udev/rules.d/70-persistent-net.rules;
# 5. 删除/var/lib/puppet;
# 6. 删除/etc/sn;
# 然后启动.
guestfish –rw -d $name <<_EOF_
run
mount /dev/domovg/root /
mount /dev/datavg/home /home/
command "sed -i ‘s/HOSTNAME=.*/HOSTNAME=${hostname}/g’ /etc/sysconfig/network"

write /etc/sysconfig/network-scripts/ifcfg-em2 "DEVICE=em2\nHWADDR=${hwaddr_em2}\nBOOTPROTO=static\nIPADDR=$ip\nNETMASK=$netmask\nONBOOT=yes\nTYPE=Ethernet"

write /etc/sysconfig/network-scripts/ifcfg-em1 "DEVICE=em1\nHWADDR=${hwaddr_em1}"

write /etc/sysconfig/network-scripts/route-em2 "192.168.0.0/16 via 10.19.28.1\n10.0.0.0/8 via 10.19.28.1\n100.64.0.0/16 via 10.19.28.1\n0.0.0.0/0 via 10.19.28.1"

command "/bin/rm -rf /etc/udev/rules.d/70-persistent-net.rules"
command "/bin/rm -rf /var/lib/puppet/"
command "/bin/rm -rf /etc/sn"
_EOF_

[/shell]

说明一下,command 命令里 echo xxx > file 是无效的。

 

根据 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 的方式,这种方式比较稳妥。

AWS 的 CodeDeploy 是如何做 「部署」的

AWS 今天发布了 CodeDeploy ,第一次有了 Service 的概念,我表示很惊喜,试想有了发布服务,开发自己可以搞定服务的发布,需要运维的需求进一步减小了,我感到 NOSA 的那一天就近了一步。

好吧,来看看 CodeDepoly 是如何做 部署 的,对我们自己做发布系统有什么启示,不过我最后发现自己做一套类似的系统也很简单。

 

先看几个关键字:

  1. Application

  2. Deployment Group

  3. Auto Scaling Group

  4. Security Group

  5. Service Role

Application ,就是应用的意思,一个 Application 可以有多个 Deployment Group ,我理解 Application 是服务的集合,而下面的每一个 Deployment Group 都代表着每一个「子服务」。

一个 Deployment Group 是一组被部署的 instance ,它可以包括 由 key 来指定的数个instance,也可以是 Auto Scaling Group;

Deployment Group 里的 instance 最好在一个 Security Group ;

Service Role 则是用来控制权限,CodeDepoly 需要访问EC2 的instance,所以定义一个Service Role 来界定。

 

部署的 instance 事实上要满足几个条件:

  1. instance 要绑定一个IMA role (就是上面说的Service Role ),而且IMA role 要有「正确」的权限。

  2. instance 要由 Tag,以供 Deployment Group 使用。

  3. 要在每台 instance 安装 CodeDeploy Agent 。

这几个条件具体可以参考官方文档

 

部署过程如下图:

D7726FB4-134D-44B3-89D7-90FDA2BC91D7

灰色的部分由 CodeDeploy Agent 自动完成,比如下载包,包的地址可以选择 「 S3 」或者 「Github」 ;
黄色(是黄色吗?)则由我们自己定义,分别是 「停止应用」、「安装前准备」、「安装之后动作」、「启动应用」、「确认服务」,这些规则写在一个 APPSpec 文件里,而且名字必须为 appspec.yml ,并且它要在 「包」 的 「根目录」。

appspec.yml 文件示例:


version: 0.0 os: linux files: - source: /index.html destination: /var/www/html/ hooks: BeforeInstall: - location: scripts/install_dependencies timeout: 300 runas: root - location: scripts/start_server timeout: 300 runas: root ApplicationStop: - location: scripts/stop_server timeout: 300 runas: root

 

 

另外 AWS 提供了 Deployment Configuration,默认有三种:

  1. One at a Time ,也就是 一个一个部署,一台失败就失败;

  2. Half at a Time,一次部署一半的 instance,只要超过一半成功就算成功;

  3. All at Once,一次部署所有 instance,只要有一台成功就算成功。

当然,AWS 也提供自定义,你可以自定义 发布过程至少有 多少个 instance 或者 至少 百分之多少 的 instance 成功,发布就算成功。