Ceph 是当前非常流行的开源分布式存储系统, 具有高扩展性, 高性能, 高可靠性等优点, 同时提供块存储服务 (rbd), 对象存储服务(rgw) 以及文件系统存储服务(cephfs). 目前也是 OpenStack 的主流后端存储, 和 OpenStack 亲如兄弟, 为 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-2 是 int32bit-test-1 的 children, 而 int32bit-test-1 是 int32bit-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 相关的实现, 如果有不清楚 OpenStack 源码架构的, 可以参考我之前写的文章如何阅读 OpenStack 源码.
阅读完本文可以理解以下几个问题:
为什么上传的镜像必须要转化为 raw 格式?
如何高效上传一个大的镜像文件?
为什么能够实现秒级创建虚拟机?
为什么创建虚拟机快照需要数分钟时间, 而创建 volume 快照能够秒级完成?
为什么当有虚拟机存在时, 不能删除镜像?
为什么一定要把备份恢复到一个空卷中, 而不能覆盖已经存在的 volume?
从镜像中创建 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 地址中下载镜像.
本文主要关注 rbd store, 它的源码在这里, 该 store 的 driver 代码主要由国内 Fei Long Wang 负责维护, 其它 store 的实现细节可以参考源码 glance store drivers.
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.py 中 VolumeDriver 类定义的接口, 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@backup.${BACKUP_ID}.snap.${TIMESTRAMP} - \| rbd import-diff --pool ${CINDER_POOL} - \volume-${DEST_VOLUME_ID}rbd -p ${CINDER_POOL} resize \--size ${new_size} volume-${DEST_VOLUME_ID}
来源: http://stor.51cto.com/art/201805/573927.htm