在线升级glance镜像技巧

主要介绍使用Ceph作为存储后端的glance镜像更新

Posted by int32bit on June 4, 2016

0.背景

我们经常需要更新glance镜像,比如上传的镜像可能有配置问题,或者我们需要对已有的镜像进行软件包升级或者预装一些新的软件包等。当时着手这件事是由于我们对cloud-init进行了大量的patch,需要同步到glance镜像中,如果重新制作镜像然后传输到生产环境再上传,不仅工作非常繁琐,还非常消耗时间,我们希望能够在原来的镜像基础之上直接升级cloud-init。

1.通过快照方式更新镜像

最简单的方式是通过创建一个云主机,然后更新cloud-init后创建快照形成新的镜像(注意删除/var/lib/cloud下的文件,否则cloud-init下次启动时不会重新拉取metadata)。可是我们遇到的问题是,老版本的cloud-init在L版中注入密码和密钥都失败,并且安装的qemu-guest-agent不支持修改密码,因此完全没有办法登录云主机。因此必须另辟蹊径。

2.glance存储后端使用本地文件系统

于是我们想到把镜像挂载到本地文件系统中,然后chroot到镜像文件系统环境中进行升级配置。如果glance镜像使用本地文件系统作为存储后端,镜像格式为raw,挂载镜像就非常简单了:

losetup /dev/loop0 image.img
kpartx -a /dev/loop0
mount /dev/mapper/loop0p1 /mnt/image

如果是qcow2格式,需要安装qemu-nbd工具包,并且加载nbd内核模块:

modprobe nbd max_part=63
qemu-nbd -c /dev/nbd0 image.img
mount /dev/nbd0p1 /mnt/image

如果镜像文件系统中使用LVM,可以使用以下方法初始化:

vgscan
vgchange -ay
mount /dev/VolGroupName/LogVolName /mnt/image

chroot进行后执行相关操作后,卸载镜像文件系统:

umount /mnt/image
vgchange -an VolGroupName
killall qemu-nbd
kpartx -d /dev/loop0
losetup -d /dev/loop0

以上方法参考Mounting-raw-and-qcow2-vm-disk-images

本文重点不在于基于本地文件系统作为存储后端的情况,重点介绍基于Ceph存储后端的操作方法,下文将详细介绍过程。

3.基于Ceph存储后端的更新方法

我们知道使用ceph作为存储后端时,镜像首先会生成一个以snap命名的快照,比如glance有一个镜像id为35fcb79c-43a1-4b59-83d7-f4e46a524419,则在rbd中有对应的image,命名与glance镜像id一致,我们查看其快照:

rbd snap ls --image openstack-images/35fcb79c-43a1-4b59-83d7-f4e46a524419 2>/dev/null

输出:

SNAPID NAME     SIZE
 94604 snap 20480 MB

我们不能直接修改快照snap,因为它是protect的,只能读不能写。直接修改镜像的话不能同步到快照中,并且出错也不好回滚。

因此我们的做法是先复制一个rbd image副本.

首先使用glance命令行工具获取需要更新的镜像的id:

IMAGE_ID=$(glance image-list --name "CentOS 7.1 64bit 2>/dev/null | awk --re-interval '/\w{8}-.*/{print $2}')

使用rbd命令拷贝一份镜像副本,假设POOL变量为glance使用的ceph池:

rbd  cp $POOL/${IMAGE_ID} $POOL/${IMAGE_ID}_copy

挂载镜像到本地文件系统中:

rbd map $POOL/${IMAGE_ID}_copy
# /dev/rbd0
mount /dev/rbd0p1  /mnt

注意由于镜像是Linux文件系统,因此通常只有一个根分区,如果是windows镜像,第一个分区通常是隐藏分区,C盘是第二个分区,因此挂载时注意选择rbd0p2,其中p表示partition。

挂载到本地后,可以拷贝软件包、配置模板等到镜像文件系统中,比如/mnt/root/,然后chroot到镜像文件系统中:

cd /mnt # /mnt是我们需要chroot的根目录
# mount -t proc proc proc/ # 挂载proc
  
# To use an internet connection in the chroot environment copy over the DNS details:
cp /etc/resolv.conf etc/resolv.conf # 覆盖即可,生成云主机会自动覆盖
  
# To change root into a bash shell:
chroot /mnt bash # chroot 到/mnt中,如果提示bash命令找不到,使用绝对路径试试 chroot /mnt /bin/bash
# 初始化环境变量
source /etc/profile
source ~/.bashrc
 
# 如果提示找不到ls等命令,需要手动设置PATH变量: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/bin
  
# Tip: Optionally, create a unique prompt to be able to differentiate your chroot environment:
export PS1="(chroot) $PS1"

接下来就可以执行命令对镜像进行更新了,比如更新cloud-init:

yum remove cloud-init # Remove the old version package
rpm -i cloud-init-0.7.6-bzr1.el7.centos.noarch.rpm # Install the pacakge

升级qemu-guest-agent:

yum install -y qemu-guest-agent 
rpm -aq | grep qemu-guest-agent
# qemu-guest-agent-2.3.0-4.el7.x86_64

执行完所有的更新操作后,完成以下清理工作:

yum clean all
rm -rf /root/* # 危险!!!删除根目录下拷贝的临时文件,执行前确认无有用文件!
exit # 退出chroot环境
cd # 切换到家目录,工作目录不能位于/mnt,否则卸载不了
umount --recursive /mnt/ # 如果busy怎么办?lsof查看哪个进程占用,如果还是不行试试使用 --lazy 参数
rbd unmap /dev/rbd0 # 从rbd中卸载

最后需要更新glance,我们的方法是先创建一个glance空镜像实例,不需要指定镜像文件,只是占个坑,拿到新镜像id:

NEW_IMAGE_ID=`glance image-create | grep id | awk '{print $4}'` || { echo 'Error: glance image-create failed !' ; exit 1; }

接下来更新我们的rbd image,命名为新创建镜像实例id并设置快照:

rbd mv  $POOL/${IMAGE_ID}_copy $POOL/${NEW_IMAGE_ID} 
rbd --pool=$POOL --image=${NEW_IMAGE_ID} --snap=snap snap create
rbd --pool=$POOL --image=${NEW_IMAGE_ID} --snap=snap snap protect

最后更新glance镜像元数据:

glance image-update --name="$DISPLAY_NAME" --disk-format=raw --container-format=bare --is-public=True ${NEW_IMAGE_ID}

glance image-update --property image_meta="$image_meta" ${NEW_IMAGE_ID}
glance image-update --property hw_qemu_guest_agent=yes $IMAGE_ID


# ...

# update image location
FSID=`ceph -s | grep cluster | awk '{print $2}'`
glance image-update --location rbd://$FSID/$POOL/$IMAGE_ID/snap ${NEW_IMAGE_ID}

if [[ $IMAGE_LABEL == "Windows" || $IMAGE_LABEL == "windows" ]]; then
       glance image-update --property hw_video_model=qxl --property hw_video_ram=64 --property os_admin_user=Administrator $IMAGE_ID
fi

最后对新镜像进行功能验证,如果没有问题,就可以安全的删除老的镜像了。