暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

ceph-bluestore-tool工具实践及源代码解析

Ceph开源社区 2020-11-13
1829

为什么会有这篇文章

笔者之前在一个集群里遇到osd
 crash的问题,google
之后都是推荐使用ceph-bluestore-tool
工具来进行运维操作。刚开始对这个工具不是很熟悉(主要也是对Ceph BlueStore结构理解不够深入),之后索性对这个工具从源码出发,好好探究下它的执行逻辑。经过以下的庖丁解牛之后, 对Ceph BlueStore的内在逻辑有了相对比较深刻的理解。限于笔者水平,难免有理解和描述上有疏漏或者错误的地方,欢迎共同交流 。

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是可分配的最小单位)

说明:
1.对象数据存储部分即osd指定的
data
设备也就是slow设备,可以是裸盘分区,或者lvm卷(下同)
2.RocksDB日志即osd指定的
wal
设备
3.RocksDB数据部分即osd指定的
db
设备
4.以上设备可以共用同一物理盘设备,也可以分开在不同的物理设备,这充分体现了ceph的灵活性

Ceph的版本

ceph version 14.2.4 (75f4de193b3ea58512f204623e6c5a16e6c1e1ba) nautilus (stable)

#   ceph-bluestore-tool  -h
All options:

Options:
-h [ --help ] produce help message
--path arg bluestore path
--out-dir arg output directory
-l [ --log-file ] arg log file
--log-level arg log level (30=most, 20=lots, 10=some, 1=little)
--dev arg device(s)
--devs-source arg bluefs-dev-migrate source device(s)
--dev-target arg target/resulting device
--deep arg deep fsck (read all data)
-k [ --key ] arg label metadata key name
-v [ --value ] arg label metadata value

Positional options:
--command arg fsck, repair, bluefs-export, bluefs-bdev-sizes,
bluefs-bdev-expand, bluefs-bdev-new-db,
bluefs-bdev-new-wal, bluefs-bdev-migrate, show-label,
set-label-key, rm-label-key, prime-osd-dir,
bluefs-log-dump

ceph-bluestore-tool
工具的源代码位于src/os/bluestore/bluestore_tool.cc

原文件里相关的函数列表如下所示:



以上功能函数都是见明知义的:

add_devices
: 添加设备

find_device_path
:寻找设备路径

Inferring_bluefs_devices
:推测bluefs
的设备

log_dump
:日志导出

main
:主入口

open_bluefs
:打开bluefs
文件系统

parse_devices
:构建设备路径和识别id的映射关系及设备标志

usage
:使用帮助

validate_path
:验证路径是否可用

ceph-bluestore-tool
主要的执行流程如下:

1.校验相关的CLI命令参数

2.通过辅助函数(如add_devices、find_devices、open_bluefs等)创建bluefs文件系统实例对象fs

3.通过--command arg
指定的action
动作进行匹配,基于fs对象进行相关的操作

main入口

int main(int argc, char **argv)
{
string out_dir;
vector<string> devs;
string path;
string action;
string log_file;
string key, value;
int log_level = 30;
bool fsck_deep = false;

....<省略> ....

### 命令行参数定义及解析 ####
...
if (vm.count("help")) {
usage(po_all);
exit(EXIT_SUCCESS);
}
if (action.empty()) {
cerr << "must specify an action; --help for help" << std::endl;
exit(EXIT_FAILURE);
}
... <省略> ....

## 参数校验 ##
if (action == "bluefs-bdev-sizes" || action == "bluefs-bdev-expand") {
if (path.empty()) {
cerr << "must specify bluestore path" << std::endl;
exit(EXIT_FAILURE);
}
inferring_bluefs_devices(devs, path); ##设备构建 ##
}
... <省略> ....
## 初始化Ceph的CephContext实例对象,解析本地的Ceph配置文件,它代表了当前环境的Ceph全局的“可视”对象 //自造的一个词儿:)
auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT,
CODE_ENVIRONMENT_UTILITY,
CINIT_FLAG_NO_DEFAULT_CONFIG_FILE);

common_init_finish(cct.get());

// action动作的匹配,然后进行相对的操作(具体见下文)
...<省略>...

下面是关于CephContext
的官方说明。

=============
CephContext
=============
A CephContext represents a single view of the Ceph cluster. It comes complete
with a configuration, a set of performance counters (PerfCounters), and a
heartbeat map. You can find more information about CephContext in
src/common/ceph_context.h.

Generally, you will have only one CephContext in your application, called
g_ceph_context. However, in library code, it is possible that the library user
will initialize multiple CephContexts. For example, this would happen if he
called rados_create more than once.

A ceph context is required to issue log messages. Why is this? Well, without
the CephContext, we would not know which log messages were disabled and which
were enabled. The dout() macro implicitly references g_ceph_context, so it
can't be used in library code. It is fine to use dout and derr in daemons, but
in library code, you must use ldout and lderr, and pass in your own CephContext
object. The compiler will enforce this restriction.

辅助功能函数解析

inferring_bluefs_devices 解读

inferring_bluefs_devices
主要用来构建db
wal
slow
设备的路径(填充到devs
这个容器变量里),如果没有独立wal/db设备(在OSD创建的时候),则wal
db
会共用slow
设备(即OSD的对象数据实际存储的设备)

void  (vector<string>& devs, std::string& path)
{
cout << "inferring bluefs devices from bluestore path" << std::endl;
for (auto fn : {"block", "block.wal", "block.db"}) { //devs是个顺序容器,第一个为即block设备,下文使用devs.front()取到的即为block
string p = path + "/" + fn; ## 拼接设备路径 ##
struct stat st;
if (::stat(p.c_str(), &st) == 0) { ## 判断是存在实际指向的设备路径 (man 2 stat 可知, 返回值为0表示成功) ##

devs.push_back(p); ## 如果存在,放在devs这个容器变量结构中 ##
}
}
}

下面示例中没有独立的wal、db设备,所以wal
db
都包含在slow设备中了,即/dev/ceph-c7590218-d31d-4b95-9ec9-16c4ee38812b/osd-block-e8164817-4103-47e6-b451-37d30d9785f8
指向的设备

# ll var/lib/ceph/osd/ceph-0
total 52
-rw-r--r-- 1 ceph ceph 499 Dec 4 2019 activate.monmap
lrwxrwxrwx 1 ceph ceph 93 Dec 4 2019 block -> dev/ceph-c7590218-d31d-4b95-9ec9-16c4ee38812b/osd-block-e8164817-4103-47e6-b451-37d30d9785f8
-rw-r--r-- 1 ceph ceph 2 Dec 4 2019 bluefs
-rw-r--r-- 1 ceph ceph 37 Oct 20 17:09 ceph_fsid
-rw-r--r-- 1 ceph ceph 37 Oct 20 17:09 fsid
-rw------- 1 ceph ceph 55 Oct 20 17:09 keyring
-rw-r--r-- 1 ceph ceph 8 Dec 4 2019 kv_backend
-rw-r--r-- 1 ceph ceph 21 Dec 4 2019 magic
-rw-r--r-- 1 ceph ceph 4 Dec 4 2019 mkfs_done
-rw-r--r-- 1 ceph ceph 41 Dec 4 2019 osd_key
-rw-r--r-- 1 ceph ceph 6 Oct 20 17:09 ready
-rw-r--r-- 1 ceph ceph 3 Dec 4 2019 require_osd_release
-rw-r--r-- 1 ceph ceph 10 Oct 20 17:09 type
-rw-r--r-- 1 ceph ceph 2 Oct 20 17:09 whoami

