Ceph ceph-volume lvm 深度解析:LVM Tag 元数据体系与 OSD 发现机制
【免费下载链接】cephCeph is a distributed object, block, and file storage platform项目地址: https://gitcode.com/gh_mirrors/ce/ceph
导读
ceph-volume lvm是 Ceph 官方推荐的 LVM 化 OSD 部署与激活工具,其核心设计思想是:把 OSD 的所有关键身份信息(OSD ID、FSID、集群 FSID、设备路径、UUID、加密状态等)以 LVM Tag 的形式写入逻辑卷元数据,从而让工具在任何时候都能仅凭这些 Tag 重新发现、识别、激活或清理设备。本文以仓库文档 doc/dev/ceph-volume/lvm.rst 为主体,结合src/ceph-volume源码,完整梳理 Tag API 的命名规范、全部元数据字段的含义、底层读写实现,以及这些 Tag 在 prepare / activate / list / deactivate / zap 全生命周期中的实际调用链。读完本文,你将能够读懂任意一块 OSD 逻辑卷上的lv_tags,并理解 ceph-volume 如何"凭标签找设备"。
一、为什么 ceph-volume lvm 需要一套 Tag 体系
LVM 本身提供了对卷元数据的扩展能力——LVM Tags。ceph-volume lvm的后端正是 LVM,它重度依赖 Tag 来完成两件事:
- 归属声明:把逻辑卷标记为"属于某个 Ceph OSD",防止与其他用途的卷混淆;
- 事后发现:OSD 被创建后,任何时刻(重启、迁移、故障恢复)都能通过查询 Tag 重新定位该 OSD 对应的全部底层设备。
在源码中,ceph-volume通过lvs、lvchange、pvchange等命令读写 Tag。例如 src/ceph-volume/ceph_volume/api/lvm.py 定义了读取逻辑卷信息时的字段集合:
LV_FIELDS = 'lv_tags,lv_path,lv_name,vg_name,lv_uuid,lv_size' LV_CMD_OPTIONS = ['--noheadings', '--readonly', '--separator=";"', '-a', '--units=b', '--nosuffix']其中lv_tags就是每次查询都会带回的 Tag 原始字符串,之后再交给解析函数转换为字典使用(详见下文第三节)。
⚠️重要警告:本套 Tag API 并非面向用户的公开接口,文档化它们只是为了说明工具在幕后做了什么。请勿手动增删改任何
ceph.*前缀的 Tag 值,否则可能破坏 OSD 的发现与激活流程。
二、Tag API 命名规范
将逻辑卷识别为 Ceph 的一部分,依赖于在所有卷上应用 Tag。Tag 遵循统一的命名空间约定:
ceph.<tag name>=<tag value>所有 Tag 都以ceph关键字为前缀,以此声明对该命名空间的所有权,并使其在众多 LVM 设备中易于识别。例如 OSD ID 在 LVM Tag 上下文中的写法是:
ceph.osd_id=0从源码角度看,这一约定在解析侧被严格执行:src/ceph-volume/ceph_volume/api/lvm.py 中的parse_tags()只提取以ceph.开头的 Tag,其余一律忽略:
def parse_tags(lv_tags: str) -> Dict[str, Any]: ... tags = lv_tags.split(',') for tag_assignment in tags: if not tag_assignment.startswith('ceph.'): continue key, value = tag_assignment.split('=', 1) tag_mapping[key] = value return tag_mapping输入形如"ceph.osd_fsid=aaa-fff-bbbb,ceph.osd_id=0"的逗号分隔字符串,输出为字典{"ceph.osd_fsid": "aaa-fff-bbbb", "ceph.osd_id": "0"}——这正是Volume对象tags属性的数据来源(见 Volume 类)。
三、元数据清单:OSD 存储在 LVM 卷上的全部 Tag
以下列出 Ceph OSD 存储在 LVM 卷上的全部元数据字段(来自 doc/dev/ceph-volume/lvm.rst)。这些字段随对象存储后端(Bluestore / Filestore)与配置不同而部分出现,下面按字段逐一说明。
3.1type—— 设备角色
描述设备是 OSD 还是 Journal,未来可扩展到其他受支持的类型(例如 lockbox):
ceph.type=osd在当前实现中,type的实际取值更丰富。从 objectstore/lvm.py 和 setup_metadata_devices 可以看出,prepare会把 block 设备标为ceph.type=block,db、wal 元数据设备则分别标为ceph.type=db、ceph.type=wal。相关判断函数volume_is_lvm_objectstore_lv()也只认可这三种取值(api/lvm.py):
def volume_is_lvm_objectstore_lv(lv: "Volume") -> bool: return lv.tags.get('ceph.type') in ('block', 'db', 'wal')3.2cluster_fsid—— 集群 FSID
标识该 OSD 所属的 Ceph 集群:
ceph.cluster_fsid=7146B649-AE00-4157-9F5D-1DBFF1D52C26在prepare阶段,该值由self.get_cluster_fsid()从本地集群配置读取并写入 Tag(objectstore/lvm.py)。
3.3data_device—— 数据设备路径
ceph.data_device=/dev/ceph/data-0主要服务于 Filestore 时代的 data LV 定位。
3.4data_uuid—— 数据设备 UUID
ceph.data_uuid=B76418EB-0024-401C-8955-AE6919D45CC33.5journal_device—— 日志设备路径
ceph.journal_device=/dev/ceph/journal-03.6journal_uuid—— 日志设备 UUID
ceph.journal_uuid=2070E121-C544-4F40-9571-0B7F35C6CB2B3.7encrypted—— 加密状态
启用 LUKS(dmcrypt)加密时:
ceph.encrypted=1不支持或禁用加密时:
ceph.encrypted=0该 Tag 在激活/销毁流程中决定是否需要对设备执行 LUKS 打开或关闭操作。例如 Volume 类 直接以它派生布尔属性:
self.encrypted = self.tags.get('ceph.encrypted', '0') == '1'而 deactivate.py 在停用 OSD 时会据此关闭 dmcrypt 映射:
for lv in lvs: if lv.tags.get('ceph.encrypted', '0') == '1': encryption.dmcrypt_close(mapping=lv.lv_uuid, skip_path_check=True)3.8osd_fsid—— OSD FSID
OSD 自身的唯一标识(与集群 FSID 不同):
ceph.osd_fsid=88ab9018-f84b-4d62-90b4-ce7c076728ffosd_fsid与osd_id一起构成"按身份查找 OSD"的主键,是激活(activate)与停用(deactivate)时最重要的查询条件。
3.9osd_id—— OSD ID
ceph.osd_id=1ceph.osd_id还是判断"设备是否属于 Ceph"的哨兵字段。is_ceph_device() 的实现如下:
def is_ceph_device(lv: "Volume") -> bool: osd_id = lv.tags.get('ceph.osd_id', 'null') if osd_id == 'null': logger.warning('device is not part of ceph: %s', lv) return False return TrueVolume对象同样用'ceph.osd_id' in self.tags计算used_by_ceph属性(api/lvm.py)。
3.10block_device—— 数据卷路径(仅 Bluestore)
捕获数据逻辑卷的路径:
ceph.block_device=/dev/mapper/vg-block-03.11block_uuid—— 数据卷 UUID(仅 Bluestore)
捕获逻辑卷 UUID 或分区 UUID:
ceph.block_uuid=E5F041BB-AAD4-48A8-B3BF-31F7AFD7D73E在 objectstore/lvm.py 中,prepare会把 block LV 的lv_path与lv_uuid直接写入这两个 Tag。
3.12db_device—— DB 设备路径(仅 Bluestore)
ceph.db_device=/dev/mapper/vg-db-03.13db_uuid—— DB 设备 UUID(仅 Bluestore)
ceph.db_uuid=F9D02CF1-31AB-4910-90A3-6A63023755253.14wal_device—— WAL 设备路径(仅 Bluestore)
ceph.wal_device=/dev/mapper/vg-wal-03.15wal_uuid—— WAL 设备 UUID(仅 Bluestore)
ceph.wal_uuid=A58D1C68-0D6E-4CB3-8E99-B261AD47CC39关于 db/wal 的补充:从 setup_metadata_devices() 的实现看,--block.db、--block.wal可以指向三种形态:已有 LV(vg/lv,直接打 Tag)、整块物理设备(新建osd-db-*/osd-wal-*LV 并打 Tag)、以及裸分区(不打设备路径 Tag,只记录ceph.<type>_uuid,后续通过blkid按 PARTUUID 反查设备)。zap 阶段正是利用这一点来还原物理设备路径(见 zap.py)。
3.16vdo—— VDO 压缩去重设备标记
VDO 启用的设备在prepare阶段被检测出来,存入 Tag 供后续激活时检查。该标记会影响挂载选项:无论用户指定了什么挂载参数,都会追加discard挂载标志。
VDO 设备启用时:
ceph.vdo=1检测逻辑位于 api/lvm.py 的_is_vdo()/is_vdo():通过遍历/sys/kvdo/<name>/statistics与/sys/block/*/slaves交叉比对,确认设备路径(含 mapper 路径与 realpath)是否由 VDO 支撑;异常时安全降级返回0,避免破坏 OSD 创建流程。
3.17 源码中额外出现的补充 Tag
除原文档列出的字段外,当前仓库实现还写入了以下 Tag(信息见 objectstore/lvm.py,一并列出便于排查lv_tags时对照):
| Tag | 含义 |
|---|---|
ceph.cluster_name | 集群名称(来自conf.cluster,默认ceph) |
ceph.crush_device_class | 指定的 CRUSH 设备类型(--crush-device-class) |
ceph.osdspec_affinity | 来自编排器 osdspec 的亲和性标记 |
ceph.objectstore | 对象存储后端(如bluestore) |
ceph.cephx_lockbox_secret | dmcrypt 加密时的 lockbox 密钥引用 |
ceph.with_tpm | 是否使用 TPM2 参与解密(1/0) |
四、底层实现:Tag 的写入、清除与查询
4.1 写入与清除:lvchange --addtag / --deltag
Volume对象继承自 Lvm 基类,Tag 写入最终落到lvchange命令。set_tags()会先清掉同名旧 Tag,再一次性批量添加(api/lvm.py):
def set_tags(self, tags: Dict[str, Any]) -> None: ... self.clear_tags(list(tags.keys())) add_tag_args = self._format_tag_args('--addtag', tags) process.call([self.binary_change] + add_tag_args + [self.path], run_on_host=True) for k, v in tags.items(): self.tags[k] = v其中binary_change在Volume类中被赋值为lvchange(api/lvm.py)。物理卷的 Tag 则由PVolume类通过pvchange --addtag/--deltag管理(api/lvm.py)。
4.2 查询:lvs -S按 Tag 过滤
按 Tag 查询逻辑卷的核心函数是 get_lvs(),它把过滤条件转成lvs -S的参数:
def get_lvs(fields: str = LV_FIELDS, filters=None, tags=None): ... filters_str = make_filters_lvmcmd_ready(filters, tags) args = ['lvs'] + LV_CMD_OPTIONS + ['-S', filters_str, '-o', fields]而get_lvs_by_tag()直接构造lv_tags={...}选择子(api/lvm.py):
def get_lvs_by_tag(lv_tag: str) -> List[Volume]: ... ['lvs', '--noheadings', '--separator=";"', '-a', '-o', LV_FIELDS, '-S', 'lv_tags={{{}}}'.format(lv_tag)]基于此提供了按 OSD 身份查询的便捷入口(api/lvm.py):
def get_lvs_from_osd_id(osd_id: str) -> List[Volume]: return get_lvs(tags={'ceph.osd_id': osd_id}) def get_single_lv_from_osd_id(osd_id: str) -> Optional[Volume]: return get_single_lv(tags={'ceph.osd_id': osd_id})测试用例对这类"按 Tag 查找"的行为有完整覆盖,可参考 tests/api/test_api.py 与 tests/objectstore/test_lvm.py(后者断言激活时按{'ceph.osd_id': '1', 'ceph.osd_fsid': ...}与{'ceph.osd_fsid': '...'}调用get_lvs)。
五、Tag 在 OSD 全生命周期中的调用链
5.1 prepare:写入 Tag 的时刻
ceph-volume lvm prepare的作用是"格式化 LVM 设备并将其与 OSD 关联"(prepare.py)。执行过程(objectstore/lvm.py)为:
pre_prepare():生成 OSD FSID / ID,向 Monitor 注册 OSD,并组装self.tags字典(上文 3.17 节);setup_metadata_devices():为--block.db/--block.wal指定的设备打上ceph.type=db|wal及对应 UUID Tag;self.tags['ceph.type'] = 'block',然后对 block LV 调用set_tags()一次性写入全部 Tag;- 若启用加密(
--dmcrypt),调用luks_format_and_open(),并利用ceph.<type>_uuid作为 dmcrypt 映射名(objectstore/lvm.py); - 执行 Bluestore
mkfs。
典型用法(摘自 prepare.py 的帮助文本):
# 使用已有逻辑卷 ceph-volume lvm prepare --data {vg/lv} # 使用整块设备(会自动创建逻辑卷) ceph-volume lvm prepare --data /path/to/device # 可选地指定 db / wal 设备(分区或逻辑卷) ceph-volume lvm prepare --data {vg/lv} --block.wal {partition} --block.db {/path/to/device}prepare 失败时,safe_prepare() 会回滚已创建的 OSD ID(调用rollback_osd),避免留下"有 Tag 无 OSD"的脏数据。
5.2 activate:凭 Tag 发现并挂载
ceph-volume lvm activate的核心是"通过 LVM 发现 OSD 并将其挂载到目标位置"(activate.py):
ceph-volume lvm activate {ID} {FSID} # 批量激活 ceph-volume lvm activate --all其底层(objectstore/lvm.py)把osd_id/osd_fsid组合成 Tag 过滤条件后执行查询:
if osd_id and osd_fsid: tags = {'ceph.osd_id': osd_id, 'ceph.osd_fsid': osd_fsid} elif not osd_id and osd_fsid: tags = {'ceph.osd_fsid': osd_fsid} ... lvs = api.get_lvs(tags=tags) if not lvs: raise RuntimeError('could not find osd.%s with osd_fsid %s' % ...)activate_all则先调用 direct_report() 列出全部 OSD,再逐个提取 Tag 中的ceph.osd_fsid进行激活(objectstore/lvm.py)。
5.3 list:按 OSD 聚合展示
ceph-volume lvm list的输出同样以 Tag 为骨架。create_report() 首先用is_ceph_device(lv)过滤出属于 Ceph 的卷,然后以lv.tags['ceph.osd_id']为 key 聚合同一 OSD 的所有设备,并对非 LV 形态的 journal/wal/db 设备,通过ceph.<type>_device/ceph.<type>_uuid还原为分区信息:
osd_id = lv.tags['ceph.osd_id'] report.setdefault(osd_id, []) ... if lv.tags.get('ceph.type', '') in ['data', 'block']: for dev_type in ['journal', 'wal', 'db']: dev = lv.tags.get('ceph.{}_device'.format(dev_type), '')5.4 deactivate:凭 Tag 卸载并关密
deactivate.py 同时支持按osd_fsid或osd_id查询:
if osd_uuid is not None: lvs = get_lvs_by_tag('ceph.osd_fsid={}'.format(osd_uuid)) osd_id = next(lv.tags['ceph.osd_id'] for lv in lvs) else: lvs = get_lvs_by_tag('ceph.osd_id={}'.format(osd_id))随后从 block/data LV 的 Tag 中读取ceph.cluster_name以确定 tmpfs 挂载路径,卸载 OSD tmpfs,并对ceph.encrypted=1的卷执行 dmcrypt 关闭。对应测试见 tests/devices/lvm/test_deactivate.py。
5.5 zap / migrate:按 Tag 清理与迁移
- zap:
ceph-volume lvm zap先用{'ceph.osd_id': ..., 'ceph.osd_fsid': ...}找到目标 LV,再按ceph.type=db|wal找出所有关联的 db/wal LV 一并销毁;对分区形态的 db/wal,则通过ceph.<type>_uuid调用blkid反查 PARTUUID 对应的物理设备(zap.py)。zap 完成后会调用lv.clear_tags()清除所有ceph.*Tag(zap.py)。 - migrate:OSD 卷迁移(如把 db/wal 挪到新设备)时,同样以
get_lvs(tags=lv_tags)定位源与目标卷,并以ceph.type=block|db|wal区分角色(migrate.py)。
六、实践建议与注意事项
- 不要手工修改 Tag:所有
ceph.*Tag 都由工具维护。手工改动可能造成"激活时找不到设备""zap 时误删卷"等连锁问题;如确需清理,应使用ceph-volume lvm zap这类官方流程。 - 用
ceph-volume lvm list --format json检查 Tag:这是排查"某块盘属于哪个 OSD"最直接的入口,输出中的tags字段即解析后的 Tag 字典;该 JSON 输出设计上兼容 ceph-ansible 等自动化系统(listing.py)。 - 分区形态的 db/wal 设备:它们不拥有
ceph.<type>_deviceTag,只保留ceph.<type>_uuid(分区 UUID),因此在设备路径漂移后仍可被准确定位——这也是设计上刻意选择"记 UUID 而非路径"的原因。 - VDO 设备会自动追加
discard挂载参数:如果底层存储经过 VDO 压缩去重,激活阶段会无视用户挂载选项强制追加discard,这是由ceph.vdo=1决定的预期行为。 - 关注加密与 TPM:
ceph.encrypted、ceph.with_tpm共同决定激活时是走 LUKS 口令解密还是 TPM2 密封解锁;停用(deactivate)时加密卷会同步关闭 dmcrypt 映射,避免密钥泄漏风险。
结语
LVM Tag 是ceph-volume lvm得以"无状态化"运转的基石:prepare写入身份,activate/list/deactivate/zap/migrate依据身份重新发现设备,全程不依赖任何额外的元数据数据库。理解这套ceph.<key>=<value>约定,无论是排查 OSD 无法激活、设备归属混乱,还是编写基于 ceph-volume 的自动化编排,都能事半功倍。后续若需深入,可继续阅读 ceph-volume 顶层文档 及其余 LVM 子命令源码(devices/lvm)。
【免费下载链接】cephCeph is a distributed object, block, and file storage platform项目地址: https://gitcode.com/gh_mirrors/ce/ceph
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考