找回密码
 注册
查看: 1967|回复: 4

ceph 存储BlueStore的OSD创建与启动

[复制链接]

1

主题

0

回帖

12

积分

管理员

积分
12
QQ
发表于 2021-12-8 15:16:54 | 显示全部楼层 |阅读模式
为什么需要BlueStore
Ceph作为软件定义存储(SDS)解决方案,其首要目标是保障存储数据的安全。为了达到数据安全的目的,Ceph使用了WAL的方式(Write-Ahead-Log),这就是我们日常最熟悉的journal.
但是写前记录日志这种技术有一个主要缺陷就是它把你的硬盘性能降低到原来的二分之一(仅当日志和OSD数据共享同一个硬盘时),因为filestore在写数据前需要先写journal,所以有一倍的写放大。
filestore设计初衷就是就是为了充分发挥普通机械盘的性能,没有对SSD进行优化考虑。但随着SSD全面普及(主要性价比越来越实惠,也是新技术不断推陈出新的结果),Ceph应用在SSD之上案例越来越多,对于性能的需求是更加迫切。基于以上现实,社区推出了Bluestore的存储引擎,剔除journal方案,缩减写放大,优化SSD写入,同时数据直写裸盘。
BlueStore架构
整体架构
内部组件
  • RocksDB: 存储预写式日志、数据对象元数据、Ceph的omap数据信息、以及分配器的元数据(分配器负责决定真正的数据应在什么地方存储)
  • BlueRocksEnv: 与RocksDB交互的接口
  • BlueFS: 迷你的文件系统(相对于xfs,ext2/3/4系列而言),解决元数据、文件空间及磁盘空间的分配和管理。因为rocksdb一般是直接存储在POSIX兼容的文件系统(如ext3/xfs等)之上,但BlueStore引擎是直接面向裸盘管理,没有直接兼容POSIX的文件接口。但幸运的是,rocksdb的开发者充分考虑了适配性,只要实现了rocksdb::Env 接口,就能持久化rocksdb的数据存储(包含RocksDB日志和sst文件)。BlueStore就是为此而设计开发的,它只包含了最小的功能,用来承接rocksdb。在osd启动的时候,它会被"mount"起来,并完全载入内存
  • Allocator: 用来从空闲空间分配block(block是可分配的最小单位)
    . T6 X  K& m) V7 |. _: l$ h
说明:1 [) ]  m) U: r* d" Z
1.对象数据存储部分即osd指定的data设备(可以是裸盘分区,或者lvm卷,下同)
: I- o1 ^' K/ X/ Y+ o% t2.RocksDB日志即osd指定的wal设备
; b1 p; }) h  X6 w( K+ e3.RocksDB数据部分即osd指定的db设备. }1 }  l8 C/ t: B$ c+ o
4.以上设备可以共用同一物理盘设备,也可以分开在不同的物理设备,这充分体现了ceph的灵活性
以上只是本人粗糙的理解(未必完全正确或者跟实际有出入),希望有大师出来指点一二。

