深入解析 Crimson OSD 生命周期状态机:从 preboot、booting 到 active 的完整启停流程
【免费下载链接】cephCeph is a distributed object, block, and file storage platform项目地址: https://gitcode.com/gh_mirrors/ce/ceph
本文以 Ceph 新一代 OSD 实现 Crimson(基于 Seastar 协程的异步 OSD 守护进程)的OSDState状态机为主题,系统讲解 OSD 从启动注册、健康检查、等待激活到优雅下线的全部状态转换及其背后消息协议。读完本文,你将掌握 Crimson OSD 五个核心状态(preboot、booting、active、prestop、waiting_for_healthy)的语义、状态迁移触发条件,并能对照源码(src/crimson/osd/state.h、src/crimson/osd/osd.cc)追踪MOSDBoot、MOSDMarkMeDown等消息在启动与停机路径中的实际作用。
为什么 Crimson OSD 需要显式的状态机
经典ceph-osd的启动与停止逻辑分散在OSD::init()、OSD::handle_osd_map()等大量回调中,状态边界并不直观。Crimson 采用 Seastar 的sharded架构,把 OSD 拆分为多个 shard 并行运行,因此需要一个集中、可广播、可被各 shard 查询的守护进程级状态。
OSDState类(src/crimson/osd/state.h)正是为此设计:
- 它继承
seastar::peering_sharded_service<OSDState>,每个 shard 持有一个本地实例; - 状态枚举定义了 7 个值(state.h#L30-L38):
INITIALIZING、PREBOOT、BOOTING、ACTIVE、PRESTOP、STOPPING、WAITING_FOR_HEALTHY; PRIMARY_CORE(core 0)是状态迁移的权威执行者,set_active()/set_stopping()通过invoke_on_all把状态广播到所有 shard(state.h#L95-L111);- 任意 shard 都可以只读查询
is_active()、is_stopping(),并通过when_active()挂起等待,直到 OSD 进入active(state.h#L68-L74)。
从源码结构看,OSDState比文档状态图多出INITIALIZING与STOPPING两个内部状态:前者表示 OSD 仍在初始化、尚未订阅 osdmap(_handle_osd_map()会因此直接丢弃收到的地图,见 osd.cc#L1183-L1186),后者表示已进入最终停止流程。
状态机总览
原始设计文档 doc/dev/crimson/osd.rst 用 graphviz 描绘了如下状态迁移(本文以 Mermaid 状态图呈现同一语义):
| 状态 | 进入条件 | 退出条件 | 语义 |
|---|---|---|---|
waiting_for_healthy | 心跳不可达或内部心跳失败 | tick周期性复查;恢复健康后转preboot | 自检等待健康 |
preboot | 守护进程启动 / 被标记 down 后 | 地图就绪后发送MOSDBoot转booting | 准备注册 |
booting | 发出MOSDBoot | 收到标记自己为 up 的 osdmap 转active | 等待被 quorum 认可 |
active | osdmap 标记自己为 up | stop()/ 被标 down / SIGINT / 不健康 | 正常服务业务 |
prestop | 收到stop请求 | 收到更新后的 osdmap 或MOSDMarkMeDown | 优雅下线前的收尾 |
各状态语义详解
waiting_for_healthy:先证明自己健康,再谈服务
如果 OSD 守护进程无法连接到自己的心跳(heartbeat)对端,或者自身内部心跳失败,它就被判定为不健康,进入waiting_for_healthy状态,并周期性检查自身的网络可达性与内部心跳状态,直到恢复健康后才继续启动流程。
对应到代码,心跳子系统由 src/crimson/osd/heartbeat.h 的Heartbeat类实现,它实现了crimson::net::Dispatcher接口,监听心跳消息并维护与对端 OSD 的连接会话。需要说明的是,从当前源码看,waiting_for_healthy的完整实现仍处于待完善状态:在 osd.cc#L1329-L1336 有一处注释明确写着TODO: Missing start_waiting_for_healthy() counterpart(关联跟踪单 66832),当前实现是在未停止时持续订阅下一份 osdmap。这意味着文档描述的是该状态的既定设计意图,而代码中"进入waiting_for_healthy并周期性 tick 自检"的完整闭环仍在演进中。
preboot:向 Monitor 报告"我准备好了"
preboot是 OSD 启动后的第一个正式状态。OSD 向已连接的 Monitor 发送MOSDBoot消息,告知集群它已准备好对外服务,从而让 quorum 在 osdmap 中把它标记为up。
源码中的启动入口是OSD::start_boot()(osd.cc#L641-L648):
seastar::future<> OSD::start_boot() { pg_shard_manager.set_preboot(); return monc->get_version("osdmap").then(this { auto [newest, oldest] = ret; return _preboot(oldest, newest); }); }它先调用pg_shard_manager.set_preboot()置位状态(该调用最终转发到OSDState::set_preboot(),见 pg_shard_manager.h#L120),再从 Monitor 查询当前 osdmap 版本区间,进入_preboot()。
_preboot()(osd.cc#L650-L682)是一系列"准入检查",只有全部通过才会真正发送 boot 消息:
- osdmap 尚未取得首个地图(
get_epoch() == 0)时,等待初始 osdmap; - 若 osdmap 标记本 OSD 已被销毁(
is_destroyed),抛出异常终止; - 若设置了
NOUP标志,则等待其清除; - 要求
SORTBITWISE标志已设置; - 要求集群
require_osd_release不低于 octopus(此处可看出 Crimson 的基线版本约束); - 当地图 epoch 接近最新时,按
osd_map_message_max配置决定是否立即_send_boot();否则通过osdmap_subscribe补齐缺失的地图。
booting:等待被 quorum 认可
在正式被标记为up之前,OSD 必须停留在booting状态。_send_boot()(osd.cc#L684-L724)完成两个动作:
- 调用
pg_shard_manager.set_booting()把状态从preboot推进到booting; - 组装并发送
MOSDBoot消息,携带 superblock、当前 osdmap epoch、boot_epoch、心跳(front/back)地址、集群地址以及CEPH_FEATURES_ALL特性位。
值得注意的是,该消息的 metadata 中显式写入了osd_type = "crimson"(osd.cc#L715-L717)。从注释可知,这与OSDMonitor::preprocess_boot的校验逻辑相呼应——Monitor 只有在 osdmap 设置了允许 crimson 的allow_crimson标志时才会受理注册,这是集群侧对 Crimson OSD 的准入控制。此外还附带从对象存储读取的osd_objectstore类型(如seastore)。
active:正式对外服务
当 OSD 收到一份把自己标记为up的 osdmap 时,迁移到active状态,此后才有资格处理客户端 I/O、参与 PG 的 peering 与 recovery。
激活判断集中在committed_osd_maps()(osd.cc#L1256-L1354)。每消费一份新地图,OSD 都会检查三个条件:
if (bind_epoch < up_from && osdmap->get_addrs(whoami) == public_msgr->get_myaddrs() && pg_shard_manager.is_booting()) { INFO("osd.{}: activating...", whoami); co_await pg_shard_manager.set_active(); beacon_timer.arm_periodic( std::chrono::seconds(local_conf()->osd_beacon_report_interval)); tick_timer.arm(std::chrono::seconds(TICK_INTERVAL)); }即:本 OSD 在地图中处于up、绑定 epoch 早于up_from、公开地址与地图记录一致、且当前正处于booting——四者齐备才调用set_active()广播激活,同时启动周期性beacon(间隔由osd_beacon_report_interval配置控制)和 tick 定时器。send_beacon()只在is_active()时发送MOSDBeacon(osd.cc#L1617-L1630),这也是 Monitor 判断 OSD 存活的依据之一。
active状态并非永久稳定,文档明确指出存在三类退出路径:
- 管理员手动停止,或 osdmap 中标记
stop:committed_osd_maps()检测到osdmap->is_stop(whoami)后调用shutdown()(osd.cc#L1339-L1340),shutdown()内部通过abort_source.request_abort()触发整体中止(osd.cc#L1609-L1615); - 被标记 down 或地址不匹配:
should_restart()(osd.cc#L1560-L1582)检查"地图是否仍把本 OSD 标为 up"、"地图中的 public/cluster 地址是否与本地 messenger 实际绑定地址一致",任一不满足就触发restart()——取消各定时器、清零 up epoch,然后重新start_boot()(osd.cc#L1597-L1607),即状态回到preboot,这也对应图中active -> preboot的迁移; - 进程收到 SIGINT:直接终止(见下文停机流程)。
prestop:优雅下线的告别仪式
无论何种原因收到stop请求,OSD 都无条件转入prestop。但在真正告别之前,它会向 Monitor 发送MOSDMarkMeDown,请求得到确认——确认的方式是收到更新后的 osdmap(其中自己已不在up集合)或另一条MOSDMarkMeDown消息。
源码中这一流程由OSD::prepare_to_stop()承载(osd.cc#L1780-L1803):
if (osdmap && osdmap->is_up(whoami)) { pg_shard_manager.set_prestop(); const auto timeout = ... local_conf().get_val<double>("osd_mon_shutdown_timeout"); try { co_await seastar::with_timeout( ... , monc->send_message( crimson::make_message<MOSDMarkMeDown>( monc->get_fsid(), whoami, osdmap->get_addrs(whoami), osdmap->get_epoch(), true)).then([this] { return stop_acked.get_future(); })); } catch (seastar::timed_out_error&) {} }要点包括:
- 只有当前地图仍把自己标为
up时才需要走确认流程,否则无需等待; set_prestop()之后,OSD 停止接收新业务;- 发送
MOSDMarkMeDown并等待stop_ackedpromise 完成,等待时长受配置osd_mon_shutdown_timeout约束,超时则放弃等待继续关闭; - 两种"确认"分别由两个代码路径兑现:
handle_mark_me_down()在收到 Monitor 回传的MOSDMarkMeDown时调用got_stop_ack()(osd.cc#L1473-L1482);committed_osd_maps()在处理到一份已不再把本 OSD 标为 up 的地图时同样调用got_stop_ack()(osd.cc#L1323-L1326)。got_stop_ack()定义于 osd.h#L259-L263。
停机链路:从信号到状态机的完整闭环
prestop之上还有STOPPING状态。OSDState::set_stopping()会把本地状态置为STOPPING,同时向所有等待when_active()的协程抛出system_shutdown_exception(state.h#L50-L54),让那些"等待激活"的异步任务以异常方式尽快终止,避免悬挂。
进程级停机入口在 src/crimson/osd/main.cc:
osd.start().get(); INFO("crimson startup completed"); should_stop.wait().get(); // 等待 SIGINT / SIGTERM INFO("crimson shutting down"); osd.stop().get(); // 触发 OSD::stop()should_stop是 Seastar 应用库的stop_signal(src/crimson/osd/stop_signal.h),注册了 SIGINT 与 SIGTERM 的处理器:收到信号后置位 abort、广播条件变量,wait()随即返回,进而调用OSD::stop()。OSD::stop()(osd.cc#L855-L885)内部先调用prepare_to_stop()(即上面所述prestop确认流程),再依次停止 public/cluster messenger、admin socket、heartbeat、对象存储、mon/mgr 客户端及各 sharded service。这正是状态图中active -> end (kill(SIGINT))的代码级还原。
值得注意的是,SIGHUP 在 Crimson 中被显式忽略(main.cc#L190-L192),即不随信号重读配置,这与经典 OSD 的行为不同,也说明 Crimson 当前不依赖 SIGHUP 做配置热更新。
状态机的并发安全设计
由于 OSD 状态迁移(set_preboot、set_booting、set_prestop)都带有ceph_assert(seastar::this_shard_id() == PRIMARY_CORE)断言(state.h),所有状态写入被严格限制在 core 0,避免多核并发写状态造成竞态;而is_active()、is_stopping()则允许任意 shard 只读访问,用于本地快速判断是否继续派发业务。此外,MOSDMap的消费在 osd.cc#L1159-L1173 通过handle_osd_map_lock唯一锁串行化——因为committed_osd_maps()中的状态判定(is_booting()、is_preboot()、is_prestop())依赖于地图按序消费,同一时刻只允许一个地图处理流程推进状态机。
这种"单一写入者 + 多读副本 + 事件串行化"的设计,是 Crimson 将经典 OSD 生命周期管理适配到 Seastar 多 shard 模型下的核心思路,也是理解 doc/dev/crimson/osd.rst 状态图在真实运行时的必要背景。
小结
Crimson OSD 的生命周期可以用一条主线概括:启动后进入preboot,通过_preboot()一系列集群准入检查后发送MOSDBoot转入booting;收到把自己标记为 up 的 osdmap 后激活为active并开启 beacon/tick 周期任务;此后可能因停止请求进入prestop(发送MOSDMarkMeDown等待确认)直至STOPPING,或因健康问题回到waiting_for_healthy、因被标记 down 而回到preboot。对照 src/crimson/osd/state.h 与 src/crimson/osd/osd.cc 阅读本文,可以清晰地看到每一个状态转换背后对应的消息与函数调用,为排查 Crimson OSD 启动失败、无法激活或优雅停机超时等问题提供直接依据。
【免费下载链接】cephCeph is a distributed object, block, and file storage platform项目地址: https://gitcode.com/gh_mirrors/ce/ceph
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考