0%

书接上文,上回书咱们了解了multisite的配置,最重要的是知道了zonegroup、zone是个什么东西,该怎么用。

那么今天我们来看看zonegroup和zone中的placement,顾名思义,用于告诉zone使用这个placement会将bucket、object要放到哪个里。

关于single rgw的模式,可以参考官网的使用配置,这里不做赘述了。下面介绍一下Multisite上的配置方法。

Placement

  1. 在master zone和非master zone上分别创建同名placement
  2. 使用的时候可以在用户信息中指定placement,也可以在创建bucket的s3请求中指定placement。

master zone

1
2
3
4
5
6
7
8
9
10
11
# radosgw-admin zonegroup placement add \
--rgw-zonegroup default \
--placement-id cold-placement

# radosgw-admin zone placement add \
--rgw-zone exter \
--placement-id cold-placement \
--data-pool exter.rgw.cold.data \
--index-pool exter.rgw.cold.index \
--data-extra-pool exter.rgw.cold.non-ec \
--compression zlib

看一下配置完的zone(此处忽略zonegroup配置完的接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# radosgw-admin zone get
{
"id": "69234f1d-8f48-4431-b45f-912087431a47",
"name": "exter",
"domain_root": "exter.rgw.meta:root",
"control_pool": "exter.rgw.control",
"gc_pool": "exter.rgw.log:gc",
"lc_pool": "exter.rgw.log:lc",
"log_pool": "exter.rgw.log",
"intent_log_pool": "exter.rgw.log:intent",
"usage_log_pool": "exter.rgw.log:usage",
"reshard_pool": "exter.rgw.log:reshard",
"user_keys_pool": "exter.rgw.meta:users.keys",
"user_email_pool": "exter.rgw.meta:users.email",
"user_swift_pool": "exter.rgw.meta:users.swift",
"user_uid_pool": "exter.rgw.meta:users.uid",
"otp_pool": "exter.rgw.otp",
"system_key": {
"access_key": "85ZFW53VCKBZR7DQ7GS8",
"secret_key": "MfyuJECq3Et2kktmf3T077rYwXQMEiIGYRlwKBnq"
},
"placement_pools": [
{
"key": "cold-placement",
"val": {
"index_pool": "exter.rgw.cold.index",
"data_pool": "exter.rgw.cold.data",
"data_extra_pool": "exter.rgw.cold.non-ec",
"index_type": 0,
"compression": "zlib"
}
},
{
"key": "default-placement",
"val": {
"index_pool": "exter.rgw.buckets.index",
"data_pool": "exter.rgw.buckets.data",
"data_extra_pool": "exter.rgw.buckets.non-ec",
"index_type": 0,
"compression": ""
}
}
],
"metadata_heap": "",
"realm_id": ""
}

让上述配置生效需要radosgw-admin period update --commit一下。

非master zone

1
2
3
4
5
6
7
8
9
10
11
# radosgw-admin zonegroup placement add \
--rgw-zonegroup default \
--placement-id cold-placement

# radosgw-admin zone placement add \
--rgw-zone backup \
--placement-id cold-placement \
--data-pool backup.rgw.cold.data \
--index-pool backup.rgw.cold.index \
--data-extra-pool backup.rgw.cold.non-ec \
--compression lz4

*** 此处用的压缩算法与之前master zone使用的不一致,不影响使用。放心。***rgw会在每个对象上记录一个压缩类型,所以即使你这一刻使用的压缩算法和之前使用的不一致也不影响解压工作。

看一下配置完的zone(此处忽略zonegroup配置完的接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# radosgw-admin zone get
{
"id": "a6621518-6f80-41f5-a736-fb5d1814e036",
"name": "backup",
"domain_root": "backup.rgw.meta:root",
"control_pool": "backup.rgw.control",
"gc_pool": "backup.rgw.log:gc",
"lc_pool": "backup.rgw.log:lc",
"log_pool": "backup.rgw.log",
"intent_log_pool": "backup.rgw.log:intent",
"usage_log_pool": "backup.rgw.log:usage",
"reshard_pool": "backup.rgw.log:reshard",
"user_keys_pool": "backup.rgw.meta:users.keys",
"user_email_pool": "backup.rgw.meta:users.email",
"user_swift_pool": "backup.rgw.meta:users.swift",
"user_uid_pool": "backup.rgw.meta:users.uid",
"otp_pool": "backup.rgw.otp",
"system_key": {
"access_key": "85ZFW53VCKBZR7DQ7GS8",
"secret_key": "MfyuJECq3Et2kktmf3T077rYwXQMEiIGYRlwKBnq"
},
"placement_pools": [
{
"key": "cold-placement",
"val": {
"index_pool": "backup.rgw.cold.index",
"data_pool": "backup.rgw.cold.data",
"data_extra_pool": "backkup.rgw.cold.non-ec",
"index_type": 0,
"compression": "lz4"
}
},
{
"key": "default-placement",
"val": {
"index_pool": "backup.rgw.buckets.index",
"data_pool": "backup.rgw.buckets.data",
"data_extra_pool": "backup.rgw.buckets.non-ec",
"index_type": 0,
"compression": ""
}
}
],
"metadata_heap": "",
"realm_id": "f334e6c2-c2b4-4c04-b541-f64d96c10c07"
}

同样为了上诉修改生效也需要执行一下radosgw-admin period update --commit

User中使用Placement

要想在User中配置Placement,首先得创建一个User。由于我们是在Multisite模式中创建的User,所以创建的动作必须在master zone上操作,如果在非master zone上操作用户不能同步到master zone上。具体原因请见Deploy multisite in 同一个集群

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# radosgw-admin user create --uid colder --display-name colder

# radosgw-admin user info --uid colder
{
"user_id": "colder",
"display_name": "colder",
"email": "",
"suspended": 0,
"max_buckets": 1000,
"auid": 0,
"subusers": [],
"keys": [
{
"user": "colder",
"access_key": "WGCQOD46GA92J7CIGXIY",
"secret_key": "8yZw0FkYojiLdebWEF61HHTGhUvBrCel6ZzvGQRu"
}
],
"swift_keys": [],
"caps": [],
"op_mask": "read, write, delete",
"default_placement": "",
"placement_tags": [],
"bucket_quota": {
"enabled": false,
"check_on_raw": false,
"max_size": -1,
"max_size_kb": 0,
"max_objects": -1
},
"user_quota": {
"enabled": false,
"check_on_raw": false,
"max_size": -1,
"max_size_kb": 0,
"max_objects": -1
},
"temp_url_keys": [],
"type": "rgw",
"mfa_ids": []
}

其中default_placement为空,此时会使用默认的placement也就是default-placement。所以需要配置这个选项。但是radosgw-admin中没有直接配置这个项目的参数,所以需要采用导出导入metadata的方式来修改该项目。

1
# radosgw-admin metadata get user:colder > colder.json

将user colder的配置导出到coder.json中,然后编辑其中的"default_placement": "cold-placement",,然后再将colder.json导入回去。

1
# radosgw-admin metadata put user:colder < colder.json

接下来可以使用该用户的access-key和secret-key去创建bucket并上传对象了。

在一个Ceph集群里部署一个multisite RGW环境。有什么意义吗?没有,存属个人折腾。由于资源有限,所以只能在一个集群里委屈了。

限盐少许,直接开整。

版本

14.2.3 (0f776cf838a1ae3130b2b73dc26be9c95c6ccc39) nautilus (stable)

RGW配置部署

一个Site的部署

修改.rgw.rootPool

修改/etc/ceph/ceph.conf中使用到.rgw.rootpool的所有配置项,将其改为exter.rgw.rootceph.conf中没有显示的配置,所以建议查找一下源代码中的配置options.cc
之后再创建realmzonegroupzone等信息都会记录到exter.rgw.root中。

1
2
3
4
5
rgw_zone_root_pool = exter.rgw.root
rgw_region_root_pool = exter.rgw.root
rgw_zonegroup_root_pool = exter.rgw.root
rgw_realm_root_pool = exter.rgw.root
rgw_period_root_pool = exter.rgw.root

配置完成后,启动RGW。本人将RGW安装到了容器中,所以下面给出容器的启动命令

1
docker run -d --net=host --name rgw -v /etc/ceph/:/etc/ceph/ -v /var/lib/ceph/:/var/lib/ceph/ ceph/daemon:latest-nautilus rgw

运行后记得用docker logs -f rgw看一下rgw日志,确保其正常运行。

然后使用ceph df看一下,当前RGW创建了哪些Pools

1
2
3
4
5
6
7
8
9
10
# ceph df
GLOBAL:
SIZE AVAIL RAW USED %RAW USED
57 GiB 56 GiB 654 MiB 1.12
POOLS:
NAME ID USED %USED MAX AVAIL OBJECTS
exter.rgw.root 18 1.2 KiB 0 27 GiB 4
default.rgw.control 19 0 B 0 27 GiB 8
default.rgw.meta 20 0 B 0 27 GiB 0
default.rgw.log 21 0 B 0 27 GiB 33

创建Realm

创建一个 realm ,若不创建,后续在设置自定义的zone为default时会报无效参数的错误。此问题,可以看rgw_admin.cc的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
4438     case OPT_ZONE_DEFAULT:
4439 {
4440 RGWZoneGroup zonegroup(zonegroup_id,zonegroup_name);
4441 int ret = zonegroup.init(g_ceph_context, store->svc()->sysobj);
4442 if (ret < 0) {
4443 cerr << "WARNING: failed to initialize zonegroup " << zonegroup_name << std::endl;
4444 }
4445 if (zone_id.empty() && zone_name.empty()) {
4446 cerr << "no zone name or id provided" << std::endl;
4447 return EINVAL;
4448 }
4449 RGWZoneParams zone(zone_id, zone_name);
4450 ret = zone.init(g_ceph_context, store->svc()->sysobj);
4451 if (ret < 0) {
4452 cerr << "unable to initialize zone: " << cpp_strerror(-ret) << std::endl;
4453 return -ret;
4454 }
4455 ret = zone.set_as_default();
4456 if (ret < 0) {
4457 cerr << "failed to set zone as default: " << cpp_strerror(-ret) << std::endl;
4458 return -ret;
4459 }
4460 }
4461 break;

failed to set zone as default这个位置报错。

所以先配置一个realm吧

1
2
3
4
5
6
7
8
9
10
11
12
[root@master ~]# radosgw-admin realm list
{
"default_info": "",
"realms": []
}
[root@master ~]# radosgw-admin realm create --rgw-realm=default --default
{
"id": "f334e6c2-c2b4-4c04-b541-f64d96c10c07",
"name": "default",
"current_period": "40775bd3-d592-46c0-98c7-33171915825b",
"epoch": 1
}

设置zonegroup的default info

此时zonegroup的default info缺失了

1
2
3
4
5
6
7
8
9
[root@master ~]# radosgw-admin zonegroup list
{
"default_info": "",
"zonegroups": [
"default"
]
}
[root@master ~]# radosgw-admin zonegroup get
failed to init zonegroup: (2) No such file or directory

使用 zonegroup default重新设置default info

1
2
3
4
5
6
7
8
[root@master ~]# radosgw-admin zonegroup default --rgw-zonegroup=default
[root@master ~]# radosgw-admin zonegroup list
{
"default_info": "57b6eaac-63da-42dd-b69d-f7e871ce74c9",
"zonegroups": [
"default"
]
}

创建Zone

接下来,创建我们需要的zone, 并设置其为default

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[root@master ~]# radosgw-admin zone list
{
"default_info": "",
"zones": [
"default"
]
}
[root@master ~]# radosgw-admin zone create --rgw-zonegroup=default --rgw-zone=exter --master --default
2019-10-14 19:00:50.322 7f3698ef7240 0 NOTICE: overriding master zone: f2a55476-fb68-4ea7-af97-251b91e94747
{
"id": "69234f1d-8f48-4431-b45f-912087431a47",
"name": "exter",
"domain_root": "exter.rgw.meta:root",
"control_pool": "exter.rgw.control",
"gc_pool": "exter.rgw.log:gc",
"lc_pool": "exter.rgw.log:lc",
"log_pool": "exter.rgw.log",
"intent_log_pool": "exter.rgw.log:intent",
"usage_log_pool": "exter.rgw.log:usage",
"reshard_pool": "exter.rgw.log:reshard",
"user_keys_pool": "exter.rgw.meta:users.keys",
"user_email_pool": "exter.rgw.meta:users.email",
"user_swift_pool": "exter.rgw.meta:users.swift",
"user_uid_pool": "exter.rgw.meta:users.uid",
"otp_pool": "exter.rgw.otp",
"system_key": {
"access_key": "",
"secret_key": ""
},
"placement_pools": [
{
"key": "default-placement",
"val": {
"index_pool": "exter.rgw.buckets.index",
"data_pool": "exter.rgw.buckets.data",
"data_extra_pool": "exter.rgw.buckets.non-ec",
"index_type": 0,
"compression": ""
}
}
],
"metadata_heap": "",
"realm_id": "f334e6c2-c2b4-4c04-b541-f64d96c10c07"
}
[root@master ~]# radosgw-admin zone list
{
"default_info": "69234f1d-8f48-4431-b45f-912087431a47",
"zones": [
"exter",
"default"
]
}

删除default zone

现在你会发现有两个zone了,一个是我们新建的,另一个是default,这个default没用了,我们把它删掉。

1
radosgw-admin zone delete --rgw-zone=default --default

细心的朋友可能会发现radosgw-admin命令中没有zone delete这个命令啊,-h中显示的是zone rm啊。这是Bug,嘿嘿嘿~~~

1
2
3
4
5
6
7
8
9
10
11
12
[root@master ~]# radosgw-admin -h | grep -i zone | grep -i rm
zonegroup rm remove a zone group info
zonegroup rm remove a zone from a zonegroup
zonegroup placement rm remove a placement target from a zonegroup
zone rm remove a zone
zone placement rm remove a zone placement target
--tags-rm=<list> list of tags to remove for zonegroup placement modify command
--sync-from-rm=[zone-name][,...]
[root@master ~]# radosgw-admin -h | grep -i delete
replica mdlog get/delete
replica datalog get/delete
(NOTE: required to delete a non-empty bucket)

重启RGW服务

1
2
[root@master ~]# docker stop rgw
[root@master ~]# docker start rgw

删除default相关的Pools

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@master ~]# ceph df
GLOBAL:
SIZE AVAIL RAW USED %RAW USED
57 GiB 56 GiB 657 MiB 1.13
POOLS:
NAME ID USED %USED MAX AVAIL OBJECTS
exter.rgw.root 18 1.7 KiB 0 27 GiB 13
default.rgw.control 19 0 B 0 27 GiB 8
default.rgw.meta 20 0 B 0 27 GiB 0
default.rgw.log 21 0 B 0 27 GiB 175
exter.rgw.control 22 0 B 0 27 GiB 8
exter.rgw.meta 23 0 B 0 27 GiB 0
exter.rgw.log 24 0 B 0 27 GiB 159

重启RGW服务后,发现多了三个Pool exter.rgw.controlexter.rgw.metaexter.rgw.log。而default开头的pool就没用了,可以删除了。

1
2
3
4
5
6
[root@master ~]# ceph osd pool rm default.rgw.control default.rgw.control --yes-i-really-really-mean-it
pool 'default.rgw.control' removed
[root@master ~]# ceph osd pool rm default.rgw.meta default.rgw.meta --yes-i-really-really-mean-it
pool 'default.rgw.meta' removed
[root@master ~]# ceph osd pool rm default.rgw.log default.rgw.log --yes-i-really-really-mean-it
pool 'default.rgw.log' removed

另一个Site的部署

*** 参考一个Site的部署方法吧 ***

Zone之间的数据同步

如果上述的部署方法你都清楚了,相信配置一个Zone之间同步数据的Multisite应该不在话下了。下面说说数据同步的几点注意事项吧。

  1. 数据同步必须是在不同zone之间进行的,但zone必须在同一zonegroup之下。
  2. 每一个zonegroup内都有一个master zone;一个realm中有多个zonegroup,但master zonegroup只有一个,realm命名空间全局唯一;
  3. 数据分为 period 数据、user数据、bucket数据、object数据。
    period数据需要用户手动配置,不能自动同步;
    user数据只能从master zone写入,然后非master zone同步;
    bucket数据可以从任意一个zone写入,但最后都会转发给master zone,待master 写入成功后,其它zone同步数据,若master失败,其它zone亦失败;
    object数据可以从任意一个zone写入,若master zone写入失败,不影响其它zone写入情况。
  4. 每个zone的endpoints需要配置,否则zone之间无法正常访问
  5. zone上配置的access keysecret要与master zone一致

补个图吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
                                          +--- master zone
|
+--- secondary zone 1
|
+---------- master zonegroup ----+--- ......
| |
| +--- secondary zone n
|
realm ---+---------- secondary zonegroup 1 --+--- master zone
| |
| +--- secondary zone 1
+---------- ...... |
| +--- ......
|
+---------- secondary zonegropu n

Ceph的部署有很多种,从最早的手动部署,到后来的Ceph-deploy,再到目前比较火的容器部署等等。但能作为生产环境部署的却不多,其中Ceph-ansible算是生产环境部署的方法之一。Ceph-ansible是基于ansible工具完成Ceph部署的。对于生产环境中动则几十、成百上千的节点数量如果一台一台安装配置效率太低。引入ansible工具可以快速完成Ceph集群的安装配置,大大提高效率。让运维人员从机械重复的工作中解脱出来。

使用

环境

三个节点,其中一个节点用作ansible和Ceph节点的复用节点。

节点IP 节点角色 OS
172.30.12.137 ansible节点、ceph节点 CentOS Linux release 7.7.1908
172.30.12.197 ceph节点 CentOS Linux release 7.7.1908
172.30.12.227 ceph节点 CentOS Linux release 7.7.1908

本文中使用的ansible工具运行在一个 CentOS7.6.1810 的容器内。

安装

ceph-ansible是基于ansible工作的,所以要先安装ansible。既然要安装ansible,首先要先搞清楚安装哪个版本?

本文部署的Ceph版本为mimic,官方推荐可以使用stable-3.1stable-3.2,我选择了较新的stable-3.2stable-3.2对应的ansible版本为2.6。(更多关于版本对应关系请见官网

选择完了ansible版本接下来就可以安装了,安装分为两种方式

  • pip 安装
  • yum/apt 安装

由于 yum/apt 安装版本选择范围比较狭窄,所以本人推荐使用 pip 安装。(关于pip安装请见pypa官网)

首先clone ceph-ansible project

1
2
# git clone git@github.com:ceph/ceph-ansible.git
# git checkout -b 3.2.0 v3.2.0

然后使用ceph-ansible中推荐的requirements.txt安装对应版本的ansible

1
pip install -r ./ceph-ansible/requirements.txt

待安装完成后,查看确认ansible版本

1
2
3
4
5
6
7
# ansible --version
ansible 2.6.19
config file = /root/ceph-ansible/ansible.cfg
configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python2.7/site-packages/ansible
executable location = /usr/bin/ansible
python version = 2.7.5 (default, Oct 30 2018, 23:45:53) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]

配置

ansible 配置

ansible inventory 自定义的配置可以放置在任何位置,只是在运行ansible时增加-i {inventory host path}指定其路径即可。若不指定inventory文件,ansible将去/etc/ansible/hosts这个路径去找。

inventory配置:

1
2
3
4
5
6
7
8
9
[mons]
172.30.12.197 ansible_ssh_user=root ansible_ssh_pass=1234\$\#

[osds]
172.30.12.137 ansible_ssh_user=root ansible_ssh_pass=1234\$\#
172.30.12.197 ansible_ssh_user=root ansible_ssh_pass=1234\$\#

[mgrs]
172.30.12.137 ansible_ssh_user=root ansible_ssh_pass=1234\$\#

密码中若存在特殊字符$,#等,需要使用\\进行转义。
配置完成后,可使用ansible all -i {inventory host} -m ping测试节点连通情况

1
2
3
4
5
6
7
8
9
10
11
12
13
# ansible all -i dummy-ansible-hosts -m ping
172.30.12.197 | SUCCESS => {
"changed": false,
"ping": "pong"
}
172.30.12.227 | SUCCESS => {
"changed": false,
"ping": "pong"
}
172.30.12.137 | SUCCESS => {
"changed": false,
"ping": "pong"
}

ceph-ansible 配置

ceph-ansible的配置,主要是对group变量的配置,一般场景中我们不需要修改role。

配置 all.yml

首先:

1
cp ./gourp_vars/all.yml.sample ./gourp_vars/all.yml

然后,修改all.yml中的配置,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# Inventory host group variables
mon_group_name: mons
osd_group_name: osds
#rgw_group_name: rgws
#mds_group_name: mdss
#nfs_group_name: nfss
#restapi_group_name: restapis
#rbdmirror_group_name: rbdmirrors
#client_group_name: clients
#iscsi_gw_group_name: iscsigws
#mgr_group_name: mgrs
# 上述 Inventory host 相关变量的默认值与 Inventory配置中的标签名一致,也就是说这里注释打开与否没有影响
...
# If configure_firewall is true, then ansible will try to configure the
# appropriate firewalling rules so that Ceph daemons can communicate
# with each others.
#configure_firewall: True
configure_firewall: False
# 建议Firewall配不明白的同学将Firewall关闭,免得找麻烦。
...
# Set type of NTP client daemon to use, valid entries are chronyd, ntpd or timesyncd
# Note that this selection is currently ignored on containerized deployments
#ntp_daemon_type: timesyncd
ntp_daemon_type: chronyd
# Ceph需要做时间同步,具体用什么做可根据自己环境来选择。目前提供支持的有三种 chronyd, ntpd, timesyncd
...
# ORIGIN SOURCE
#
# Choose between:
# - 'repository' means that you will get ceph installed through a new repository. Later below choose between 'community', 'rhcs', 'dev' or 'obs'
# - 'distro' means that no separate repo file will be added
# you will get whatever version of Ceph is included in your Linux distro.
# 'local' means that the ceph binaries will be copied over from the local machine
#ceph_origin: dummy
ceph_origin: repository
#valid_ceph_origins:
# - repository
# - distro
# - local

ceph_repository: community
#valid_ceph_repository:
# - community
# - rhcs
# - dev
# - uca
# - custom
# - obs

# REPOSITORY: COMMUNITY VERSION
#
# Enabled when ceph_repository == 'community'
#
#ceph_mirror: http://download.ceph.com
#ceph_stable_key: https://download.ceph.com/keys/release.asc
#ceph_stable_release: dummy
#ceph_stable_repo: "{{ ceph_mirror }}/debian-{{ ceph_stable_release }}"
ceph_mirror: http://mirrors.163.com/ceph
ceph_stable_key: https://mirrors.163.com/ceph/keys/release.asc
ceph_stable_release: mimic
ceph_stable_repo: "{{ ceph_mirror }}/rpm-{{ ceph_stable_release }}"
# Ceph 软件包的安装方式,有三种,repository使用一个新的源进行安装;distro使用linux发行版自带的源进行安装;local使用本地安装包的形式进行安装。
# 具体情况根据自身要求而定吧。
...
## Monitor options
#
# You must define either monitor_interface, monitor_address or monitor_address_block.
# These variables must be defined at least in all.yml and overrided if needed (inventory host file or group_vars/*.yml).
# Eg. If you want to specify for each monitor which address the monitor will bind to you can set it in your **inventory host file** by using 'monitor_address' variable.
# Preference will go to monitor_address if both monitor_address and monitor_interface are defined.
#monitor_interface: interface
monitor_interface: ens33
#monitor_address: 0.0.0.0
#monitor_address_block: subnet
# set to either ipv4 or ipv6, whichever your network is using
#ip_version: ipv4
#mon_use_fqdn: false # if set to true, the MON name used will be the fqdn in the ceph.conf
# Monitor的配置,必须要在 interface, address, address_block 中选择一个定义。(更多使用方法,请仔细阅读上面的英文吧。)
...
## OSD options
#
#is_hci: false
#hci_safety_factor: 0.2
#non_hci_safety_factor: 0.7
#osd_memory_target: 4294967296
#journal_size: 5120 # OSD journal size in MB
journal_size: 1024 # OSD journal size in MB
#block_db_size: -1 # block db size in bytes for the ceph-volume lvm batch. -1 means use the default of 'as big as possible'.
#public_network: 0.0.0.0/0
public_network: 172.30.12.0/24
cluster_network: 172.30.12.0/24
#cluster_network: "{{ public_network | regex_replace(' ', '') }}"
#osd_mkfs_type: xfs
#osd_mkfs_options_xfs: -f -i size=2048
#osd_mount_options_xfs: noatime,largeio,inode64,swalloc
#osd_objectstore: bluestore
osd_objectstore: filestore
# 根据硬盘的存储介质与速度决定Journal size的大小;配置public,cluster newtwork;选择objectstore filestore or bluestore
...

配置 osds.yml

首先

1
cp ./gourp_vars/osds.yml.sample ./gourp_vars/osds.yml

然后,修改osds.yml配置,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
...
# Even though OSD nodes should not have the admin key
# at their disposal, some people might want to have it
# distributed on OSD nodes. Setting 'copy_admin_key' to 'true'
# will copy the admin key to the /etc/ceph/ directory
#copy_admin_key: false
copy_admin_key: true
# 根据个人喜好来吧,我喜欢各个OSD节点都有admin key
...
# Declare devices to be used as OSDs
# All scenario(except 3rd) inherit from the following device declaration
# Note: This scenario uses the ceph-disk tool to provision OSDs

devices:
- /dev/sdb
- /dev/sdc
# - /dev/sdd
# - /dev/sde

#devices: []
# 根据主机情况配置硬盘路径相关信息
...
osd_scenario: collocated
#valid_osd_scenarios:
# - collocated
# - non-collocated
# - lvm
# collocated是将日志和数据部署到同一个硬盘上;non-collocated是分硬盘部署日志和数据;这两个选项都是使用ceph-disk创建的。
# lvm使用ceph-volume创建osd,需要指定vg、lvm等信息。
# 此处具体信息,请见`osds.yml.sample`里面有很详细的注解。
...

配置 mgrs.yml

首先

1
cp ./gourp_vars/mgrs.yml.sample ./gourp_vars/mgrs.yml

然后,修改mgrs.yml配置,如下:

1
2
3
4
5
6
7
...
###########
# MODULES #
###########
# Ceph mgr modules to enable, current modules available are: status,dashboard,localpool,restful,zabbix,prometheus,influx
ceph_mgr_modules: [status]
...

部署

集群初始化部署

首先

1
cp ./site.yml.sample ./site.yml

然后

1
2
ansible-playbook -i {inventory host} site.yml
...

部署过程需要下载RPM包安装,所以网速对部署速度的影响很大。请耐心等待。

增加OSD节点部署

首先

1
cp ./infrastructure-playbooks/add-osd.yml ./add-osd.yml

然后,修改inventory host文件,在[osds]中增加172.30.12.227这条记录,相当于增加一个osd节点。

1
2
3
4
5
6
7
8
9
10
[mons]
172.30.12.197 ansible_ssh_user=root ansible_ssh_pass=1234\$\#

[osds]
172.30.12.137 ansible_ssh_user=root ansible_ssh_pass=1234\$\#
172.30.12.197 ansible_ssh_user=root ansible_ssh_pass=1234\$\#
172.30.12.227 ansible_ssh_user=root ansible_ssh_pass=1234\$\#

[mgrs]
172.30.12.137 ansible_ssh_user=root ansible_ssh_pass=1234\$\#

最后

1
ansible-playbook -i {inventory host} add-osd.yml

清除集群部署

首先

1
cp ./infrastructure-playbooks/purge-cluster.yml ./purge-cluster.yml

然后

1
ansible-playbook -i {inventory host} purge-cluster.yml

Ceph配置文件分发

配置文件分发各个节点

1
ansible all -i dummy-ansible-hosts -m copy -a "src=/root/ceph.conf dest=/etc/ceph/"

参考&鸣谢

RGW支持很多种认证AWS标准的V2、V4;还有开放的LDAP,MFA,keystone,barbican等。太多了就不一一列举了。

LDAP

要想使用LDAP认证,首先你的有个LDAP Server。然后将RGW的LDAP开关打开,LDAP相关配置项配置好。重启RGW,你就可以使用LDAP认证了,简单不!

关于LDAP的搭建请见参考&鸣谢中的23

RGW配置

1
2
3
4
5
6
7
8
...
rgw_ldap_uri = ldap://10.100.13.111:389
rgw_ldap_binddn = "cn=Manager,dc=my-domain,dc=com"
rgw_ldap_secret = "/etc/ceph/bindpass"
rgw_ldap_searchdn = "dc=my-domain,dc=com"
rgw_ldap_dnattr = "cn"
rgw_s3_auth_use_ldap = true
...
  • rgw_ldap_binddn
    RGW访问LDAP Server使用的账户名
  • rgw_ldap_secret
    访问LDAP Server账户名对应的密码,必须放到文件中,这里指定一个访问文件的路径(明文存储文件就行)
  • rgw_ldap_searchdn
    验证是检索的范围
  • rgw_ldap_dnattr
    构建检索filter的属性名,RGW在程序中会将需要验证的账户名与之组对儿 eg: cn=ldapuser1

其它别的参数就不用解释了,看不懂可以先测一下智商。

LDAP_Server

截止目前未知,我都依然没有搞清楚什么事 cn,ou,dn,dc。嗯~~~~一头雾水啊!不管了,LDAP相关的自己问度娘去吧。

验证流程

Todo…

参考&鸣谢

  1. CEPH OBJECT GATEWAY WITH LDAP/AD GUIDE
  2. CentOS 7下安装部署OpenLDAP+phpLDAPadmin
  3. LDAP落地实战(一):OpenLDAP部署及管理维护

** 背景 **

容器是一个很老的技术,但这几年却很火热。好多应用都已经容器化了,只要是在用户空间运行的,几乎都可以被容器化。就是放到docker里运行。

面对着容器里运行的应用程序它与外界隔离了,这种隔离有优点,也有缺点。今天我们不讲优点,说一说它的一个缺点。。。

如果你是在linux上写C/C++的朋友,你肯定会用到gdb这个神一样的调试工具。在当下这么火爆的容器面前,你的APP可能早就被扔到容器里了。

有一天,除了一个问题,需要你用gdb去调试一下,我靠,ns完全是隔离的,根本无法调试,火大了吧,别急!我来帮你~v~

调试方法

方法一

其实gdb调试容器内app也不难,只要做到两点就可以成功调试了。

  1. 你所使用的镜像里必须得有gdb工具,没有怎么调试啊。要么做镜像的时候将gdb安装进去,要么使你的gdb与被调试app的容器处于同一命名空间。
  2. 使用一些工具(nsenter)侵入到容器命名空间后再运行gdb调试。eg: sudo nsenter -t {容器的PID} -m -p gdb -p {容器内被调试app的PID}

方法二

方法二也需要在被调试的容器上先装有gdb,然后在启动容器的时候通过增加一些特权参数。待需要调试的时候直接登录到容器内就可以调试了。

  1. 在启动docker容器的时候需要增加--privileged参数
  2. 由于gdb调试需要的SYS_PTRACE属性被禁止掉了,所以在启动容器时候需要增加这个属性--cap-add sys_ptrace
  3. gdb调试时,需要关闭linux虚拟地址随机化(虚拟地址随机化是为了安全考虑而出现的,gdb调试只是暂时关闭)。关闭有两种方法,一是通过gdb的命令set disable-randomization off关闭。二是通过设置docker的参数--security-opt seccomp=unconfined

有此三步,可保gdb无告警,并正常使用。

参考&鸣谢

容器化、容器化、容器化。。。都说重要的事情说三遍,那么Ceph部署有必要容器化吗?

有必要,非常有必要。容器化以后,不仅不会损失性能,而且对以后更新、回退都很方便。而且一个物理机(或虚拟机)上可以跑多个Ceph集群。对于一个开发人员,修改调试都实在是太方便了~~~~~!

闲言碎语不要讲,先拉个集群起来看看吧。

mimic

镜像

ceph/daemon:latest-mimic

网络

使用docker bridge网络,每个容器指定静态IP。

1
docker network create --subnet 192.168.44.0/24 cephnet

拉起Ceph集群

MON

1
docker run -d --net cephnet --ip 192.168.44.11 -v /Users/zhoub/temp/etc_ceph/:/etc/ceph -v /Users/zhoub/temp/var_lib_ceph/:/var/lib/ceph -e MON_IP=192.168.44.11 -e CEPH_PUBLIC_NETWORK=192.168.44.0/24 --name mon --hostname mon  ceph/daemon:latest-mimic mon

MGR

MGR节点很重要,如果没有MGR节点,你就看不到OSD的使用量了。现在的MON真是翻身农奴把歌唱啊,一心只做心跳了。

1
docker run -d --net cephnet --ip 192.168.44.12 -v /Users/zhoub/temp/etc_ceph/:/etc/ceph -v /Users/zhoub/temp/var_lib_ceph/:/var/lib/ceph --name mgr --hostname mgr ceph/daemon:latest-mimic mgr

OSD

OSD有两种部署方式,一种是将硬盘部署;另一种是目录部署。由于本人使用mac版本docker,所以在映射硬盘过程不太方便,所以使用目录部署。后面会给出硬盘部署的方式。

目录

1
docker run -d --net cephnet --ip 192.168.44.13 -v /Users/zhoub/temp/etc_ceph/:/etc/ceph -v /Users/zhoub/temp/var_lib_ceph/:/var/lib/ceph --name osd --hostname osd -e OSD_TYPE=directory ceph/daemon:latest-mimic osd

由于mac上的docker是运行在一个虚拟机中的,目前不太清楚该虚拟机使用的文件系统是什么,所以osd会暴如下错误:

1
2
3
4
5
6
7
8
9
10
11
$ docker logs -f osd
2019-09-23 10:59:14 /opt/ceph-container/bin/entrypoint.sh: static: does not generate config
2019-09-23 10:59:14 /opt/ceph-container/bin/entrypoint.sh: Creating osd
2019-09-23 10:59:14 /opt/ceph-container/bin/entrypoint.sh: OSD created with ID: 0
2019-09-23 10:59:14 /opt/ceph-container/bin/entrypoint.sh: created folder /var/lib/ceph/osd/ceph-0/
creating /var/lib/ceph/osd/ceph-0//keyring
added entity osd.0 auth auth(auid = 18446744073709551615 key=AQCCpYhdbE1EGxAAxX2Av3Gzez/5j8sijQg1jQ== with 0 caps)
2019-09-23 10:59:15.014 7f255136fd80 -1 filestore(/var/lib/ceph/osd/ceph-0) WARNING: max attr value size (1024) is smaller than osd_max_object_name_len (2048). Your backend filesystem appears to not support attrs large enough to handle the configured max rados name size. You may get unexpected ENAMETOOLONG errors on rados operations or buggy behavior
2019-09-23 10:59:15.084 7f255136fd80 -1 filestore(/var/lib/ceph/osd/ceph-0) mkjournal(1101): error creating journal on /var/lib/ceph/osd/ceph-0//journal: (22) Invalid argument
2019-09-23 10:59:15.084 7f255136fd80 -1 OSD::mkfs: ObjectStore::mkfs failed with error (22) Invalid argument
2019-09-23 10:59:15.084 7f255136fd80 -1 ** ERROR: error creating empty object store in /var/lib/ceph/osd/ceph-0: (22) Invalid argument

硬盘

在硬盘部署以前,需要先对硬盘进行格式化

1
docker run -d --privileged=true --rm -v /dev/:/dev/ -e OSD_DEVICE=/dev/sdb ceph/daemon:latest-mimic zap_device

格式化后再启动容器

1
docker run -d --net cephnet --ip 192.168.44.13 --hostname osd --name=osd --privileged  -v /Users/zhoub/temp/etc_ceph/:/etc/ceph -v /Users/zhoub/temp/var_lib_ceph/:/var/lib/ceph -v /dev/:/dev/ -e OSD_DEVICE=/dev/sdb ceph/daemon:latest-mimic osd

总结

集群除OSD服务外其它服务均可以正常容器化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@mon /]# ceph -s
cluster:
id: 61a4f9d8-fdc7-4883-8b71-7f93c2017f86
health: HEALTH_OK

