blob.png blob.png

 上面左边是我的个人微信,如需进一步沟通,请加微信。  右边是我的公众号“Openstack私有云”,如有兴趣,请关注。


原文链接

1 背景知识

1.1 Ceph简介

Ceph是当前非常流行的开源分布式存储系统,具有高扩展性、高性能、高可靠性等优点,同时提供块存储服务(rbd)、对象存储服务(rgw)以及文件系统存储服务(cephfs)。目前也是OpenStack的主流后端存储,和OpenStack亲如兄弟,为OpenStack提供统一共享存储服务。使用Ceph作为OpenStack后端存储,具有如下优点:

  • 所有的计算节点共享存储,迁移时不需要拷贝根磁盘,即使计算节点挂了,也能立即在另一个计算节点启动虚拟机(evacuate)。

  • 利用COW(Copy On Write)特性,创建虚拟机时,只需要基于镜像clone即可,不需要下载整个镜像,而clone操作基本是0开销,从而实现了秒级创建虚拟机。

  • Ceph RBD支持thin provisioning,即按需分配空间,有点类似Linux文件系统的sparse稀疏文件。创建一个20GB的虚拟硬盘时,最开始并不占用物理存储空间,只有当写入数据时,才按需分配存储空间。

Ceph的更多知识可以参考官方文档,这里我们只关注RBD,RBD管理的核心对象为块设备(block device),通常我们称为volume,不过Ceph中习惯称之为image(注意和OpenStack image的区别)。Ceph中还有一个pool的概念,类似于namespace,不同的pool可以定义不同的副本数、pg数、放置策略等。每个image都必须指定pool。image的命名规范为pool_name/image_name@snapshot,比如openstack/test-volume@test-snap,表示在openstackpool中test-volumeimage的快照test-snap。因此以下两个命令效果是等同的:

rbd snap create --pool openstack --image test-image --snap test-snap
rbd snap create openstack/test-image@test-snap

openstack pool上创建一个1G的image命令为:

rbd -p openstack create --size 1024 int32bit-test-1

image支持快照(snapshot)的功能,创建一个快照即保存当前image的状态,相当于git commit操作,用户可以随时把image回滚到任意快照点上(git reset)。创建快照命令如下:

rbd -p openstack snap create int32bit-test-1@snap-1

查看rbd列表:

$ rbd -p openstack ls -l | grep int32bit-test
int32bit-test-1        1024M 2
int32bit-test-1@snap-1 1024M 2

基于快照可以创建一个新的image,称为clone,clone不会立即复制原来的image,而是使用COW策略,即写时拷贝,只有当需要写入一个对象时,才从parent中拷贝那个对象到本地,因此clone操作基本秒级完成,并且需要注意的是基于同一个快照创建的所有image共享快照之前的image数据,因此在clone之前我们必须保护(protect)快照,被保护的快照不允许删除。clone操作类似于git branch操作,clone一个image命令如下:

rbd -p openstack snap protect int32bit-test-1@snap-1
rbd -p openstack clone int32bit-test-1@snap-1 int32bit-test-2

我们可以查看一个image的子image(children)有哪些,也能查看一个image是基于哪个image clone的(parent):

$ rbd -p openstack children int32bit-test-1@snap-1
openstack/int32bit-test-2
$ rbd -p openstack info int32bit-test-2 | grep parent
parent: openstack/int32bit-test-1@snap-1

以上我们可以发现int32bit-test-2int32bit-test-1的children,而int32bit-test-1int32bit-test-2的parent。

不断地创建快照并clone image,就会形成一条很长的image链,链很长时,不仅会影响读写性能,还会导致管理非常麻烦。可幸的是Ceph支持合并链上的所有image为一个独立的image,这个操作称为flatten,类似于git merge操作,flatten需要一层一层拷贝所有顶层不存在的数据,因此通常会非常耗时。

$ rbd -p openstack flatten int32bit-test-2
Image flatten: 31% complete...

此时我们再次查看其parrent-children关系:

rbd -p openstack children int32bit-test-1@snap-1

此时int32bit-test-1没有children了,int32bit-test-2完全独立了。

当然Ceph也支持完全拷贝,称为copy