以下是ceph-volume初始化OSD时指定了独立的wal/db/slow设备

# ll  var/lib/ceph/osd/ceph-0
total 24
lrwxrwxrwx 1 ceph ceph 14 Oct 26 18:05 block -> dev/slow/slow
lrwxrwxrwx 1 ceph ceph 10 Oct 26 18:05 block.db -> dev/db/db
lrwxrwxrwx 1 ceph ceph 12 Oct 26 18:05 block.wal -> dev/wal/wal
-rw------- 1 ceph ceph 37 Oct 26 18:05 ceph_fsid
-rw------- 1 ceph ceph 37 Oct 26 18:05 fsid
-rw------- 1 ceph ceph 55 Oct 26 18:05 keyring
-rw------- 1 ceph ceph 6 Oct 26 18:05 ready
-rw------- 1 ceph ceph 10 Oct 26 18:05 type
-rw------- 1 ceph ceph 2 Oct 26 18:05 whoami

parse_devices 解读

parse_devices
主要用来获取设备和id的映射关系(代码里的got结构),以及设置相关设备的标志

//以下代码有删减,只保留核心逻辑
void parse_devices(
CephContext *cct,
const vector<string>& devs,
map<string, int>* got,
bool* has_db,
bool* has_wal)
{
string main;
//初始标志
bool was_db = false;
if (has_wal) {
*has_wal = false;
}
if (has_db) {
*has_db = false;
}
//遍历设备的label(下文有讲解)
for (auto& d : devs) { ## devs变量已经经过 inferring_bluefs_devices 函数处理,存有设备的路径地址 ##
bluestore_bdev_label_t label;
// 获取设备的label信息
int r = BlueStore::_read_bdev_label(cct, d, &label);

int id = -1;
// 根据label的description字段,判断是什么设备,并存储在got的map结构里
//如【<'/var/lib/ceph/osd/ceph-0/block',1>】,这个是只有一个设备(wal和db共享slow)
// 又或者如:【<'/var/lib/ceph/osd/ceph-0/block.wal’,0>,<'/var/lib/ceph/osd/ceph-0/block.db',1>,<'/var/lib/ceph/osd/ceph-0/block',2>】
if (label.description == "main")
main = d;
else if (label.description == "bluefs db") {
id = BlueFS::BDEV_DB;
//设置db标志
was_db = true;
if (has_db) {
*has_db = true;
}
}
else if (label.description == "bluefs wal") {
id = BlueFS::BDEV_WAL;
//设置wal标志
if (has_wal) {
*has_wal = true;
}
}
if (id >= 0) {
got->emplace(d, id);
}
}
//如果有独立的db,则添加<'.../block',2>,否则设置为<'.../block',1>
if (main.length()) {
int id = was_db ? BlueFS::BDEV_SLOW : BlueFS::BDEV_DB;
got->emplace(main, id);
}
}

关于BDEV_WAL
BDEV_SLOW
BDEV_SLOW
等的定义(BlueFS.h
)

class BlueFS {
public:
CephContext* cct;

### 设备标识符的定义 ###
static constexpr unsigned MAX_BDEV = 5;
static constexpr unsigned BDEV_WAL = 0;
static constexpr unsigned BDEV_DB = 1;
static constexpr unsigned BDEV_SLOW = 2;
static constexpr unsigned BDEV_NEWWAL = 3;
static constexpr unsigned BDEV_NEWDB = 4;
...

add_devices解读

add_devices
主要作用就是把设备(包括slow/db/wal,如果存在的话)添加到bluefs文件系统的实例fs
中去

void add_devices(
BlueFS *fs,
CephContext *cct,
const vector<string>& devs)
{
map<string, int> got;
parse_devices(cct, devs, &got, nullptr, nullptr);
for(auto e : got) {
char target_path[PATH_MAX] = "";
// 取设备所在最终路径,如 dev/dm-3
if(!e.first.empty()) {
if (realpath(e.first.c_str(), target_path) == nullptr) {
cerr << "failed to retrieve absolute path for " << e.first
<< ": " << cpp_strerror(errno)
<< std::endl;
}
}

//标准输出
cout << " slot " << e.second << " " << e.first;
if (target_path[0]) {
cout << " -> " << target_path;
}
cout << std::endl;
// 把要关设备添加到bluefs的实例fs中里
int r = fs->add_block_device(e.second, e.first, false);
if (r < 0) {
cerr << "unable to open " << e.first << ": " << cpp_strerror(r) << std::endl;
exit(EXIT_FAILURE);
}
}
}

open_bluefs 解读

open_bluefs
主要用来“挂载”bluefs文件系统,返回一个bluefs
文件系统的实例化对象fs
,类似于其他本地系统文件系统(如ext4、xfs)的mount
操作

BlueFS *open_bluefs(
CephContext *cct,
const string& path,
const vector<string>& devs)
{
validate_path(cct, path, true);
BlueFS *fs = new BlueFS(cct);

add_devices(fs, cct, devs); //给fs实例添加设备

int r = fs->mount(); //调用fs自身的mount函数,实现“挂载”
if (r < 0) {
cerr << "unable to mount bluefs: " << cpp_strerror(r)
<< std::endl;
exit(EXIT_FAILURE);
}
return fs;
}

关于bluefs的mount的定义(BlueFS.cc
),bluefs是一个纯内存文件系统,需要把相关的元数据全部载入内存


int BlueFS::mount()
{
dout(1) << __func__ << dendl;

int r = _open_super();//读取bluefs的superblock
....省略

// 初始化块分配器
block_all.clear();
block_all.resize(MAX_BDEV);
_init_alloc();

r = _replay(false, false); //回放日志文件,在内存构建文件系统
....省略
// init freelist
## 遍历file_map,从fnode里获取文件占在用的空间,把已分配的空间中从空闲空间去掉(全局的file_map结构<inode,FileRef>)
for (auto& p : file_map) {
dout(30) << __func__ << " noting alloc for " << p.second->fnode << dendl;
//每一个fnode结构里,有已经分配的空间信息
for (auto& q : p.second->fnode.extents) {
alloc[q.bdev]->init_rm_free(q.offset, q.length);
}
}

// set up the log for future writes
// 定位日志文件的末尾位置,实现后续日志追加写
log_writer = _create_writer(_get_file(1));
ceph_assert(log_writer->file->fnode.ino == 1);
log_writer->pos = log_writer->file->fnode.size;
...省略
_init_logger();
return 0;
... 省略
}

bluefs的mount
主要流程:

  1. 读取superblock
    (即bluefs的超级块,即文件系统本身的元数据),就是bluefs指定设备的第2个4096

  2. superblock
    里会指向文件系统的日志的fnode
    (即journal文件的位置,且ino索引号为1,日志文件时记录了关于所有文件系统的操作事务日志),然后开始replay(回放)整个文件系统的操作记录

