Ceph ceph-volume lvm 深度解析:LVM Tag 元数据体系与 OSD 发现机制
2026/9/21 19:57:59 网站建设 项目流程

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 Tagsceph-volume lvm的后端正是 LVM,它重度依赖 Tag 来完成两件事:

  1. 归属声明:把逻辑卷标记为"属于某个 Ceph OSD",防止与其他用途的卷混淆;
  2. 事后发现:OSD 被创建后,任何时刻(重启、迁移、故障恢复)都能通过查询 Tag 重新定位该 OSD 对应的全部底层设备。

在源码中,ceph-volume通过lvslvchangepvchange等命令读写 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=dbceph.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-AE6919D45CC3

3.5journal_device—— 日志设备路径

ceph.journal_device=/dev/ceph/journal-0

3.6journal_uuid—— 日志设备 UUID

ceph.journal_uuid=2070E121-C544-4F40-9571-0B7F35C6CB2B

3.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-ce7c076728ff

osd_fsidosd_id一起构成"按身份查找 OSD"的主键,是激活(activate)与停用(deactivate)时最重要的查询条件。

3.9osd_id—— OSD ID

ceph.osd_id=1

ceph.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 True

Volume对象同样用'ceph.osd_id' in self.tags计算used_by_ceph属性(api/lvm.py)。

3.10block_device—— 数据卷路径(仅 Bluestore)

捕获数据逻辑卷的路径:

ceph.block_device=/dev/mapper/vg-block-0

3.11block_uuid—— 数据卷 UUID(仅 Bluestore)

捕获逻辑卷 UUID 或分区 UUID:

ceph.block_uuid=E5F041BB-AAD4-48A8-B3BF-31F7AFD7D73E

在 objectstore/lvm.py 中,prepare会把 block LV 的lv_pathlv_uuid直接写入这两个 Tag。

3.12db_device—— DB 设备路径(仅 Bluestore)

ceph.db_device=/dev/mapper/vg-db-0

3.13db_uuid—— DB 设备 UUID(仅 Bluestore)

ceph.db_uuid=F9D02CF1-31AB-4910-90A3-6A6302375525

3.14wal_device—— WAL 设备路径(仅 Bluestore)

ceph.wal_device=/dev/mapper/vg-wal-0

3.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_secretdmcrypt 加密时的 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_changeVolume类中被赋值为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)为:

  1. pre_prepare():生成 OSD FSID / ID,向 Monitor 注册 OSD,并组装self.tags字典(上文 3.17 节);
  2. setup_metadata_devices():为--block.db/--block.wal指定的设备打上ceph.type=db|wal及对应 UUID Tag;
  3. self.tags['ceph.type'] = 'block',然后对 block LV 调用set_tags()一次性写入全部 Tag;
  4. 若启用加密(--dmcrypt),调用luks_format_and_open(),并利用ceph.<type>_uuid作为 dmcrypt 映射名(objectstore/lvm.py);
  5. 执行 Bluestoremkfs

典型用法(摘自 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_fsidosd_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 清理与迁移

  • zapceph-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)。

六、实践建议与注意事项

  1. 不要手工修改 Tag:所有ceph.*Tag 都由工具维护。手工改动可能造成"激活时找不到设备""zap 时误删卷"等连锁问题;如确需清理,应使用ceph-volume lvm zap这类官方流程。
  2. ceph-volume lvm list --format json检查 Tag:这是排查"某块盘属于哪个 OSD"最直接的入口,输出中的tags字段即解析后的 Tag 字典;该 JSON 输出设计上兼容 ceph-ansible 等自动化系统(listing.py)。
  3. 分区形态的 db/wal 设备:它们不拥有ceph.<type>_deviceTag,只保留ceph.<type>_uuid(分区 UUID),因此在设备路径漂移后仍可被准确定位——这也是设计上刻意选择"记 UUID 而非路径"的原因。
  4. VDO 设备会自动追加discard挂载参数:如果底层存储经过 VDO 压缩去重,激活阶段会无视用户挂载选项强制追加discard,这是由ceph.vdo=1决定的预期行为。
  5. 关注加密与 TPMceph.encryptedceph.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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询