rbd -p openstack cp int32bit-test-1 int32bit-test-3

copy会完全拷贝一个image,因此会非常耗时,但注意copy不会拷贝原来的快照信息。

Ceph支持将一个RBD image导出(export):

rbd -p openstack export int32bit-test-1 int32bit-1.raw

导出会把整个image导出,Ceph还支持差量导出(export-diff),即指定从某个快照点开始导出:

rbd -p openstack export-diff \
int32bit-test-1 --from-snap snap-1 \
--snap snap-2 int32bit-test-1-diff.raw

以上导出从快照点snap-1到快照点snap-2的数据。

当然与之相反的操作为import以及import-diff。通过export/import支持image的全量备份,而export-diff/import-diff实现了image的差量备份。

Rbd image是动态分配存储空间,通过du命令可以查看image实际占用的物理存储空间:

$ rbd du int32bit-test-1
NAME            PROVISIONED   USED
int32bit-test-1       1024M 12288k

以上image分配的大小为1024M,实际占用的空间为12288KB。

删除image,注意必须先删除其所有快照,并且保证没有依赖的children:

rbd -p openstack snap unprotect int32bit-test-1@snap-1
rbd -p openstack snap rm int32bit-test-1@snap-1
rbd -p openstack rm int32bit-test-1

1.2 OpenStack简介

OpenStack是一个IaaS层的云计算平台开源实现,关于OpenStack的更多介绍欢迎访问我的个人博客,这里只专注于当OpenStack对接Ceph存储系统时,基于源码分析一步步探测Ceph到底做了些什么工作。本文不会详细介绍OpenStack的整个工作流程,而只关心与Ceph相关的实现。

阅读完本文可以理解以下几个问题:

  1. 为什么上传的镜像必须要转化为raw格式?

  2. 如何高效上传一个大的镜像文件?

  3. 为什么能够实现秒级创建虚拟机?

  4. 为什么创建虚拟机快照需要数分钟时间,而创建volume快照能够秒级完成?

  5. 为什么当有虚拟机存在时,不能删除镜像?

  6. 为什么一定要把备份恢复到一个空卷中,而不能覆盖已经存在的volume?

  7. 从镜像中创建volume,能否删除镜像?

注意本文都是在基于使用Ceph存储的前提下,即Glance、Nova、Cinder都是使用的Ceph,其它情况下结论不一定成立。

(注:原文有源代码,已经超过5000字的篇幅限制,因此做了精简,如果需要看详细推导验证过程,请查看原文链接,另外你可以快速跳到总结部分查看OpenStack各个操作对应的Ceph工作。)

2 Glance

2.1 Glance介绍

Glance管理的核心实体是image,它是OpenStack的核心组件之一,为OpenStack提供镜像服务(Image as Service),主要负责OpenStack镜像以及镜像元数据的生命周期管理、检索、下载等功能。Glance支持将镜像保存到多种存储系统中,后端存储系统称为store,访问镜像的地址称为location,location可以是一个http地址,也可以是一个rbd协议地址。只要实现store的driver就可以作为Glance的存储后端,其中driver的主要接口如下:

  • get: 获取镜像的location。

  • get_size: 获取镜像的大小。

  • get_schemes: 获取访问镜像的URL前缀(协议部分),比如rbd、swift+https、http等。

  • add: 上传镜像到后端存储中。

  • delete: 删除镜像。

  • set_acls: 设置后端存储的读写访问权限。

为了便于维护,glance store目前已经作为独立的库从Glance代码中分离出来,由项目glance_store维护。目前社区支持的store列表如下:

  • filesystem: 保存到本地文件系统,默认保存/var/lib/glance/images到目录下。

  • cinder: 保存到Cinder中。

  • rbd:保存到Ceph中。

  • sheepdog:保存到sheepdog中。

  • swift: 保存到Swift对象存储中。

  • vmware datastore: 保存到Vmware datastore中。

  • http: 以上的所有store都会保存镜像数据,唯独http store比较特殊,它不保存镜像的任何数据,因此没有实现add方法,它仅仅保存镜像的URL地址,启动虚拟机时由计算节点从指定的http地址中下载镜像。