部署实战基础环境
1
+ B  ^) w# v" x( {& F/ ]2  [4 |  D0 b" _* ~! V
3
, c& H  F& F/ B$ K6 m6 ]0 n
- E2 A. Y. f! \; l8 `# ?
[root@compute ~]# ceph -v
- s- [# ^/ W, s& ?ceph version 13.2.0 (79a10589f1f80dfe21e8f9794365ed98143071c4) mimic (stable)
" }3 R, k# X0 Q+ M2 Z
在创建osd之前,集群已经初始化完毕(即已经有Mon节点)。先熟悉下ceph-volume,可以看到当前支持lvm和simple两类子命令集。lvm用来创建osd,simple则是管理已经创建的osd。
1
- C: w5 L" o: b3 J8 y* T( {2# O* `5 D  j5 E3 x3 X
3
/ r/ @/ e4 a9 X0 v; j$ g4
" J4 }6 D3 m% _7 M. Y+ q5
8 k: z& [& n& }% s( @66 h9 {. x$ S; P* M! |) @1 J* t+ |
75 N. p6 ]* E% o$ n
ceph-volume -h* n$ F4 [* e$ ^

3 Q7 f( r$ C9 P7 |... <中间省略>% ?# R& n3 M# `4 O! f
Available subcommands:
7 R/ c% h7 j: T1 A4 M7 C9 h7 ~( m9 @" \$ ~5 v# J6 o
lvm                      Use LVM and LVM-based technologies like dmcache to deploy OSDs
+ d" u& c* x  F9 N. l$ [. U3 Esimple                   Manage already deployed OSDs with ceph-volume7 ^, i5 }( `* ]/ }: o) N. @
预先创建好需要的lvm卷,如果没有独立的盘来单独存放wal和db,不推荐再在设备上面创建lvm卷分配给db和wal。默认只需要指定数据路径(–data)即可。Bluestore会自动管理所有的空间(包括data、db、wal)。
注意
2 W3 Q7 o8 f6 u2 R! P; x如果有独立的盘来存放db,官方推荐db的空间不应小于数据空间的4%,以1T的数据空间为例,db的大小不应该小于40G.官方连接
创建lvm卷
以下创建的lvm卷只做演示用,data、db、wal分别创建独立的卷
1& S& W% G8 [8 F* C2 i
2
  o9 F/ g' V* z' c# }) p30 w, j  H# g7 t9 L
4( r( |8 d! J1 s7 s3 v0 V6 G; \
5! W% ?: v  k4 |& J9 \5 s7 A
60 a* `# i, Y. e- v6 G* _6 O
7
9 q! _5 k5 |( m( F9 N8
! U% }8 R( W: K( j9  _: k4 Q, S. _+ N" `
100 a6 N  b) J5 h5 A3 c0 A
11
7 p" N. g" |: I. F7 d
[root@compute osd]# vgcreate   osd.3  /dev/sde6 `; s0 U5 V( G6 ]; z2 @/ T
  Volume group "osd.3" successfully created
3 O9 {5 w# ~  S/ a
5 H; h. v# @( ]3 R" E1 ~[root@compute osd]# lvcreate  -L 1G  -n  osd.3.db   osd.3
( c/ `3 C: A" C  Logical volume "osd.3.db" created.: U0 t- x6 @2 _5 t

" `( }% o7 N5 ]. j: x[root@compute osd]# lvcreate  -L 1G  -n  osd.3.wal   osd.3
! W' t( ~& u. A9 a' n& h  Logical volume "osd.3.wal" created.
# |+ E' C: Z# c% U1 G- r
$ T- G# r9 y2 X) p$ s[root@compute osd]# lvcreate  -l 100%FREE -n osd.3.data osd.3
7 i9 w( }: \1 J! h  i  A  Logical volume "osd.3.data" created.. g$ @  F+ K! o  p
创建osd
image_1cn618mlp1eskvqdeov12o1vgj19.png-232.8kB
从上面的截图其实可以把整个create流程分解为prepare和activate阶段,下面就开始庖丁解牛。
prepare 阶段
  • 收集keyring
    1
    ) B5 k7 \' |+ M$ s
    Running command: /bin/ceph-authtool --gen-print-key9 ^+ n( w3 `% x0 n' O
  • 创建新的osd id
    1  g4 @/ V! D% u+ E) a
    Running command: /bin/ceph --cluster ceph --name client.bootstrap-osd --keyring /var/lib/ceph/bootstrap-osd/ceph.keyring -i - osd new 9501a491-625f-4c2c-bf8e-a1a27cc0e6e5; g$ }8 S9 M7 x  \( O
  • 挂载tmpfs目录,这不同于旧版本使用本地文件系统。BlueStore把这些依赖的文件信息都写入到了裸盘里(在osd启动之前,会重新生成)
    1" k* ~0 J" q! @& E6 p# G
    Running command: /bin/mount -t tmpfs tmpfs
    * [; D6 ?. M' W1 c
  • data对应设备创建软链
    1' @( n& o& X" [" @# f% W! j9 O
    Running command: /bin/ln -s /dev/osd.3/osd.3.data /var/lib/ceph/osd/ceph-3/block
    % z9 c- t, U5 b  |
  • 获取当前集群的monmap
    1
    , W! d2 L/ [! o' e1 b& d
    Running command: /bin/ceph --cluster ceph --name client.bootstrap-osd --keyring /var/lib/ceph/bootstrap-osd/ceph.keyring mon getmap -o /var/lib/ceph/osd/ceph-3/activate.monmap
    & D3 B; ?7 Z2 q* t- V
  • 写入keyring文件并在集群注册
    13 l, G! ?* T  L4 r
    Running command: /bin/ceph-authtool /var/lib/ceph/osd/ceph-3/keyring --create-keyring --name osd.3 --add-key AQCy/Zdb4rTZIRAApGGe5QENqb/UPUHVRzI5Dw==* z, ~& I" H$ ]7 j; f
  • keyring文件和工作目录以及设备的权限设置
    1# L& g# [+ ^  h$ u5 _
    2# @5 T7 I( b# J. g# r
    3/ ]3 D5 @0 R4 ]" X: t
    4
    ! n/ |0 S$ O& g' @& @
    Running command: /bin/chown -R ceph:ceph /var/lib/ceph/osd/ceph-3/keyring
    ( B( H6 z. n: zRunning command: /bin/chown -R ceph:ceph /var/lib/ceph/osd/ceph-3/
    7 W9 g! c) R4 r. h- w3 o2 zRunning command: /bin/chown -R ceph:ceph /dev/dm-16
    5 J; ~. s3 F1 ]- f5 d) l. U+ `Running command: /bin/chown -R ceph:ceph /dev/dm-156 S) Q5 e7 E, q$ Y7 d, `
  • mkfs初始化bluestore
    1
    9 s4 d- _" b/ q
    Running command: /bin/ceph-osd --cluster ceph --osd-objectstore bluestore --mkfs -i 3 --monmap /var/lib/ceph/osd/ceph-3/activate.monmap --keyfile - --bluestore-block-wal-path /dev/osd.3/osd.3.wal --bluestore-block-db-path /dev/osd.3/osd.3.db --osd-data /var/lib/ceph/osd/ceph-3/ --osd-uuid 9501a491-625f-4c2c-bf8e-a1a27cc0e6e5 --setuser ceph --setgroup ceph
    - l8 U9 {. D* l8 H( a
    以上就是prepare的过程中涉及到的相关操作。
    / F' ^, c+ O6 k% I4 x
acitivate 阶段
  • 从裸设备里直接获取启动OSD需要的相关元数据信息并写入到工作目录里(这些信息都存储在BlueStore的label里)
    1& L+ j# p5 F, r9 }/ P) b) w
    Running command: /bin/ceph-bluestore-tool --cluster=ceph prime-osd-dir --dev /dev/osd.3/osd.3.data --path /var/lib/ceph/osd/ceph-3, t! F- \3 i. Z' y+ A
  • 可以通过以下命令获取label信息
    1
    2 k$ e- H% `7 C* w24 k& t3 J% M7 P: g; Z9 A: B6 m6 O
    3  P9 m2 L! f" f7 F6 H; R: m
    4) K: O/ m# L3 {5 t: O' K  S8 {# D
    54 G. J' e9 V6 F
    6
    9 `" t2 k; y# |+ b+ M3 P% M7
    0 D% A: ]9 E/ i1 r7 u80 d6 v6 W) J6 _3 `, t
    94 X: y$ O  ?1 [" B
    10
    5 W3 N0 a8 h$ x; ^2 ~11; L! Z7 L' w' O
    12
    8 M& Z8 b$ f" C) x( y- r130 d4 o. P9 H( _5 i2 O8 k  V
    14$ b' N! c- p' }! {+ U& U
    15
    # K: d/ a9 c* |: K( _& Z16
    : S# P0 I0 u- ~; i" k- o17. g+ Y, B9 V, r0 _1 r& b& k. j
    18
    $ T1 W/ g9 o0 `+ [. T- u; \19
    8 _5 J5 i' |9 V- _: ?4 \202 x! B& G, T* d8 w
    21
    0 n3 p+ D& d' ~7 M; z: ?22
    % I, r1 {3 C+ n/ b23
    1 ]0 C; n; H* X4 s' m24' i0 l1 A% m% v
    25* a& P- r6 T8 p, x/ m
    26
    8 y7 H% t7 O; k3 r+ p271 P7 u$ r7 I9 Z2 D& j
    28. g4 a. _% L+ ?* ?, u" b
    296 }( Z0 T6 J5 Q
    30! M, e6 s/ i- j" F
    31
    $ B5 k3 R+ R2 q' ?9 n% X
    ceph-bluestore-tool  show-label --path /var/lib/ceph/osd/ceph-3
    8 t) N7 S; b6 [& ^; H{9 M0 q6 v6 S7 l' {
        "/var/lib/ceph/osd/ceph-3/block": {
      R3 J" h1 g$ y        "osd_uuid": "9501a491-625f-4c2c-bf8e-a1a27cc0e6e5",( S4 M9 L' H+ s) ~! F! D0 C
            "size": 2997882978304,  H. n3 {# u1 ~0 v% h4 T
            "btime": "2018-09-11 17:39:00.380028",) @) G# S: R; [8 L: X* ^
            "description": "main",& ~* s; N% r5 ^- |8 d
            "bluefs": "1",
    8 |9 N' }: S+ m        "ceph_fsid": "a02fbd16-0db7-477d-8f16-a7d3cbfd8d73",
    # W7 v& ]+ N6 |5 X- J# Y# h        "kv_backend": "rocksdb",1 Z8 H5 k5 q  h9 E7 w; b
            "magic": "ceph osd volume v026",
    7 h4 [: S3 `: l( c# X/ B        "mkfs_done": "yes",( _" w- W2 L0 i% q% Y+ b
            "osd_key": "AQCy/Zdb4rTZIRAApGGe5QENqb/UPUHVRzI5Dw==",
    * l' d: L% X5 a; I9 G- d        "path_block.db": "/dev/osd.3/osd.3.db",
    ! {$ P, k2 e) M0 I        "path_block.wal": "/dev/osd.3/osd.3.wal",
    6 B, A1 o% M: N/ H! ]5 C# J7 A        "ready": "ready",5 b1 c4 x; d  u3 P
            "whoami": "3"
    5 D0 g( E, B9 r+ M    },! I( t9 O/ O/ \5 q, Z6 i& Z$ s( d
        "/var/lib/ceph/osd/ceph-3/block.wal": {
    7 y$ E! G0 b0 F7 D* u5 q4 d3 O        "osd_uuid": "9501a491-625f-4c2c-bf8e-a1a27cc0e6e5",
    0 a- n7 K8 u2 F- \# V        "size": 1073741824,: Z* A2 |3 t$ A8 P& S0 N# _
            "btime": "2018-09-11 17:39:00.381666",
    : h3 P( Z5 V1 F3 {. g0 j        "description": "bluefs wal"
    ' U, J3 O. ^, x: g9 U    },: I0 U' D; A  y0 R5 I$ ?
        "/var/lib/ceph/osd/ceph-3/block.db": {; f0 o) m7 r2 M$ T9 ?1 Z  F2 O" K! Z
            "osd_uuid": "9501a491-625f-4c2c-bf8e-a1a27cc0e6e5",
    / o) x$ w- g; L: X: W* h        "size": 1073741824,  l9 U) \6 F/ f5 P# V( K
            "btime": "2018-09-11 17:39:00.380983",# O5 e5 S4 C2 M2 c$ b' ?! ~7 c  D
            "description": "bluefs db"
    6 g) U% w6 P8 M    }
    : C7 y5 s, g0 H: }. L! E* v% {}
    & z) O' Q1 c6 Q
  • 创建设备文件软链并变更设备的所有者和组
    1) g! n9 p0 ~5 y. A7 C5 M3 Y( p
    2
      D- e. m; ~7 y/ k6 o, M3 C5 c+ q3
    3 U( l" M9 W' T( B$ R. [4
    % d% @+ W* S' ^" R- v4 P/ C5
    & K6 l' z$ u9 y% V% z63 G  z' i& n& j/ ^- X
    7
    0 R0 [8 o' M2 g! s& h$ j* j
    Running command: /bin/ln -snf /dev/osd.3/osd.3.data /var/lib/ceph/osd/ceph-3/block& _4 ?1 \5 p( l# z
    Running command: /bin/chown -R ceph:ceph /dev/dm-17
    , ~0 W3 ~, p9 ~- r0 X. U" k% jRunning command: /bin/chown -R ceph:ceph /var/lib/ceph/osd/ceph-31 u. P9 P$ P6 s4 F1 i5 w4 Q
    Running command: /bin/ln -snf /dev/osd.3/osd.3.db /var/lib/ceph/osd/ceph-3/block.db' z# S. }$ g% {7 U
    Running command: /bin/chown -R ceph:ceph /dev/dm-15
    $ p8 T' L( F  l9 T. f6 GRunning command: /bin/ln -snf /dev/osd.3/osd.3.wal /var/lib/ceph/osd/ceph-3/block.wal% Y$ f0 n$ c6 w1 \! `/ H5 H
    Running command: /bin/chown -R ceph:ceph /dev/dm-16; M% p. ]$ l2 b& K5 y# ?1 |+ m
  • 注册系统服务(稍后分析,继续往下看)
    1" v; o3 I( \* W: a2 k, x
    2
    . }0 Z% q& D$ y  j1 `4 H* y
    Running command: /bin/systemctl enable ceph-volume@lvm-3-9501a491-625f-4c2c-bf8e-a1a27cc0e6e5: |" O  U7 S. p2 K# J
    stderr: Created symlink from /etc/systemd/system/multi-user.target.wants/ceph-volume@lvm-3-9501a491-625f-4c2c-bf8e-a1a27cc0e6e5.service to /usr/lib/systemd/system/ceph-volume@.service.
    ) I3 G2 F: \" [' i
  • 启动osd
    15 m5 a6 }" h/ j7 ]
    2/ S, Z% q( k# X  Q
    Running command: /bin/systemctl start ceph-osd@3+ g4 w! v( @. d2 n% }9 [* _) j7 a
    --> ceph-volume lvm activate successful for osd ID: 3
    & P+ K7 L) W9 C8 y( t# |/ i
    acitivate阶段结束,osd进程就起来了
    : R+ I& m; d7 i0 P+ U; J; A
小结:. p/ W1 P" t7 ^5 x, e
以上分解的osd创建步骤,为后续使用ansible来自动化部署Ceph集群至关重要。后续打算写个简单部署ceph的playbook,在此先mark下。
启动分析
BlueStore的OSD启动不同于老版本基于udev规则触发ceph-disk相关命令来启动,它依赖于ceph-volume相关服务与命令。
ceph-volume 系统服务
上面可以看到,在创建osd的过程中有注册一个系统服务ceph-volume@lvm-3-9501a491-625f-4c2c-bf8e-a1a27cc0e6e5,
19 M$ X! Z) l3 O
2" l7 F/ z6 ?: a. h% ~' q( l
3
5 j  |0 b2 U# Y4 }- Y4
6 e" i4 [! e, Q9 I5 U9 j5
. y# C1 M9 g! y0 G0 h6
' S- f7 K$ \, S- J+ p* }# I7% c+ h! U' s4 Z4 I
8# Q1 l* {' M6 {
99 q+ }9 \0 k) x( d. _
10/ B1 M! A9 g+ r3 Y: \
11( m  F! C2 R/ e- l) A. F+ e
120 i: L! S, V1 Z" o! c
13) l% |2 f- U: m5 ?& {, m/ E
140 \# @) G" L1 }/ t+ ?7 N" C
15
& C( @  a* u0 f4 ]0 {  l. y* S
#cat /usr/lib/systemd/system/ceph-volume@.service
5 P! Q+ t- P$ v; `$ O- X$ [[Unit]& s, j/ X! L4 I5 N  {1 {; y
Description=Ceph Volume activation: %i
/ b3 w# k; f8 M+ i5 @& EAfter=local-fs.target
  a. r! k& g* o( ^Wants=local-fs.target* \+ s4 V6 D5 ?

: r9 O6 M: i6 }1 d[Service]  B: g* w% a+ Y) H
Type=oneshot
: A. l. v2 E$ K7 ]% u, tKillMode=none
! l6 U6 q* A1 DEnvironment=CEPH_VOLUME_TIMEOUT=10000
5 g8 @4 [# m8 IExecStart=/bin/sh -c 'timeout $CEPH_VOLUME_TIMEOUT /usr/sbin/ceph-volume-systemd %i'
' a% d1 v" [) QTimeoutSec=09 U+ C% Q4 R* \1 m) ?! W( |& A
1 s3 Q+ z+ R0 V, \; g( r
[Install]
2 A7 P  Y$ S: ?WantedBy=multi-user.target: F3 K. S8 P4 y, J6 ^# H
这个系统服务是在local-fs之后就执行了,优先级别是比较高的。这个服务传入的参数是lvm-3-9501a491-625f-4c2c-bf8e-a1a27cc0e6e5。其时可以分解为lvm、3、9501a491-625f-4c2c-bf8e-a1a27cc0e6e5.见明知意,分别是是lvm标签,osd的id,osd的uuid。0 k2 g0 V5 h6 |3 ~
系统服务只有一个简单的脚本。
1$ y& f6 F: u/ p
2
5 _. ?$ ~8 H" q7 J) c33 f3 u6 V! H, P$ a; B% ~4 s' m6 r
4( t# }+ U1 f6 e1 v8 _
5
/ v0 |2 g2 p/ A# q6
' }( Q1 ]! P# z" R; \; B4 p7( j: c) |/ V; l& q) [" I
# cat /usr/sbin/ceph-volume-systemd
& q, f0 Y. E) v3 z" s  m, o+ m( ^#!/usr/bin/python2.70 `8 q* t& f7 y5 W2 H6 N

; y+ c6 o7 e, h* M2 yfrom ceph_volume.systemd import main
& r# B. _8 Y9 y& x( o0 y
' D5 Z" u3 V- T  J9 dif __name__ == '__main__':" ?: t' E% X. Z9 ~, |8 P; v4 o0 H. t8 S
    main.main()
' ]2 }- Z% y4 l0 y: K# W; e; T
从上面看,已经开始进入ceph-volume项目了。
& A" [% c6 P$ k# D8 ~项目路径是ceph/src/ceph-volume
1
; ?6 [- |! T( G# W( m, Z. Z( F2
; @6 G" ~" s) S8 h. U3, [9 c! V4 i; {4 j6 X2 a( `
4
: P; R  ~4 |6 j1 z5
, i9 y- a& O& d8 f. P8 i6
) k" ?+ P7 ~. ~' K/ F/ U7 ]* U5 k7# z. m+ V8 U+ d  p, C
8
! }  b4 k$ `9 R2 _& w4 y9! P( i4 h* ?, U+ Y2 f
10
' a- t6 _  a4 P( G( N11' a' f- ^! C& h, j: `1 i
12$ A) W! h) _8 B; Y
131 Y1 D( R1 i, V. G
14
) s) r# U& o6 _* G- i- r15, r* p/ D8 G' J+ |# z
16& I9 p4 ]- G* I- m1 H
17
* u2 G& O4 B& b6 P7 S5 ]! i' [4 V18
7 }* V: a! a% R19
" j7 ?0 k' X0 S; ?, O20: j1 |; v; u8 B* J5 q0 P. Q
21
! L( G  \* o3 O& x# H( p. ?
## ceph/src/ceph-volume/systemd/main
" N; z5 y1 R" X$ Idef main(args=None):% D6 S: g( X  [9 {7 x6 w
    """
0 J* W! ^* D; q' j* ~$ C    ... < 中间省略> " P$ D0 r# r4 V# w( S: m6 Y) ~
        ## 转换为ceph-volume lvm 命令 继续执行 ## 9 \2 @! l  n( x  F
        ceph-volume lvm trigger 0-8715BEB4-15C5-49DE-BA6F-401086EC7B41" k' f, t9 ?9 t* y% H

9 q- Q) D) d. \5 i, v    """4 F  O7 r7 B3 q3 @/ _( a
    log.setup(name='ceph-volume-systemd.log', log_path='/var/log/ceph/ceph-volume-systemd.log')
( m) B2 L0 L4 @: r    logger = logging.getLogger('systemd')
5 q# D, u4 ]1 c1 e5 q     ... < 中间省略> ; Y3 `' r$ v. n9 u# d
     ## 参数解析
- X8 r; V' |% P* Y. y( g' q  L" t    sub_command = parse_subcommand(suffix)
2 ]( D0 v8 M5 r; G    extra_data = parse_extra_data(suffix)  G7 @$ h% W9 b
    ... < 中间省略>
% G; p, X1 d, U7 }+ Z    ## 构造成为['ceph-volume','lvm','trigger','id-uuid']的格式' f$ r$ K$ K! ]5 O' P9 w( {7 |
    command = ['ceph-volume', sub_command, 'trigger', extra_data]
! c6 ~7 x* _# Z+ C- z9 N$ A    tries = os.environ.get('CEPH_VOLUME_SYSTEMD_TRIES', 30)- Z+ `  @  Z1 H2 M
    interval = os.environ.get('CEPH_VOLUME_SYSTEMD_INTERVAL', 5)
9 B+ w  ~9 {# n: W    ... < 中间省略> ) I+ u3 U* M9 V* [0 ~; y
            process.run(command, terminal_logging=False) ##下一步执行入口##
9 b' X9 o- W  Y. z
这里只是对传入的参数进行处理,最后调用ceph-volume lvm trigger
ceph-volume lvm trigger 处理
1& @1 V' U" f3 a) c4 q$ C1 @2 {5 t
2
6 p& y! _) {; w6 x' d6 }3* P2 H6 X" a0 k& J7 I
4
# |0 X* L2 R3 s* y" a2 W5
, J# N' t; k+ O$ a8 u7 q6
6 V$ {0 t+ s- H- x9 o2 A7
& j, q  I8 U; I, k- }. M/ ~
#cat  /usr/sbin/ceph-volume
& i: X) d- g! j. s) _" l8 G' f#!/usr/bin/python2.7
. Z3 ?9 z$ u2 Q/ t7 N1 p$ C
7 i1 a& r7 v8 j( }$ Vfrom ceph_volume import main
5 x; t+ n  d! J
, r; h4 _( p- y- l% Vif __name__ == '__main__':
4 f5 ^. N+ P0 }' k1 b$ G    main.Volume()4 i) g/ u3 G/ b9 ]( x
下面进入ceph/src/ceph-volume/main,找到入口函数main()
1
' `7 G# D0 M: |: }( ^& {. p+ h6 M2
9 C6 k! q- ^6 l% t; b6 ~" P7 o- v3
5 [# O+ s3 |2 C) g) s% ]4; ]2 k# o6 ^  z5 N* `+ ]' u
52 k: @% Q4 v) {, K- |: k
66 A/ ]& i. `0 K
71 J& M/ e- w7 r- h) B
8: R' e0 I* x! b0 U* i. @" N
9
) S. }1 e9 x$ H% s2 w10
& }: O" P$ r, V7 T" S  q' H
def main(self, argv):* D- m5 y; J9 f- P! r( N8 }4 d/ f
    # these need to be available for the help, which gets parsed super
8 O6 F! A% q3 r    # early1 o8 s3 g' w: ?4 d
    ## 一些预备处理,比如日志,ceph配置路径等& B) f7 K" I8 C: P% [
    self.load_ceph_conf_path()1 |' d- `6 a! M* _) j# A
    self.load_log_path(); e! j# b& w/ \0 d5 c4 A: `0 c
    self.enable_plugins()
9 \9 r6 x9 D3 s& [0 j* R$ q! q/ l    main_args, subcommand_args = self._get_split_args(): {9 E  r: \) c2 C, c7 d" e" ?
    ...<中间省略>$ h* f6 @" t8 B$ ]
    terminal.dispatch(self.mapper, subcommand_args)3 _8 C; ^" P! T2 c( P9 z
最终进入的是terminal.dispatch,核心是传入的参数。4 b; t+ A0 C' U/ B5 s' U* D
以下是pdb的结果,其实这是一个工厂模式的设计实现,根据subcommand_args里类型,创建对应的实例,通过实例方法实现功能处理。$ w7 [; K, L# a/ n0 l) f& l
image_1cn6k8a3e1f754eu15armjhcgj9.png-25.6kB
1" p. z; s  I( ]
2
& u  Y% J; S( C8 f3
: [7 N# e5 x1 b, \4
  L) s6 P$ o% H5
4 V/ k0 l8 F4 k& l68 x8 ^. v- l$ F1 Z* R
7
+ ~* L6 c" X1 _3 _- e8
! [$ \/ j$ S) r9
3 N- J8 c" W! _% K105 ^# x- X/ t* D% W
11. O6 E8 m6 i/ k# Q; H3 e) W8 ^, S
## ceph/src/ceph-volume/ceph_volume/terminal.py
3 S" [2 D- L) udef dispatch(mapper, argv=None):* w( w6 A: P! w9 Y" A) K9 P1 l# N
    argv = argv or sys.argv0 F6 n' v% f9 W/ \1 ^- k
    for count, arg in enumerate(argv, 1):
8 s, K8 [& {7 W0 n; w2 t7 _0 j5 L        if arg in mapper.keys():
9 A: l2 Z3 E$ m* O5 R2 r1 s            ## 创建实例1 O2 t+ x7 w/ ]% J
            instance = mapper.get(arg)(argv[count:]): q9 {1 Y6 e; k, t+ S
            if hasattr(instance, 'main'):
& n7 ~7 U: C0 e2 c: G8 b                ## 实例main方法! O* @8 h4 W0 V- U& l5 d: b
                instance.main()
5 u5 g8 L1 O# r, E5 ?                raise SystemExit(0)8 I% i  E& \: R
显然根据传入的参数创建了LVM的实例,然后进入实例的main方法
1
7 M5 K7 E4 {/ G1 I6 {2+ H8 A6 K, y5 ~5 M% h3 F
3; ^! t: E" d: S" ?: a$ }
4
; g- u7 N4 H( K5
# i& J: g, m  R6
& v$ L6 M9 {/ o: @1 K7
0 Q( }" I  j3 a4 v) i2 Z88 t# [7 x* z( \5 n" D* W( [& b
9
4 b9 f0 |' d- a3 B# N10: O+ c; _. X) W- {  e0 \+ _) `
11
  j/ T- _7 x  s6 H0 S12
0 ]3 K+ M# O# b; j3 u. I13
$ {0 N( ]3 Z/ L% M, d' l14* _. K: l- f- {  K, x6 L; B
## ceph/src/ceph-volume/ceph_volume/devices/lvm/main.py
" f# F7 f  M: c* E* F6 h6 E$ Mclass LVM(object):   
- b* N  H! k& A       mapper = {1 Z3 ?3 t5 P8 k/ {$ Q
       'activate': activate.Activate,
" m2 q: c& l& D$ q       'batch': batch.Batch,
0 Z9 A7 J6 \# _8 z2 V7 c       'prepare': prepare.Prepare,
; n# h2 J$ o/ t; R8 B# ]- P$ i       'create': create.Create,, n2 O# Z: B+ q* c7 h/ D
       'trigger': trigger.Trigger,  ### 关注这里### $ K, k' G( f9 G1 V  E
       'list': listing.List,; ]" P9 Z; x! G2 v6 ?7 ~" x- k
       'zap': zap.Zap,
% ]8 c! u. ^- T/ L: K5 z   }; ?+ P# x) a! U2 |+ }5 I8 M8 ?
   def main(self):
% c- S( ~/ m$ ~. w$ @% M$ C# E       terminal.dispatch(self.mapper, self.argv): j: Y4 _1 P6 N2 H" a4 I
       ...<中间省略>
9 W! d* P: a( O) m
跟前面如出一辙,根据传入的参数对应进入trigger.Trigger实例的main方法
1
7 S! G, L/ y% R0 u; n' Z7 W8 m2
3 r& ?; q1 X# O1 h" I# X& K% W3! [6 X: E% x4 @; }
4  ?7 d. k# V: G3 `- @' G# V
5
/ t3 d8 w' u% @7 Y' C6, C5 b* _. @6 }: J; Y
7* g7 B( _$ [! V6 N1 g
8! g$ R/ X* O/ |2 B7 ~, S& r  l2 P
9  W3 c0 t$ R  G) e
10
# J2 q7 X' t- q$ A' t2 u8 u114 Q. T3 i- r! \( d: E
12
) {' P( I. Y( v0 k13
& A3 m4 ]0 z0 H+ T; f' A) Y14
; u6 O+ R$ W& l, A/ c+ G' K+ L15
1 ~9 ?" q4 u  j; R" I165 i$ ~; H5 M) ^% G
17: y& W! h8 q" i; e1 y; g
189 ]1 M8 _& Y& {6 C
19
5 V3 a4 U. h& j. m1 I0 V
## ceph/src/ceph-volume/ceph_volume/devices/lvm/trigger.py
0 f: {: e! k0 e& qclass Trigger(object):. @6 a4 i4 C) K2 ?/ l. N

9 T2 ~4 L' R, E' q) U; ]$ c    help = 'systemd helper to activate an OSD'& [. i3 }2 `$ h) X4 c) V
# |% z2 q, F$ o$ ]
    def __init__(self, argv):6 H$ U/ `& ?0 N  a* H2 t% M" H) ]9 x
        self.argv = argv2 j: l/ M( L8 x  f" {% `

1 ^7 u% B) r4 e6 ^    @decorators.needs_root
" a3 E3 h  z) }* h    def main(self):8 Z7 p; c5 |! |- _
          ... <中间省略>) u! a1 a. G+ ?5 _# ]7 `+ J
        ## 解析参数7 D  T0 Q) E& K7 K0 f4 ?
        args = parser.parse_args(self.argv)
/ \5 Y; p+ v7 F! p* [- ?$ Q( `; y9 V        ## 检验osd id
( z  a# ~: h  J$ ?        osd_id = parse_osd_id(args.systemd_data)1 v& l  T# ?" t0 z; ^6 x
        ## 检验uuid
4 z5 x3 U. D  S2 m5 N/ t7 B        osd_uuid = parse_osd_uuid(args.systemd_data)
/ t+ [% z9 B% Y7 \  c% Q        ## 再次跳转到Activate实例main方法
$ X' F" ]2 I. ]' ^) X+ N4 n        Activate(['--auto-detect-objectstore', osd_id, osd_uuid]).main()
: ?& l4 W+ C& Z( w4 _6 s  ~+ w
以下是Activate类的main方法
18 i1 `4 j  F4 |+ s5 n) W  y  b5 ~, {
2' @$ ]3 s8 T- ]) D+ d9 t
3
; ^% t1 \/ E/ {* O) n$ V" @4 u9 A42 t, i' |- y- z7 w
5
7 s5 I* ]% b/ }0 \9 p6
2 l6 M7 X, J4 k7  o% X5 C9 d3 P
8
# M) {" H- o4 n+ W" n, }9
0 o0 ^" l9 g+ p10, H  r' q' c6 \
11
+ h8 F6 _3 Z1 {: \5 h, e( |& w12) ?, h; m; ?  e. c2 E, l
13
/ T  J7 W$ c7 F3 N14* q8 j3 o' [7 s, ]' V* h6 E
##ceph/src/ceph-volume/ceph_volume/devices/lvm/activate.py
; o5 g' B6 v3 I3 H( Y+ F8 G) Zdef main(self):
# E  [& @, V$ B5 n2 ~4 D6 e$ ^+ e* e    sub_command_help = dedent("""
- W5 G- p4 s% d6 ^0 M    ...<中间省略>
. A3 c. j) f  V. P( r( q    )2 k  G" Z2 T, Z) h0 }. l7 R
    ## 参数解析$ n( O3 n$ W, t9 E4 c
    args = parser.parse_args(self.argv)+ f3 L; R! H. J0 r7 F4 A
    ## 如果不指定是bluestore或者filestore,默认按bluestore来处理- h6 @" |2 X, s
    if not args.bluestore and not args.filestore:$ f+ j+ ^' I! K9 }
        args.bluestore = True3 C4 t' a; c$ ^
    if args.activate_all:
# J* a4 x( `: J8 D5 ~- k3 i" K& w        self.activate_all(args)0 o) Y/ s3 r( ]8 ~6 {
    else:
: s# V. H+ C. U. K# t: m% M  c        self.activate(args)
: z% u& F% O/ R' q
最后通过实例的activate方法来激活
1: @" h; X% B: o  O  z9 n
2
: f% u) o! ~3 d- @/ r( C' u3% f! M* n2 c" M# x/ `& m- H
4$ ?1 G0 M; V0 u% K9 ]2 ?
5. I- z5 t. |! c. _. S
6
/ g( _8 i% O( a6 O. a. K( l( z7
- A1 W0 U+ S7 v6 ?$ h. l86 B! K5 }# y! {
99 f9 _) I+ Y, l, j$ P
103 R$ M( w0 R2 h% y) x9 L9 T# f
11# L! M3 D* j2 u
12
2 V) @" [# A- \& ]8 r13. a' b- |4 U% _+ N/ o+ I
14/ V! L! h7 I9 n- e1 O) ?2 T
##ceph/src/ceph-volume/ceph_volume/devices/lvm/activate.py
- b3 r5 Z, O9 Ndef activate(self, args, osd_id=None, osd_fsid=None):- m: {) v: q5 q8 z
        ...<中间省略>" Z7 o  g* D7 y4 }1 [) X' l* o
       ## 获取本地所有的lvm卷信息$ d; j6 f4 p/ C6 o* T, S
       lvs = api.Volumes()
' a: d/ n+ C. S       ...<中间省略>' k% Z+ h3 j3 K0 |; L8 x" c
           for lv in lvs:
9 X/ r& c  G/ ?( U- Q1 r; Q                   ....4 i# T1 l8 P+ C& B
       ## 通过lvm的tags来判断传入的osd id跟uuid是否已经存在,这些tags是在创建osd的时候写入(请继续往下看^_^)            
2 Q% N! T" u2 ?; e' B5 Z           return activate_bluestore(lvs)
- B: s/ t6 \' h/ i8 X  i       if args.bluestore:5 V6 A3 ^# F/ N
           activate_bluestore(lvs, no_systemd=args.no_systemd)
. ]( Q( z5 ^* @1 m  v, S       elif args.filestore:
& ]: B4 J+ Y; @3 Z; F; E           activate_filestore(lvs, no_systemd=args.no_systemd)
; D  _5 b; j5 P
BlueStore的osd则继续跳转到activate_bluestore
17 a) |9 _! w" d- ]1 W
26 K5 }. I+ o# c7 ^
3
8 f) U! P+ Y) u1 r! [% Z9 D6 M45 W. D- G, n2 z  v% k
5
$ e2 Q' Y8 G* |3 n6
; V' x. H3 z/ e1 {0 m& J& p7
/ x3 v* ~) O& e4 B6 ]/ |83 e6 Z* \/ f4 k5 Q4 u
99 _8 O. _7 J) U) p! d
10
9 l% V; s0 q" ?# m4 }7 C11- V0 K, H3 M8 D7 R- B% y5 V' \) X
12
% f: K: W' U. y1 I3 c) r13  I; i$ A1 v1 ~$ D
141 }, w  i" O) B! J; V' I8 X
15
5 Y- f; I2 D7 ]1 @9 c1 b16$ \% x( `9 f* Y# z0 u& d% s
17! H7 {! e2 X/ c4 I# \
18
1 h3 G$ g" T  g0 u# X6 ]9 P2 V19( e3 B+ u; Q4 j8 G$ p! e
201 W! k4 _* P7 J* h/ J" D
21
1 I; Z/ [3 G/ |  }, {22
& v& O: c2 X* F23
- K5 F+ O8 @% T$ m" o% E24
* t% x2 M+ S* A" Q' t* W/ q257 h, t; b2 z" d: G) S3 f
26
( h& z8 x* o" q27
& A' ^! Y9 \  H4 F2 D% b1 K6 _4 b/ n284 s( w( v" n, r4 V
29
, ?) `' n# d4 b9 A30* w8 x3 h) K0 Q6 S* J& T( _
31! {  \5 A5 y; W4 G/ W# o- I3 N
327 W3 |& Y0 o; j2 B) U# I/ d, G# f  N
33! T7 M) y* c, Q& w
34
) `* n! s5 H9 ^( h35
: q4 S3 q  V& k% c364 V* E" j9 n1 {+ _( {) c
37
. _5 E2 ^5 g; V0 d% M38
  p4 x$ H1 K7 z; I39- A3 E; s6 z( g7 D. D
401 O* O: V$ h8 q6 _( ~- K
416 ]5 A* }1 F3 q
42/ `$ E4 k  J& r6 |" d+ B$ z
43
: |5 `, p# c' {& J! A44! U. g+ i/ D6 @8 l+ P+ l
45
: h2 O) ~2 |4 @8 R6 x46
9 \* z! C5 k8 }47
+ Q4 Q: a& s2 s2 V' N6 n2 j, g48
7 f6 P& `/ g  i8 b49
9 V7 d4 g" t' m* |3 m# [! M50# L  M% I4 `0 x* d1 g
51! x7 y9 A7 G. L! g. y' Y
523 ~4 F, O; ~+ E) w* |
##ceph/src/ceph-volume/ceph_volume/devices/lvm/activate.py, o8 N+ H$ F, |/ Z/ r
def activate_bluestore(lvs, no_systemd=False):
. c( }* ~9 E9 t    # 从tags里找osd相关id、fsid、cluster_name等信息3 d! j0 }1 E4 f1 {7 m' X- k
    osd_lv = lvs.get(lv_tags={'ceph.type': 'block'})
* r8 D$ i2 n4 n    if not osd_lv:
, w0 A4 F  ]" f- |8 @        raise RuntimeError('could not find a bluestore OSD to activate')( }: V8 w, k# W! L; G
    is_encrypted = osd_lv.tags.get('ceph.encrypted', '0') == '1'
  z% ]  ]9 Z: ]& v& [    dmcrypt_secret = None- f5 x3 O+ ^% B2 H
    osd_id = osd_lv.tags['ceph.osd_id']
' X. D4 b8 w3 w5 _% L/ \    conf.cluster = osd_lv.tags['ceph.cluster_name']
9 i- K# X. A8 ]5 X    osd_fsid = osd_lv.tags['ceph.osd_fsid']
  \2 K  B7 M9 u3 S( c5 r* t& ]) `: F# `
    osd_path = '/var/lib/ceph/osd/%s-%s' % (conf.cluster, osd_id); B2 b, y' E& v8 P9 l5 d: v
    ### 有没有很熟悉,其时就是前面分析的activate阶段的相关命令###