services:
mon: 1 daemons, quorum mon
mgr: mgr(active)
osd: 1 osds: 0 up, 0 in

data:
pools: 0 pools, 0 pgs
objects: 0 objects, 0 B
usage: 0 B used, 0 B / 0 B avail
pgs:

OSD目录部署不能正常提供服务主要是因为宿主机的文件系统类型未知,若宿主机可正常登录操作,此问题可解;
OSD硬盘部署不能正常提供服务主要是因为宿主机不能增加硬盘设备,若宿主机可以添加硬盘,此问题可解。

综上所述,问题根本在于对宿主机的操作了解不够清晰。

参考&鸣谢

说起环境变量,可能首先想到的是shell中敲入env所看到的这些。没错,这些的确是环境变量。

还有吗。。。
有!你再试试set命令,看看是不是比env的多啊。是不是有点儿小懵逼。怎么还不一样呢!set看到的是shell变量,那shell变量是环境变量吗?是。

还有吗。。。
有!在你输入set的时候你看到$@$?这些了吗?貌似没有吧。据说这些属于系统变量。(听说而已。。。)

还有吗。。。
目前为止,我只知道这么多,如果我了解到更多的再补充吧。

说了这么多,上一张图,就一目了然了。

linux_env_variable

参考&鸣谢

