由于公司需要对ceph如何部署RDMA(IB卡)+nvmf+存储后端裸盘用uio驱动进行了一些性能相关研究
monitor:Ceph监视器(ceph-mon)维护集群状态的映射,包括监视器映射、管理器映射、OSD映射、MDS映射和CRUSH映射。这些映射是Ceph守护进程相互协调所需的关键集群状态。监视器还负责管理守护进程和客户端之间的身份验证。通常需要至少三个监视器以实现冗余和高可用性。
manager:Ceph管理器守护进程(ceph-mgr)负责跟踪运行时指标和Ceph集群的当前状态,包括存储利用率、当前性能指标和系统负载。Ceph管理器守护进程还托管基于Python的模块来管理和公开Ceph集群信息,包括基于Web的Ceph仪表板和REST API。通常需要至少两个管理器以实现高可用性。
Ceph OSD:对象存储守护进程(Ceph OSD,ceph-osd)存储数据,处理数据复制、恢复、重新平衡,并通过检查其他Ceph OSD守护进程的心跳向Ceph监视器和管理器提供一些监视信息。通常需要至少三个Ceph OSD以实现冗余和高可用性。
MDS:Ceph元数据服务器(MDS,ceph-mds)代表CephFS文件系统存储元数据(即,Ceph块设备和Ceph对象存储不使用MDS)。Ceph元数据服务器允许POSIX文件系统用户执行基本命令(如ls、find等),而不会对Ceph存储集群造成巨大负担。
前后端分离
public network:前端发读/写/查询集群状态等请求用
cluster network:后端数据复制、心跳等用
无论是用rbd,cephFs,ceph对象存储,最终ceph都是通过object存储
ceph OSD守护进程将数据存储为对象,这些对象位于一个平坦的命名空间中(例如,没有目录层级结构)。每个对象都有一个唯一的标识符,并包含二进制数据和元数据,元数据由kv组成)
objID唯一
Ceph去除了中心化的网关(主要是用CRUSH去中心化),使客户端可以直接与Ceph OSD守护进程交互。 Ceph OSD守护进程在其他Ceph节点上创建对象副本,以确保数据的安全性和高可用性。
默认是cephx,可以对接LDAP
客户端将对象写入主OSD中标识的pg。然后,拥有自己CRUSH映射副本的主OSD将识别出用于复制目的的辅助和三次OSD,并将对象复制到辅助和三次OSD中适当的pg中(如果需要额外的副本,则复制到多个OSD中),并在确认对象成功存储后向客户端做出响应。
其实跟我们的chunk差不多的,只不过这个映射我们fe做的,他们是mon做的
ceph支持块存储、对象存储、分布式文件系统
primary OSD 当作leader去写
基于以上信息,由于要部署能够支持rdma+nvmf并且bluestore用spdk uio驱动的ceph
坑1:使用官方镜像部署不支持裸设备 #E91E63
希望有现成的能够快速部署ceph集群,拉了官方镜像有很多问题:
官方镜像拉ceph集群的逻辑
mon,mgr,osd,mds等需要依赖的组件各自运行在一个容器里,通过网络交互
坑3:准备和zstorage保持一致,用的centos 7 并且用最新的ceph代码 #F44336
基于以上理由最终重来了,基本使用的版本如下:
安装完镜像后修改bashrc,后面会解释为什么要这么做
export LD_LIBRARY_PATH=¥LD_LIBRARY_PATH:/usr/local/lib
ulimit -n 123456
ulimit -l unlimited
export RDMAV_FORK_SAFE="yes"
export RDMAV_HUGEPAGES_SAFE=
docker pull ubuntu:20.04
# 这里之所以不用init.d,是因为容器里的systemd和ib卡驱动有冲突,由于驱动报错短时间看不出来,所以放弃了系统服务的方案,后面会说
docker run -itd --privileged=true -h cephnode --name cephnode --net=host--restart=always -v /dev:/dev -v /root/docker/cephnode:/root/share ubuntu:20.04 bash
# 进入容器后安装一些必要依赖比如git、cmake、build-essential等
# 第一步编译ceph
git clone https://github.com/ceph/ceph/tree/v18.0.0
# 下载子模块所有依赖
git submodule update --init --recursive
# 安装所有子模块依赖
./install-deps.sh
# 生成makefile文件,注意这里要打开spdk选项,不然初始化bluestore的时候osd不能用uio
# -DCMAKE_BUILD_TYPE=RelWithDebInfo 开启性能模式
./do_cmake.sh -DWITH_SPDK=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo
cd build && ninjia -j 32
坑4:编译报错 #E91E63
前面ceph安装的时候会下载东西,下载一些依赖库可能会出现如下报错:
GnuTLS recv error (-110): The TLS connection was non-properly terminated.
# git clone 报错 GnuTLS recv error (-110): The TLS connection was non-properly terminated.
apt-get install gnutls-bin
git config --global http.sslVerify false
git config --global http.postBuffer 1048576000
# 域名解析失败的话需要配置/etc/hosts
140.82.113.4 github.com
坑5:如果调用ceph do_cmake.sh打开spdk,后面会出现手动初始化osd的时候,不会走spdk,创建bdev,配置没生效 #E91E63
文档里没说看代码才发现的
// ceph osd 格式化创建bdev的时候有一个默认顺序,如果编译的时候所有支持模式都打开的情况下,优先走spdk_bdev
BlockDevice::detect_device_type(const std::string& path)
{
#if defined(HAVE_SPDK)
if (NVMEDevice::support(path)) {
return block_device_t::spdk;
}
#endif
#if defined(HAVE_BLUESTORE_PMEM)
if (PMEMDevice::support(path)) {
return block_device_t::pmem;
}
#endif
#if (defined(HAVE_LIBAIO) || defined(HAVE_POSIXAIO)) && defined(HAVE_LIBZBD)
if (HMSMRDevice::support(path)) {
return block_device_t::hm_smr;
}
#endif
#if defined(HAVE_LIBAIO) || defined(HAVE_POSIXAIO)
return block_device_t::aio;
#else
return block_device_t::unknown;
#endif
}
BlockDevice *BlockDevice::create(
CephContext* cct, const string& path, aio_callback_t cb,
void *cbpriv, aio_callback_t d_cb, void *d_cbpriv)
{
const string blk_dev_name = cct->_conf.get_val("bdev_type");
block_device_t device_type = block_device_t::unknown;
if (blk_dev_name.empty()) {
device_type = detect_device_type(path);
} else {
device_type = device_type_from_name(blk_dev_name);
}
return create_with_type(device_type, cct, path, cb, cbpriv, d_cb, d_cbpriv);
}
# HAVE_SPDK 这个宏编译加了-DWITH_SPDK=ON才会设置
# cmake 部分代码
if(WITH_SPDK)
if(NOT WITH_BLUESTORE)
message(SEND_ERROR "Please enable WITH_BLUESTORE for using SPDK")
endif()
include(BuildSPDK)
build_spdk()
set(HAVE_SPDK TRUE)
endif(WITH_SPDK)
git clone https://github.com/ceph/ceph-nvmeof
# 安装grpc的依赖
make setup
# 编译安装grpc相关依赖,主要是生成python控制文件control目录和脚本
make grpc
# 编译spdk要使用rdma要加上对应参数(文档没写)
cd spdk && ./scripts/pkgdep.sh --rdma
# 生成makefile等编译依赖
./configure --with-rdma --with-rbd
make -j 32
配置文件参考解释:
配置ceph-nvmeof.conf
#
# Copyright (c) 2021 International Business Machines
# All rights reserved.
#
# SPDX-License-Identifier: LGPL-3.0-or-later
#
# Authors: [email protected], [email protected]
#
[gateway]
name = nn #随便
group = gg #随便
addr = 1.1.1.2 #本节点IB卡ip
port = 5001
enable_auth = False #不打开认证
[ceph]
pool = pool01 #ceph rbd pool 要是真实的pool
config_file = /etc/ceph/ceph.conf
[spdk]
spdk_path = /root/ceph-nvmeof #项目绝对路径
tgt_path = spdk/build/bin/nvmf_tgt #spdk target相对路径,记住一定要相对路径,他用的时候会拼spdk_path用,很奇怪
rpc_socket = /var/tmp/spdk.sock
timeout = 60.0
log_level = ERROR
# conn_retries = 10
tgt_cmd_extra_args =
transports = RDMA #也可以选tcp,选RDMA需要spdk有IB驱动支持
# transport_tcp_options = {"max_queue_depth" : 16, "max_io_size" : 4194304, "io_unit_size" : 1048576, "zcopy" : false}
坑6:spdk就算指定用rmda,如果没有ib驱动的话,默认不会编译带rdma的版本 #E91E63
解决:参照FE文档,让容器也可以用ib驱动,注意里外版本要一致
坑7:容器里的系统服务systemd和ib驱动有冲突,ib卡会一直反复挂 #E91E63
暂时没法解决,选择绕过
停止容器里的systemd,选择看他的.service文件里cpeh依赖的服务cmd,手动用nohup + &后台起ceph依赖的服务后面怎么起ceph集群会详细介绍
nohup ceph-osd -f --cluster ceph --id 2 &
基本上按照文档来就行,有小部分坑:
https://docs.ceph.com/en/latest/install/index_manual/#install-manual
ceph.conf配置文件
[global]
fsid = a58f18c5-d1cd-4f75-afc7-3ee62c72c7d3 #集群唯一id
mon_initial_members = cephnode2 # ceph mon的hostname
mon_host = 1.1.1.2,1.1.1.3,1.1.1.4 # mon ip
auth_cluster_required = none #ceph认证这里很坑,如果配了就要注意同步keyring很麻烦
auth_service_required = none
auth_client_required = none
mon_max_pg_per_osd = 800 #这里默认256,1024pg这里会warning
osd max pg per osd hard ratio = 10 # < default is 2, try to set at least 5. It will be
mon allow pool delete = true # without it you can't remove a pool
cluster network = 1.1.2.0/24 # 控制网络
public network = 1.1.1.0/24 # 集群网络,用于节点间通信,心跳,副本复制等操作
mon_data_avail_crit = 1
osd_class_dir = /usr/local/lib/rados-classes # 这个必须配置,rbd命令会去默认路径下找动态库,手动安装需要配置这个路径,不然创建image就会报错找不到文件
log_file=/var/log/ceph/1.log # 日志路径,默认不打开
log_level=1 # 日志级别 ceph日志级别比较多,1就是所有都开
bluestore_block_db_create = false
bluestore_block_db_path = ""
bluestore_block_db_size = 0
bluestore_block_wal_create = false
bluestore_block_wal_path = ""
bluestore_block_wal_size = 0
#bluestore_block_path=spdk:0000:31:00.0
#bluestore_block_path=spdk:0000:32:00.0
#bluestore_block_path=spdk:0000:65:00.0
#bluestore_block_path=spdk:0000:66:00.0
#bluestore_block_path=spdk:0000:e3:00.0
#bluestore_block_path=spdk:0000:e4:00.0
# RDMA conf
ms_type = async+rdma # rdma配置
ms_async_rdma_device_name = mlx5_0 #指定cluster net的device name
ms_async_rdma_polling_us = 0
ms_async_rdma_local_gid=fe80:0000:0000:0000:043f:7203:00da:2628 #官网提供的脚本拿得一个信息,类似于机器id身份标识
[osd]
bluestore = true
# 使用uio就需要手动解绑内核驱动并且挂在uio上
# 每个osd要单独配置,手动起
[osd.0]
host = cephnode2
osd data = /var/lib/ceph/osd/ceph-0/
bluestore_block_path = spdk:trtype:PCIe traddr:0000:31:00.0
[osd.1]
host = cephnode2
osd data = /var/lib/ceph/osd/ceph-1/
bluestore_block_path = spdk:trtype:PCIe traddr:0000:32:00.0
[osd.2]
host = cephnode2
osd data = /var/lib/ceph/osd/ceph-2/
bluestore_block_path = spdk:trtype:PCIe traddr:0000:65:00.0
[osd.3]
host = cephnode2
osd data = /var/lib/ceph/osd/ceph-3/
bluestore_block_path = spdk:trtype:PCIe traddr:0000:66:00.0
[osd.4]
host = cephnode2
osd data = /var/lib/ceph/osd/ceph-4/
bluestore_block_path = spdk:trtype:PCIe traddr:0000:e3:00.0
[osd.5]
host = cephnode2
osd data = /var/lib/ceph/osd/ceph-5/
bluestore_block_path = spdk:trtype:PCIe traddr:0000:e4:00.0
mon:是集群启动的关键
# 首先要关闭认证,否则mkfs会失败
monmaptool --create --add cephnode2 1.1.1.2 --fsid a58f18c5-d1cd-4f75-afc7-3ee62c72c7d3 /tmp/monmap
# mkdir /var/lib/ceph/mon/{cluster-name}-{hostname} 这里cluster name默认是ceph,hostname
mkdir /var/lib/ceph/mon/ceph-cephnode2
ceph-mon --mkfs -i mon-node1 --monmap /tmp/monmap # --keyring /tmp/ceph.mon.keyring 如果配置了认证就要指定keyring
# 如果上一步成功了,没报啥错就可以直接手动起了
# 正常来说:systemctl start ceph-mon@cephnode2
nohup ceph-mon -f --cluster ceph --id cephnode2 &
# 启动 ceph -s 可即可观察到集群信息
坑8:ceph认证默认是cephx,一定要手动关闭 #E91E63
ceph支持多种认证方式,cephx、ldap等,作用是集群的敏感操作要配置权限不是所有人都能修改集群信息的(可以自定义不同用户可读可写啥的)
解决方法:
auth_cluster_required = none #ceph认证这里很坑,如果配了就要注意同步keyring很麻烦
auth_service_required = none
auth_client_required = none
坑9:之前开了认证,修改配置文件关闭了认证,但是没删keyring还是会走认证报错 #E91E63
报错entity osd.0 exists but key does not match
解决方法:如果一开始就关了认证就没问题,中途关闭认证要同时删除/var/lib/ceph/mon/* /var/lib/ceph/osd/* /etc/ceph/目录下的keyring文件
坑10:可能会出现报错找不到ceph用户 #E91E63
我没搞懂为什么跟用户组有关系,实际上在容器里都是root
解决方案:创建一个空的ceph用户就行
useradd ceph
坑11:所有命令都很慢 #E91E63
没有设置进程可以打开的最大句柄数,导致执行shell命令打开关闭很多空的句柄
内核bug:https://bugzilla.redhat.com/show_bug.cgi?id=1537564
解决:ulimit -l 123456设置小一点,写到
坑12:使用rdma后由于用了系统大页,ulimit -n要设置成无限制 #E91E63
解决:ulimit -n unlimited
坑13:系统默认链接路径没有ceph默认编译的目录 #E91E63
解决:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
基本上同mon启动一样的逻辑
# 以创建osd0为例
mkdir /var/lib/ceph/osd/ceph-0
ceph-osd --mkfs -i 0 --osd-data /var/lib/ceph/osd/ceph-0 # --debug_ms 1 要是报错可以加这个参数打开日志调试
# 启动
nohup ceph-osd -f --cluster ceph --id 0 &
坑14:ceph osd依赖动态库librados.so找不到导致报错、osd初始化失败 #E91E63
// 报错信息如下
2021-03-16 01:26:59.013 7f6c6dff3640 10 _load_class rbd from /usr/local/lib64/rados-classes/libcls_rbd.so
2021-03-16 01:26:59.013 7f6c6dff3640 0 _load_class could not stat class /usr/local/lib64/rados-classes/libcls_rbd.so: (2) No such file or directory
2021-03-16 01:26:59.013 7f6c6dff3640 -1 osd.3 112 class rbd open got (2) No such file or directory
2021-03-16 01:26:59.013 7f6c6dff3640 1 -- [...] --> [...] -- osd_op_reply(5 rbd_directory [call rbd.dir_add_image] v0'0 uv0 ondisk = -95 ((95) Operation not supported)) v8 -- 0x7f6c6800fed0 con 0x7f6cb80100c0
解决:手动指定osd_class_dir路径
osd_class_dir = /usr/local/lib/rados-classes
坑15:ceph conf uio路径的格式问题 #E91E63
刚开始填的spdk:0000:01:00.0,报错找不到osd type
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BhohQKgs-1681974946674)(./images/1681896565341.png)]
# ctrler probe 报错找不到类型
int NVMEManager::try_get(const spdk_nvme_transport_id& trid, SharedDriverData **driver)
{
std::lock_guard l(lock);
for (auto &&it : shared_driver_datas) {
if (it->is_equal(trid)) {
*driver = it;
return 0;
}
}
auto coremask_arg = g_conf().get_val("bluestore_spdk_coremask");
int m_core_arg = find_first_bitset(coremask_arg);
// at least one core is needed for using spdk
if (m_core_arg <= 0) {
derr << __func__ << " invalid bluestore_spdk_coremask, "
<< "at least one core is needed" << dendl;
return -ENOENT;
}
m_core_arg -= 1;
uint32_t mem_size_arg = (uint32_t)g_conf().get_val("bluestore_spdk_mem");
if (!dpdk_thread.joinable()) {
dpdk_thread = std::thread(
[this, coremask_arg, m_core_arg, mem_size_arg, trid]() {
struct spdk_env_opts opts;
struct spdk_pci_addr addr;
int r;
bool local_pci_device = false;
int rc = spdk_pci_addr_parse(&addr, trid.traddr);
if (!rc) {
local_pci_device = true;
opts.pci_whitelist = &addr;
opts.num_pci_addr = 1;
}
spdk_env_opts_init(&opts);
opts.name = "nvme-device-manager";
opts.core_mask = coremask_arg.c_str();
opts.master_core = m_core_arg;
opts.mem_size = mem_size_arg;
spdk_env_init(&opts);
spdk_unaffinitize_thread();
std::unique_lock l(probe_queue_lock);
while (!stopping) {
if (!probe_queue.empty()) {
ProbeContext* ctxt = probe_queue.front();
probe_queue.pop_front();
r = spdk_nvme_probe(local_pci_device ? NULL : &trid, ctxt, probe_cb, attach_cb, NULL);
if (r < 0) {
ceph_assert(!ctxt->driver);
derr << __func__ << " device probe nvme failed" << dendl;
}
ctxt->done = true;
probe_queue_cond.notify_all();
} else {
probe_queue_cond.wait(l);
}
}
for (auto p : probe_queue)
p->done = true;
probe_queue_cond.notify_all();
}
);
}
static bool probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, struct spdk_nvme_ctrlr_opts *opts)
{
NVMEManager::ProbeContext *ctx = static_cast(cb_ctx);
bool do_attach = false;
if (trid->trtype == SPDK_NVME_TRANSPORT_PCIE) {
do_attach = spdk_nvme_transport_id_compare(&ctx->trid, trid) == 0;
if (!do_attach) {
dout(0) << __func__ << " device traddr (" << ctx->trid.traddr
<< ") not match " << trid->traddr << dendl;
}
} else {
// for non-pcie devices, should always match the specified trid
assert(!spdk_nvme_transport_id_compare(&ctx->trid, trid));
do_attach = true;
}
if (do_attach) {
dout(0) << __func__ << " found device at: "
<< "trtype=" << spdk_nvme_transport_id_trtype_str(trid->trtype) << ", "
<< "traddr=" << trid->traddr << dendl;
opts->io_queue_size = UINT16_MAX;
opts->io_queue_requests = UINT16_MAX;
opts->keep_alive_timeout_ms = nvme_ctrlr_keep_alive_timeout_in_ms;
}
return do_attach;
}
解决方案:在path前面加上spdk:trtype:PCIe
# 创pool
ceph osd pool create pool01 1024 1024
# 创image
for i in $(seq 1 12); do rbd create img$i --size 800G --pool pool01 --image-format 2 --image-feature layering; done
# 关闭校验和
ceph osd set noscrub
ceph osd set nodeep-scrub
# 注意需要提前配置好系统大页
# 启动nvmf网关
nohup python3 -m control &
# 基于rbd image创建 spdk bdev
python3 -m control.cli create_bdev -i lun1 -p pool01 -b Ceph0
# 输出结果:INFO:root:Created bdev Ceph0: True
# 初始化nvme 子系统
python3 -m control.cli create_subsystem -n nqn.2016-06.io.spdk:cnode1 -s SPDK00000000000001
# 输出结果:INFO:root:Created subsystem nqn.2016-06.io.spdk:cnode1: True
# 添加名字空间(类似于盘)
python3 -m control.cli add_namespace -n nqn.2016-06.io.spdk:cnode1 -b Ceph0
# 输出结果:INFO:root:Added namespace 1 to nqn.2016-06.io.spdk:cnode1: True
# 设置host访问权限(无限制)
python3 -m control.cli add_host -n nqn.2016-06.io.spdk:cnode1 -t '*'
# 输出结果:INFO:root:Allowed open host access to nqn.2016-06.io.spdk:cnode1: True
# 添加listener
python3 -m control.cli create_listener -n nqn.2016-06.io.spdk:cnode1 -s 5001 -t rdma
# 输出结果:INFO:root:Created nqn.2016-06.io.spdk:cnode1 listener: True
坑16:基于rbd image创建spdk bdev assert #E91E63
// 报错信息/src/msg/async/rdma/Infiniband.cc: 1027: ceph_abort_msg("abort() called")
//On RDMA MUST be called before fork
rc = ibv_fork_init();
if (rc) {
lderr(cct) << __func__ << " failed to call ibv_for_init(). On RDMA must be called before fork. Application aborts." << dendl;
ceph_abort();
}
// 报错代码如上
解决方案:注释ibv_fork_init(),按照官方文档增加两个环境变量或者提前ibv_fork_init()
参考文档:https://www.rdmamojo.com/2012/05/24/ibv_fork_init/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8rkkt8Wn-1681974946674)(./images/1681958907386.png)]
# 最后确认是ib卡初始化晚了,fork之前没准备好
# 解决注释ibv_fork_init,在bashrc里面设置两个环境变量
export RDMAV_FORK_SAFE="yes"
export RDMAV_HUGEPAGES_SAFE=
坑17: ceph-nvmf项目没有用上多核优势 #E91E63
解决:改项目启动spdk target脚本里面cpu mask写死了,需要修改代码
# Start target
tgt_path = self.config.get("spdk", "tgt_path")
spdk_rpc_socket = self.config.get("spdk", "rpc_socket")
spdk_tgt_cmd_extra_args = self.config.get("spdk",
"tgt_cmd_extra_args")
spdk_cmd = os.path.join(spdk_path, tgt_path)
# cmd = [spdk_cmd, "-u", "-r", spdk_rpc_socket] 这里原来默认没有配置cpu mask
cmd = [spdk_cmd, "-u", "-m", "0x222200002222", "-r" , spdk_rpc_socket] # 配置成跟zstorage一样的
if spdk_tgt_cmd_extra_args:
cmd += shlex.split(spdk_tgt_cmd_extra_args)
self.logger.info(f"Starting {' '.join(cmd)}")
try:
self.spdk_process = subprocess.Popen(cmd,
preexec_fn=set_pdeathsig(
signal.SIGTERM))
nvme discover -t rdma -a 1.1.1.2 -s 5001
nvme connect -t rdma --traddr 1.1.1.2 -s 5001 -n nqn.2016-06.io.spdk:cnode1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1vrDoFe0-1681974946674)(./images/1681956302185.png)]
坑18:nvme配置的多路径实际上没用,导致下IO的时候都走的一个节点,导致性能很差 #E91E63
解决办法:在不同的节点上创建spdk bdev初始化subsys和listener,host均匀使用三个节点创建出来的卷
性能拉了,一般来说是硬件到了瓶颈或者负载不均衡
对于因特尔架构