- q# J& |! P6 P1 k+ p% M- g0 t    if not system.path_is_mounted(osd_path):
! |" f9 [4 {5 C& c        # ## 创建工作目录并挂载tmpfs & q1 K: M6 h) e
        prepare_utils.create_osd_path(osd_id, tmpfs=True)
! S- C  a  x7 a. F8 i( d$ Z    ## 构建data、db、wal的软链路径
/ V5 o9 C0 Z( w; K+ T$ ?% |    for link_name in ['block', 'block.db', 'block.wal']:
' h, m4 y$ A: ?9 r1 S, x3 K        link_path = os.path.join(osd_path, link_name)
5 T- ?* U; @2 c* L/ Z        if os.path.exists(link_path):
1 ~4 B- c+ q8 r  ~            os.unlink(os.path.join(osd_path, link_name))" I( u4 q4 T- _. Q
    ## 获取设备路径 ##
3 b& T5 @. `+ d" ~    db_device_path = get_osd_device_path(osd_lv, lvs, 'db', dmcrypt_secret=dmcrypt_secret)  f5 W: l1 F/ T9 N! o
    wal_device_path = get_osd_device_path(osd_lv, lvs, 'wal', dmcrypt_secret=dmcrypt_secret)
* c+ ^1 w$ ~; U
, m7 X2 ~# [/ T& c: ~0 J    ### 看这里!看这里!看这里!重要的事情说三遍,就是调用ceph-bluestore-tool工具读出元数据并写入工作目录,一模一样,下面都一样了。很熟悉了吧  ^__^. d' N- a' l( U# m
    process.run([5 w2 j" @- s+ O, O% j% f
        'ceph-bluestore-tool', '--cluster=%s' % conf.cluster,
6 \) [$ d$ L5 Y! f0 Z        'prime-osd-dir', '--dev', osd_lv_path,
1 p: m2 j4 e7 _/ x        '--path', osd_path])
; S! a1 f2 a$ A2 {# ]1 v0 J7 p/ r
    process.run(['ln', '-snf', osd_lv_path, os.path.join(osd_path, 'block')])5 P- L$ X% m% E. [1 o3 s
    ## 所有者修改以及创建软连
: F6 X: z8 b# q    system.chown(os.path.join(osd_path, 'block'))
; V& v8 m; k- t' s0 D0 Z0 Y9 F# R    system.chown(osd_path)
3 ]/ z3 ^) g/ w. }    if db_device_path:
  {  j, X, b5 H( E  G8 I/ p        destination = os.path.join(osd_path, 'block.db')/ Q6 N+ f% M" A: A1 S, Q0 l' R+ X
        process.run(['ln', '-snf', db_device_path, destination])2 P$ b8 H% a- p- B
        system.chown(db_device_path)
& F/ g0 R) k' ]* _    if wal_device_path:# k$ {  {' B2 N- n) u9 [5 {2 b+ M$ p
        destination = os.path.join(osd_path, 'block.wal')! F7 ]; x8 Q1 o- ]9 _8 V
        process.run(['ln', '-snf', wal_device_path, destination])' T8 `7 f0 }, r/ h7 X4 D- n. }
        system.chown(wal_device_path)