RGW == Rados Gateway 中文名字叫对象存储网关。

问题

  • 一个RGW能承载的访问量是有限的,若出现故障或性能瓶颈怎么办?若是部署成多个,业务低谷时造成资源浪费
  • 一个集群与单节点有何区别,若整个集群崩溃宕机是小,丢数据为大
  • 客户端的访问协议千奇百怪若只支持S3不足以服众
  • 单纯的V2认证安全性差
  • 客户端压缩、加密数据代价太大,而且难以实现
  • 所有用户、bucket在一个命名空间内安全性太差

为解决上述问题,提出如下方案

方案

rgwcluster.png

  • 管理控制I/O与数据读写I/O分开处理
    管理控制I/O负责设置获取RGW集群信息;
    数据读写I/O负责上传下载对象或上传下载文件。
  • 不同Ceph集群之间互为主备关系
    主集群采用副本集数据保护机制;
    备份集群采用EraseCode数据保护机制。
  • RGW实例由K8s对外发布并提供服务
    利用K8s的Autoscaling可以根据负载情况动态调整RGW实例数量;
    利用K8s的分布式LB可以有效的分散RGW上访问压力。

涉及到的模块

  • RGW Cluster Manager
  • S3 API
  • NFS
  • Admin API
  • RGW

功能

