導(dǎo)讀
ceph luminous版本中新增加了一個(gè)組件: Ceph Manager Daemon赴魁,簡(jiǎn)稱ceph-mgr氨距。 該組件的主要作用是分擔(dān)和擴(kuò)展monitor的部分功能恩伺,減輕monitor的負(fù)擔(dān)徙瓶,讓更好地管理ceph存儲(chǔ)系統(tǒng)喊积。
本文檔基于luminous版本簡(jiǎn)單介紹ceph-mgr的源碼實(shí)現(xiàn)烹困。由于ceph-mgr還在開(kāi)發(fā)完善,可能最新版與本文檔部分內(nèi)容有所出入乾吻,但是大體框架上應(yīng)該是沒(méi)什么變化的髓梅。
本文檔不介紹ceph-mgr的安裝部署與使用,具體請(qǐng)參照官網(wǎng):Ceph Manager Daemon
本文檔代碼基于Luminous 12.2版本進(jìn)行解析绎签,由于社區(qū)存在代碼變動(dòng)枯饿,其它版本有可能對(duì)不上號(hào),但是整體架構(gòu)應(yīng)該不會(huì)有太大的變化诡必。
mgr的實(shí)現(xiàn)與用途
ceph-mgr是由C/C++奢方、python以及Cpython等共同編寫完成的,mgr的實(shí)現(xiàn)使用了大量的Extending Python with C or C++的語(yǔ)法爸舒,不熟悉這塊的可以先在python官網(wǎng)中科普一下蟋字。
由ceph-mgr的實(shí)現(xiàn)其實(shí)大概可以猜到,其將ceph的部分C/C++實(shí)現(xiàn)的接口python化(即以前只能通過(guò)調(diào)用c/c++接口發(fā)送msg獲取比如osdmap碳抄、monmap等集群狀態(tài)愉老,現(xiàn)通過(guò)mgr可以很方便地拿到。同時(shí)剖效,ceph-mgr支持用戶自定義的plugin(插件純python開(kāi)發(fā)嫉入,特別方便),用以實(shí)現(xiàn)特殊功能璧尸。
截至目前為止咒林,ceph-mgr的官方plugins包括:
Dashboard(WEB界面的管理)
Restful API(API方式獲取ceph信息僵井,應(yīng)該與之前的ceph-rest-api功能一致)官疲、
Zabbix掠拳、Prometheus崎淳、Influx(這三個(gè)實(shí)現(xiàn)了ceph的數(shù)據(jù)收集济炎、監(jiān)控等功能)
mgr組件
ceph-mgr的重要類或模塊包括:MgrStandy、Mgr敌完、DaemonServer碉纺、PyModules、ClusterState遣鼓、DaemonState等啸盏。其主要功能描述如下:
MgrStandby
所有mgr服務(wù)啟動(dòng)時(shí)身份都是standby,唯一作用是包含一個(gè)mgr的client端骑祟,獲取mgrmap及相關(guān)msg回懦。在獲取了mgr-map發(fā)現(xiàn)自己為當(dāng)前active時(shí),才會(huì)初始化mgr主服務(wù)進(jìn)程次企。當(dāng)mgrmap中變?yōu)榉莂ctive狀態(tài)怯晕,則shutdown mgr主服務(wù)進(jìn)程,釋放資源缸棵。
Mgr
主要工作是初始化daemonserver舟茶、pymodules、clusterstate等主要功能類蛉谜,并handle standby mgr client的非mgrmap的消息(osdmap稚晚、pgmap、fsmap等)型诚。執(zhí)行了monc->sub_want()函數(shù)客燕,注冊(cè)了定期獲取數(shù)據(jù)操作。
**DaemonServer **
為mgr主要的服務(wù)進(jìn)程狰贯,和osd也搓、mds等類似,初始化了一個(gè)mgr類型的Messenger涵紊,監(jiān)聽(tīng)有關(guān)mgr消息傍妒,主要是MSG_PGSTATS、MSG_MGR_REPORT摸柄、MSG_MGR_OPEN颤练、MSG_COMMAND。比如執(zhí)行‘ceph tell mgr {command}’時(shí)就被發(fā)送到daemonserver中handle_command函數(shù)進(jìn)行處理(包括了native命令和plugin的commands)
**PyModules **
包含ActivePyModule驱负、StandbyPyModules嗦玖、ActivePyModules、BaseMgrModules跃脊、BaseMgrStandbyModules宇挫、PyModulesRegistry、PyModuleRunner等類酪术,分別處理mgr處于active和standby時(shí)對(duì)plugins的處理器瘪,并在active時(shí)初始化python的運(yùn)行環(huán)境,將plugin模塊初始化并加載運(yùn)行。該類大量使用了python的c++擴(kuò)展接口橡疼。
**ClusterState **
保存了cluster的狀態(tài)援所,部分狀態(tài)在monc中,由mgr類定期更新?tīng)顟B(tài)(ms_dispatch)
**DaemonState **
保存了DaemonServer的狀態(tài)信息
這些類之間的關(guān)系如下圖所示:
vCeph-Mgr 啟動(dòng)過(guò)程**
圖片已經(jīng)很詳細(xì)了衰齐,文字就不多說(shuō)了任斋。
mgr基于Active-Standby模式继阻,大體流程就是mgr進(jìn)程啟動(dòng)時(shí)先以MgrStandby身份啟動(dòng)耻涛,即所有mgr啟動(dòng)時(shí)都是standby,然后通過(guò)map知道自己是Active的mgr后瘟檩,才會(huì)啟動(dòng)實(shí)際處理程序初始化DaemonServer抹缕,監(jiān)聽(tīng)有關(guān)mgr的消息,加載并運(yùn)行modules(一部分為用戶自定義plugins)墨辛。
Mgr的一些功能介紹
mgr提供了常用的幾種函數(shù)接口卓研,只要重載這些接口,就能開(kāi)發(fā)plugin實(shí)現(xiàn)特定功能睹簇。以下是官方的介紹奏赘,即實(shí)現(xiàn)服務(wù)器、消息通知太惠、自定義命令等功能:
serve: member function for server-type modules. This function should block forever.
notify: member function if your module needs to take action when new cluster data is available.
handle_command: member function if your module exposes CLI commands.
編寫自定義plugin
下面以簡(jiǎn)單的自定義命令為例磨淌,描述如何編寫mgr的plugin。其他功能可自行參考源碼中的ceph plugin凿渊,自行學(xué)習(xí)模仿編寫梁只。
步驟一:在src/pybind/mgr/ 目錄下新建一個(gè)plugin,名字為hello埃脏,然后在hello目錄下新建一個(gè)名為init.py和module.py(名字必須是module.py搪锣,這是mgr程序識(shí)別這個(gè)plugin的核心文件)。兩個(gè)文件內(nèi)容如下所示:
# __init__.py
from module import * # NOQA
# module.py
from mgr_module import MgrModule
class Module(MgrModule):
COMMANDS = [
{
"cmd": "hello",
"desc": "Say Hello",
"perm": "r"
}
]
def handle_hello(self, cmd):
return 0, "", "Hello World"
def handle_command(self, cmd):
self.log.error("handle_command")
if cmd['prefix'] == "hello":
return self.handle_hello(cmd)
else:
raise NotImplementedError(cmd['prefix'])
步驟二:配置ceph.conf文件彩掐,讓ceph-mgr啟動(dòng)時(shí)加載hello這個(gè)plugin构舟,修改如下:
[mgr]
mgr modules = restful dashboard hello
mgr data = /home/hhd/github/ceph/build/dev/mgr.$id
mgr module path = /home/hhd/github/ceph/src/pybind/mgr
步驟三:重啟ceph-mgr,然后執(zhí)行hello命令:ceph tell mgr hello 堵幽,那么狗超,CLI就會(huì)執(zhí)行并返回“Hello World”
handle_command 處理流程
只需要重載handle_command
通常情況下,我們都是使用ceph官方自帶的命令谐檀,如ceph -s抡谐、rbd create等等,通過(guò)ceph-mgr桐猬,我們可以自定義命令了麦撵。運(yùn)行命令如下所示:
ceph tell mgr <command | help>
該CLI命令最終會(huì)將請(qǐng)求Message發(fā)送到DaemonServer中進(jìn)行處理。DaemonServer根據(jù)Comand的類型調(diào)用對(duì)應(yīng)native函數(shù)或者plugin的handle_command函數(shù)進(jìn)行處理。其handle_command的處理流程如下所示:
CommandContext為handle_command()函數(shù)內(nèi)部定義的一個(gè)內(nèi)部類免胃,主要作用是管理執(zhí)行command的上下文管理音五。
如下為DaemonServer的dispatch實(shí)現(xiàn),發(fā)現(xiàn)MSG類型為COMMAND時(shí)轉(zhuǎn)到handle_command函數(shù)進(jìn)行處理羔沙。
bool DaemonServer::ms_dispatch(Message *m)
{
// Note that we do *not* take ::lock here, in order to avoid
// serializing all message handling. It's up to each handler
// to take whatever locks it needs.
switch (m->get_type()) {
case MSG_PGSTATS:
cluster_state.ingest_pgstats(static_cast<MPGStats*>(m));
maybe_ready(m->get_source().num());
m->put();
return true;
case MSG_MGR_REPORT:
return handle_report(static_cast<MMgrReport*>(m));
case MSG_MGR_OPEN:
return handle_open(static_cast<MMgrOpen*>(m));
case MSG_COMMAND:
return handle_command(static_cast<MCommand*>(m));
default:
dout(1) << "Unhandled message type " << m->get_type() << dendl;
return false;
};
}
在handle_command()中躺涝,大量代碼是檢索prefix對(duì)應(yīng)的command prefix,然后做處理扼雏。如果沒(méi)有找到native command坚嗜,才搜尋用戶自定義的plugin command。
bool DaemonServer::handle_command(MCommand *m)
{
...
// None of the special native commands,
ActivePyModule *handler = nullptr;
auto py_commands = py_modules.get_py_commands();
for (const auto &pyc : py_commands) {
auto pyc_prefix = cmddesc_get_prefix(pyc.cmdstring);
dout(1) << "pyc_prefix: '" << pyc_prefix << "'" << dendl;
if (pyc_prefix == prefix) {
handler = pyc.handler;
break;
}
}
if (handler == nullptr) {
ss << "No handler found for '" << prefix << "'";
dout(4) << "No handler found for '" << prefix << "'" << dendl;
cmdctx->reply(-EINVAL, ss);
return true;
} else {
// Okay, now we have a handler to call, but we must not call it
// in this thread, because the python handlers can do anything,
// including blocking, and including calling back into mgr.
dout(4) << "passing through " << cmdctx->cmdmap.size() << dendl;
finisher.queue(new FunctionContext([cmdctx, handler](int r_) {
std::stringstream ds;
std::stringstream ss;
int r = handler->handle_command(cmdctx->cmdmap, &ds, &ss);
cmdctx->odata.append(ds);
cmdctx->reply(r, ss);
}));
return true;
}
處理notify流程
編寫plugin時(shí)诗充,也可以重載notify函數(shù)苍蔬,目的是當(dāng)cluster狀態(tài)有變化時(shí),可以通知plugin做相應(yīng)的處理操作蝴蜓。其操作流程如下圖所示:
Mgr::ms_dispatch() --> PyModules::notify_all() --> PyMgrModules::motify()
Mgr定時(shí)獲取fsmap碟绑、osdmap等數(shù)據(jù),當(dāng)數(shù)據(jù)來(lái)臨時(shí)茎匠,一方面更新clusterState值格仲,一方面調(diào)用notify_all函數(shù)通知所有的Plugin模塊;notify_all函數(shù)會(huì)遍歷每個(gè)plugin诵冒,調(diào)用plugin的notify函數(shù)凯肋,以便plugin做相應(yīng)處理。
其代碼實(shí)現(xiàn)如下造烁,根據(jù)cluster狀態(tài)變化進(jìn)行notify:
bool Mgr::ms_dispatch(Message *m)
{
dout(4) << *m << dendl;
Mutex::Locker l(lock);
switch (m->get_type()) {
case MSG_MGR_DIGEST:
handle_mgr_digest(static_cast<MMgrDigest*>(m));
break;
case CEPH_MSG_MON_MAP:
py_module_registry->notify_all("mon_map", "");
m->put();
break;
case CEPH_MSG_FS_MAP:
py_module_registry->notify_all("fs_map", "");
handle_fs_map((MFSMap*)m);
return false; // I shall let this pass through for Client
break;
case CEPH_MSG_OSD_MAP:
handle_osd_map();
py_module_registry->notify_all("osd_map", "");
// Continuous subscribe, so that we can generate notifications
// for our MgrPyModules
objecter->maybe_request_map();
m->put();
break;
case MSG_SERVICE_MAP:
handle_service_map((MServiceMap*)m);
py_module_registry->notify_all("service_map", "");
m->put();
break;
case MSG_LOG:
handle_log(static_cast<MLog *>(m));
break;
default:
return false;
}
return true;
}
根據(jù)消息向注冊(cè)的plugin進(jìn)行通知否过。
void ActivePyModules::notify_all(const std::string ¬ify_type,
const std::string ¬ify_id)
{
Mutex::Locker l(lock);
dout(10) << __func__ << ": notify_all " << notify_type << dendl;
for (auto& i : modules) {
auto module = i.second.get();
// Send all python calls down a Finisher to avoid blocking
// C++ code, and avoid any potential lock cycles.
finisher.queue(new FunctionContext([module, notify_type, notify_id](int r){
module->notify(notify_type, notify_id);
}));
}
}
ceph集群狀態(tài)處理
在寫Mgr的plugin時(shí),經(jīng)常會(huì)導(dǎo)入ceph_module這個(gè)模塊惭蟋,通過(guò)ceph_module獲取cluster的狀態(tài)苗桂,具體操作如下:
import ceph_module
fsmap = ceph_module.get("fs_map")
# MgrModule包含了ceph_state,集成它可以入func這樣使用
class Module(MgrModule):
def func():
....
self.get("fs_map")
....
其原理如下所示:
定義BaseMgrModule告组,BaseMgrModule結(jié)構(gòu)體是python用戶接口的 plugin的基類煤伟,定義在mgr_module.py中。
// mgr/BaseMgrModule.cc
typedef struct {
PyObject_HEAD
ActivePyModules *py_modules;
ActivePyModule *this_module;
} BaseMgrModule;
python類MgrModule繼承于BaseMgrModule木缝,而所有的用戶plugin Module都需要繼承MgrModule便锨,其定義如下:
# pybind/mgr/mgr_module.py
class MgrModule(ceph_module.BaseMgrModule):
pass
而B(niǎo)aseMgrModule的方法定義在此:
// mgr/BaseMgrModule.cc
PyMethodDef BaseMgrModule_methods[] = {
{"_ceph_get", (PyCFunction)ceph_state_get, METH_VARARGS,
"Get a cluster object"},
{"_ceph_get_server", (PyCFunction)ceph_get_server, METH_VARARGS,
"Get a server object"},
{"_ceph_get_metadata", (PyCFunction)get_metadata, METH_VARARGS,
"Get a service's metadata"},
...
以我們經(jīng)常會(huì)使用到的get方法為例,其調(diào)用過(guò)程為:
MgrModule::get()-->BaseMgrModule::_ceph_get() --> ActiveModules::get_python()
在get_python()函數(shù)我碟,基于參數(shù)值進(jìn)行判斷放案,調(diào)用相應(yīng)的ceph C++庫(kù)函數(shù)向mon進(jìn)行數(shù)據(jù)獲取。其處理程序如下:
// ActiveModules.cc
PyObject *ActivePyModules::get_python(const std::string &what)
{
...
if (what == "fs_map") {
PyFormatter f;
cluster_state.with_fsmap([&f](const FSMap &fsmap) {
fsmap.dump(&f);
});
return f.get();
} else if (what == "osdmap_crush_map_text") {
...
}
// ClusterState.h
template<typename Callback, typename...Args>
void with_fsmap(Callback&& cb, Args&&...args) const
{
Mutex::Locker l(lock);
std::forward<Callback>(cb)(fsmap, std::forward<Args>(args)...);
}
上述大量使用了C++ 11的特性矫俺,包括lamda表達(dá)式吱殉、模板掸冤、decltype、forward友雳、尾置返回等語(yǔ)言特性稿湿。
PyFormatter類控制格式化輸出,fsmap為ClusterState的成員變量押赊,由Mgr在每次處理ms_dispatch時(shí)進(jìn)行更新饺藤。