  3. replay的操作相当于把整个文件系统所有文件元数据再次操作,还原文件系统的本来面目,最后在在内存中构建全局的dir_map
    file_map
    结构

  4. 标记已分配空间,所有文件元数据全部加载到内存中,再通过遍历file_map
    中的文件,获取地址空间分配(映射)信息,移除相应的块分配器中的空闲空间,防止已分配空间的重复分配

  5. 创建log_writer,log_writer为日志文件的句柄,用于向日志中追加日志项

关于bluefs里”文件”的定义(bluefs_types.h
)

struct bluefs_fnode_t {
uint64_t ino; //inode编号
uint64_t size; //文件的大小
utime_t mtime; //访问时间
uint8_t prefer_bdev; //文件优先使用什么设备,默认优先使用DB,在DB不足的情况下,可以使用SLOW
mempool::bluefs::vector<bluefs_extent_t> extents; //文件对应分配的空间

// precalculated logical offsets for extents vector entries
// allows fast lookup for extent index by the offset value via upper_bound()
mempool::bluefs::vector<uint64_t> extents_index; //extend索引,加快查询

由于bluefs
主要用来承载RocksDB
使用的文件,其本身结构不需要很多的文件数量及特性的支持,数量最多的也就是sst
文件,且有内置的压缩处理,所以把所有文件元数据载入内存是可行的(这跟ext4/xfs这类型本地文件系统不同)。更通俗些讲,bluefs是”阉割“过的文件系统。

Tips:

关于bluefs的分析,笔者尽量后续再发一文来解读。

validate_path 解读

validate_path
函数的名字也可以推测出主要是用户来验证路径是否可用。

void validate_path(CephContext *cct, const string& path, bool bluefs)
{
BlueStore bluestore(cct, path); //构建bluestore实例对象
string type;
int r = bluestore.read_meta("type", &type); //读取设备label数据,判断是否有type这个字段,下面判断类似
if (r < 0) {
cerr << "failed to load os-type: " << cpp_strerror(r) << std::endl;
exit(EXIT_FAILURE);
}
...省略...
}

find_device_path 解读

find_device_path
通过给定的设备id标识符,查找对应设备的路径

const char* find_device_path(
int id,
CephContext *cct,
const vector<string>& devs)
{
for (auto& i : devs) {
bluestore_bdev_label_t label;
// 读取设备的label数据
int r = BlueStore::_read_bdev_label(cct, i, &label);
if (r < 0) {
cerr << "unable to read label for " << i << ": "
<< cpp_strerror(r) << std::endl;
exit(EXIT_FAILURE);
}
// 通过 id 和 label的description字段的判断, 返回设备的路径
if ((id == BlueFS::BDEV_SLOW && label.description == "main") ||
(id == BlueFS::BDEV_DB && label.description == "bluefs db") ||
(id == BlueFS::BDEV_WAL && label.description == "bluefs wal")) {
return i.c_str();
}
}
return nullptr;
}

command 具体指令的解析

接上文的main
入口, 下面就是具体用户发出指令的具体操作(即实现用户的需求)

fsck 、repair 解读

通过调用bluestore实例对象的内置方法函数(fsck 、 repair)进行相应处理。内部详细的处理流程在此不再详细展开

  if (action == "fsck" ||
action == "repair") {
validate_path(cct.get(), path, false);
BlueStore bluestore(cct.get(), path); //创建BlueStore实例对象
int r;

//调用bluestore实例对象的方法函数进行相关的操作(fsck or repair)
if (action == "fsck") {
r = bluestore.fsck(fsck_deep);
} else {
r = bluestore.repair(fsck_deep);
}
if (r < 0) {
cerr << "error from fsck: " << cpp_strerror(r) << std::endl;
exit(EXIT_FAILURE);
}
cout << action << " success" << std::endl;
}

Tips:

这里fsck和repair其实在bluestore内部调用的是同一个BlueStore::_fsck(bool deep, bool repair)
(详见BlueStore.cc
文件),在此不再一一展开,有兴趣的读者可以自行前往查看。

prime-osd-dir 解读

prime-osd-dir
主要用来把设备的label
元数据导入到本地工作目录下,为启动osd进程做铺垫作用。早期的bluestore
还需要独立的xfs小分区来放一些元数据信息(比如osd的uuid、key、后端存储等),之后的bluestore版本这些信息都存储在设备的label
 上面了(即设备的第1个4k
)。所以即使osd工作目录(/var/lib/ceph/osd/ceph-{id}/
)里的元数据文件不小心删除了, 只要激活下osd,就会重新生成(激活操作实际就是调用了ceph-bluestore-tool
prime-osd-dir
操作)

// 以下代码有删减,只保留核心逻辑 
else if (action == "prime-osd-dir") {
bluestore_bdev_label_t label;
//获取slow设备label,devs容器变量第一个元素就是指向slow设备
int r = BlueStore::_read_bdev_label(cct.get(), devs.front(), &label);

// kludge some things into the map that we want to populate into
// target dir
// 组装需要的label
label.meta["path_block"] = devs.front();
label.meta["type"] = "bluestore";
label.meta["fsid"] = stringify(label.osd_uuid);

//需要的元数据文件
for (auto kk : {
"whoami",
"osd_key",
"ceph_fsid",
"fsid",
"type",
"ready" }) {
string k = kk;
auto i = label.meta.find(k);
if (i == label.meta.end()) {
continue;
}
string p = path + "/" + k;
string v = i->second;
//keyring文件的内容的拼接,label里有key的内容, keyring文件需要自己的格式 ##
if (k == "osd_key") {
p = path + "/keyring";
v = "[osd.";
v += label.meta["whoami"];
v += "]\nkey = " + i->second;
}
v += "\n";

//把从label里获取的信息写入osd的工作目录下面去
int fd = ::open(p.c_str(), O_CREAT|O_TRUNC|O_WRONLY|O_CLOEXEC, 0600);
int r = safe_write(fd, v.c_str(), v.size());
....
::close(fd);
}
}

osd的激活操作如下所示:

# ceph-volume lvm  activate --all
--> Activating OSD ID 0 FSID e8164817-4103-47e6-b451-37d30d9785f8
## 创建临时挂载 ##
Running command: bin/mount -t tmpfs tmpfs var/lib/ceph/osd/ceph-0
Running command: usr/sbin/restorecon var/lib/ceph/osd/ceph-0
Running command: bin/chown -R ceph:ceph var/lib/ceph/osd/ceph-0
## 实际上就是这里,调用了ceph-bluestore-tool的 prime-osd-dir操作 ###
Running command: bin/ceph-bluestore-tool --cluster=ceph prime-osd-dir --dev dev/ceph-c7590218-d31d-4b95-9ec9-16c4ee38812b/osd-block-e8164817-4103-47e6-b451-37d30d9785f8 --path var/lib/ceph/osd/ceph-0 --no-mon-config
Running command: bin/ln -snf dev/ceph-c7590218-d31d-4b95-9ec9-16c4ee38812b/osd-block-e8164817-4103-47e6-b451-37d30d9785f8 var/lib/ceph/osd/ceph-0/block
.....

show-label解读

show-label
就是展示设备的label的完整信息