资源管理功能

存储资源管理

  • 存储资源分配
    按容量(初始容量)优先分配
    按性能优先分配
  • 存储资源回收
  • 存储资源使用情况查询
    可以为计费模块统计提供资源使用情况

用户及权限管理

  • 用户管理
    用户的增加、删除、属性修改
  • 用户权限管理
    用户权限的授权、回收

配额管理

  • user 配额设置
    针对用户名下的对象数量、可使用容量(byte)进行配额设置
  • bucket 配额设置
    针对bucket下的对象数量、可使用容量(byte)进行配额设置

RGW节点网络控制管理

  • 访问RGW节点客户端黑、白名单机制
  • RGW节点接收请求的流量配额管理
  • RGW节点读写Ceph集群的流量配额管理

数据压缩、加密

  • RGW节点接收数据后在Server端完成对数据的压缩,压缩算法需要由用户或运维人员指定
  • RGW节点接收数据后在Server端完成对数据的加密,加密使用的密钥可人为指定

多种认证支持

  • MFA(Multi-Factor Authentication)认证
  • keystone认证
  • LDAP认证
  • V4认证

多租户管理

对多租户的支持可以允许不同的租户使用相同的用户名、bucket名。更友好的隔离数据,防止对用户和bucket的嗅探。

接口功能

