本系列文章会深入研究 Ceph 以及 Ceph 和 OpenStack 的集成:
(1)安装和部署
(2)Ceph RBD 接口和工具
(3)Ceph 物理和逻辑结构
(4)Ceph 的基础数据结构
(5)Ceph 与 OpenStack 集成的实现
(6)TBD
从前一篇文章我们知道,从物理上来讲,一个 Ceph 集群内部其实有几个子集群存在:
(1)MON(Montior)集群:MON 集群有由少量的、数目为奇数个的 Monitor 守护进程(Daemon)组成,它们负责通过维护 Ceph Cluster map 的一个主拷贝(master copy of Cluster map)来维护整 Ceph 集群的全局状态。理论上来讲,一个 MON 就可以完成这个任务,之所以需要一个多个守护进程组成的集群的原因是保证高可靠性。每个 Ceph node 上最多只能有一个 Monitor Daemon。
root@ceph1:~# ps -ef | grep ceph-mon root 964 1 0 Sep18 ? 00:36:33 /usr/bin/ceph-mon --cluster=ceph -i ceph1 -f
实际上,除了维护 Cluster map 以外,MON 还承担一些别的任务,比如用户校验、日志等。详细的配置可以参考 MON 配置 。
(2)OSD (Object Storage Device)集群:OSD 集群由一定数目的(从几十个到几万个) OSD Daemon 组成,负责数据存储和复制,向 Ceph client 提供存储资源。每个 OSD 守护进程监视它自己的状态,以及别的 OSD 的状态,并且报告给 Monitor;而且,OSD 进程负责在数据盘上的文件读写操作;它还负责数据拷贝和恢复。在一个服务器上,一个数据盘有一个 OSD Daemon。
root@ceph1:~# ps -ef | grep ceph-osd root 1204 1 0 Sep18 ? 00:24:39 /usr/bin/ceph-osd --cluster=ceph -i 3 -f root 2254 1 0 Sep18 ? 00:20:52 /usr/bin/ceph-osd --cluster=ceph -i 6 -f
(3)若干个数据盘:一个Ceph 存储节点上可以有一个或者多个数据盘,每个数据盘上部署有特定的文件系统,比如 xfs,ext4 或者 btrfs,由一个 OSD Daemon 负责照顾其状态以及向其读写数据。
Disk /dev/vda: 21.5 GB, 21474836480 bytes /dev/vda1 1 41943039 20971519+ ee GPT Disk /dev/vdb: 32.2 GB, 32212254720 bytes /dev/vdb1 1 62914559 31457279+ ee GPT
(MON 和 OSD 可以共同在一个节点上,也可以分开)
关于Ceph 支持的数据盘上的 xfs、ext4 和 btrfs 文件系统,它们都是 日志文件系统 (其特点是文件系统将没提交的数据变化保存到日志文件,以便在系统崩溃或者掉电时恢复数据),三者各有优势和劣势:
(4)要使用 CephFS,还需要 MDS 集群,用于保存 CephFS 的元数据
(5)要使用对象存储接口,还需要 RADOS Gateway, 它对外提供REST接口,兼容S3和Swift的API。
Ceph 使用以太网连接内部各存储节点以及连接 client 和集群。Ceph 推荐使用两个网络:
下图( 来源 )显示了这种网络拓扑结构:
这么做,主要是从性能(OSD 节点之间会有大量的数据拷贝操作)和安全性(两网分离)考虑。你可以在 Ceph 配置文件的 [global] 部分配置两个网络:
public network = {public-network/netmask} cluster network = {cluster-network/netmask}
具体可以参考:
Ceph 集群的逻辑结构由 Pool 和 PG (Placement Group)来定义。
Pool 类似于 LVM 中的 Volume Group,类似于一个命名空间。RBD Image 类似于 LVM 中的 Logical Volume。RBD Image 必须且只能在一个 Pool 中。
Ceph Pool 有两种类型:
Pool 提供如下的能力:
PG 映射一个 Pool 到它使用的若干个 OSD。如果一个拷贝型 Pool 的size(拷贝份数)为 2,它会包含指定数目的 PG,每个 PG 使用两个 OSD,其中,第一个为主 OSD (primary),其它的为从 OSD (secondary)。不同的 PG 可能会共享一个 OSD,类似于下图:
那如何确定一个 Pool 中有多少 PG?Ceph 不会自己计算,而是给出了一些参考原则,让 Ceph 用户自己计算。
1.3 Ceph 结构和状态地图:Cluster map
Ceph 要求 ceph 客户端和 OSD 守护进程需要知晓整个集群的拓扑结构,它们可以通过 Monitor 获取 cluster map 来达到这一点。Cluster map 包括:
(1)Monitor Map:MON 集群的状态(包括 the cluster fsid, the position, name address and port of each monitor, 创建时间,最后的更新时间等)。
root@ceph1:/osd/data# ceph mon dump dumped monmap epoch 1 epoch 1 fsid 4387471a-ae2b-47c4-b67e-9004860d0fd0 last_changed 0.000000 created 0.000000 0: 9.115.251.194:6789/0 mon.ceph1 1: 9.115.251.195:6789/0 mon.ceph2 2: 9.115.251.218:6789/0 mon.ceph3
(2)OSD Map: 当前所有 Pool 的状态和所有 OSD 的状态 (包括 the cluster fsid, map 创建和最后修改时间, pool 列表, replica sizes, PG numbers, a list of OSDs and their status (e.g., up, in) 等)。通过运行 ceph osd dump 获取。
root@ceph1:~# ceph osd dump epoch 76 fsid 4387471a-ae2b-47c4-b67e-9004860d0fd0 created 2015-09-18 02:16:19.504735 modified 2015-09-21 07:58:55.305221 flags pool 0 'data' replicated size 3 min_size 2 crush_ruleset 0 object_hash rjenkins pg_num 64 pgp_num 64 last_change 1 flags hashpspool crash_replay_interval 45 stripe_width 0 osd.3 up in weight 1 up_from 26 up_thru 64 down_at 25 last_clean_interval [7,23) 9.115.251.194:6801/1204 9.115.251.194:6802/1204 9.115.251.194:6803/1204 9.115.251.194:6804/1204 exists,up d55567da-4e2a-40ca-b7c9-5a30240c895a
......
(3)PG Map:包含PG 版本(version)、时间戳、最新的 OSD map epoch, full ratios, and 每个 PG 的详细信息比如 PG ID, Up Set, Acting Set, 状态 (e.g., active + clean), pool 的空间使用统计。可以使用命令 ceph pg dump 来获取 PG Map。
这里 有段代码可以以表格形式显示这些映射关系:
ceph pg dump | awk ' /^pg_stat/ { col=1; while($col!="up") {col++}; col++ } /^[0-9a-f]+/.[0-9a-f]+/ { match($0,/^[0-9a-f]+/); pool=substr($0, RSTART, RLENGTH); poollist[pool]=0; up=$col; i=0; RSTART=0; RLENGTH=0; delete osds; while(match(up,/[0-9]+/)>0) { osds[++i]=substr(up,RSTART,RLENGTH); up = substr(up, RSTART+RLENGTH) } for(i in osds) {array[osds[i],pool]++; osdlist[osds[i]];} } END { printf("/n"); printf("pool :/t"); for (i in poollist) printf("%s/t",i); printf("| SUM /n"); for (i in poollist) printf("--------"); printf("----------------/n"); for (i in osdlist) { printf("osd.%i/t", i); sum=0; for (j in poollist) { printf("%i/t", array[i,j]); sum+=array[i,j]; poollist[j]+=array[i,j] }; printf("| %i/n",sum) } for (i in poollist) printf("--------"); printf("----------------/n"); printf("SUM :/t"); for (i in poollist) printf("%s/t",poollist[i]); printf("|/n"); }'
(4)CRUSH Map:包含当前磁盘、服务器、机架等层级结构 (Contains a list of storage devices, the failure domain hierarchy (e.g., device, host, rack, row, room, etc.), and rules for traversing the hierarchy when storing data)。 要查看该 map 的话,先运行 ceph osd getcrushmap -o {filename} 命令,然后运行 crushtool -d {comp-crushmap-filename} -o {decomp-crushmap-filename} 命令,在vi 或者 cat {decomp-crushmap-filename} 即可。
(5)MDS Map:包含当前所有 MSD 的状态 (the current MDS map epoch, when the map was created, and the last time it changed. It also contains the pool for storing metadata, a list of metadata servers, and which metadata servers are up and in)。通过执行 ceph mds dump 获取。
root@ceph1:~# ceph mds dump dumped mdsmap epoch 13 epoch 13 flags 0 created 2015-09-18 02:16:19.504427 modified 2015-09-18 08:05:55.438558 4171: 9.115.251.194:6800/962 'ceph1' mds.0.2 up:active seq 7 5673: 9.115.251.195:6800/959 'ceph2' mds.-1.0 up:standby seq 1
Ceph 架构中,Ceph client 是直接读或者写存放在 RADOS 对象存储中的对象(data object)的,因此,Ceph 需要走完 (Pool, Object) → (Pool, PG) → OSD set → Disk 完整的链路,才能让 ceph client 知道目标数据 object:
(1)Ceph client 通过 CRUSH 算法计算出存放 object 的 PG 的 ID:
(2)通过 CRUSH 算法计算出将 object 保存到 PG 中哪个 OSD 上。
每个 Pool 包含一定数量的 PG。CRUSH 算法动态地将 PG 映射到 OSD。这种映射在 Ceph client 和 OSD Daemon 之间建立了一个间接层。这是因为,Ceph 集群需要能够膨胀(增加 OSD)或者缩小(减少 OSD),以及再平衡(rebalance)。如果 Ceph client 知道 OSD Daemon 上保存了哪个对象,那么两者之间就建立了一个强绑定关系。因此,Ceph 使用 CRUSH 算法,得以动态地将每个 object 映射到 PG,以及将 PG 映射到 OSD。
(object - PG --- OSD(s) 映射关系)
对 Ceph client 来说,只要它获得了 Cluster map,就可以使用 CRUSH 算法计算出某个 object 所在的 OSD。
(a)Ceph client 从 MON 获取最新的 cluster map,它包含所有的 monitors, OSDs, 和 metadata servers。
(b)Ceph client 根据上面的第(1)步计算出该 object 所在的 PG 的 ID。
(c)Ceph client 再根据 CRUSH 算法计算出 PG 中目标主 OSD 的 ID。
(d)Ceph client 向主 OSD 写入二进制数据块。
以存放一个文件为例,下图( 来源 )说明了完整的计算过程:
CRUSH 算法是相当复杂,快速看看的话可以参考 官方文章 ,或者直接读代码和作者的论文。几个简单的结论或原则:
使用 这里 的脚本,可以看出在我的环境中,6 个pool,7 个 OSD,每个 pool 中有 192 个PG,每个 OSD 在128+ PG 中:
pool : 4 5 0 1 2 3 | SUM ---------------------------------------------------------------- osd.4 20 19 21 23 21 24 | 128 osd.5 38 28 30 28 34 44 | 202 osd.6 30 33 33 32 34 36 | 198 osd.7 23 19 22 21 22 21 | 128 osd.8 26 36 34 36 30 20 | 182 osd.9 21 26 21 20 21 19 | 128 osd.3 34 31 31 32 30 28 | 186 ---------------------------------------------------------------- SUM : 192 192 192 192 192 192 |
这张图( 来源 )也有助于理清其中的关系:
总之,Ceph 采用的是通过计算得出对象的位置,这比通过常见的查询算法获取位置快得多。CRUSH 算法是的 ceph 客户端自己计算对象要被保存在哪里(哪个 OSD),也使得客户端可以从主 OSD 上保存或者读取数据。
如下图所示,Ceph 系统中不同层次的组件/用户所看到的数据的形式是不一样的:
Ceph client 向一个 RBD image 写入二进制数据(假设 pool 的拷贝份数为 3):
(1)Ceph client 调用 librados 创建一个 RBD image,这时候不会做存储空间分配,而是创建若干元数据对象来保存元数据信息。
(2)Ceph client 调用 librados 开始写数据。librados 计算条带、object 等,然后开始写第一个 stripe 到特定的目标 object。
(3)librados 根据 CRUSH 算法,计算出 object 所对应的主 OSD ID,并将二进制数据发给它。
(4)主 OSD 负责调用文件系统接口将二进制数据写入磁盘上的文件(每个 object 对应一个 file,file 的内容是一个或者多个 stripe)。
(5)主 ODS 完成数据写入后,它使用 CRUSH 算啊计算出第二个OSD(secondary OSD)和第三个OSD(tertiary OSD)的位置,然后向这两个 OSD 拷贝对象。都完成后,它向 ceph client 反馈该 object 保存完毕。
(6)然后写第二个条带,直到全部写入完成。全部完成后,librados 还应该会做元数据更新,比如写入新的 size 等。
完整的过程( 来源 ):
该过程具有强一致性的特点:
一个 RDB image 会被条带化为多个条带(stripe unit),每个 unit 会被保存在 Ceph 分布式对象存储(RADOS) 中的一个对象(object)内,而一个 object 可能会保存一个或者多个条带。这样的话,对 image 的读和写操作可以被分散到集群的多个 OSD 上,通常来讲这样可以防止某个 image 非常大或者非常忙时单个节点称为性能瓶颈。条带(stripe)是 librados 通过 ODS 写入数据的基本单位。
Ceph 的条带化行为(如果划分条带和如何写入条带)受三个参数控制:
以下图为例:
(1)RBD image 会被保存在总共 8 个 RADOS object (计算方式为 client data size 除以 2^[ order ])中。
(2)stripe_unit 为 object size 的四分之一,也就是说每个 object 可以保存四个 stripe。
(3)stripe_count 为 4,即每个 object set 包含四个 object。这样,client 以 4 为一个循环,向一个 object set 中的每个 object 依次写入 stripe,写到第 16 个 stripe 后,按照同样的方式写第二个 object set。
默认的情况下,[stripe_unit] 等于 object size;stripe_count 为1。意味着 ceph client 在将第一个 object 写满后再去写下一个 object。要设置其他的 [ stripe_unit ] 值,需要Ceph v0.53 版本及以后版本对 STRIPINGV2 的支持以及使用 format 2 image 格式。
参考链接:
http://www.wzxue.com/ceph-librbd-block-library/
http://docs.ceph.com/docs/master/architecture/
https://www.ustack.com/blog/ceph_infra/
https://hustcat.github.io/rbd-image-internal-in-ceph/
http://www.wzxue.com/category/ceph-2/