。。。。。。。。。。。此处省略分析验证过程。。。。。。。。。。。。。

3 Nova

3.1 Nova介绍

Nova管理的核心实体为server,为OpenStack提供计算服务,它是OpenStack最核心的组件。注意Nova中的server不只是指虚拟机,它可以是任何计算资源的抽象,除了虚拟机以外,也有可能是baremetal裸机、容器等。

不过我们在这里假定:

  • server为虚拟机。

  • image type为rbd。

  • compute driver为libvirt。

启动虚拟机之前首先需要准备根磁盘(root disk),Nova称为image,和Glance一样,Nova的image也支持存储到本地磁盘、Ceph以及Cinder(boot from volume)中。需要注意的是,image保存到哪里是通过image type决定的,存储到本地磁盘可以是raw、qcow2、ploop等,如果image type为rbd,则image存储到Ceph中。不同的image type由不同的image backend负责,其中rbd的backend为nova/virt/libvirt/imageackend中的Rbd类模块实现。

。。。。。。。。。。。此处省略分析验证过程。。。。。。。。。。。。。

4 Cinder

4.1 Cinder介绍

Cinder是OpenStack的块存储服务,类似AWS的EBS,管理的实体为volume。Cinder并没有实现volume provide功能,而是负责管理各种存储系统的volume,比如Ceph、fujitsu、netapp等,支持volume的创建、快照、备份等功能,对接的存储系统我们称为backend。只要实现了cinder/volume/driver.pyVolumeDriver类定义的接口,Cinder就可以对接该存储系统。

Cinder不仅支持本地volume的管理,还能把本地volume备份到远端存储系统中,比如备份到另一个Ceph集群或者Swift对象存储系统中,本文将只考虑从源Ceph集群备份到远端Ceph集群中的情况。

。。。。。。。。。。。此处省略分析验证过程。。。。。。。。。。。。。

5 总结

5.1 Glance

1. 上传镜像

rbd -p ${GLANCE_POOL} create --size ${SIZE} ${IMAGE_ID}rbd -p ${GLANCE_POOL} snap create ${IMAGE_ID}@snap
rbd -p ${GLANCE_POOL} snap protect ${IMAGE_ID}@snap

2. 删除镜像

rbd -p ${GLANCE_POOL} snap unprotect ${IMAGE_ID}@snap
rbd -p ${GLANCE_POOL} snap rm ${IMAGE_ID}@snap
rbd -p ${GLANCE_POOL} rm ${IMAGE_ID}

5.2 Nova

1 创建虚拟机

rbd clone \${GLANCE_POOL}/${IMAGE_ID}@snap \${NOVA_POOL}/${SERVER_ID}_disk

2 创建虚拟机快照

# Snapshot the disk and clone # it into Glance's storage poolrbd -p ${NOVA_POOL} snap create \${SERVER_ID}_disk@${RANDOM_UUID}rbd -p ${NOVA_POOL} snap protect \${SERVER_ID}_disk@${RANDOM_UUID}rbd clone \${NOVA_POOL}/${SERVER_ID}_disk@${RANDOM_UUID} \${GLANCE_POOL}/${IMAGE_ID} # Flatten the image, which detaches it from the # source snapshotrbd -p ${GLANCE_POOL} flatten ${IMAGE_ID} # all done with the source snapshot, clean it uprbd -p ${NOVA_POOL} snap unprotect \${SERVER_ID}_disk@${RANDOM_UUID}rbd -p ${NOVA_POOL} snap rm \${SERVER_ID}_disk@${RANDOM_UUID} # Makes a protected snapshot called 'snap' on # uploaded images and hands it outrbd -p ${GLANCE_POOL} snap create ${IMAGE_ID}@snap
rbd -p ${GLANCE_POOL} snap protect ${IMAGE_ID}@snap

3 删除虚拟机

for image in $(rbd -p ${NOVA_POOL} ls | grep "^${SERVER_ID}");do 
    rbd -p ${NOVA_POOL} rm "$image"; done

5.3 Cinder

1 创建volume

(1) 创建空白卷

rbd -p ${CINDER_POOL} create \--new-format --size ${SIZE} \volume-${VOLUME_ID}