支持Compatible-S3、Swift、NFS等多种接口。

NFS

支持将S3接口导出为NFS接口,方便符合POSIX标准的请求使用。

故障切换

RGW故障

由于RGW采用分布式部署方式,单个RGW的故障不影响整体使用。RGW由K8s发布提供服务,即使出现性能不足或多点故障,也都由K8s调度恢复。

Ceph集群故障

由于集群之间采用主备方式存储数据,即使主集群停止服务,也可通过备集群继续提供服务。

环境

硬件环境

  • CPU
  • 内存
  • 硬盘
    SAS 300G x 2
    SATA 6T x 4
    SSD 480G x 6

软件环境

  • 操作系统
    Centos 7.6.1810
  • Ceph软件版本
    14.2.2

部署方案

changcun_ceph_solution.png

OS

两个SAS盘(300G)做成RAID1,用于存储操作系统。

SATA Pool

  • OSD Data
    每个SATA(6T)做成RAID0,用于存储每个OSD数据
  • OSD Journal
    两个SSD(480G)做成RAID1,再分成4个分区给每台主机中的4个OSD提供Journal数据存储服务
  • ObjectStore
    OSD后端存储引擎使用Filestore
  • Replication
    副本数:3
    最小允许副本数:1

SSD Pool

  • OSD
    每个SSD(480G)做成RAID0,用于存储每个OSD的数据
  • ObjectStore
    OSD后端存储引擎使用Bluestore
  • Replication
    副本数:3
    最小允许副本数:1

部署步骤

Ceph集群部署

Ceph集群的节点分为管理节点、MON节点、OSD节点

节点 用途
MON节点 用于监控集群、为客户端提供ClusterMap等服务
OSD节点 数据存储节点,用于完成数据的持久化工作
ADMIN节点 集群维护管理节点,提供管理员权限
MDS节点 提供Cephfs服务
RGW节点 提供对象存储服务
MGR节点 提供监控(zabbix、prometheus等)、dashboard等服务
iSCSI-GW节点 提供iSCSI Target 服务

更多Ceph内容,请见此链接

  • 需要在部署集群前,做通管理节点与集群节点(包括:MON节点、OSD节点)之间的ssh无密码访问。
  • 将各个节点的hostname和ip地址对应后,写入/etc/hosts文件,并将此文件同步到各个节点上
    以ceph13为例:
    1
    2
    3
    4
    5
    6
    7
    [root@ceph13 cluster]# cat /etc/hosts
    127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
    ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6

    10.53.2.13 ceph13 ceph13
    10.53.2.16 ceph16 ceph16
    10.53.2.19 ceph19 ceph19

Ceph-deploy 安装

在管理节点上安装Ceph-deploy工具。

安装EPEL

1
yum install -y epel-release

修改/增加ceph.repo,并安装ceph-deploy

1
2
3
4
5
6
7
[ceph-noarch]
name=Ceph noarch packages
baseurl=https://download.ceph.com/rpm-nautilus/el7/noarch
enabled=1
gpgcheck=1
type=rpm-md
gpgkey=https://download.ceph.com/keys/release.asc
1
2
yum update -y
yum install -y ceph-deploy

Create the cluster

1
2
3
mkdir {my-cluster}
cd {my-cluster}
ceph-deploy new {initial-monitor-node(s)}

eg:

1
2
3
[root@ceph13 ~]# mkdir cluster
[root@ceph13 ~]# cd cluster
[root@ceph13 cluster]# ceph-deploy new ceph13 ceph16 ceph19

修改集群初始化配置

修改{my-cluster}目录中的ceph.conf