: C7 @+ e, d6 F
! z, e1 ~+ Z9 W8 v    if no_systemd is False:
; O1 Q+ Z3 C& h* L( F        # enable the ceph-volume unit for this OSD  u  X% s* H2 B1 a4 n  U, D  Q
        systemctl.enable_volume(osd_id, osd_fsid, 'lvm')
4 D0 c6 G1 P0 }/ Q" ~2 ^& r. i. w  }6 A9 t
        # 启动osd进程
7 a4 r5 M# L$ O: d        systemctl.start_osd(osd_id)
/ F9 k& E5 B1 d4 [/ j    terminal.success("ceph-volume lvm activate successful for osd ID: %s" % osd_id)
5 W. Q7 |  C  Q- g$ i6 v
到此,相信大部分读者已经豁然开朗了。ceph-volume系统服务会触发ceph-volume lvm trigger, 再进入activate阶段,最后一系列的系统命令调用,完成osd启动。
整个启动流程大致如下图所示:
- ?3 p% K, C6 V4 [& `image_1cn8kcnnbh521niu16djegnqhb4d.png-40.8kB
创建分析
osd的创建跟启动代码入口是一样的,都是从ceph/src/ceph-volume/main的main()开始
image_1cn8melrej3a5glk0c1cpqeqv4q.png-21.5kB
8 d0 k4 k( p. z+ A, q* [根据传入的mapper和argv,再次回看下terminal.py里的dispatch
1" j! e/ D2 U/ K$ c  N1 T, b
2
  ]$ E0 z1 u* y) t3
4 v5 I9 P3 W$ M1 n4 ?- V4
# l' ?& f9 Z1 x  A! z53 \4 f6 Y# _+ p3 \# m
6
% {) ^0 o! A9 I4 Q7
/ ?# ^3 }% D) r2 F2 O/ @8/ _, f$ x7 c8 ~
9+ M' s- A3 \! x. p" E
10: r/ O+ C, @' e; \2 m* o8 Z9 c
11
# J3 x$ v+ R! F
## ceph/src/ceph-volume/ceph_volume/terminal.py
9 Z$ }/ q, ?3 idef dispatch(mapper, argv=None):
- H. b. d2 Y/ A4 v    argv = argv or sys.argv
% R) r6 r6 v6 C/ e5 O    for count, arg in enumerate(argv, 1):: J% z) [$ P2 A$ h" `
        if arg in mapper.keys():- P, O" E  A3 v1 `$ o, u9 z/ z! u
            ## 创建实例2 t+ g" M" l1 [
            instance = mapper.get(arg)(argv[count:])
2 b2 p0 A$ {0 `            if hasattr(instance, 'main'):
2 R! M' `6 g0 B0 B                ## 实例main方法0 ]4 r. M/ o) _% y2 }0 H' n! [
                instance.main()
1 ?8 z/ C1 A# @' m! F                raise SystemExit(0)
1 t  o- u4 J# L) o3 N
如下图所示的LVM的类变量mapper,结合上面内容,实例instacne实际上就是create.Create(),然后执行实例的main()方法
9 R( T4 J" _8 C, r( @3 s- Qimage_1cn8i7f3l12b61ap1i0a1u1h124440.png-65.5kB
1: Z, S( l! m+ q" C5 d
22 c! y! i2 J6 r( D
35 k9 Y( e- a. d" s: o
46 s: o: t: _4 k" [: _4 E7 }
5
; X! v: F- v6 ~& g6
  \7 I. o6 `' g  @) k, Y3 P74 `; L* D% F& \6 v; ~2 d
88 `, w6 l2 V9 w: d- G' e; G+ e. A4 Y
9
2 q$ ]. j$ M5 k: d( r" f5 {1 W10% v* V4 @& y# ^6 n2 L
114 G* @  i/ c7 e/ ~5 @& `! N7 g; r
120 M. T/ Z" v$ `1 l
13# c* s  y$ U4 ^2 p
143 I" e3 T# C' B, r# K8 c
## ceph/src/ceph-volume/ceph_volume/devices/lvm/create.py* A8 V  h. [- k: N, S
def main(self):
% `) F% q( I- ]3 @     ... <中间省略>* y, B) L0 c$ `7 D3 U' v
     ## 解析参数
# V$ V% R( c& w     if len(self.argv) == 0:+ B! E; |  y3 P! F. k2 M6 F
         print(sub_command_help)
  j4 n9 c/ p, ~1 R1 I  y         return& L, D3 M1 G& F- a; f, F
     exclude_group_options(parser, groups=['filestore', 'bluestore'], argv=self.argv)$ j  N" _$ m, J2 ]
     args = parser.parse_args(self.argv)/ v  _3 ?1 V9 t1 a
     ## 判断是bluestore还是filestore,默认为bluestore( [9 r% b- M/ ?$ a
     if not args.bluestore and not args.filestore:
- z0 w  q; o0 @& p$ A2 P8 S         args.bluestore = True
. q2 ^8 e: o; Y: @+ p     ## 调用create()方法1 C0 z: c5 \3 T) f9 v/ x. _: A
     self.create(args)+ ]" c8 L* N6 m0 `9 C
进入create()方法,重点关注它里包含了创建Prepare实例,然后进入Activate().activate(),这在前面启动阶段已经分析了。下面重点分析下Prepare里具体做了哪些处理。
16 S$ l# x5 K) g
2
' U% B# s& k" n5 F5 x3+ V6 D. D% N  q' z
41 h/ C% N) v; m
5: G# ]3 q- D8 N' L& Q7 C. |
6% X! }% _7 }( ]' Z! n% U% b) q, p8 I
7/ ~) f, h' I- V' D. m. B; X
8
4 \& K3 Y" }. o+ e9  {1 U3 ~  A% ~! y
10! l; K1 e# o9 D: W
11
& J' q, y! g7 H: F7 ~; S  x" [12, Y5 N8 A3 s$ E; [& x: O; O* O8 ?7 j
13
( W3 g4 }2 I5 C/ s) _* G144 M, B7 o5 s1 v+ b
15
) ^! q0 ?, c9 E7 R$ U$ h16
4 e) U  p3 T! Q17( P0 j2 L/ K) q, R' U+ _4 N2 ~
18
3 B. f6 G$ W6 {8 P2 q
## ceph/src/ceph-volume/ceph_volume/devices/lvm/create.py
% I' E. u7 j; E6 K) _% `@decorators.needs_root
  m6 s3 f8 t- {) \3 i/ rdef create(self, args):) U2 L; ~) j$ k4 T) i' }
    if not args.osd_fsid:) s  C- ^$ b0 `2 }+ E6 b
        args.osd_fsid = system.generate_uuid()
) h: @, k1 ]2 }3 H0 n    ### 注意看这里!! 包含了prepare ###; O! g9 C0 ~( O2 k& m
    prepare_step = Prepare([])
" ?1 [$ W$ j. `7 ]" m: {7 B    prepare_step.safe_prepare(args)0 o$ }0 i' n- F: ]) _( Z
    osd_id = prepare_step.osd_id