(2) 从快照中创建

rbd clone \${CINDER_POOL}/volume-${SOURCE_VOLUME_ID}@snapshot-${SNAPSHOT_ID} \${CINDER_POOL}/volume-${VOLUME_ID}rbd resize --size ${SIZE} \openstack/volume-${VOLUME_ID}

(3) 从volume中创建

# Do full copy if rbd_max_clone_depth <= 0.if [[ "$rbd_max_clone_depth" -le 0 ]]; then
    rbd copy \
    ${CINDER_POOL}/volume-${SOURCE_VOLUME_ID} \
    ${CINDER_POOL}/volume-${VOLUME_ID}
    exit 0fi# Otherwise do COW clone.# Create new snapshot of source volumerbd snap create \${CINDER_POOL}/volume-${SOURCE_VOLUME_ID}@volume-${VOLUME_ID}.clone_snap
rbd snap protect \${CINDER_POOL}/volume-${SOURCE_VOLUME_ID}@volume-${VOLUME_ID}.clone_snap# Now clone source volume snapshotrbd clone \${CINDER_POOL}/volume-${SOURCE_VOLUME_ID}@volume-${VOLUME_ID}.clone_snap \${CINDER_POOL}/volume-${VOLUME_ID}# If dest volume is a clone and rbd_max_clone_depth reached,# flatten the dest after cloning.depth=$(get_clone_depth ${CINDER_POOL}/volume-${VOLUME_ID})if [[ "$depth" -ge "$rbd_max_clone_depth" ]]; then
    # Flatten destination volume 
    rbd flatten ${CINDER_POOL}/volume-${VOLUME_ID}
    # remove temporary snap
    rbd snap unprotect \
    ${CINDER_POOL}/volume-${SOURCE_VOLUME_ID}@volume-${VOLUME_ID}.clone_snap
    rbd snap rm \
    ${CINDER_POOL}/volume-${SOURCE_VOLUME_ID}@volume-${VOLUME_ID}.clone_snapfi

(4) 从镜像中创建

rbd clone \${GLANCE_POOL}/${IMAGE_ID}@snap \${CINDER_POOL}/volume-${VOLUME_ID}if [[ -n "${SIZE}" ]]; then
    rbd resize --size ${SIZE} ${CINDER_POOL}/volume-${VOLUME_ID}fi

2 创建快照

rbd -p ${CINDER_POOL} snap create \volume-${VOLUME_ID}@snapshot-${SNAPSHOT_ID}rbd -p ${CINDER_POOL} snap protect \volume-${VOLUME_ID}@snapshot-${SNAPSHOT_ID}

3 创建备份

(1) 第一次备份

rbd -p ${BACKUP_POOL} create \
--size ${VOLUME_SIZE} \
volume-${VOLUME_ID}.backup.base
NEW_SNAP=volume-${VOLUME_ID}@backup.${BACKUP_ID}.snap.${TIMESTAMP}
rbd -p ${CINDER_POOL} snap create ${NEW_SNAP}
rbd export-diff ${CINDER_POOL}/volume-${VOLUME_ID}${NEW_SNAP} - \
| rbd import-diff --pool ${BACKUP_POOL} - \
volume-${VOLUME_ID}.backup.base

(2) 增量备份

rbd -p ${CINDER_POOL} snap create \volume-${VOLUME_ID}@backup.${BACKUP_ID}.snap.${TIMESTAMP} rbd export-diff  --pool ${CINDER_POOL} \--from-snap backup.${PARENT_ID}.snap.${LAST_TIMESTAMP} \${CINDER_POOL}/volume-${VOLUME_ID}@backup.${BACKUP_ID}.snap.${TIMESTRAMP} - \| rbd import-diff --pool ${BACKUP_POOL} - \${BACKUP_POOL}/volume-${VOLUME_ID}.backup.base
rbd -p ${CINDER_POOL} snap rm \volume-${VOLUME_ID}.backup.base@backup.${PARENT_ID}.snap.${LAST_TIMESTAMP}

4 备份恢复

rbd export-diff --pool ${BACKUP_POOL} \volume-${SOURCE_VOLUME_ID}.backup.base@backu