如何实现OpenStack磁盘数据的可靠性?——Cinder磁盘备份原理和实践

54 篇文章 17 订阅
3 篇文章 0 订阅

本文介绍了数据保护的几种常用技术,重点介绍了Cinder Backup的原理,对比了基于分块的备份策略和直接导入策略。最后,分享了云极星创在实践中踩到的各种“坑”,以及我们做的一些优化改进和后续的规划。

背 景

1.1 数据保护技术概述

快照(Snapshot)、复制(Replication)、备份(Backup)是存储领域中最为常见的数据保护技术。快照用于捕捉数据卷在某一个时刻的状态,用户可以随时回滚到这个状态,也可以基于该快照创建新的数据卷。备份就是对数据进行导出拷贝并传输到远程存储设备中。当数据损坏时,用户可以从远端下载备份的数据,手动从备份数据中恢复,从而避免了数据损失。快照类似于Git的commit操作,我们可以随时reset/checkout到任意历史commit中,但一旦保存Git仓库的磁盘损坏,提交的commit信息将永久丢失,不能恢复。而备份则类似于Git的push操作,即使本地的数据损坏,我们也能从远端的Git仓库中恢复。简而言之,快照主要用于快速回溯,而备份则用于容灾。数据复制则类似于MySQL的Master/Slave主从同步,通常只有Master支持写操作,Slave不允许用户直接写数据,它只负责自动同步Master的数据,但一旦Master出现故障,Slave能够提升为Master接管写操作。因此复制不仅提供了实时备份的功能,还实现了故障自动恢复(即高可用)。

1.2 Cinder数据保护功能介绍

Cinder是OpenStack中相对成熟的组件(总分为8分的成熟度评分中获得了8分满分),也是OpenStack的核心组件之一,为OpenStack云主机提供弹性的块存储服务,承载着用户大多数的业务数据,即使出现数据的丝毫损坏也将可能导致灾难性后果,因此数据的完整性保护至关重要。不得不说Cinder对数据卷保护方面支持度还是比较给力的,目前Cinder已经同时支持了对数据卷的快照、复制和备份功能。

快照应该是Cinder非常熟悉非常受欢迎的功能,也是Cinder默认支持的功能,几乎所有的存储后端都支持快照。而备份作为Cinder的可选功能之一,由于数据卷的存储后端很多已经提供了多副本功能(比如Ceph存储后端默认为三副本),通常很少人会再部署一套备份存储集群,因此部署率并不是很高。复制也是Cinder的可选功能之一,目前支持的存储后端还非常有限,最常采用的RBD存储后端也是在Ocata版本才开始支持,并且要求Ceph版本需要支持RBD-mirror(jewel版本以上),因此用户关注度还不是很高,部署率较低。

云极星创认为,用户的数据是至关重要的,因此我们的存储后端即使已经使用了多副本技术来保证数据的可靠性,我们仍然提供了数据卷的备份功能。通过备份功能,不仅更进一步加强了数据的可靠性,保障了数据的安全性,更有效避免了用户误删除导致数据的损失。

云极星创对Cinder数据卷备份功能进行了大量的调研工作,踩过不少坑,也做了许多优化,积累了较多的实战经验,希望能分享给大家。

深入理解Cinder 数据卷备份原理

2.1 Cinder Backup功能介绍

Cinder磁盘备份为用户的数据卷实例提供备份和恢复功能,实现了基于块的容灾功能。从K版本开始,Cinder引入了增量备份功能,相对全量备份需要拷贝和传输整个数据卷,增量备份只需要传输变化的部分,大大节省了传输开销和存储开销。通常情况下,当用户执行备份或者恢复操作时,需要手动卸载数据卷,即数据卷不支持在挂载状态下热备份。从L版本开始,新增了force选项,当用户指定force选项时能够对挂载的数据卷强制执行备份操作,这样可能带来数据不一致的风险,不过社区针对这种情况做了些优化,比如在创建备份前先基于该数据卷快照创建临时数据卷,然后基于临时数据卷执行后续备份操作。

Cinder开启备份功能,需要单独部署cinder-backup服务。cinder-backup服务和cinder-volume服务类似,也支持各种不同的驱动,对接不同的存储后端,目前支持的存储驱动列表如下:

● Swift,备份数据保存在OpenStack Swift对象存储中。
● Google,备份数据保存在Google Cloud Storage(GCS)中。
● Glusterfs,保存到Glusterfs中。
● NFS,保存到NFS中。
● Posix,保存到本地文件系统。
● TSM,保存在IBM Tivoli Storage Manager(TSM)。
● Ceph,保存到Ceph集群中。

从列表中看,目前Cinder Backup尚不支持备份数据到AWS S3中。
除了数据卷本身的备份,Cinder Backup还支持将元数据序列化导出(export record),这样即使数据库中的数据丢失了,也能从导出的元数据中快速恢复。

2.2 Cinder Backup原理剖析

前面提到Cinder Backup支持多种后端存储驱动,但大体可以分为两类:

● 存储系统本身就提供块存储服务,比如Ceph。这种情况只需要直接导入到该存储系统即可。
● 存储系统不支持块存储服务,只支持基于文件的存储,以上除了Ceph和TSM都属于此类。此时备份采取了分块备份策略,即首先把数据卷切割为一个个独立的文件,然后分别把这些文件存储到设备中。恢复时只需要重组这些小文件即可。

接下来我们针对此两种情况深入研究下Cinder Backup的实现原理。

2.2.1 分块备份策略

在介绍之前先了解两个重要的参数,参考[6]:

chunk_size: 表示将Volume切割成多大的块进行备份,一个块称为一个chunk。在NFS中这个值叫做backup_file_size,默认是1999994880Byte,大约1.8G。在Swift中这个值叫做backup_swift_object_size,默认是52428800Byte,也就是50M。这个参数决定数据卷备份后块的数量(object count),比如一个2GB的数据卷,如果chunk_size为500MB,则大约需要4个块,如果使用本地文件系统存储的话,对应就是4个文件。
sha_block_size: 这个值用于增量备份,决定多大的块求一次Hash,Hash相同说明内容没有变化,不需要备份。它决定了增量备份的粒度。在NFS中,这个值叫做backup_sha_block_size_bytes,在Swift中,这个值叫做backup_swift_block_size。默认都是32768Byte,也就是32K。在Ceph,没有对应的概念。

那恢复的时候怎么重组呢?这就需要保存元数据信息,元数据信息包括:

● Backup信息:其实就是数据库中的信息,或者说就是一个Backup object实例的序列化,包括backup name、description、volume_id等。
● Volume信息:数据卷信息,即volume实例的序列化,包括size、name等。
● 块信息:即objects信息,这是最重要的数据,记录了每一个块的长度、偏移量、压缩算法、md5值,备份恢复时主要通过这些块信息拼接而成。
● 版本:序列化和持久化必不可少的参数,决定升级后能否保证老版本的备份数据能否成功恢复。

除了保存以上元数据信息,还会按顺序保存每一个block的SHA256值。这些信息主要用于支持增量备份。做增量备份时,也是每次从数据卷读入chunk_size字节的Chunk数据,然后计算该chunk的每个block的SHA值。不同的是,Cinder会把每一个block的SHA值与其父备份对应的SHA值比较,仅当该Block的SHA值与父备份block的SHA值不一样时,才保存对应的block数据。如果SHA值和父备份的SHA值相同,说明这个block的数据没有更新,不需要重新保存该block数据,而只需要保存SHA值。当然,如果有多个连续Block的SHA值都不一样,则保存时会合并成一个object,通过元数据记录该object在原volume的偏移量以及长度。

如图1所示,假设一个chunk分为9个block,每个block为100KB,注意每个block都保存了sha256值,图中没有标识。基于该chunk做一次增量备份后,假设只有block 2、7、8有更新,则增量备份只会保存block 2、7、8,由于7和8是连续的,因此会自动合并成一个chunk,而block 2单独形成一个chunk,即原来的chunk分裂成了两个chunk,但总大小为300KB,节省了1/3的存储空间。
增量备份原理图

备份的恢复参考文献[6]讲得非常清楚,这里直接引用:

全量备份的恢复很简单,只需要找到对应的备份,将其内容写回对应的volume即可。那么这里有个问题,每个备份都对应存储上哪些文件呢,每个文件又对于原始volume中哪些数据?还记得创建备份时生成的Metadata文件吗,答案就在其中。恢复备份时,会读取这个文件,然后将每一个备份文件恢复到对应的位置。当然,如果有压缩也会根据Metadata中的描述,先解压再写入对应的volume中[6]。