1
2
3
4
5
6
7
8
9
10
11
12
[root@ceph13 cluster]# ls -al
总用量 600
drwxr-xr-x. 2 root root 244 8月 21 15:51 .
dr-xr-x---. 8 root root 4096 8月 11 16:27 ..
-rw-------. 1 root root 113 8月 10 16:17 ceph.bootstrap-mds.keyring
-rw-------. 1 root root 113 8月 10 16:17 ceph.bootstrap-mgr.keyring
-rw-------. 1 root root 113 8月 10 16:17 ceph.bootstrap-osd.keyring
-rw-------. 1 root root 113 8月 10 16:17 ceph.bootstrap-rgw.keyring
-rw-------. 1 root root 151 8月 10 16:17 ceph.client.admin.keyring
-rw-r--r--. 1 root root 293 8月 10 15:15 ceph.conf
-rw-r--r--. 1 root root 580431 8月 10 17:13 ceph-deploy-ceph.log
-rw-------. 1 root root 73 8月 10 15:12 ceph.mon.keyring
  • 配置public、cluster网络
  • 配置osd rebalance
  • 配置scrub和deep-scrub
  • 关闭crushmap 启动更新配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    [global]

    public network = {ip-address}/{bits}
    cluster network = {ip-address}/{bits}

    osd crush update on start = false

    [osd]
    osd max backfills = 1
    osd recovery op priority = 1
    osd recovery max active = 1
    osd client op priority = 63
    osd recovery delay start = 0.5

    osd scrub chunk min = 1
    osd scrub chunk max = 5
    osd scrub sleep = 5
    osd deep scrub interval = 2592000 # 60(秒)*60(分)*24(时)*30(天)

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@ceph13 cluster]# cat /etc/ceph/ceph.conf 
[global]
fsid = 20192dd5-3228-4135-9831-c9d7de74a890
mon_initial_members = ceph13, ceph16, ceph19
mon_host = 10.53.2.13,10.53.2.16,10.53.2.19
auth_cluster_required = cephx
auth_service_required = cephx
auth_client_required = cephx
osd crush update on start = false

public network = 10.53.2.0/24
cluster network = 10.53.1.0/24

[osd]
osd max backfills = 1
osd recovery op priority = 1
osd recovery max active = 1
osd client op priority = 63
osd recovery delay start = 0.5

osd scrub chunk min = 1
osd scrub chunk max = 5
osd scrub sleep = 5
osd deep scrub interval = 2592000

ceph软件安装

1
ceph-deploy install --stable nautilus {ceph-node(s)}

eg:

1
ceph-deploy install --stable nautilus ceph13 ceph16 ceph19

*** 此处安装过程比较慢,失败的话建议重试几次。***

MON节点创建初始化

1
ceph-deploy mon create-initial

此命令会根据ceph.conf进行MON节点的创建及初始化工作

部署管理节点

1
ceph-deploy admin {ceph-node(s)}

eg:

1
ceph-deploy admin ceph13 ceph16 ceph19

部署mgr节点(可选)

1
ceph-deploy mgr create {ceph-node(s)}

eg:

1
ceph-deploy mgr create ceph13 ceph16 ceph19

*** 若不部署mgr服务,集群会有告警,但不影响使用。***

部署OSD节点(Filestore)

1
ceph-deploy osd create --filestore --data {device path} --journal {device path} {ceph-node} 
  • --filestore
    指定数据存储引擎
  • --data
    指定osd数据存储设备(SATA盘)
  • --journal
    指定osd journal存储设备(SSD Journal盘)
  • ceph-node
    指定osd所在主机的hostname

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
ceph-deploy osd create ceph13 --filestore --data /dev/sdb --journal /dev/sdf1
ceph-deploy osd create ceph13 --filestore --data /dev/sdb --journal /dev/sdf1
ceph-deploy osd create ceph13 --filestore --data /dev/sdc --journal /dev/sdf2
ceph-deploy osd create ceph13 --filestore --data /dev/sdd --journal /dev/sdf3
ceph-deploy osd create ceph13 --filestore --data /dev/sde --journal /dev/sdf4
ceph-deploy osd create ceph16 --filestore --data /dev/sdb --journal /dev/sdf1
ceph-deploy osd create ceph16 --filestore --data /dev/sdc --journal /dev/sdf2
ceph-deploy osd create ceph16 --filestore --data /dev/sdd --journal /dev/sdf3
ceph-deploy osd create ceph16 --filestore --data /dev/sde --journal /dev/sdf4
ceph-deploy osd create ceph19 --filestore --data /dev/sdb --journal /dev/sdf1
ceph-deploy osd create ceph19 --filestore --data /dev/sdc --journal /dev/sdf2
ceph-deploy osd create ceph19 --filestore --data /dev/sdd --journal /dev/sdf3
ceph-deploy osd create ceph19 --filestore --data /dev/sde --journal /dev/sdf4

部署OSD节点(Bluestore)

1
ceph-deploy osd create --data {device path} {ceph-node}
  • --data
    指定osd数据存储设备(SSD盘)
  • ceph-node
    指定osd所在主机的hostname

eg:

1
2
3
4
5
6
7
8
9
10
11
12
ceph-deploy osd create ceph13 --data /dev/sdg
ceph-deploy osd create ceph13 --data /dev/sdh
ceph-deploy osd create ceph13 --data /dev/sdi
ceph-deploy osd create ceph13 --data /dev/sdj
ceph-deploy osd create ceph16 --data /dev/sdg
ceph-deploy osd create ceph16 --data /dev/sdh
ceph-deploy osd create ceph16 --data /dev/sdi
ceph-deploy osd create ceph16 --data /dev/sdj
ceph-deploy osd create ceph19 --data /dev/sdg
ceph-deploy osd create ceph19 --data /dev/sdh
ceph-deploy osd create ceph19 --data /dev/sdi
ceph-deploy osd create ceph19 --data /dev/sdj

部署完所有OSD节点后,此集群部署完成可以通过ceph -s查看集群状态。

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
[root@ceph13 cluster]# ceph -s
cluster:
id: 20192dd5-3228-4135-9831-c9d7de74a890
health: HEALTH_OK

services:
mon: 3 daemons, quorum ceph13,ceph16,ceph19 (age 10d)
mgr: ceph13(active, since 10d), standbys: ceph16, ceph19
osd: 24 osds: 24 up (since 9d), 24 in (since 10d)

data:
pools: 3 pools, 1536 pgs
objects: 20.80k objects, 81 GiB
usage: 244 GiB used, 70 TiB / 71 TiB avail
pgs: 1536 active+clean

io:
client: 309 KiB/s rd, 145 KiB/s wr, 41 op/s rd, 12 op/s wr

[root@ceph13 cluster]# ceph osd tree
ID CLASS WEIGHT TYPE NAME STATUS REWEIGHT PRI-AFF
-15 5.21997 root ssd
-9 1.73999 host ceph13_ssd
12 ssd 0.43500 osd.12 up 1.00000 1.00000
13 ssd 0.43500 osd.13 up 1.00000 1.00000
14 ssd 0.43500 osd.14 up 1.00000 1.00000
15 ssd 0.43500 osd.15 up 1.00000 1.00000
-11 1.73999 host ceph16_ssd
16 ssd 0.43500 osd.16 up 1.00000 1.00000
17 ssd 0.43500 osd.17 up 1.00000 1.00000
18 ssd 0.43500 osd.18 up 1.00000 1.00000
19 ssd 0.43500 osd.19 up 1.00000 1.00000
-13 1.73999 host ceph19_ssd
20 ssd 0.43500 osd.20 up 1.00000 1.00000
21 ssd 0.43500 osd.21 up 1.00000 1.00000
22 ssd 0.43500 osd.22 up 1.00000 1.00000
23 ssd 0.43500 osd.23 up 1.00000 1.00000
-1 65.41200 root default
-3 21.80400 host ceph13_sata
0 hdd 5.45099 osd.0 up 1.00000 1.00000
1 hdd 5.45099 osd.1 up 1.00000 1.00000
2 hdd 5.45099 osd.2 up 1.00000 1.00000
3 hdd 5.45099 osd.3 up 1.00000 1.00000
-5 21.80400 host ceph16_sata
4 hdd 5.45099 osd.4 up 1.00000 1.00000
5 hdd 5.45099 osd.5 up 1.00000 1.00000
6 hdd 5.45099 osd.6 up 1.00000 1.00000
7 hdd 5.45099 osd.7 up 1.00000 1.00000
-7 21.80400 host ceph19_sata
8 hdd 5.45099 osd.8 up 1.00000 1.00000
9 hdd 5.45099 osd.9 up 1.00000 1.00000
10 hdd 5.45099 osd.10 up 1.00000 1.00000
11 hdd 5.45099 osd.11 up 1.00000 1.00000

iscsi-gw部署

依赖软件安装