3 P& d2 `$ b' A    try:
0 ^7 k+ A$ ]( o: W        Activate([]).activate(args): R: C& c' ^  b' z" i/ Y# y9 w
    except Exception:
6 Z- Z6 A! x0 i1 o& y" r: y        logger.error('lvm activate was unable to complete, while creating the OSD')
: m$ o1 x6 u9 N: S" t4 ^8 K        logger.info('will rollback OSD ID creation')! h1 `* A! s$ e- ^
        ## 创建失败,就回滚,删除osd,其实就是调用ceph osd purge 相关命令 ##& d+ T+ G* F) v) c- D1 k) ~; G2 S6 I
        rollback_osd(args, osd_id)
4 l- f3 X$ \. y( \) D% {$ c4 b- u        raise
- C) B+ S; b; L% ?. i, T    terminal.success("ceph-volume lvm create successful for: %s" % args.data)' R3 P" E$ Y8 c, i" z
1; T; \# r4 ?4 H: P
2; x# Z5 m* d+ E% a$ o
3
9 U* e) k4 L1 J1 \4; M$ @$ q: x* t8 F
5( H( `* e( ]# E0 R" S
6
& K) h' A  s5 a7 p7
( }1 P* y0 k, ?( t# F# S( }8; r/ z2 X+ G2 J5 h& ~$ J
9
. \3 t7 t" U& T+ [  ^3 ]10" E" }' z4 A; h+ Y$ R
11. O0 n+ a, g+ G7 D9 k8 ^2 F; G& p* d' @
12
4 \- N2 b9 Y, S8 g% q& D139 H4 n- g6 x8 S. [
14
1 r. x$ ], m6 L' r/ e15
+ o2 s: ~5 e6 D. o% E2 y! u  a16
& [+ R! O, `, |) p1 L17; J6 `9 P: q4 A! Q; j8 S$ h
18
. o/ ]8 u+ `; [7 |' E4 s190 T6 K. m+ U6 s1 _" {5 T
20, [, a: n/ P% l' Z4 L8 N8 B
216 ~! c; }. r; K9 F2 j
222 D% c  N8 a# |4 r
23- V7 \, I) Y$ h/ N; t  N8 p, y
24/ }) d! T7 h* j$ j3 y$ r0 u
25
0 R" w& {) M. R# {268 k1 z1 S3 H( ~' V& }
27
/ F6 c# H4 `2 T, ^( q; U* Y1 H, i1 ^28" I/ h1 Q, e9 Z/ }. f6 N+ F
299 q! p1 A- y2 ?/ j- `5 `
30# V; ?! c5 n, _
31% C/ y* ], g- T+ q
32
1 h. U" ?( i' x3 F3 B$ i* f33& o0 Z2 i2 p1 p$ Y2 Q. ~5 D/ V
34
2 d, H% N7 P/ e4 C8 r1 n35& n& v/ I: d, [: ], D$ C* ?
36* [( E. A1 {5 i- M( y* R
376 ^% v7 n% \9 w2 A5 Z9 D
38$ a; f+ x1 c% {# X8 L: q) m+ q
396 B# }3 M' J) v0 ^( Z
40/ K; }+ U, X3 o
##ceph/src/ceph-volume/ceph_volume/devices/lvm/prepare.py
" o! X# {+ e4 f@decorators.needs_root
. z- {" I! w! U4 I- V& i- Gdef prepare(self, args):
4 C* t9 z. \3 x
2 G- Q$ ~' l" E2 {    ...<中间省略>
. L1 q2 L3 K8 ?( W+ W4 y
; K" s( d/ T6 W) [    ### 创建osd的一些准备工作,比如keyring,osd id,osd uuid等
& h" s/ Y+ u4 y    cluster_fsid = conf.ceph.get('global', 'fsid')+ v6 y  C/ q4 L
    osd_fsid = args.osd_fsid or system.generate_uuid(); y% k8 L: E; V
    crush_device_class = args.crush_device_class
& Q8 g. @- h9 v9 Q$ X9 N5 D% j    if crush_device_class:
, _+ M3 S4 I4 h        secrets['crush_device_class'] = crush_device_class
- ?) X" K* }: X: W. S/ G& `: w    self.osd_id = prepare_utils.create_id(osd_fsid, json.dumps(secrets), osd_id=args.osd_id)' z* l7 ]4 Y! B% q- e
    ## 这些元数据打包到tags里- u7 e# W! f) H9 g
    tags = {
. V: m6 f! `# b1 ]/ u        'ceph.osd_fsid': osd_fsid,$ l9 ~1 E. T& |2 a5 ~5 n" O1 q2 O
        'ceph.osd_id': self.osd_id,' y0 r3 M7 D4 D: W$ T5 @% u& e
        'ceph.cluster_fsid': cluster_fsid,0 m' G  n: C9 M, v( F0 }$ p
        'ceph.cluster_name': conf.cluster,* Y( l$ k. f/ m7 ?2 x
        'ceph.crush_device_class': crush_device_class,
% @/ u! ~: z* T9 G" E% e! Q    }5 d, N9 T; e5 F. o7 P/ l
     ...<中间省略>
, V: h9 b' E  L( s# _, T7 h        #### 看重点!!!根据传入的数据先去扫描本地所有lvm卷信息,是否已经存在,如果没有,就把这些tags写入lvm的tags里
8 c, g. \2 \$ p* Z- H" k2 c        data_lv = self.get_lv(args.data)8 i& P' \  m9 ~+ Y' t4 j" e
        if not data_lv:
; @3 a1 i" `) ~5 k+ T             ### prepare_device方法里会把tags写入到lvm卷的tags" V& N% K. x, B
            data_lv = self.prepare_device(args.data, 'data', cluster_fsid, osd_fsid)
5 X' u  ]( b$ V# w. t
. \+ U" U, d9 K" g5 g         ...<中间省略>! J) a' [' H: e: L: q* q2 k# T* w  ?
$ k( q1 @1 {# M4 b+ m4 }  F. _2 W
        #### bluestore 初始化准备,跳转进入preabluestore继续处理
" s. F1 E' S& u& A8 K        prepare_bluestore(
+ ^( ?+ V" i3 q# V5 Q% y            block_lv.lv_path,& H: l( Y0 f1 w: ~9 a; e0 X2 k" e
            wal_device,
1 v( @! [! ~! T+ F2 u9 |            db_device,& _% |4 m& S7 M7 t
            secrets,8 z; ?  _+ i, h# N$ [8 X
            tags,
6 D- `; x2 ~+ _0 }! ?/ P9 T- B* b            self.osd_id,7 r0 ]3 O+ m  Z6 e7 Z% W
            osd_fsid,
; U% Y: k( `- v        )9 A; a& @* [/ F$ L
16 X  B5 W( w  Z$ e1 o! q
2& A) I& Y. j& x
3" U6 g! l5 e$ i* g5 F# u: E1 ]5 q
4
9 R. t7 P0 W4 v& z1 X& V- T/ z5) c: }1 e9 |: h7 C. k+ i. Z
63 P1 @2 Z/ T; g% q7 S; x
7
% K/ J8 m# u& L9 Y1 @; H% D8: s  i9 y+ _- C5 Y' ?
9
2 G# H6 T& i1 d/ Z. ^9 i$ u108 o" \9 Q+ F  u. Y8 Q7 R* L
### ceph/src/ceph-volume/ceph_volume/devices/lvm/prepare.py
9 W9 u2 }4 g. j- `* M" D4 [def prepare_bluestore(block, wal, db, secrets, tags, osd_id, fsid):
- l" b, L5 N( `7 c    ...<中间省略>: N' v% @9 _/ Z  q" R, B. {7 d
    ### 其中都是一些准备工作,核心就是这个初始化了,也就是前面出面过的初始化
/ j: H% h" X' s5 U5 f/ {6 P    prepare_utils.osd_mkfs_bluestore(
2 m) Y* {' C4 X0 H& e# N& \3 V        osd_id, fsid,) X/ H1 j. s% o) P9 J8 {9 {
        keyring=cephx_secret,% E  q+ T4 }: V+ G( z1 ?5 i4 m
        wal=wal,
3 q* n# x# h7 y; }% n: B6 k        db=db! s2 q  L: y% B6 x) Y
    )2 r7 y3 P4 F1 x+ |1 N7 E
至此,bluestore已经初始化完毕了,然后接着走启动流程就可以了。
, z' v$ W( E3 M8 L) r) P; Z" Ximage_1cn8n9740i5nilf19jgjl1l1264.png-45.7kB
小结
通过阅读学习ceph-volume的代码,可以清楚地理解整个osd的创建与启动流程

1

主题

0

回帖

12

积分

管理员

积分
12
QQ
 楼主| 发表于 2021-12-8 15:24:42 | 显示全部楼层
1.集群硬件配置
典型硬件资源配置建议:
组件CPU内存网络存储空间! s: P4 Y* r3 x& r+ y
Monitor1vCore2GB1x 1GbE+ NICs单个Mon 10GB+0 D' j5 L% d( y5 a9 I; u6 c
OSD1vCoreBlueStore后端,单个OSD 至少3 GB。裸容量每增加1 TB,则内存相应增加1 GB1x 1GbE+ NICs (建议10GbE+)一个OSD 对应一块独立的硬盘
  • public network和cluster network 分开。
  • 操作系统、OSD data、OSD 日志分别使用独立的硬盘,使整体吞吐量最大化。
  • 一般,建议单OSD 分配4GB以上的内存,多小对象或有大对象场景下对性能有提升。不建议低于2GB。
  • 对于OSD 除显式分配的内存外,还会多约20%的额外内存开销,需要考虑到。
    . {# S9 S. r9 ]# i" q" O& G2 ?
对于采用的BlueStore的Ceph,将SSD 用在合适的地方一般可以显著提升性能:
  • OSD 日志建议使用SSD。如果采用bluestore,则建议裸容量配比—— block : block.db : block.wal = 100:1:1,后两者建议采用SSD或NVMe SSD。
  • 采用cache-tiering,其中cache pool 采用SSD。
  • CephFS 的metadata pool 采用SSD。
  • RGW index pool 采用SSD。& p. u5 O8 @% u5 i
2.常见性能影响因素集群性能评估
根据采用的硬件和集群规模,应当对集群有个大致的性能估算。集群性能影响因素主要有:硬盘(单个硬盘的性能和硬盘总数)、网络性能、内存和CPU。其中前两个是估算集群整体性能的主要因素,而根据场景,性能主要是IOPS和带宽。. g% ?4 H* S- U; r/ r" d9 }. l- K
一般:
  • 集群读取性能:
    " y* n0 h3 `9 i, H8 ?, q5 V
集群读取性能:W*n*μ,无论在FileStore还是BlueStore下其中,W: 单块裸盘读带宽n: OSD数量μ: 损耗系数 一般为0.7~0.8
  • 集群写入性能:
    ; z9 h. g4 s5 Q% a$ {9 O
集群写入性能:[(W*n)/WAF]*μW: 单块裸盘写入带宽n: OSD数量WAF: 写放大系数μ: 损耗系数X: 写入数据块大小(KiB)N: 多副本Size大小K: 纠删码K值M: 纠删码M值FileStore 5: 5KiB, FileStore中transaction元数据的数据量大小(推测值)BlueStore 5: 5KiB, BlueStore中RocksDB的WAL数据大小(推测值)BlueStore 20: 20KiB, BlueStore小文件写入时产生的Zero-filled数据块大小性能瓶颈定位
经过对集群的性能评估,结合主要的影响因素,试着找出性能瓶颈的大方向。! W- [9 e  Z. V& Z3 y: }
准确的性能评估需要进行严格的性能测试:
  • 首先是基准测试,包括硬盘基线测试(如dd)和网络基线测试(如iperf),测试前应当drop cache。
  • 然后分别对于集群做性能测试:rados bench
  • RBD 性能测试:rbd bench
  • RGW 性能测试:cosbench1 A3 \' G5 e8 b) P  G% B3 p
通过ceph osd perf可以看出整个集群所有OSDs 的时延数据。6 f- R/ z: P2 B/ _8 K, [# w# \
更详细的时延信息可以通过perf dump调查,如
1.ceph daemon osd.15 perf reset all2.rados bench WRITE3.ceph daemon osd.15 perf dump常见性能优化点
排除硬件瓶颈的可能,则可以从常见的几项对照排查修改。
  • 存储池的PG 数是否合理:一般,集群PGs总数 = (OSD总数 * 100 / 最大副本数),具体可参考pgcal
  • monitor 采用3或5个即可。由于需要再monitor之间做数据同步,过多的monitor 会影响性能。
  • 建议Ceph 集群和其他系统独立部署,以免资源抢占影响性能,且混合部署影响troubleshooting。
    % B* v- ]/ \7 {. E; U
3.使用Cache-tiering
使用缓存分层,可以根据需求在热层和冷层之间自动迁移数据,从而提高群集的性能。6 i6 y/ A# [6 ]" s5 l/ n9 k6 y
采用的cache-tiering的前提是要搞清业务场景,因为cache-tiering 是工作负载相关的,不合适的场景匹配不合适的缓存模式(cache mode)反而会让整体性能下降。
  • write-back:Ceph 客户端写数据至cache tier,随后会将数据迁移至storage tier。客户端读取数据也是直接读取cache tier,若cache tier 没有会从storage tier 中获取并迁移至cache tier。客户端的读写始终不直接跟storage tier 关联。 这种模式适用于可变数据的存储访问。
  • readproxy:使用已存在与cache tier 内的对象, 如果cache tier 内无该对象则会将请求代理至storage tier。
  • readonly:cache tier 仅接受读操作,写操作都会指向storage tier,预读取的对象会被迁移至cache tier,一定条件下会被迁移出cache tier。这种模式不保证一致性,读取的数据可能是过期的,适用于不变数据的存储访问。
  • none:完全disable cache tiering。. C$ M7 m  J+ ]. Y
cache-tiering 配置流程
首先,除storage pool 外,需要创建一个全SSD 的cache pool(通过修改 crushmap)。
; n; X; U% j& M9 x. i# N$ |根据实际场景:
  • 数据对象是更偏向不变对象还是可变对象,决定采用什么缓存模式(cache-mode);
  • 根据客户端负载情况,设置和调整缓存池的参数(大小、数量等);
  • 其他诸如cache age、target size 等参数。) A& }" N' K' W3 k7 q! T) `6 a
必要操作步骤:
  u5 q; l. `0 L, ?6 }1 Z0 A7 T1)关联cache pool 和后端存储池:ceph osd tier add
2)设置cache-mode:ceph osd tier cache-mode writeback
3)将原storage pool的流量指向cache pool:ceph osd tier set-overlay
4)必要的缓存阈值设置,所有的请求在达到target_max_bytes 或target_max_objects 设定值时会被阻塞
ceph osd pool set target_max_bytes {#bytes}ceph osd pool set target_max_objects {#objects}4.Damons 相关配置优化
常见配置优化项及建议值,根据实际场景可再做调整。3 `" |# v3 L4 v! I* r
默认应将RGW Cache 和RBD cache打开。
OSDosd_scrub_begin_hour = 1 #根据业务实际设置在非业务时间scrubosd_scrub_end_hour = 5osd_recovery_op_priority = 3osd_client_op_priority = 63osd_recovery_max_active = 10osd_recovery_sleep = 0osd_max_backfills = 10RGW(对象存储)rgw_cache_enabled = true # 开启RGW cachergw_thread_pool_size = 2000rgw_cache_lru_size = 20000rgw_num_rados_handles = 128RBD(块存储)rbd_cache_enabled = true # 开启RBD cacherbd_cache_size = 268435456rbd_cache_max_dirty = 134217728rbd_cache_max_dirty_age = 5

1

主题

0

回帖

12

积分

管理员

积分
12
QQ
 楼主| 发表于 2021-12-8 15:25:02 | 显示全部楼层
1.集群硬件配置
$ z7 l: k) T4 K0 p! {0 H典型硬件资源配置建议:
9 v9 c9 Y) F  l6 @! x$ t" `组件 CPU 内存 网络 存储空间8 K/ I5 J8 G" [7 @6 S4 J
Monitor 1vCore 2GB 1x 1GbE+ NICs 单个Mon 10GB+! R# Y5 @, J" G: [6 d
OSD 1vCore BlueStore后端,单个OSD 至少3 GB。裸容量每增加1 TB,则内存相应增加1 GB 1x 1GbE+ NICs (建议10GbE+) 一个OSD 对应一块独立的硬盘
' R4 e1 b: L; z4 Rpublic network和cluster network 分开。
1 @& C  ?3 F9 n- T# j/ ^1 [操作系统、OSD data、OSD 日志分别使用独立的硬盘,使整体吞吐量最大化。# U2 v# d% ^- T, v" D
一般,建议单OSD 分配4GB以上的内存,多小对象或有大对象场景下对性能有提升。不建议低于2GB。
  c/ x; X" |: I% `/ I对于OSD 除显式分配的内存外,还会多约20%的额外内存开销,需要考虑到。9 v1 K7 G0 H$ m( N
对于采用的BlueStore的Ceph,将SSD 用在合适的地方一般可以显著提升性能:
3 [. F  z" w! tOSD 日志建议使用SSD。如果采用bluestore,则建议裸容量配比—— block : block.db : block.wal = 100:1:1,后两者建议采用SSD或NVMe SSD。+ g) l, m8 A0 {% x+ J
采用cache-tiering,其中cache pool 采用SSD。
: W4 `2 ?. J* R8 P, mCephFS 的metadata pool 采用SSD。: K# @# w* ^$ v- z( V2 U' T
RGW index pool 采用SSD。
* \/ X! o" P# Z3 j2 a& i2.常见性能影响因素
' D1 U- M' y0 t# m: G) k% L集群性能评估2 G) O" u6 o8 O* U5 X
根据采用的硬件和集群规模,应当对集群有个大致的性能估算。集群性能影响因素主要有:硬盘(单个硬盘的性能和硬盘总数)、网络性能、内存和CPU。其中前两个是估算集群整体性能的主要因素,而根据场景,性能主要是IOPS和带宽。
0 v6 a4 P; i7 P1 f' s3 ~, C9 I一般:, ]. G8 q7 ^! }. [; m8 L4 q0 J3 b
集群读取性能:
# S, V# V$ v1 q0 x' \集群读取性能:W*n*μ,无论在FileStore还是BlueStore下" u' }* t: f- o
其中,
, o2 o/ f& o* J; w: y" VW: 单块裸盘读带宽2 n" p5 Q3 B2 n7 |% B& ]
n: OSD数量
1 Y5 A  I/ [' i: o5 B; L* yμ: 损耗系数 一般为0.7~0.8) G, H! ~3 p1 c/ o9 J# B0 j
集群写入性能:/ a1 s* Y" t$ K9 f  [6 |7 h) W7 [9 t% E" n
集群写入性能:[(W*n)/WAF]*μ
' Y) }, U- h( Y9 z4 rW: 单块裸盘写入带宽
4 v& O, e" k! H+ Yn: OSD数量
. f! S* q  G9 ^" b& r. a" nWAF: 写放大系数* J& z& Q5 n: T; L( k) ^
μ: 损耗系数
2 B# F8 [$ x$ R& ]X: 写入数据块大小(KiB), v7 x3 B' F( y: m
N: 多副本Size大小8 j7 Y- w3 E* ?& r
K: 纠删码K值; w: |/ s! u, m2 Z6 s
M: 纠删码M值) ^- i! z& y4 R* _0 m+ @
FileStore 5: 5KiB, FileStore中transaction元数据的数据量大小(推测值)
) D! y# m5 k( C; k9 d; g; d6 {. W3 YBlueStore 5: 5KiB, BlueStore中RocksDB的WAL数据大小(推测值)3 Z$ O$ M  ^, K, u+ z
BlueStore 20: 20KiB, BlueStore小文件写入时产生的Zero-filled数据块大小
- \) v* }! [4 L性能瓶颈定位2 @+ v! u4 R3 [% Z% t
经过对集群的性能评估,结合主要的影响因素,试着找出性能瓶颈的大方向。; H0 K  R) g( O" J  h+ `$ k
准确的性能评估需要进行严格的性能测试:
. j* \* {0 a( f# l) T5 h& O0 u首先是基准测试,包括硬盘基线测试(如dd)和网络基线测试(如iperf),测试前应当drop cache。+ h- M2 {. Q  n" u1 w. g* Q8 l
然后分别对于集群做性能测试:rados bench' n) t& s2 r) a- C) h' ~
RBD 性能测试:rbd bench  a. K' p. ~/ n8 P
RGW 性能测试:cosbench. i2 B: X1 j$ `9 }  Q. `0 x
通过ceph osd perf可以看出整个集群所有OSDs 的时延数据。
7 S- S4 l3 E; X( x; G; d$ y! l6 b更详细的时延信息可以通过perf dump调查,如* Q0 U; s7 S. K& R" k% G, V
1.ceph daemon osd.15 perf reset all
' h( a- s; v4 Y2.rados bench WRITE
( a1 L* E% `4 {- z& X2 i3.ceph daemon osd.15 perf dump
4 {! b: j+ ~& _常见性能优化点
4 q: P+ W* k6 X, O( ^- F8 e排除硬件瓶颈的可能,则可以从常见的几项对照排查修改。3 p7 E; p2 h) V$ Q+ E" h7 P; a
存储池的PG 数是否合理:一般,集群PGs总数 = (OSD总数 * 100 / 最大副本数),具体可参考pgcal. ~9 H9 D6 R* O/ \" `
monitor 采用3或5个即可。由于需要再monitor之间做数据同步,过多的monitor 会影响性能。! A. }* \5 I" W( y0 n
建议Ceph 集群和其他系统独立部署,以免资源抢占影响性能,且混合部署影响troubleshooting。
9 x0 W5 S' r/ a3.使用Cache-tiering0 @+ b/ J# N$ r+ J. t, w
使用缓存分层,可以根据需求在热层和冷层之间自动迁移数据,从而提高群集的性能。6 t. g9 g2 D, o% I) D* W( x  b6 t
采用的cache-tiering的前提是要搞清业务场景,因为cache-tiering 是工作负载相关的,不合适的场景匹配不合适的缓存模式(cache mode)反而会让整体性能下降。
. {* n3 H: w$ n7 Nwrite-back:Ceph 客户端写数据至cache tier,随后会将数据迁移至storage tier。客户端读取数据也是直接读取cache tier,若cache tier 没有会从storage tier 中获取并迁移至cache tier。客户端的读写始终不直接跟storage tier 关联。 这种模式适用于可变数据的存储访问。4 n) z1 O9 K9 `4 N, Q
readproxy:使用已存在与cache tier 内的对象, 如果cache tier 内无该对象则会将请求代理至storage tier。3 z9 F1 }5 I/ N; m
readonly:cache tier 仅接受读操作,写操作都会指向storage tier,预读取的对象会被迁移至cache tier,一定条件下会被迁移出cache tier。这种模式不保证一致性,读取的数据可能是过期的,适用于不变数据的存储访问。
( J0 Q: M! s- c' ~. f5 Inone:完全disable cache tiering。9 J3 b% _# n! c$ ~/ a# ^
cache-tiering 配置流程* G1 v2 P" p6 l$ w+ k) P2 f$ n6 N
首先,除storage pool 外,需要创建一个全SSD 的cache pool(通过修改 crushmap)。& d* M5 Y" {2 R7 j/ _
根据实际场景:
. N' @$ N( @1 [- C' j4 S* U9 R; x数据对象是更偏向不变对象还是可变对象,决定采用什么缓存模式(cache-mode);
$ i& v. U  t# D* |) g1 S根据客户端负载情况,设置和调整缓存池的参数(大小、数量等);
1 y& x5 A" t. X其他诸如cache age、target size 等参数。
  L( ?( x& H: j! H9 T必要操作步骤:( N6 b2 g3 e6 ^0 Q. f, d
1)关联cache pool 和后端存储池:ceph osd tier add1 {$ a$ F6 @; e
2)设置cache-mode:ceph osd tier cache-mode writeback
7 {; W( D; s( h( K, N3)将原storage pool的流量指向cache pool:ceph osd tier set-overlay' d: V/ F+ l8 e& N# d4 m- v) i0 L
4)必要的缓存阈值设置,所有的请求在达到target_max_bytes 或target_max_objects 设定值时会被阻塞' Z. O4 W) j+ E6 d: \: h3 r' _! o
ceph osd pool set target_max_bytes {#bytes}* A3 w; g  `1 [+ }7 O# `
ceph osd pool set target_max_objects {#objects}* @# h% j1 [, O( N& t
4.Damons 相关配置优化
" C0 t9 p. v7 `4 X- o- Q' C8 e常见配置优化项及建议值,根据实际场景可再做调整。5 o  ~/ G; x! r' o: k/ p
默认应将RGW Cache 和RBD cache打开。
# d0 r/ i- w- y! L/ y3 S5 ?OSD( _9 S3 {' ?' s
osd_scrub_begin_hour = 1 #根据业务实际设置在非业务时间scrub
8 z2 h9 k0 C  {& R& Kosd_scrub_end_hour = 5" s8 e- I, `: _, A. T% T
osd_recovery_op_priority = 3* R  q  l- R3 W! \# h
osd_client_op_priority = 63# Y# N( U' g9 s
osd_recovery_max_active = 10
  v! u4 b5 _4 Qosd_recovery_sleep = 0
$ ?4 D* ~' H8 B6 d4 oosd_max_backfills = 10. B- Z" m0 J7 q, p  v
RGW(对象存储)# ]3 W3 d. d9 ?% R
rgw_cache_enabled = true # 开启RGW cache; l3 \2 e9 E8 j* b& }
rgw_thread_pool_size = 2000
& u$ l, i' a9 H5 x  ~2 W3 srgw_cache_lru_size = 20000
  N3 M4 h8 x6 Q7 f- b4 trgw_num_rados_handles = 128
( O1 H) @5 G% ?) s+ v5 ^RBD(块存储)
& X7 Q* N# `# a1 ~rbd_cache_enabled = true # 开启RBD cache- m+ a1 |2 }+ J0 D) |
rbd_cache_size = 268435456
6 g: @) q/ U3 e6 K% Xrbd_cache_max_dirty = 134217728
) r/ t, ^; ]9 Z; e6 hrbd_cache_max_dirty_age = 5
7 T6 G% t5 c! q$ q0 ?

1

主题

0

回帖

12

积分

管理员

积分
12
QQ
 楼主| 发表于 2021-12-8 15:27:54 | 显示全部楼层
将OSD转换为Bluestore
+ h  G7 Z* ]8 f  J; {% r请按照以下步骤操作,安全的清理回收磁盘,然后重新部署为类型的。
  r+ U0 V( k% [. O# Stop the OSD process% K# g4 O0 E& A7 V: @8 L; J
systemctl stop ceph-osd@.service
/ z, _1 Q. a9 B4 ~% ~# Unmount the OSD- s: \  G& x; c
umount /dev/- p6 }; _' _  @: K  C5 v- ~+ J
# Zap the disk' v" X" c6 T& r& _
ceph-disk zap; k' @' M3 [; d
# Mark the OSD as destroyed
$ X/ T) l1 g& }0 m5 A2 Y7 Gceph osd destroy --yes-i-really-mean-it
  |: j) i% X# C6 I# Prepare the disk as Bluestore+ E& e/ Z# U: f7 ?0 }& h
ceph-disk prepare --bluestore /dev/ --osd-id
9 S: R) b* o4 i2 b0 p- @# D

1

主题

0

回帖

12

积分

管理员

积分
12
QQ
 楼主| 发表于 2021-12-8 15:50:43 | 显示全部楼层
下一代对象存储引擎BlueStore
相比于目前FileStore,BlueStore拥有无与伦比的优势:
  • 充分考虑下一代全SSD以及NVMe SSD闪存阵列的适配。例如将高效索引元数据的引擎由LevelDB替换为RocksDB。
  • 传统的基于POSIX接口的FileStore需要通过操作系统自带的文件系统间接管理磁盘。BlueStore选择绕开文件系统,从而使得I/O路径大大减小。
  • 在设计中将元素据和用户数据严格分离,因此元素据可以单独采用高速固态存储设备,诸如NVMe SSD,以实现性能加速。
  • 与传统机械硬盘相比,SSD普遍采用4k 或者更大的块大小,因此采用位图进行管理可以获得更高的空间收益。
    ) s8 f/ ]  C; L& R
1 设计理念
在存储系统中,所有读操作都是同步的,即除非在缓存命中,否则必须从磁盘中读到指定内容才向客户端返回。而写操作则不一样,一般处于效率考虑,所有写操作都会在内存中进行缓存,由文件系统进行组织后再批量写入磁盘。
数据可靠性:我们考虑写的期间发生断电的情况,因为内存是易失性的,所有数据会丢失。针对这个问题,有人提出用一个掉电不丢失的中间设备作为过渡设备,等数据写入普通磁盘后再释放中间设备上的空间,这个写中间设备的过程被称为写日志。中间设备被称为日志设备。但这样会消耗额外硬件资源。
数据一致性:数据修改要么全部完成,要么没有变化(All or nothing). 具体而言,我们用ACID(A: Atomicity, C: Consistency, I:Isolation, D:Durability)来描述这种系统,即事务型系统
术语
块大小: 指对磁盘进行操作的最小粒度。 对普通机械硬盘为512字节,而SSD为4KB。
RMW:覆盖写。 如果本次改写的内容不足一个块,那么需要将对应的块读进来,将待修改的内容与原先内容进行合并。它的问题在于:额外的读惩罚,以及潜在的数据丢失风险。
COW:写时重定向。在磁盘分配新的空间,再写入,写完成后再释放旧数据。
2 BlueStore写策略
BlueStore综合运用了RMW和COW,任何一个写请求,根据磁盘块大小,分为三个部分,即首尾非块大小对齐部分和中间块大小对齐部分,针对两边RMW,针对中间采用COW。
BlueStore提供的读写访问接口都是基于PG粒度的。
3 缓存替换机制
LRU算法:最近最少使用,时间局部性原理。
LFU算法:最近不经常使用,SDD访问模型。
ARC算法,同时考虑了LRU和LFU的长处,同时使用两个队列对缓存中页面进行管理:
  • MRU (Most Recently Used) 队列保存最近访问过的页面
  • MFU(Most Frequently Used)队列保存最近一段时间至少被访问过两次的界面。
  • 两个队列的长度是可变的,会根据请求队列的特征自动进行调整,取LRU和LFU共同之所长。
    • 当系统中请求序列呈现明显的时间局部性,MFU队列长度变为0,从而退化为LRU。
    • 当系统中请求序列呈现明显的空间局部性,MRU队列长度变为0,从而退化为LFU。
      $ D( B0 M7 b; p) j/ L
    9 c8 I( G% Y3 B8 [% Q+ f$ }: c1 @
2Q算法:双队列热点算法,一种针对数据库特别是关系数据库系统优化的缓存淘汰算法:
数据库系统由于需要保证每个操作的原子性,所以经常存在多个事务操作同一块热点数据的场景,因此针对数据库系统的缓存淘汰算法主要关注如何识别多个并发事务之间的数据相关性。
与ARC类似,2Q也使用了多个队列来管理整个缓存空间,分布称为<span tabindex="0" class="mjx-chtml MathJax_CHTML" id="MathJax-Element-1-Frame" role="presentation" style="margin: 0px; padding: 1px 0px; border: 0px currentColor; border-image: none; text-align: left; text-transform: none; line-height: 0; text-indent: 0px; letter-spacing: normal; font-size: 19.52px; font-style: normal; font-weight: normal; word-spacing: normal; float: none; display: inline-block; white-space: nowrap; position: relative; direction: ltr; min-height: 0px; max-height: none; min-width: 0px; max-width: none; box-sizing: border-box; overflow-wrap: normal;" data-mathml='A1in,A1out,Am'>A1in,A1out,AmA1in,A1out,Am。这些队列都是LRU队列,其中<span tabindex="0" class="mjx-chtml MathJax_CHTML" id="MathJax-Element-2-Frame" role="presentation" style="margin: 0px; padding: 1px 0px; border: 0px currentColor; border-image: none; text-align: left; text-transform: none; line-height: 0; text-indent: 0px; letter-spacing: normal; font-size: 19.52px; font-style: normal; font-weight: normal; word-spacing: normal; float: none; display: inline-block; white-space: nowrap; position: relative; direction: ltr; min-height: 0px; max-height: none; min-width: 0px; max-width: none; box-sizing: border-box; overflow-wrap: normal;" data-mathml='A1in'>A1inA1in与<span tabindex="0" class="mjx-chtml MathJax_CHTML" id="MathJax-Element-3-Frame" role="presentation" style="margin: 0px; padding: 1px 0px; border: 0px currentColor; border-image: none; text-align: left; text-transform: none; line-height: 0; text-indent: 0px; letter-spacing: normal; font-size: 19.52px; font-style: normal; font-weight: normal; word-spacing: normal; float: none; display: inline-block; white-space: nowrap; position: relative; direction: ltr; min-height: 0px; max-height: none; min-width: 0px; max-width: none; box-sizing: border-box; overflow-wrap: normal;" data-mathml='Am'>AmAm是真正的缓存队列,<span tabindex="0" class="mjx-chtml MathJax_CHTML" id="MathJax-Element-4-Frame" role="presentation" style="margin: 0px; padding: 1px 0px; border: 0px currentColor; border-image: none; text-align: left; text-transform: none; line-height: 0; text-indent: 0px; letter-spacing: normal; font-size: 19.52px; font-style: normal; font-weight: normal; word-spacing: normal; float: none; display: inline-block; white-space: nowrap; position: relative; direction: ltr; min-height: 0px; max-height: none; min-width: 0px; max-width: none; box-sizing: border-box; overflow-wrap: normal;" data-mathml='A1out'>A1outA1out是影子队列,i.e.只保存相关页面的管理结构。
  • 新的页面一开始总是被加入A1in,当某个页面被频繁访问,2Q认为这些访问是相关的,不会针对该页面执行任何热度提升的操作,直到其被正常淘汰至Aout。这个时间间隔被称为“相关时间间隔”。
  • 当A1out中某个页面被再次访问时,2Q认为这些访问不再相关,此时执行页面热度提升,将其加入Am头部。Am队列中的页面再次被命中时,同样将其加入Am队列头部进行页面热度提升。从Am中淘汰的页面也进入A1out。这个时间间隔被称为“热度保留间隔”。% f. H6 |7 b& }" l3 J2 y
4 缓存管理
BlueStore 目前采用了LRU和2Q两种算法。
参考Theodore和Dennis的测试结论,推荐A1in和Am队列的容量配比1:1.
BlueStore的cache既可以用于缓存用户数据,也可以用于缓存元数据。bluestore中默认元数据的比重位90%。
BlueStore中元素据分为两类:Collection和Onode. Collection是PG在BlueStore中内存管理结构。每个OSD最多承载100个PG而且Collection管理结构本身比较小,故被设计成常驻内存。而Onode的数量和其管理的磁盘空间成正比,因而不可能常驻内存,需要引入淘汰机制。Onode采用LRU。
5 BlueFS
诞生于2011年的LevelDB是基于Google的BigTable数据库系统发展而来。然而随着SSD普及,LevelDB无法发挥SSD全部性能,因而诞生了RocksDB。
  • RocksDB适合存储小型或者中型键值对;性能随着键值对长度上升下降很快。
  • 性能随CPU核数以及后端存储设备的I/O能力呈线性扩展。
    - I) l7 _; ^" `
传统的本地文件系统(XFS,ext4,ZFS)等不能与RocksDB完全兼容,因而专门为其量身打造一款本地文件系统——BlueFS。在逻辑空间上分为三个层次
(1)慢速空间
主要用于存储对象数据,可由大容量机械硬盘担任存储。
(2)高速空间(DB)
主要存储BlueStore内部的元素据,比如Onode。 可以由SSD提供。
(3)超高速(WAL)
WAL(Write Ahead Log)指日志。 可以由NVMe SSD或NVRAM等高速设备充当。
BlueFS上的磁盘数据包括文件、目录、日志三种类型。其定位文件分为两步:1. 通过dir_map找到文件的最底层文件夹 2.通过file_map找到对应的文件。其磁盘数据结构如下:
成员
含义

$ Q+ X; l% |3 O) _
ino
唯一标识一个fnode
  t- `' T3 Z: {
size
文件大小

3 C. n  j/ Q/ y  o
mtime
文件上一次被修改时间
$ r+ H9 _( U" t
prefer_bdev
存储该文件优先使用的设备

/ l/ [+ O% p# V" V" q+ Q
extents
磁盘上物理段集合包括{bdev,offset,length}

5 Z1 S, O+ f# `$ g
6 ObjectStore(OS)
Ceph是一个指导原则是所有存储的不管是块设备、对象存储、文件存储最后都转化成了底层的对象object,这个object包含3个元素data,xattr,omap。data是保存对象的数据;xattr是保存对象的扩展属性,每个对象文件都可以设置文件的属性,这个属性是一个key/value值对,这类操作的特征是kv对并且与某一个Object关联,但是受到文件系统的限制,key/value对的个数和每个value的大小都进行了限制。如果要设置的对象的key/value不能存储在文件的扩展属性中;还存在另外一种方式保存omap(在Ceph中称为omap),omap实际上是保存到了key/vaule 值对的RocksDB中,在这里value的值限制要比xattr中好的多。
对于FileStore实现,每个Object在FileStore层会被看成是一个文件,Object的属性(xattr)会利用文件的xattr属性存取,因为有些文件系统(如Ext4)对xattr的长度有限制,因此超出长度的Metadata会被存储在DBObjectMap里。而Object的omap则直接利用DBObjectMap实现。因此,可以看出xattr和omap操作是互通的,在用户角度来说,前者可以看作是受限的长度,后者更宽泛(API没有对这些做出硬性要求)。目前纠删码还不支持omap。
而在BlueStore则没有这种限制。

部署和操作BlueStoreBLUESTORE迁移
每个OSD都可以运行BlueStore或FileStore,并且单个Ceph集群可以包含两者的混合。先前已部署FileStore的用户可能希望过渡到BlueStore,以利用改进的性能和健壮性。有几种策略可以实现这种过渡。
单个OSD不能单独进行原地转换,但是:BlueStore和FileStore根本不同,以致于无法实用。“转换”将依靠群集的正常复制和修复支持,或者依靠将OSD内容从旧的(FileStore)设备复制到新的(BlueStore)设备的工具和策略。
部署新的OSD与BLUESTORE
可以使用BlueStore部署任何新的OSD(例如,在扩展群集时)。这是默认行为,因此不需要进行特定更改。
同样,更换故障驱动器后重新配置的任何OSD都可以使用BlueStore。
将现有的OSD标记并替换
最简单的方法是依次标记每个设备,等待数据在群集中复制,重新配置OSD,然后再次将其标记回。它很容易实现自动化。但是,它需要的数据迁移量超出了必要,因此不是最佳选择。
  • 确定要替换的FileStore OSD:
    ID=<osd-id-number>DEVICE=<disk-device>Copy
    您可以使用以下命令判断给定的OSD是FileStore还是BlueStore:
    ceph osd metadata $ID | grep osd_objectstoreCopy
    您可以使用以下命令获取文件存储与bluestore的当前计数:
    ceph osd count-metadata osd_objectstoreCopy
  • 将文件存储OSD标记为:
    ceph osd out $IDCopy
  • 等待数据从有问题的OSD迁移:
    while ! ceph osd safe-to-destroy $ID ; do sleep 60 ; doneCopy
  • 停止OSD:
    systemctl kill ceph-osd@$IDCopy
  • 记下此OSD使用的设备:
    mount | grep /var/lib/ceph/osd/ceph-$IDCopy
  • 卸载OSD:
    umount /var/lib/ceph/osd/ceph-$IDCopy
  • 销毁OSD数据。请格外小心,因为这会破坏设备的内容;在继续操作之前,请确保不需要设备上的数据(即,群集运行状况良好)。
    ceph-volume lvm zap $DEVICECopy
  • 告诉集群OSD已被破坏(并且可以使用相同的ID重新配置新的OSD):
    ceph osd destroy $ID --yes-i-really-mean-itCopy
  • 使用相同的OSD ID在其位置重新配置BlueStore OSD。这要求您确实根据上面看到的内容确定要擦除的设备。小心!
    ceph-volume lvm create --bluestore --data $DEVICE --osd-id $IDCopy
  • 重复。

    * s: P8 n& U! @5 _5 Q1 d% G  C
您可以允许替换OSD的重新填充与下一个OSD的排空同时进行,或者对多个OSD并行执行相同的步骤,只要确保在销毁群集之前群集是完全干净的(所有数据具有所有副本)即可。任何OSD。否则,将减少数据的冗余,并增加(甚至可能导致)数据丢失的风险。
优点:
  • 简单。
  • 可以逐个设备完成。
  • 不需要备用设备或主机。, E7 l  ~: V/ N
缺点:
  • 数据通过网络复制了两次:一次复制到集群中的其他OSD(以保持所需的副本数),然后再次返回到重新配置的BlueStore OSD。' y. W  a# A5 j  J; @* N
整个主机更换
如果集群中有一个备用主机,或者有足够的可用空间来疏散整个主机以用作备用主机,则可以在每个主机的基础上使用存储的每个数据副本进行转换仅迁移一次。
首先,您需要有一个没有数据的空主机。有两种方法可以执行此操作:从尚未包含在群集中的新的空主机开始,或者从群集中现有主机上卸载数据。
使用新的,空的主机
理想情况下,主机应具有与将要转换的其他主机大致相同的容量(尽管并不严格)。
NEWHOST=<empty-host-name>Copy
将主机添加到CRUSH层次结构,但不要将其附加到根目录:
ceph osd crush add-bucket $NEWHOST hostCopy
确保已安装ceph软件包。
使用现有的主机
如果要使用已经是群集一部分的现有主机,并且该主机上有足够的可用空间,以便可以迁移其所有数据,则可以执行以下操作:
OLDHOST=<existing-cluster-host-to-offload>ceph osd crush unlink $OLDHOST defaultCopy
其中“默认”是CRUSH地图中的直接祖先。(对于具有未修改配置的较小群集,通常将是“默认”,但也可能是机架名称。)现在,您应该在OSD树输出的顶部看到没有父节点的主机:
$ bin/ceph osd treeID CLASS WEIGHT  TYPE NAME     STATUS REWEIGHT PRI-AFF-5             0 host oldhost10   ssd 1.00000     osd.10        up  1.00000 1.0000011   ssd 1.00000     osd.11        up  1.00000 1.0000012   ssd 1.00000     osd.12        up  1.00000 1.00000-1       3.00000 root default-2       3.00000     host foo 0   ssd 1.00000         osd.0     up  1.00000 1.00000 1   ssd 1.00000         osd.1     up  1.00000 1.00000 2   ssd 1.00000         osd.2     up  1.00000 1.00000...Copy
如果一切正常,请直接跳到下面的“等待数据迁移完成”步骤,然后从那里继续进行操作以清理旧的OSD。
迁移过程
如果您使用的是新主机,请从步骤1开始。对于现有主机,请跳至下面的步骤5。
  • 为所有设备配置新的BlueStore OSD:
    ceph-volume lvm create --bluestore --data /dev/$DEVICECopy
  • 验证OSD通过以下方式加入集群:
    ceph osd treeCopy
    您应该看到新主机$NEWHOST与它下面的所有的OSD的,但主机应该被嵌套任何其他节点下的层次结构(像)。例如,如果是空主机,则可能会看到以下内容:root default``newhost
    $ bin/ceph osd treeID CLASS WEIGHT  TYPE NAME     STATUS REWEIGHT PRI-AFF-5             0 host newhost10   ssd 1.00000     osd.10        up  1.00000 1.0000011   ssd 1.00000     osd.11        up  1.00000 1.0000012   ssd 1.00000     osd.12        up  1.00000 1.00000-1       3.00000 root default-2       3.00000     host oldhost1 0   ssd 1.00000         osd.0     up  1.00000 1.00000 1   ssd 1.00000         osd.1     up  1.00000 1.00000 2   ssd 1.00000         osd.2     up  1.00000 1.00000...Copy
  • 确定要转换的第一个目标主机
    OLDHOST=<existing-cluster-host-to-convert>Copy
  • 将新主机交换到群集中旧主机的位置:
    ceph osd crush swap-bucket $NEWHOST $OLDHOSTCopy
    此时,所有数据$OLDHOST将开始迁移到上的OSD $NEWHOST。如果新旧主机的总容量不同,您可能还会看到一些数据迁移到集群中的其他节点或从集群的其他节点迁移,但是只要这些主机的大小相同,这将是相对少量的数据。
  • 等待数据迁移完成:
    while ! ceph osd safe-to-destroy $(ceph osd ls-tree $OLDHOST); do sleep 60 ; doneCopy
  • 停止所有空的旧OSD $OLDHOST:
    ssh $OLDHOSTsystemctl kill ceph-osd.targetumount /var/lib/ceph/osd/ceph-*Copy
  • 销毁并清除旧的OSD:
    for osd in `ceph osd ls-tree $OLDHOST`; do    ceph osd purge $osd --yes-i-really-mean-itdoneCopy
  • 擦拭旧的OSD设备。这要求您确定要手动擦除哪些设备(请小心!)。对于每个设备:
    ceph-volume lvm zap $DEVICECopy
  • 将现在为空的主机用作新主机,然后重复:
    NEWHOST=$OLDHOSTCopy  v% r0 W0 F- C* x6 ~/ Y0 o
优点:
  • 数据只能通过网络复制一次。
  • 一次转换整个主机的OSD。
  • 可以并行转换为一次转换多个主机。
  • 每个主机上都不需要备用设备。
    / Q1 O: k9 S) q6 W2 ?