 else if (action == "show-label") {
JSONFormatter jf(true); ## 一种Ceph内部自定义的json格式化的对象 ##
jf.open_object_section("devices");
//遍历所有设备
for (auto& i : devs) {
bluestore_bdev_label_t label; ## bluestore设备标签对象 ##
int r = BlueStore::_read_bdev_label(cct.get(), i, &label); ## 读出label的内容 ##
if (r < 0) {
cerr << "unable to read label for " << i << ": "
<< cpp_strerror(r) << std::endl;
exit(EXIT_FAILURE);
}
jf.open_object_section(i.c_str());
label.dump(&jf); ## 把label信息dump到jf ###
jf.close_section();
}
jf.close_section();
## 标准输出到控制台 ###
jf.flush(cout);
}

bluestore_bdev_label_t
结构定义(bluestore_types.h
)

struct bluestore_bdev_label_t {
uuid_d osd_uuid; ///< osd uuid
uint64_t size; ///< device size
utime_t btime; ///< birth time 创建时间
string description; ///< device description 描述信息,即'main','bluefs db','bluefs wal'中的一个

map<string,string> meta; ///< {read,write}_meta() content from ObjectStore ## 元数据信息的map结构 ##

void encode(bufferlist& bl) const; ## 序列化方法 ##
void decode(bufferlist::iterator& p); ##反序列化方法 ##
void dump(Formatter *f) const; ## dump方法 ##
static void generate_test_instances(list<bluestore_bdev_label_t*>& o);
};


int BlueStore::_read_bdev_label(CephContext* cct, string path,
bluestore_bdev_label_t *label)
{
....<省略> ....
bufferlist bl;
int r = bl.read_fd(fd, BDEV_LABEL_BLOCK_SIZE); ## label定义就是设备的第1个 4096 byte的位置,这个是固定的 ##

...<省略> ....
uint32_t crc, expected_crc;
bufferlist::iterator p = bl.begin();
try { ## 解析bl里的内容并“写入”label结构里
decode(*label, p);
bufferlist t;
t.substr_of(bl, 0, p.get_off());
crc = t.crc32c(-1); ##生成CRC数据 ##
decode(expected_crc, p); ##读取CRC数据 ##
}

## CRC校验,生成的和读取的是否一致 ##
if (crc != expected_crc) {
derr << __func__ << " bad crc on label, expected " << expected_crc
<< " != actual " << crc << dendl;
return -EIO;
}
}

关于BDEV_LABEL_BLOCK_SIZE
的定义(BlueStore.cc)

// write a label in the first block.  always use this size.  note that
// bluefs makes a matching assumption about the location of its
// superblock (always the second block of the device).
#define BDEV_LABEL_BLOCK_SIZE 4096
// reserve: label (4k) + bluefs super (4k), which means we start at 8k.
#define SUPER_RESERVED 8192 ## 保留位置 ##

注意:

设备的第1个4k
是设备的label,第二个4k是预留给了bluefs
superblock
,所以数据区域都是从第三个4k位置开始。

void bluestore_bdev_label_t::dump(Formatter *f) const
{
f->dump_stream("osd_uuid") << osd_uuid;
f->dump_unsigned("size", size);
f->dump_stream("btime") << btime;
f->dump_string("description", description);
for (auto& i : meta) {
f->dump_string(i.first.c_str(), i.second); ## 遍历 meta 这个map结构 ##
}
}

最终show-label
的呈现效果如下所示(单个设备)

#    ceph-bluestore-tool    --log-level 30     --path   var/lib/ceph/osd/ceph-0     show-label
inferring bluefs devices from bluestore path
{
"/var/lib/ceph/osd/ceph-0/block": {
"osd_uuid": "e8164817-4103-47e6-b451-37d30d9785f8",
"size": 106300440576,
"btime": "2019-12-04 19:35:51.883943",
"description": "main",
### 以下属于上面的meta结构 ##
"bluefs": "1",
"ceph_fsid": "1ec958df-eaf9-4385-892b-4f7b3be5112a",
"kv_backend": "rocksdb",
"magic": "ceph osd volume v026",
"mkfs_done": "yes",
"osd_key": "AQD+medd0pPJEhAAQ6bq12xnQvJ7l3bMldTEtw==",
"ready": "ready",
"require_osd_release": "14",
"whoami": "0"
}
}

有多个设备的展示如下:

# ceph-bluestore-tool  --path    var/lib/ceph/osd/ceph-0  show-label
inferring bluefs devices from bluestore path
{
"/var/lib/ceph/osd/ceph-0/block": {
"osd_uuid": "e8d0e9cb-7cf2-4be1-af39-6c5dbfcaf1f5",
"size": 29997662208,
"btime": "2020-10-26 17:21:38.414471",
"description": "main",
"bluefs": "1",
"ceph_fsid": "1ec958df-eaf9-4385-892b-4f7b3be5112a",
"kv_backend": "rocksdb",
"magic": "ceph osd volume v026",
"mkfs_done": "yes",
"osd_key": "AQDylJZfB+e0ARAAKWMjgP+miVpq//4lTnh1Mg==",
"ready": "ready",
"require_osd_release": "14",
"whoami": "0"
},
"/var/lib/ceph/osd/ceph-0/block.wal": {
"osd_uuid": "e8d0e9cb-7cf2-4be1-af39-6c5dbfcaf1f5",
"size": 21994930176,
"btime": "2020-10-26 17:21:38.421721",
"description": "bluefs wal"
},
"/var/lib/ceph/osd/ceph-0/block.db": {
"osd_uuid": "e8d0e9cb-7cf2-4be1-af39-6c5dbfcaf1f5",
"size": 17997758464,
"btime": "2020-10-26 17:21:38.418186",
"description": "bluefs db"
}
}

set-label-key、rm-label-key 解读

set-label-key
rm-label-key
主要是对label.meta
进行增删。

// 只保留核心代码逻辑,  
else if (action == "set-label-key") {
bluestore_bdev_label_t label;
int r = BlueStore::_read_bdev_label(cct.get(), devs.front(), &label);

...省略...
} else if (key =="description") {
label.description = value;
} else {
//非osd_uuid、size、description 字段,统一放在meta这个map结构占
label.meta[key] = value;
}
//重新写入新的label
r = BlueStore::_write_bdev_label(cct.get(), devs.front(), label);
if (r < 0) {
cerr << "unable to write label for " << devs.front() << ": "
<< cpp_strerror(r) << std::endl;
exit(EXIT_FAILURE);
}
}

}

int BlueStore::_write_bdev_label(CephContext *cct,
string path, bluestore_bdev_label_t label)
{
dout(10) << __func__ << " path " << path << " label " << label << dendl;
bufferlist bl;
encode(label, bl);
uint32_t crc = bl.crc32c(-1);
encode(crc, bl);
bl的长度是否小于等于预设的4096大小
ceph_assert(bl.length() <= BDEV_LABEL_BLOCK_SIZE);
bufferptr z(BDEV_LABEL_BLOCK_SIZE - bl.length());
剩余空间填充0
z.zero();
bl.append(std::move(z));

...<忽略>....
int r = bl.write_fd(fd);

...
r = ::fsync(fd); ### 同步写入到设备 ###
...
}

核心逻辑:

1.label结构的内容序列化到bl数据结构

2.添CRC
校验内容