iscsi-gw软件在192.168.1.70/home/zhoub/rpms目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[zhoub@CentOS ceph-iscsi-rpms]$ ls -al
总用量 588
drwxr-xr-x. 9 zhoub zhoub 4096 6月 19 16:31 .
drwxrwxr-x. 3 zhoub zhoub 53 8月 8 13:42 ..
drwxrwxr-x. 10 zhoub zhoub 4096 6月 19 16:10 ceph-iscsi
drwxrwxr-x. 9 zhoub zhoub 213 6月 19 16:10 configshell-fb
-rw-r--r--. 1 root root 284340 8月 11 2017 libnl3-3.2.28-4.el7.x86_64.rpm
drwxrwxr-x. 2 zhoub zhoub 240 6月 19 15:51 pip
drwxr-xr-x. 2 zhoub zhoub 4096 6月 19 16:10 pip_packages
-rw-r--r--. 1 root root 137912 11月 12 2018 pyOpenSSL-0.13.1-4.el7.x86_64.rpm
-rw-r--r--. 1 root root 96088 7月 4 2014 pyparsing-1.5.6-9.el7.noarch.rpm
-rw-r--r--. 1 root root 58008 7月 4 2014 python-kmod-0.9-4.el7.x86_64.rpm
-rw-r--r--. 1 zhoub zhoub 76 6月 19 16:10 requirement.txt
drwxrwxr-x. 11 zhoub zhoub 223 6月 19 16:10 rtslib-fb
drwxrwxr-x. 10 zhoub zhoub 248 6月 19 16:10 targetcli-fb
drwxr-xr-x. 2 root root 112 6月 19 16:28 tcmu-runner

pip安装

rpms/ceph-iscsi-rpms/pip目录下。

pip 安装依赖的软件

1
2
3
4
5
6
7
8
9
[zhoub@CentOS pip]$ ls -al
总用量 4080
drwxrwxr-x. 2 zhoub zhoub 240 6月 19 15:51 .
drwxr-xr-x. 9 zhoub zhoub 4096 6月 19 16:31 ..
-rw-rw-r--. 1 zhoub zhoub 3700178 6月 19 15:31 pip_19.1.1.tar.gz
-rw-r--r--. 1 root root 5932 3月 14 2015 python-backports-1.0-8.el7.x86_64.rpm
-rw-r--r--. 1 root root 12896 4月 25 2018 python-backports-ssl_match_hostname-3.5.0.1-1.el7.noarch.rpm
-rw-r--r--. 1 root root 35176 11月 21 2016 python-ipaddress-1.0.16-2.el7.noarch.rpm
-rw-r--r--. 1 root root 406404 8月 11 2017 python-setuptools-0.9.8-7.el7.noarch.rpm

安装pip及其依赖的软件

1
2
3
4
5
cd pip
yum localinstall *.rpm
tar -xzvf ./pip_19.1.1.tar.gz
cd pip_19.1.1
python setup.py install

安装pip package

在线安装使用rpms/ceph-iscsi-rpms/requirement.txt文件进行安装

1
2
3
4
5
6
7
8
9
[zhoub@CentOS ceph-iscsi-rpms]$  cat requirement.txt
pyudev>=0.16.1
Flask
requests
crypto
netifaces
urwid
pyparsing
cryptography
1
pip install -r ./requirement.txt

离线安装包在rpms/ceph-iscsi-rpms/pip_packages

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[zhoub@CentOS pip_packages]$ ls -al
总用量 5828
drwxr-xr-x. 2 zhoub zhoub 4096 6月 19 16:10 .
drwxr-xr-x. 9 zhoub zhoub 4096 6月 19 16:31 ..
-rw-r--r--. 1 zhoub zhoub 101571 6月 19 16:10 asn1crypto-0.24.0-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 157119 6月 19 16:10 certifi-2019.6.16-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 415091 6月 19 16:10 cffi-1.12.3-cp27-cp27mu-manylinux1_x86_64.whl
-rw-r--r--. 1 zhoub zhoub 133356 6月 19 16:10 chardet-3.0.4-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 81299 6月 19 16:10 Click-7.0-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 18019 6月 19 16:10 crypto-1.4.1-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 2288455 6月 19 16:10 cryptography-2.7-cp27-cp27mu-manylinux1_x86_64.whl
-rw-r--r--. 1 zhoub zhoub 12427 6月 19 16:10 enum34-1.1.6-py2-none-any.whl
-rw-r--r--. 1 zhoub zhoub 92053 6月 19 16:10 Flask-1.0.3-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 58594 6月 19 16:10 idna-2.8-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 18155 6月 19 16:10 ipaddress-1.0.22-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 16743 6月 19 16:10 itsdangerous-1.1.0-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 124883 6月 19 16:10 Jinja2-2.10.1-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 24348 6月 19 16:10 MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl
-rw-r--r--. 1 zhoub zhoub 590559 6月 19 16:10 Naked-0.1.31-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 31249 6月 19 16:10 netifaces-0.10.9-cp27-cp27mu-manylinux1_x86_64.whl
-rw-r--r--. 1 zhoub zhoub 158295 6月 19 16:10 pycparser-2.19.tar.gz
-rw-r--r--. 1 zhoub zhoub 62288 6月 19 16:10 pyparsing-2.4.0-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 89469 6月 19 16:10 pyudev-0.21.0.tar.gz
-rw-r--r--. 1 zhoub zhoub 274442 6月 19 16:10 PyYAML-5.1.1.tar.gz
-rw-r--r--. 1 zhoub zhoub 57952 6月 19 16:10 requests-2.22.0-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 4933 6月 19 16:10 shellescape-3.4.1-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 10586 6月 19 16:10 six-1.12.0-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 150942 6月 19 16:10 urllib3-1.25.3-py2.py3-none-any.whl
-rw-r--r--. 1 zhoub zhoub 604167 6月 19 16:10 urwid-2.0.1.tar.gz
-rw-r--r--. 1 zhoub zhoub 327611 6月 19 16:10 Werkzeug-0.15.4-py2.py3-none-any.whl

安装依赖rpms

rpm包在rpms/ceph-iscsi-rpms/目录下。

1
2
3
4
yum localinstall libnl3-3.2.28-4.el7.x86_64.rpm
yum localinstall pyOpenSSL-0.13.1-4.el7.x86_64.rpm
yum localinstall pyparsing-1.5.6-9.el7.noarch.rpm
yum localinstall python-kmod-0.9-4.el7.x86_64.rpm

安装TCMU-RUNNER

rpm包在rpms/ceph-iscsi-rpms/tcmu-runner目录下。

1
2
cd tcmu-runner
yum localinstall *.rpm

启动tcmu-runner服务

1
2
3
systemctl daemon-reload
systemctl enable tcmu-runner
systemctl start tcmu-runner

源码地址:https://github.com/open-iscsi/tcmu-runner
commit:8d8e612e50b7787c57f32f59bba7def9bb06954b

安装RTSLIB-FB

1
2
cd rtslib-fb
python setup.py install

源码地址:https://github.com/open-iscsi/rtslib-fb
commit: 03d6c7813187cd141a3a08f4b7978190187d56c1

安装CONFIGSHELL-FB

1
2
cd configshell-fb
python setup.py install

源码地址:https://github.com/open-iscsi/configshell-fb
commit: 166ba97e36d7b53e7fa53d7853a8b9f5a509503c

安装TARGETCLI-FB

1
2
3
4
cd targetcli-fb
python setup.py install
mkdir /etc/target
mkdir /var/target

源码地址:https://github.com/open-iscsi/targetcli-fb
commit: 85031ad48b011f5626cd0a287749abcaa145277b

安装CEPH-ISCSI

1
2
3
4
cd ceph-iscsi
python setup.py install --install-scripts=/usr/bin
cp ./usr/lib/systemd/system/rbd-target-gw.service /lib/systemd/system/
cp ./usr/lib/systemd/system/rbd-target-api.service /lib/systemd/system/

启动rbd-target-gwrbd-target-api服务

1
2
3
4
5
systemctl daemon-reload
systemctl enable rbd-target-gw
systemctl enable rbd-target-api
systemctl start rbd-target-gw
systemctl start rbd-target-api

源码地址:https://github.com/ceph/ceph-iscsi
commit: c5ebf29e1a0caa2e4bef44370b40fe940b66aaad

配置iscsi-gateway.cfg

创建一个/etc/ceph/iscsi-gateway.cfg文件,在文件中增加如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[config]
# Name of the Ceph storage cluster. A suitable Ceph configuration file allowing
# access to the Ceph storage cluster from the gateway node is required, if not
# colocated on an OSD node.
cluster_name = ceph

# Place a copy of the ceph cluster's admin keyring in the gateway's /etc/ceph
# drectory and reference the filename here
gateway_keyring = ceph.client.admin.keyring


# API settings.
# The API supports a number of options that allow you to tailor it to your
# local environment. If you want to run the API under https, you will need to
# create cert/key files that are compatible for each iSCSI gateway node, that is
# not locked to a specific node. SSL cert and key files *must* be called
# 'iscsi-gateway.crt' and 'iscsi-gateway.key' and placed in the '/etc/ceph/' directory
# on *each* gateway node. With the SSL files in place, you can use 'api_secure = true'
# to switch to https mode.

# To support the API, the bear minimum settings are:
api_secure = false