增量备份的恢复稍微复杂一些,正如之前的描述,增量备份之间有依赖,会形成备份链,我们需要恢复所选备份及其在备份链上之前所有的数据。在恢复时,需要查询数据库,获取当前备份及备份链上之前的所有备份,其顺序是[所选备份,父备份,父父备份,…,全量备份],恢复的时候会按照相反的顺序依次进行,即首先恢复全量备份,接着创建的第一个增量备份,第二个增量备份,直到所选的备份。每个备份恢复时依赖创建备份时生成的Metadata文件,将备份包含的文件,恢复到volume中。每个备份的恢复和全量备份的恢复方式一样。

从备份的原理可以看出,增量备份能够节省存储空间,但随着备份链长度越来越长,恢复时会越来越慢,性能越来越差,实际生产环境中应该权衡存储空间和性能,控制备份链的长度。

Swift、NFS、本地文件系统、GCS等都是使用以上的备份策略,实际上实现也是完全一样的,区别仅仅在于实现不同存储系统的Reader、Writer驱动。

2.2.2 直接导入策略

直接导入策略即把原数据卷导出后直接导入到目标存储系统中。对于支持差量导入的存储系统,增量备份时则可以进一步优化。

以Ceph为例,我们知道Ceph RBD支持将某个image在不同时刻的状态进行比较后导出(export-diff)补丁(Patch)文件,然后可以随时将这个补丁文件打到某个image中(import-diff)。即Ceph原生支持差量备份,利用该特性实现增量备份就不难了。不过有个前提是,必须保证cinder-volume后端和cinder-backup后端都是Ceph后端,否则仍然是一块一块的全量拷贝。
如果是对volume进行第一次备份,则:

  1. 在用于备份的Ceph集群创建一个base image,size和原volume一样,name为”volume-VOLUMD_UUID.backup.base” % volume_id。
  2. 在原volume创建一个新的快照,name为backup.BACKUP_ID.snap.TIMESTRAMP
  3. 在原RBD image上使用export-diff命令导出与创建时比较的差量数据,然后通过管道将差量数据导入刚刚在备份集群上新创建的RBD image中。

如果不是对volume第一次备份,则:

  1. 在原volume中找出满足r"^backup\.([a-z0-9\-]+?)\.snap\.(.+)$"的最近的一次快照。
  2. 在原v创建一个新的快照,name为backup.BACKUP_ID.snap.TIMESTRAMP
  3. 在原RBD image上使用export-diff命令导出与最近的一次快照比较的差量数据,然后通过管道将差量数据导入到备份集群的RBD
    image中。

恢复时相反,只需要从备份集群找出对应的快照并导出差量数据,导入到原volume即可。
注意:
● Volume和Backup都使用Ceph后端存储时,每次都会尝试使用增量备份,无论用户是否传递incremental参数值。
● 使用直接导入策略,不需要元数据信息以及SHA256信息。

踩过的“坑”

虽然在前期做了大量关于Cinder backup的调研工作,但实际部署过程中仍然踩了不少坑,PoC测试过程也非一帆风顺,还好我们在填坑的过程还是比较顺利的。本小节总结我们在实践过程中遇到的坑,避免后来者重复踩“坑”。

3.1 热备份导致quota值异常

我们知道备份是一个IO开销和网络开销都比较大的操作,非常耗时。当对已经挂载的数据卷执行在线备份时,Cinder为了优化性能,减少数据不一致的风险,首先会基于该数据卷创建一个临时卷,然后基于临时卷创建备份,备份完成时会自动删除临时数据卷。从代码中看,创建临时卷时并没有计算quota,换句话说,创建的临时磁盘是不占用quota值的。但删除时调用的是标准的删除接口,该接口会释放对应数据卷占用的数据卷quota值(主要影响gigabytes和volumes值)。也就是说,创建的临时磁盘使volume quota值只减不增,用户可以通过这种方式绕过quota限制。

3.2 不支持Ceph多后端情况

我们内部Cinder对接了多个Ceph集群,不同的Ceph集群通过不同的配置文件区分。但cinder-backup服务向cinder-volume服务获取connection info时并没有返回Ceph的配置文件路径,于是cinder-backup服务使用默认的配置文件/etc/ceph/ceph.conf,该Ceph集群显然没有对应volume的RBD image,因此备份100%失败。