3.长度不足4096
,那就用0填充

4.同步写入到设备的第一个4096位置

做个有趣的验证,往label
里塞一过量的自定义元数据(最大不过4k),不一会儿就把打爆osd :)

# # for i  in ` seq 0 100`;do ceph-bluestore-tool  --path  var/lib/ceph/osd/ceph-0  --dev var/lib/ceph/osd/ceph-0/block   set-label-key  -k key$i    -v  "HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH";done 
6: (()+0x1bd79f) [0x560b79a2e79f]

*** Caught signal (Aborted) **
in thread 7fb7d43f7f80 thread_name:ceph-bluestore-
2020-10-22 17:28:16.003 7fb7d43f7f80 -1 data01/code/nautilus/rpmbuild/BUILD/ceph-14.2.4/src/os/bluestore/BlueStore.cc: In function 'static int BlueStore::_write_bdev_label(CephContext*, std::string, bluestore_bdev_label_t)' thread 7fb7d43f7f80 time 2020-10-22 17:28:16.002988
/data01/code/nautilus/rpmbuild/BUILD/ceph-14.2.4/src/os/bluestore/BlueStore.cc: 4566: FAILED ceph_assert(bl.length() <= 4096) ##bl的数据大小超过4096

Tips:

读者可以自行修改默认元数据,验证osd是否还能正常启动。操作有风险,请慎重。

bluefs-export 解读

bluefs-export
 使用是把bluefs文件系统的所有目录和文件导出到本地。

// 只保留核心代码逻辑,   
else if (action == "bluefs-export") {
BlueFS *fs = open_bluefs(cct.get(), path, devs); ## 创建bluefs实例 ##
vector<string> dirs; ## 目录容器
// readdir函数第一个变量为空,则为遍历dir_map,获取当前所有目录名
int r = fs->readdir("", &dirs);

if (::access(out_dir.c_str(), F_OK)) {
r = ::mkdir(out_dir.c_str(), 0755); ## 创建指向本地的目录 ##
}

for (auto& dir : dirs) {
if (dir[0] == '.') ## 忽略.文件 ##
continue;
cout << dir << "/" << std::endl;
vector<string> ls;

// readdir函数指定第一个参数时,即为获取这个目录下的所有文件名,并存储在ls这个容器变量里
r = fs->readdir(dir, &ls);
string full = out_dir + "/" + dir; ## 拼接本地基础目录名
if (::access(full.c_str(), F_OK)) {
r = ::mkdir(full.c_str(), 0755); ## 创建本地目录
}
for (auto& file : ls) { ## 遍历取本目录下面的所有文件 ##
if (file[0] == '.')
continue;
cout << dir << "/" << file << std::endl;
uint64_t size;
utime_t mtime;
r = fs->stat(dir, file, &size, &mtime); ## 获取文件元数据信息,类似本地文件系统的stat函数(命令) ##

string path = out_dir + "/" + dir + "/" + file; ## 拼接导出文件的完整路径 ##
int fd = ::open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC|O_CLOEXEC, 0644); ## 创建导出到本地的文件,返回本地文件的句柄 ##
if (size > 0) {
BlueFS::FileReader *h; ## bluefs的FileReader的实例 ##
r = fs->open_for_read(dir, file, &h, false); ## 打开bluefs里的“文件”(下文有分析)

int pos = 0;
int left = size; //文件末尾边界
// 关于读的内在逻辑,此处作者也只能大致猜测(很多理解估计不是很准确),作用就是把bluefs的文件内容读取出来并写入导出的本地文件中
while (left) { ## 只要有剩余位置,继续读取 ##
bufferlist bl;
r = fs->read(h, &h->buf, pos, left, &bl, NULL); ##在pos到left的区间内读取数据到bl结构里 ##

int rc = bl.write_fd(fd); ## 把读取到bl结构里数据写入到本地文件系统的文件 ##

pos += r; ## pos位置指针后移r距离##
left -= r; ## left位置指针前移r距离##
}
delete h;
}
::close(fd);
}
}
fs->umount();
delete fs;

bluefs-export 核心功能:

  1. 遍历bluefs文件系统里的所有所有文件(和目录),拼接出文件路径

  2. 把bluefs文件系统里的所有文件的内容"导出"
    到本地指定的目录下面

前文提到,BlueFS通过一个dir_map
建立目录名到目录结构的映射,BlueFS只有一级目录,不存在多级目录,例如目录/a
 和 /a/b
为同一级目录;另外目录结构中有一个file_map
,为文件名到文件结构的映射,表示该目录包含的文件。

BlueFS在定位一个具体的文件时会在内存中经过两次查找:第一次通过dir_map
找到文件所在的最底层文件夹,第二次通过该文件夹下的file_map
找到对应的文件。

举个示例 ,比如要打开的文件完整路径为/a/b/c/d.txt
(实际BlueFS里没有这样的路径结构,只为举例), 先在dir_map
结构里查看名为/a/b/c
Dir
结构的引用指针,遍历Dir
内部的file_map
,获取d.txt
文件

全局的dir_map和file_map的定义(BlueFS.h
)

  mempool::bluefs::map<string, DirRef> dir_map;              ///< dirname -> Dir
mempool::bluefs::unordered_map<uint64_t,FileRef> file_map; ///< ino -> File ,这是全局的file_map结构,replay时间所用到的

Dir的定义(BlueFS.h)

  struct Dir : public RefCountedObject {
MEMPOOL_CLASS_HELPERS();

mempool::bluefs::map<string,FileRef> file_map; // Dir内部的关于本目录下面文件名,文件映射的map
Dir() : RefCountedObject(NULL, 0) {}

friend void intrusive_ptr_add_ref(Dir *d) {
d->get();
}
friend void intrusive_ptr_release(Dir *d) {
d->put();
}
};
typedef boost::intrusive_ptr<Dir> DirRef; // Dir结构的引用指针

dir_map
Dir
内部的file_map
的关系如下所示:

 

图片来源知乎:https://zhuanlan.zhihu.com/p/46362124

下面是查找文件(二次查找)具体实现(BlueFS.cc
)

// 只保留核心代码逻
int BlueFS::open_for_read(
const string& dirname,
const string& filename,
FileReader **h,
bool random)
{

// 第一次查找, 遍历dir_map结构,找对应的目录名的DirRef结构
map<string,DirRef>::iterator p = dir_map.find(dirname);
if (p == dir_map.end()) {
dout(20) << __func__ << " dir " << dirname << " not found" << dendl;
return -ENOENT;
}
//找到了,取得相应的DirRef结构
DirRef dir = p->second;
//第二次查看, 遍历该目录下面的file_map,通过文件名找对应的文件
map<string,FileRef>::iterator q = dir->file_map.find(filename);
if (q == dir->file_map.end()) {
dout(20) << __func__ << " dir " << dirname << " (" << dir
<< ") file " << filename
<< " not found" << dendl;
return -ENOENT;
}
//获取文件
File *file = q->second.get();
//实例化FileReader,后续实现文件操作的具体对象(句柄)
*h = new FileReader(file, random ? 4096 : cct->_conf->bluefs_max_prefetch,
random, false);
dout(10) << __func__ << " h " << *h << " on " << file->fnode << dendl;
return 0;
}