# Additional API configuration options are as follows, defaults shown.
# api_user = admin
# api_password = admin
# api_port = 5001
# iscsi-gw 最少需要部署两个服务
trusted_ip_list = 10.53.2.13,10.53.2.16,10.53.2.19

重启rbd-target-api服务

1
2
3
systemctl daemon-reload
systemctl stop rbd-target-api
systemctl start rbd-target-api

*** rbd-target-api 需要依赖 rbd pool,若集群中没有rbd pool ,需要手动创建。 ***

配置iscsi-target

在iscsi-gw服务所在节点上输入gwcli进入gw交互模式

创建target iqn

eg:

1
2
/> cd /iscsi-target
/iscsi-target> create iqn.2019-08.com.hna.iscsi-gw:iscsi-igw

创建iscsi-gateway

eg:

1
2
3
4
/iscsi-target> cd iqn.2019-08.com.hna.iscsi-gw:iscsi-igw/gateways
/iscsi-target...-igw/gateways> create ceph13 10.53.2.13
/iscsi-target...-igw/gateways> create ceph16 10.53.2.16
/iscsi-target...-igw/gateways> create ceph19 10.53.2.19

创建gateway时,输入的gateway名称为hostname, ip要与其对应上,并且在/etc/hosts文件中也要将hostnameip对应上。

创建rbd image

eg:

1
2
3
/iscsi-target...-igw/gateways> cd /disks
/disks> create pool=sata image=vol_sata_23t size=23T
/disks> create pool=ssd image=vol_ssd_1800g size=1800G

创建client iqn

eg:

1
2
3
4
5
6
7
8
/disks> cd /iscsi-targets/iqn.2019-08.com.hna.iscsi-gw:iscsi-igw/hosts/
/iscsi-target...csi-igw/hosts> create iqn.2019-08.com.example:2b14c50a

/disks> cd /iscsi-targets/iqn.2019-08.com.hna.iscsi-gw:iscsi-igw/hosts/
/iscsi-target...csi-igw/hosts> create iqn.2019-08.com.example:479172ae

/disks> cd /iscsi-targets/iqn.2019-08.com.hna.iscsi-gw:iscsi-igw/hosts/
/iscsi-target...csi-igw/hosts> create iqn.2019-08.com.example:066f5c54

设置chap用户名密码
eg:

1
2
3
4
5
6
7
8
/> cd iscsi-targets/iqn.2019-08.com.hna.iscsi-gw:iscsi-igw/hosts/iqn.2019-08.com.example:2b14c50a/
/iscsi-target...mple:2b14c50a> auth username=iqn.2019-08.com.example password=1qaz2wsx3edc

/> cd iscsi-targets/iqn.2019-08.com.hna.iscsi-gw:iscsi-igw/hosts/iqn.2019-08.com.example:479172ae/
/iscsi-target...mple:479172ae> auth username=iqn.2019-08.com.example password=1qaz2wsx3edc

/> cd iscsi-targets/iqn.2019-08.com.hna.iscsi-gw:iscsi-igw/hosts/iqn.2019-08.com.example:066f5c54/
/iscsi-target...mple:066f5c54> auth username=iqn.2019-08.com.example password=1qaz2wsx3edc

创建host-groups

eg:

1
2
/> cd iscsi-targets/iqn.2019-08.com.hna.iscsi-gw:iscsi-igw/host-groups/
/iscsi-target...w/host-groups> create xen

添加client iqn

eg:

1
2
3
4
/> cd iscsi-targets/iqn.2019-08.com.hna.iscsi-gw:iscsi-igw/host-groups/xen/
/iscsi-target...st-groups/xen> host add iqn.2019-08.com.example:066f5c54
/iscsi-target...st-groups/xen> host add iqn.2019-08.com.example:2b14c50a
/iscsi-target...st-groups/xen> host add iqn.2019-08.com.example:479172ae

添加rbd image

eg:

1
2
3
/> cd iscsi-targets/iqn.2019-08.com.hna.iscsi-gw:iscsi-igw/host-groups/xen/
/iscsi-target...st-groups/xen> disk add sata/vol_sata_23t
/iscsi-target...st-groups/xen> disk add ssd/vol_ssd_1800g

至此iscsi-gw的搭建及配置完成,客户端可以通过iscsi协议访问ceph存储了。

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/> ls
o- / ......................................................................................................................... [...]
o- cluster ......................................................................................................... [Clusters: 1]
| o- ceph ............................................................................................................ [HEALTH_OK]
| o- pools .......................................................................................................... [Pools: 3]
| | o- rbd ................................................................... [(x3), Commit: 0.00Y/21703946M (0%), Used: 6823b]
| | o- sata ......................................................... [(x3), Commit: 23.0T/21703946M (111%), Used: 14325219841b]
| | o- ssd ........................................................ [(x3), Commit: 1800G/1744741760K (108%), Used: 27569961653b]
| o- topology ............................................................................................... [OSDs: 24,MONs: 3]
o- disks ...................................................................................................... [25352G, Disks: 2]
| o- sata ......................................................................................................... [sata (23.0T)]
| | o- vol_sata_23t .................................................................................. [sata/vol_sata_23t (23.0T)]
| o- ssd ........................................................................................................... [ssd (1800G)]
| o- vol_ssd_1800g ................................................................................. [ssd/vol_ssd_1800g (1800G)]
o- iscsi-targets ............................................................................... [DiscoveryAuth: None, Targets: 1]
o- iqn.2019-08.com.hna.iscsi-gw:iscsi-igw ........................................................................ [Gateways: 3]
o- disks .......................................................................................................... [Disks: 2]
| o- sata/vol_sata_23t ....................................................................................... [Owner: ceph13]
| o- ssd/vol_ssd_1800g ....................................................................................... [Owner: ceph19]
o- gateways ............................................................................................ [Up: 3/3, Portals: 3]
| o- ceph13 ................................................................................................ [10.53.2.13 (UP)]
| o- ceph16 ................................................................................................ [10.53.2.16 (UP)]
| o- ceph19 ................................................................................................ [10.53.2.19 (UP)]
o- host-groups .................................................................................................. [Groups : 1]
| o- xen ................................................................................................ [Hosts: 3, Disks: 2]
| o- iqn.2019-08.com.example:066f5c54 ............................................................................... [host]
| o- iqn.2019-08.com.example:2b14c50a ............................................................................... [host]
| o- iqn.2019-08.com.example:479172ae ............................................................................... [host]
| o- sata/vol_sata_23t .............................................................................................. [disk]
| o- ssd/vol_ssd_1800g .............................................................................................. [disk]
o- hosts .............................................................................................. [Hosts: 3: Auth: CHAP]
o- iqn.2019-08.com.example:2b14c50a .............................................. [LOGGED-IN, Auth: CHAP, Disks: 2(25352G)]
| o- lun 0 ....................................................................... [sata/vol_sata_23t(23.0T), Owner: ceph13]
| o- lun 1 ....................................................................... [ssd/vol_ssd_1800g(1800G), Owner: ceph19]
o- iqn.2019-08.com.example:479172ae .............................................. [LOGGED-IN, Auth: CHAP, Disks: 2(25352G)]
| o- lun 0 ....................................................................... [sata/vol_sata_23t(23.0T), Owner: ceph13]
| o- lun 1 ....................................................................... [ssd/vol_ssd_1800g(1800G), Owner: ceph19]
o- iqn.2019-08.com.example:066f5c54 .............................................. [LOGGED-IN, Auth: CHAP, Disks: 2(25352G)]
o- lun 0 ....................................................................... [sata/vol_sata_23t(23.0T), Owner: ceph13]
o- lun 1 ....................................................................... [ssd/vol_ssd_1800g(1800G), Owner: ceph19]

参考&鸣谢

部署创建

可以使用ceph-osd命令单独创建初始化OSD日志

1
ceph-osd -i {osd-id} --mkjournal

默认在/var/lib/ceph/osd/ceph-{osd.id}目录下的journal文件中初始化日志数据;也可以通过-c制定配置文件ceph.conf,再在配置文件中制定OSD日志所在路径。

配置

osd journal

journal所在的位置,默认为/var/lib/ceph/osd/{cluster}-{osd.id}/jouranl。可以是一个文件也可以是一个块设备。

osd jouranl size

日志的大小,不同版本其默认值也不一样。通常根据OSD盘的性能来设置其大小。
osd journal size = 2 * 写入带宽 * filestore max sync interval 写入带宽一般是固定的,所以可以根据filestore max sync interval来计算journal的大小。或者根据journal大小来设置filestore max sync interval

更换迁移

更换Journal盘有2种情况

Journal盘挂了

只能删除对应的OSD重新创建

Journal还在运行

  1. 设置noout标记
  2. 停止关联的OSD
  3. 下刷 journal 到 osd ceph-osd -i {osd.io} --flush-journal
  4. 删除旧的 journal 链接
  5. 重建journal 链接
  6. 重建 journal ceph-osd -i {osd.id} --mkjournal
  7. 启动关联的OSD
  8. 去除noout标记

参考&鸣谢