3.3 使用Ceph存储后端时不支持差量备份

我们前面提到如果cinder-volume和cinder-backup后端都是Ceph,则会利用Ceph内置的RBD差量备份实现增量备份。那cinder-backup服务怎么判断数据卷对应的后端是否Ceph呢?实现非常简单,只需要判断数据卷的连接信息是否存在rbd_image属性,实现代码如下:

def _file_is_rbd(self, volume_file):    
"""Returns True if the volume_file is actually an RBD image."""    
return hasattr(volume_file, 'rbd_image')

社区从M版开始把与存储后端交互的代码独立出来,建立了一个新的项目--os-brick,与之前的Ceph驱动存在不兼容,没有rbd_image这个属性。因此backup服务会100%判断数据卷不是Ceph后端,因此100%执行全量备份。

我们的改进

4.1 获取父备份ID

当备份存在子备份时,用户无法直接删除该备份,而必须先删除所有依赖的子备份。目前Cinder API只返回备份是否存在依赖的子备份,而没有返回子备份的任何信息,也没有返回父备份的信息。当用户创建了很多备份实例时,很难弄清楚备份之间的父子关系。我们修改了Cinder API,向用户返回备份的父备份ID(parent_id),并且支持基于parent_id过滤搜索备份。当用户发现备份存在依赖时,能够快速检索出被依赖的子备份。当然,如果存在很长的父子关系时,需要一层一层判断,仍然不太方便,并且不能很清楚的输出备份的父子关系。于是我们引入了备份链的概念,下节详细讨论。

4.2 引入备份链概念

为了方便查看备份之间的父子关系,我们引入了备份链(backup chain)的概念,一个数据卷可以有多个备份链,每条备份链包括一个全量备份以及多个增量备份组成。我们新增了两个API,其中一个API输出指定数据卷的备份链列表,另一个API输出指定备份链的所有备份点,按照父子关系输出。目前我们的备份链只支持线性链,暂时不支持分叉的情况。通过备份链,用户能够非常方便地查看备份之间的父子关系和备份时间序列。

4.3 创建备份时指定备份链

创建增量备份时,默认是基于时间戳选择最新的备份点作为父备份,我们扩展了该特性,支持用户选择在指定备份链上创建备份,这样也可以避免备份链过长的情况。

后续工作

Cinder Backup功能已经相对比较完善了,但仍然存在一些功能不能满足客户需求,我们制定了二期规划,主要工作包括如下:

5.1 级联删除

目前Cinder不支持备份的级联删除,即如果一个备份实例存在依赖的子备份,则不能删除该备份,必须先删除其依赖的所有子备份。如果备份链很长时,删除备份时非常麻烦。在二期规划中,我们将实现备份的级联删除功能,通过指定–force选项,支持删除备份以及其依赖的所有备份,甚至删除整个备份链。

5.2 获取增量备份大小

目前Cinder备份的实例大小是继承自原volume的大小,基于分块策略备份还有object count(chunk 数量)的概念,但这只是显示分成了几个chunk,每个chunk大小不一定是一样的,并不能根据chunk数量计算实际占用的存储空间。备份存储空间是我们计费系统的计量标准之一,全量备份和增量备份成本肯定是不一样的,如果价钱一样,则用户并不一定乐于使用增量备份。在二期规划中,我们将实现计算备份占用的实际存储空间的接口。

5.3 备份到S3

很多私有云用户考虑各种成本,不一定会部署额外用于备份的Ceph集群,也不一定需要Swift对象存储,而更倾向于将数据备份到价格低廉、稳定可靠的AWS S3中。目前Cinder Backup后端还不支持S3接口,为了满足客户需求,我们计划在二期中实现S3接口,用户可以随时把volume数据备份到S3中。

参考文献

  1. Wikipedia: Backup.
  2. Backup vs replication, snapshots, CDP in data protection strategy.
  3. Back up and restore volumes and snapshots.
  4. Inside Cinder’s Incremental Backup.
  5. OpenStack and Backup.
  6. Openstack 中cinder backup三种backend的对比.

作者简介
付广平 云极星创研发工程师,北京邮电大学硕士,从事Nova、Cinder研发工作,OpenStack代码贡献者。

转载自云极星创微信公众号。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值