缺点:
  • 需要备用主机。
  • 整个主机的OSD值将同时迁移数据。这很可能会影响整个群集的性能。
  • 所有迁移的数据仍然在网络上进行了一整跳。& m( X  e7 r6 S/ {* \, k$ O
每OSD设备副本
可以使用的copy功能转换单个逻辑OSD ceph-objectstore-tool。这要求主机具有一个或多个空闲设备来供应新的空BlueStore OSD。例如,如果群集中的每个主机都有12个OSD,则需要第13个可用设备,以便可以依次转换每个OSD,然后再收回旧设备以转换下一个OSD。
注意事项:
  • 此策略要求准备一个空白的BlueStore OSD,而无需分配该ceph-volume 工具不支持的新OSD ID 。更重要的是,dmcrypt的设置与OSD身份紧密相关,这意味着该方法不适用于加密的OSD。
  • 设备必须手动分区。
  • 工具未实现!
  • 没有记录!5 O$ i/ ]  N2 N! L
优点:
  • 在转换期间,很少或没有数据在网络上迁移。
    9 Y1 d8 ?# W( P+ Y; y5 {
缺点:
  • 工具尚未完全实现。
  • 流程未记录。
  • 每个主机必须具有备用或空设备。
  • OSD在转换过程中处于脱机状态,这意味着新的写入操作将仅写入OSD的一部分。这会增加由于后续故障而导致数据丢失的风险。(但是,如果在转换完成之前出现故障,则可以启动原始FileStore OSD来提供对其原始数据的访问。)
    8 ~$ u( p+ q$ H  }
您需要登录后才可以回帖 登录 | 注册

本版积分规则

返回首页|Archiver|手机版|小黑屋|易陆发现技术论坛 ( 蜀ICP备2026014127号-1 )

GMT+8, 2026-6-12 01:05 , Processed in 0.036159 second(s), 29 queries .

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表