导出的实际效果如下呈现,其中db就是就是RocksDB的结构,db.slow实际指向就是BlueStore的实际数据存储设备, db.wal指向WAL日志。

#ceph-bluestore-tool    --log-level 30   --path   var/lib/ceph/osd/ceph-0   --out-dir   var/opt/   bluefs-export
inferring bluefs devices from bluestore path
slot 1 var/lib/ceph/osd/ceph-0/block -> dev/dm-3
db/
db/000087.sst
db/000089.sst
db/000092.sst
db/000095.sst
db/000098.sst
db/CURRENT
db/IDENTITY
db/LOCK
db/MANIFEST-000102
db/OPTIONS-000073.dbtmp
db/OPTIONS-000102
db/OPTIONS-000105
db.slow/
db.wal/
db.wal/000103.log

bluefs-bdev-sizes 解读

bluefs-bdev-sizes
用来展示bluefs管理设备的大小及分配的空间的大小

  else if (action == "bluefs-bdev-sizes") {
BlueFS *fs = open_bluefs(cct.get(), path, devs);
fs->dump_block_extents(cout);
delete fs;
}

dump_block_extents
的定义(BlueFS.cc
)

void BlueFS::dump_block_extents(ostream& out)
{
for (unsigned i = 0; i < MAX_BDEV; ++i) {
if (!bdev[i]) {
continue;
}
//获取设备的大小
auto owned = get_total(i);
//获取设备空闲大小
auto free = get_free(i);
out << i << " : device size 0x" << std::hex << bdev[i]->get_size() //获取分配空间的大小
<< " : own 0x" << block_all[i]
<< " = 0x" << owned
<< " : using 0x" << owned - free //计算已使用的大小
<< std::dec << "(" << byte_u_t(owned - free) << ")"
<< "\n";
}
}

#    ceph-bluestore-tool    --log-level 30     --path   var/lib/ceph/osd/ceph-0    bluefs-bdev-sizes
inferring bluefs devices from bluestore path
slot 1 var/lib/ceph/osd/ceph-0/block -> dev/dm-3
1 : device size 0x18c0000000 : own 0x[be1400000~fd800000] = 0xfd800000 : using 0xd00000(13 MiB)

WAL
,DB
,SLOW
拥有独立设备的OSD

# ceph-bluestore-tool   bluefs-bdev-sizes --path  var/lib/ceph/osd/ceph-0
inferring bluefs devices from bluestore path
slot 2 var/lib/ceph/osd/ceph-0/block -> dev/dm-6
slot 1 var/lib/ceph/osd/ceph-0/block.db -> dev/dm-5
slot 0 var/lib/ceph/osd/ceph-0/block.wal -> dev/dm-3
0 : device size 0x51f000000 : own 0x[1000~51efff000] = 0x51efff000 : using 0x15ff000(22 MiB)
1 : device size 0x430c00000 : own 0x[2000~430bfe000] = 0x430bfe000 : using 0xdfe000(14 MiB)
2 : device size 0x6fc000000 : own 0x[35a300000~47900000] = 0x47900000 : using 0x0(0 B)

bluefs-bdev-expand 解读

bluefs-bdev-expand
的作用是用于设备的扩容,底层依赖lvm的特性(lvm卷的扩展)。bluestore实例对象中的bdev会重新识别设备的大小,然后把新增的空间添加到FreelistManager的实例对象中,以事务的形式持久化到数据库中(RocksDB),最后更新设备的label

  else if (action == "bluefs-bdev-expand") {
BlueStore bluestore(cct.get(), path);
//调用bluestore实例对象的expand_devices方法
auto r = bluestore.expand_devices(cout);
if (r <0) {
cerr << "failed to expand bluestore devices: "
<< cpp_strerror(r) << std::endl;
exit(EXIT_FAILURE);
}
}

expand_devices
的定义(BlueStore.cc
)

// 只保留核心代码逻辑
int BlueStore::expand_devices(ostream& out)
{
int r = _mount(false);
ceph_assert(r == 0);
bluefs->dump_block_extents(out);
out << "Expanding..." << std::endl;
// 独立的db/slow设备
for (auto devid : { BlueFS::BDEV_WAL, BlueFS::BDEV_DB}) {
//忽略共享
if (devid == bluefs_shared_bdev ) {
continue;
}
//获取当前设备的末尾边界
uint64_t size = bluefs->get_block_device_size(devid);
interval_set<uint64_t> before;
//获取原来的设备空间分配,即extents
bluefs->get_block_extents(devid, &before);
ceph_assert(!before.empty());
//取原来设备的末尾边界
uint64_t end = before.range_end();
if (end < size) { ## 新的边界大于老的边界
out << devid
<<" : expanding " << " from 0x" << std::hex
<< end << " to 0x" << size << std::dec << std::endl;
bluefs->add_block_extent(devid, end, size-end); ## 给设备添加新增空间
string p = get_device_path(devid);
const char* path = p.c_str();
//更新设备label
bluestore_bdev_label_t label;
int r = _read_bdev_label(cct, path, &label);
label.size = size;
r = _write_bdev_label(cct, path, label);

}
//空间管理器(FreeListManager)当前的大小
uint64_t size0 = fm->get_size();
//设备的大小
uint64_t size = bdev->get_size();
if (size0 < size) {
out << bluefs_shared_bdev
<<" : expanding " << " from 0x" << std::hex
<< size0 << " to 0x" << size << std::dec << std::endl;
//创建事务,持久化到db
KeyValueDB::Transaction txn;
txn = db->get_transaction();
//空间管理器(FreelistManager)空间更新
int r = fm->expand(size, txn);
ceph_assert(r == 0);
//提交事务
db->submit_transaction_sync(txn);

// always reference to slow device here
//slow设备的label更新
string p = get_device_path(BlueFS::BDEV_SLOW);
ceph_assert(!p.empty());
const char* path = p.c_str();
bluestore_bdev_label_t label;
r = _read_bdev_label(cct, path, &label);
label.size = size;
r = _write_bdev_label(cct, path, label);
}
umount();
return r;
}

bluefs-bdev-expand
操作示例:

创建pv&&扩容vg卷组,然后扩容lvm卷,如下是把wal卷扩容至最大空间

# lvextend  -l +100%FREE wal/wal
Size of logical volume wal/wal changed from <1.86 GiB (476 extents) to 20.48 GiB (5244 extents).
Logical volume wal/wal successfully resized.

bluefs-bdev-expand
扩容操作:

# ceph-bluestore-tool  --dev dev/mapper/wal-wal   bluefs-bdev-expand --path var/lib/ceph/osd/ceph-0
inferring bluefs devices from bluestore path
0 : device size 0x51f000000 : own 0x[1000~76fff000] = 0x76fff000 : using 0x14ff000(21 MiB)
1 : device size 0x430c00000 : own 0x[2000~430bfe000] = 0x430bfe000 : using 0x11fe000(18 MiB)
2 : device size 0x6fc000000 : own 0x[35a300000~47900000] = 0x47900000 : using 0x0(0 B)
Expanding...
0 : expanding from 0x77000000 to 0x51f000000
0 : size label updated to 21994930176 ## 扩容至最新大小 ##

# ceph-bluestore-tool  --dev dev/mapper/wal-wal  show-label
{
"/dev/mapper/wal-wal": {
"osd_uuid": "e8d0e9cb-7cf2-4be1-af39-6c5dbfcaf1f5",
"size": 21994930176, ## 更后的大小 ##
"btime": "2020-10-26 17:21:38.421721",
"description": "bluefs wal"
}
}

Tips:

基于bluefs-bdev-extend
的特性,在存储节点上增加新硬盘做成PV并扩容OSD的SLOW设备(对应的lvm卷),可以实现在不增加osd( 和PG)的情况实现集群扩容, 且不会进行数据均衡。这只是笔者想到的一种新的扩容方案,这需要依据实际情况进行权衡。

bluefs-log-dump 解读

 bluefs-log-dump
用来展示bluefs的journal内容,最终调用的是bluefs的_replay
方法,只是添加不操作的标记及标准输出到控制台

if (action == "bluefs-log-dump") {
log_dump(cct.get(), path, devs);

_replay
的定义(BlueFS.cc
)

// 只保留核心代码逻辑
int BlueFS::_replay(bool noop, bool to_stdout)
....
auto p = t.op_bl.cbegin();
while (!p.end()) {
__u8 op;
decode(op, p); ## 解析op操作日志

switch (op) {
case bluefs_transaction_t::OP_INIT: ## 匹配操作符
dout(20) << __func__ << " 0x" << std::hex << pos << std::dec
<< ": op_init" << dendl;
if (unlikely(to_stdout)) { ## 打印日志内容
std::cout << " 0x" << std::hex << pos << std::dec
<< ": op_init" << std::endl;
}
....

bluefs-log-dump
输出示例:

 ceph-bluestore-tool    --log-level 30     --path   var/lib/ceph/osd/ceph-0    bluefs-log-dump
inferring bluefs devices from bluestore path
slot 2 var/lib/ceph/osd/ceph-0/block -> dev/dm-6
slot 1 var/lib/ceph/osd/ceph-0/block.db -> dev/dm-5
slot 0 var/lib/ceph/osd/ceph-0/block.wal -> dev/dm-3

##解析日志文件,inode编号为1 ,具体分配空间地址是 extents [0:0x100000~400000] ,其中extents表示物理空间的集合
log_fnode file(ino 1 size 0x1000 mtime 0.000000 bdev 0 allocated 400000 extents [0:0x100000~400000])
0x0: txn(seq 1 len 0x37 crc 0x78abd786) ## 事务1 ,包含多个op操作
0x0: op_init
0x0: op_alloc_add 0:0x1000~76fff000
0x0: op_alloc_add 1:0x2000~430bfe000
0x0: op_alloc_add 2:0x35a300000~47900000
### 以上是一个事务 ###

0x1000: txn(seq 2 len 0xa6 crc 0x9a3b99ab) 事务2
0x1000: op_dir_create db ##op操作下同
0x1000: op_dir_create db.wal
0x1000: op_dir_create db.slow
## fnode的实际内容,见前文的定义
0x1000: op_file_update file(ino 2 size 0x0 mtime 2020-10-26 17:21:38.480085 bdev 0 allocated 0 extents [])
0x1000: op_dir_link db/LOCK to 2
0x1000: op_file_update file(ino 3 size 0x0 mtime 2020-10-26 17:21:38.480187 bdev 1 allocated 0 extents [])
0x1000: op_dir_link db/MANIFEST-000001 to 3
0x1000: op_file_update file(ino 3 size 0xd mtime 2020-10-26 17:21:38.480424 bdev 1 allocated 100000 extents [1:0x100000~100000])
0x2000: txn(seq 3 len 0x58 crc 0xeebdeb19)
...

Tips:

关于bluefs的分析,笔者尽量后续再发一文来解读。

bluefs-bdev-new-db、bluefs-bdev-new-wal 解读

bluefs-bdev-new-db
bluefs-bdev-new-wal
主要用于为wal/db共享slow设备添加独立的专有设备。如果已经有独立设备,则不支持新增。

// 只保留核心代码逻辑
else if (action == "bluefs-bdev-new-db" || action == "bluefs-bdev-new-wal") {
map<string, int> cur_devs_map;
//标志位
bool need_db = action == "bluefs-bdev-new-db";
bool has_wal = false;
bool has_db = false;
char target_path[PATH_MAX] = "";
//构建设备跟id映射关系以及设备标记
parse_devices(cct.get(), devs, &cur_devs_map, &has_db, &has_wal);
//判断是否已经存储独立的wal/db设备
if (has_db && has_wal) {
cerr << "can't allocate new device, both WAL and DB exist"
<< std::endl;
exit(EXIT_FAILURE);
....
}
// Create either DB or WAL volume
// 需要指定相关配置参数,默认为0
int r = EXIT_FAILURE;
if (need_db && cct->_conf->bluestore_block_db_size == 0) {
cerr << "DB size isn't specified, "
"please set Ceph bluestore-block-db-size config parameter "
<< std::endl;
} else if (!need_db && cct->_conf->bluestore_block_wal_size == 0) {
cerr << "WAL size isn't specified, "
"please set Ceph bluestore-block-wal-size config parameter "
<< std::endl;
} else {
//创建bluestore实例对象,调用add_new_bluefs_device方式实现新增。
//根据设备类型,传入新设备的id(见前文定义)
BlueStore bluestore(cct.get(), path);
r = bluestore.add_new_bluefs_device(
need_db ? BlueFS::BDEV_NEWDB : BlueFS::BDEV_NEWWAL,
target_path);
...
return r;
}

add_new_bluefs_device
的定义(BlueStore.cc
)

// 只保留核心代码逻辑
int BlueStore::add_new_bluefs_device(int id, const string& dev_path)
{
//设备id判断, 是不是为新设备
ceph_assert(id == BlueFS::BDEV_NEWWAL || id == BlueFS::BDEV_NEWDB);
//挂载bluefs
r = _mount_for_bluefs();
int reserved = 0;
//设备id判断,设置软连路径,添加设备、设置label
if (id == BlueFS::BDEV_NEWWAL) {
string p = path + "/block.wal";

r = bluefs->add_block_device(BlueFS::BDEV_NEWWAL, p,
cct->_conf->bdev_enable_discard);

if (bluefs->bdev_support_label(BlueFS::BDEV_NEWWAL)) {
r = _check_or_set_bdev_label(
p,
bluefs->get_block_device_size(BlueFS::BDEV_NEWWAL),
"bluefs wal",
true);
ceph_assert(r == 0);
}
//保留的label大小, 4096
reserved = BDEV_LABEL_BLOCK_SIZE;
....
}
bluefs->umount();
bluefs->mount();
//初始化空间
bluefs->add_block_extent(
id,
reserved,
bluefs->get_block_device_size(id) - reserved);
//为设备初始化
r = bluefs->prepare_new_device(id);

...
}

核心逻辑:

  1. 确认新设备id

  2. 设置设备的软链

  3. 设置设备的label

  4. 为bluefs添加设备并初始化空间

  5. 修改bluefs journal
    设置并持久化变更事务日志

在Bluestore内部的主要调用路径:

  • BlueStore::add_new_bluefs_device()--->
    BlueFS::add_block_extent()--->
    BlueFS::prepare_new_device()--->
    BlueFS::_rewrite_log_sync()`

bluefs-bdev-new-db
 实操举例:

CEPH_ARGS="--bluestore_block_db_size=1073741824 --bluestore_clock_db_create=true"    ceph-bluestore-tool    --log-level 30     --path   var/lib/ceph/osd/ceph-0   bluefs-bdev-new-db   --dev-target   dev/new_db/new_db

注意

  1. 这里实际是有个坑的,在代码里需要指bluestore-block-db-size
    或者bluestore-block-wal-size
    这个参数大小,但实际ceph-bluestore-tool
    使用过程中没有解析本地的ceph.conf
    配置文件, 所以无论把这个配置项添加到哪个配置文件,不会生效。会提示你"please set Ceph bluestore-block-wal-size config parameter "

  2. 正确的添加配置项的方式是通过环境变量(如上所示)

  3. bluestore_block_db_size
    bluestore_block_wal_size
    的配置并不会影响创建的db和wal设备的大小, 默认会使用设备的所有空间(除保留空间)

bluefs-bdev-migrate 解读

bluefs-bdev-migrate
 主要作用是用于wal/db设备的迁移。支持以下迁移:

  • wal设备迁移到现有的db设备

  • wal设备迁移到新的wal设备

  • db设备迁移到到新的db设备

// 只保留核心代码逻辑
else if (action == "bluefs-bdev-migrate") {
map<string, int> cur_devs_map;
set<int> src_dev_ids;
map<string, int> src_devs;
parse_devices(cct.get(), devs, &cur_devs_map, nullptr, nullptr);
...
auto i = cur_devs_map.find(dev_target);

if (i != cur_devs_map.end()) {
// Migrate to an existing BlueFS volume
auto dev_target_id = i->second;
if (dev_target_id == BlueFS::BDEV_WAL) {
//wal设备没有superblock保留空间,不支持目标设备为WAL旧设备。
// currently we're unable to migrate to WAL device since there is no space
// reserved for superblock
cerr << "Migrate to WAL device isn't supported." << std::endl;
exit(EXIT_FAILURE);
}
BlueStore bluestore(cct.get(), path);
// 调用bluestore实例的migrate_to_existing_bluefs_device方法进行迁移
int r = bluestore.migrate_to_existing_bluefs_device(
src_dev_ids,
dev_target_id);
......

} else {
// Migrate to a new BlueFS volume
// via creating either DB or WAL volume
char target_path[PATH_MAX] = "";
int dev_target_id;
if (src_dev_ids.count(BlueFS::BDEV_DB)) {
// if we have DB device in the source list - we create DB device
// (and may be remove WAL).
dev_target_id = BlueFS::BDEV_NEWDB;
} else if (src_dev_ids.count(BlueFS::BDEV_WAL)) {
dev_target_id = BlueFS::BDEV_NEWWAL;
} else {
// 不允许把slow迁移迁移
cerr << "Unable to migrate Slow volume to new location, "
"please allocate new DB or WAL with "
"--bluefs-bdev-new-db(wal) command"
<< std::endl;
exit(EXIT_FAILURE);
}

.....
BlueStore bluestore(cct.get(), path);

bool need_db = dev_target_id == BlueFS::BDEV_NEWDB;
// 调用bluestore实例的migrate_to_new_bluefs_device方法进行迁移
int r = bluestore.migrate_to_new_bluefs_device(
src_dev_ids,
dev_target_id,
target_path);

......

return 0;

核心逻辑:

  • bluefs-bdev-migrate
    主要通过命令行指定的--dev-target
     设备变量是否是新设备,来确认bluestore
    实例方法调用。

  • 如果--dev-target
    指向原有的设备(db或者slow),则调用BlueStore::migrate_to_existing_bluefs_device()
    实施迁移

  • 如果--dev-target
    指向一个新的设备,则调用BlueStore::add_new_bluefs_device()
    实施迁移

  • 把wal设备迁移到新的wal设备操作示例,其中 /dev/mapper/mig_wal-mig_wal
    使用lvm提前准备好的迁移目标设备.

# ll /var/lib/ceph/osd/ceph-0/  | grep wal 
lrwxrwxrwx 1 ceph ceph 12 Oct 28 14:19 block.wal -> /dev/wal/wal ## 原路径


# ceph-bluestore-tool --log-level 30 --path /var/lib/ceph/osd/ceph-0 --devs-source /var/lib/ceph/osd/ceph-0/block.wal --dev-target /dev/mapper/mig_wal-mig_wal bluefs-bdev-migrate
inferring bluefs devices from bluestore path
device removed:0 /var/lib/ceph/osd/ceph-0/block.wal ## 删除旧的wal设备
device added: 1 /dev/dm-6 ##添加新的wal设备

# ll /var/lib/ceph/osd/ceph-0 | grep wal
lrwxrwxrwx 1 root root 9 Oct 30 17:31 block.wal -> /dev/dm-6 ### 迁移之后的路径 ###

  • 把wal设备迁移到原来的db设备操作示例:

# ll /var/lib/ceph/osd/ceph-0/  | grep wal 
lrwxrwxrwx 1 ceph ceph 12 Oct 30 17:49 block.wal -> /dev/wal/wal ## 原路径

# ceph-bluestore-tool --log-level 30 --path /var/lib/ceph/osd/ceph-0 --devs-source /var/lib/ceph/osd/ceph-0/block.wal --dev-target /var/lib/ceph/osd/ceph-0/block.db bluefs-bdev-migrate
inferring bluefs devices from bluestore path
device removed:0 /var/lib/ceph/osd/ceph-0/block.wal ## 删除旧的wal设备

## ll /var/lib/ceph/osd/ceph-0 | grep wal 为空,不再有独立的wal设备

后记

通过以上的分析和实践,希望能够帮助读者加深对bluestore
的理解。Ceph分布式存储系统博大而精深,笔者也只是管中窥豹而已。限于笔者水平,难免有理解和描述上有疏漏或者错误的地方,欢迎共同交流。有任何侵权或者不明确的地方,欢迎指出,必定及时更正或者删除。

参考学习

BlueStore-先进的用户态文件系统《二》-BlueFS

谢型果等. Ceph设计原理与实现[M]. 北京:机械工业出版社,2017.12.

Ceph BlueStore BlueFS

番外彩弹

首先声明这是一条招聘广告,以下内容与前文无关。笔者所在的杭州挖财刚从P2P暴雷区中凤凰涅磐,向新的方向继续前行。现需求DBA资深中间件研发工程师存储平台资深研发工程师(中间件)高级/资深java开发工程师云平台研发工程师(监控、日志方向)等技术职位需求。这里没有996、没有PUA,有浓厚的技术氛围还有可爱的同事,欢迎简历砸过来 ,我微信:jayway8023

Ceph中国社区

是国内唯一官方正式授权的社区,

为广大Ceph爱好者提供交流平台!

↓↓↓

开源-创新-自强

官方网站:www.ceph.org.cn

合作邮箱:devin@ceph.org.cn

投稿地址:tougao@ceph.org.cn

长期招募热爱翻译人员,

参与社区翻译外文资料工作。

文章转载自Ceph开源社区,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论