Salt linux_lvm 执行模块完全指南:用 Salt 自动化管理 LVM2 物理卷、卷组与逻辑卷

原创2026-09-23 00:00:111,856 阅读
文章标签:运维配置管理后端

Salt linux_lvm 执行模块完全指南:用 Salt 自动化管理 LVM2 物理卷、卷组与逻辑卷

本指南以 Salt 官方 API 文档 doc/ref/modules/all/salt.modules.linux_lvm.rst 所对应的 linux_lvm 执行模块 为核心,系统讲解如何通过 Salt 在 Linux 主机上自动化完成 LVM2 的完整生命周期管理:从版本查询、物理卷(PV)/卷组(VG)/逻辑卷(LV)信息盘点,到物理卷创建与移除、卷组创建与扩容、逻辑卷创建(含快照、精简配置 thin pool/thin volume)、扩容、缩容与删除。读完本文,你将掌握 lvm.* 系列函数的全部参数、底层命令映射与返回值结构,并能够借助配套的 lvm 状态模块 写出幂等、可下发的声明式 SLS 配置。

一、模块概览与加载机制

linux_lvm 是 Salt 中专门对接 Linux LVM2(Logical Volume Manager 2)工具链的执行模块,为方便调用,其虚拟名(__virtualname__)为 lvm,因此所有函数通过 salt '*' lvm.<function> 形式调用,例如 salt '*' lvm.version。

从源码 salt/modules/linux_lvm.py 可以看到它的加载条件非常严格:

__virtualname__ = "lvm"

def __virtual__():
    if salt.utils.path.which("lvm"):
        return __virtualname__
    return (
        False,
        "The linux_lvm execution module cannot be loaded: the lvm binary is not in the"
        " path.",
    )

这意味着:只有当目标 minion 上安装了 LVM2 工具包(提供 lvm 可执行文件,通常由发行版的 lvm2 软件包安装)且该二进制出现在 PATH 中时,模块才会被加载;否则模块加载失败,并在错误信息中明确提示 "the lvm binary is not in the path"。因此在使用本模块前,建议先通过 pkg 状态或 cmd.run 确保目标主机已安装 lvm2:

lvm2:
  pkg.installed

模块实现特点

该模块的每个函数都通过 __salt__["cmd.run"] / __salt__["cmd.run_all"] 调用系统 LVM 命令(如 pvdisplay、vgcreate、lvcreate 等),并且在绝大多数情况下使用 python_shell=False,直接以参数列表方式执行,避免 shell 注入风险。所有返回结构统一为 Python 字典,方便与其他 Salt 模块、状态模块或 reactor 事件配合使用。

二、版本信息查询:version 与 fullversion

lvm.version

返回 LVM 版本号字符串。底层执行 lvm version 并取第一行中冒号后的内容:

salt '*' lvm.version

对应源码 version():cmd.run("lvm version") 输出的首行类似 LVM version: 2.02.168(2) (2016-11-30),函数按 ": " 分割后返回 "2.02.168(2) (2016-11-30)"。

lvm.fullversion

返回包含全部版本信息的字典。同样的 lvm version 输出,但会逐行解析每一行冒号前后的内容,得到类似下面的结构:

{
    "LVM version":     "2.02.168(2) (2016-11-30)",
    "Library version": "1.03.01 (2016-11-30)",
    "Driver version":  "4.35.0",
}

调用方式:

salt '*' lvm.fullversion

这一能力可用于在 SLS 中判断 LVM 版本是否满足特定功能(如 thin provisioning 支持)的前置条件。单元测试 test_linux_lvm.py 使用模拟的 cmd.run 输出验证了这两个函数的解析逻辑。

三、信息盘点:pvdisplay / vgdisplay / lvdisplay

三个 *display 函数分别返回物理卷、卷组、逻辑卷的信息字典,底层均以 -c(machine-readable,冒号分隔)模式调用对应命令,并把每一列映射为带语义的字段名。它们的共同点是:

  • 不指定名称时返回全部对象的字典;指定名称时只返回该对象;
  • 底层命令返回码非 0 时函数返回空字典 {},不会抛异常(配合 quiet=True 可完全静默,方便做存在性判断);
  • 返回字典的键(key)是设备/卷组/逻辑卷名称,值是字段字典。

lvm.pvdisplay —— 物理卷信息

salt '*' lvm.pvdisplay
salt '*' lvm.pvdisplay /dev/md0

参数说明:

  • pvname:物理设备名称,可选;
  • real:若为 True,则通过 os.path.realpath 解引用符号链接,报告真实设备,并在结果中额外增加 Real Physical Volume Device 字段。该参数自 2015.8.7 版本引入;
  • quiet:若为 True,当物理卷不存在时不显示错误(映射为 cmd.run_all 的 ignore_retcode=True)。

底层执行 pvdisplay -c,每行冒号分隔的 11 个字段被映射为:

返回字段 说明
Physical Volume Device 物理卷设备名
Volume Group Name 所属卷组名
Physical Volume Size (kB) 物理卷大小(KB)
Internal Physical Volume Number 内部物理卷编号
Physical Volume Status 物理卷状态
Physical Volume (not) Allocatable 是否(不可)分配
Current Logical Volumes Here 该 PV 上的逻辑卷数
Physical Extent Size (kB) 物理扩展块大小(KB)
Total Physical Extents 总物理扩展块数
Free Physical Extents 空闲物理扩展块数
Allocated Physical Extents 已分配物理扩展块数

对应源码 pvdisplay() 会跳过包含 "is a new physical volume" 的提示行(即尚未初始化的设备)。

lvm.vgdisplay —— 卷组信息

salt '*' lvm.vgdisplay
salt '*' lvm.vgdisplay nova-volumes

参数 vgname 指定卷组名,quiet 与 pvdisplay 语义相同。底层执行 vgdisplay -c,17 个字段映射为:

返回字段 说明
Volume Group Name 卷组名
Volume Group Access 卷组访问权限
Volume Group Status 卷组状态
Internal Volume Group Number 内部卷组编号
Maximum Logical Volumes 最大逻辑卷数
Current Logical Volumes 当前逻辑卷数
Open Logical Volumes 打开的逻辑卷数
Maximum Logical Volume Size 最大逻辑卷大小
Maximum Physical Volumes 最大物理卷数
Current Physical Volumes 当前物理卷数
Actual Physical Volumes 实际物理卷数
Volume Group Size (kB) 卷组大小(KB)
Physical Extent Size (kB) 物理扩展块大小(KB)
Total Physical Extents 总物理扩展块数
Allocated Physical Extents 已分配物理扩展块数
Free Physical Extents 空闲物理扩展块数
UUID 卷组 UUID

对应源码 vgdisplay()。

lvm.lvdisplay —— 逻辑卷信息

salt '*' lvm.lvdisplay
salt '*' lvm.lvdisplay /dev/vg_myserver/root

参数 lvname 指定逻辑卷设备路径,quiet 语义同上。底层执行 lvdisplay -c,13 个字段映射为:

返回字段 说明
Logical Volume Name 逻辑卷名
Volume Group Name 所属卷组名
Logical Volume Access 逻辑卷访问权限
Logical Volume Status 逻辑卷状态
Internal Logical Volume Number 内部逻辑卷编号
Open Logical Volumes 打开的逻辑卷数
Logical Volume Size 逻辑卷大小
Current Logical Extents Associated 当前关联逻辑扩展块数
Allocated Logical Extents 已分配逻辑扩展块数
Allocation Policy 分配策略
Read Ahead Sectors 预读扇区数
Major Device Number 主设备号
Minor Device Number 次设备号

对应源码 lvdisplay()。注意 Logical Volume Size 的单位是 512 字节扇区数(sectors),状态模块中会将其换算为 MB 以进行比较(见下文"状态模块集成"一节)。

四、物理卷管理:pvcreate / pvremove / pvresize

lvm.pvcreate —— 初始化物理卷

将物理设备初始化为 LVM 物理卷:

salt mymachine lvm.pvcreate /dev/sdb1,/dev/sdb2
salt mymachine lvm.pvcreate /dev/sdb1 dataalignmentoffset=7s

参数与行为(对应源码 pvcreate()):

  • devices:必填,可以传字符串(多个设备用逗号分隔)或列表;为空时返回错误 "Error: at least one device is required";
  • 设备不存在(os.path.exists 为假)时返回 "<device> does not exist";
  • override=True(默认):跳过已经是 LVM 物理卷的设备;若 override=False 且设备已是 PV,则直接返回 'Device "<device>" is already an LVM physical volume.';
  • force=True(默认):向命令追加 --yes,跳过交互确认;否则追加 -qq(安静模式);
  • 若所有指定设备都已是 PV,函数直接返回 True,不会执行任何命令(幂等);
  • 支持的 kwargs(会映射为 --<name> <value> 追加到 pvcreate 命令):
关键字参数 对应 pvcreate 选项
metadatasize --metadatasize
dataalignment --dataalignment
dataalignmentoffset --dataalignmentoffset
pvmetadatacopies --pvmetadatacopies
metadatacopies --metadatacopies
metadataignore --metadataignore
restorefile --restorefile
norestorefile --norestorefile(无值开关,仅追加参数名)
labelsector --labelsector
setphysicalvolumesize --setphysicalvolumesize
  • 命令执行失败(retcode 非 0)时返回 stderr 内容;成功后会逐个调用 pvdisplay 校验,若某设备未生效则返回 'Device "<device>" was not affected.';全部生效返回 True。

lvm.pvremove —— 移除物理卷

将设备从 LVM 中移除(取消 PV 标记):

salt mymachine lvm.pvremove /dev/sdb1,/dev/sdb2

对应源码 pvremove():只有 pvdisplay 能确认的 PV 才会被加入命令;若设备不是 PV 且 override=False,返回 "<device> is not a physical volume";所有设备都不是 PV 时返回 True。执行后同样会校验每个设备确实已不再是 PV,否则返回 'Device "<device>" was not affected.'。

lvm.pvresize —— 调整物理卷大小

将 PV 调整为底层物理设备的大小(常用于底层磁盘扩容后同步 LVM 元数据):

salt mymachine lvm.pvresize /dev/sdb1,/dev/sdb2

对应源码 pvresize():逻辑与 pvremove 类似,仅对 pvdisplay 确认存在的 PV 执行 pvresize;override=False 时对非 PV 设备返回 "<device> is not a physical volume"。

五、卷组管理:vgcreate / vgextend / vgremove

lvm.vgcreate —— 创建卷组

salt mymachine lvm.vgcreate my_vg /dev/sdb1,/dev/sdb2
salt mymachine lvm.vgcreate my_vg /dev/sdb1 clustered=y

对应源码 vgcreate():

  • vgname 与 devices 均必填,缺失时返回 "Error: vgname and device(s) are both required";
  • force=False(默认)时追加 -qq;force=True 时追加 --yes;
  • 支持的 kwargs(映射为 --<name> <value>):
关键字参数 对应 vgcreate 选项
addtag --addtag
alloc --alloc
autobackup --autobackup
clustered --clustered
maxlogicalvolumes --maxlogicalvolumes
maxphysicalvolumes --maxphysicalvolumes
metadatatype --metadatatype
vgmetadatacopies --vgmetadatacopies
metadatacopies --metadatacopies
physicalextentsize --physicalextentsize
zero --zero
  • 返回值是 vgdisplay(vgname) 的结果字典,并额外包含 "Output from vgcreate" 键(成功时为 'Volume group "<vgname>" successfully created',失败时为 stderr 内容)。

lvm.vgextend —— 扩展卷组

向已有卷组添加物理卷:

salt mymachine lvm.vgextend my_vg /dev/sdb1,/dev/sdb2
salt mymachine lvm.vgextend my_vg /dev/sdb1

对应源码 vgextend():参数校验与 vgcreate 相同;返回 {"Output from vgextend": "<消息>"},成功消息为 'Volume group "<vgname>" successfully extended'。

lvm.vgremove —— 删除卷组

salt mymachine lvm.vgremove vgname
salt mymachine lvm.vgremove vgname force=True

对应源码 vgremove():force=True(默认)追加 --yes,否则 -qq;成功返回 'Volume group "<vgname>" successfully removed',失败返回 stderr 内容。

六、逻辑卷管理:lvcreate / lvremove / lvresize / lvextend

lvm.lvcreate —— 创建逻辑卷(含快照与精简配置)

基础用法:

salt '*' lvm.lvcreate new_volume_name     vg_name size=10G
salt '*' lvm.lvcreate new_volume_name     vg_name extents=100 pv=/dev/sdb
salt '*' lvm.lvcreate new_snapshot        vg_name snapshot=volume_name size=3G

自 0.12.0 版本起支持 thin pool(精简池)与 thin volume(精简卷):

salt '*' lvm.lvcreate new_thinpool_name   vg_name               size=20G thinpool=True
salt '*' lvm.lvcreate new_thinvolume_name vg_name/thinpool_name size=10G thinvolume=True

参数说明(对应源码 lvcreate()):

  • lvname:逻辑卷名;vgname:所在卷组名(thin volume 场景下可写为 vg_name/thinpool_name);
  • size 与 extents:二选一,同时指定返回 "Error: Please specify only one of size or extents";两者都未指定返回 "Error: Either size or extents must be specified"。size 映射为 -L,extents 映射为 -l(支持 100%FREE 等百分比写法);thin volume 的虚拟大小使用 -V;
  • snapshot:指定快照源卷,映射为 -s <vgname>/<snapshot>,创建的是源卷的快照;
  • pv:指定在哪个物理卷上创建,作为命令尾参追加;
  • thinvolume=True 时使用 --thin -n <lvname>;thinpool=True 时使用 --thinpool <lvname>;两者同时为 True 返回错误 "Error: Please set only one of thinvolume or thinpool to True";thin volume 的大小不能用 extents 指定(返回 "Error: Thin volume size cannot be specified as extents");
  • force=True 追加 --yes,否则 -qq;
  • 支持的 kwargs(映射为 --<name> <value>):activate、chunksize、contiguous、discards、stripes、stripesize、minor、persistent、mirrors、nosync、noudevsync、monitor、ignoremonitoring、permission、poolmetadatasize、readahead、regionsize、type、virtualsize、zero;
  • 无值开关类 kwargs(仅追加参数名):nosync、noudevsync、ignoremonitoring、thin;
  • 返回值:lvdisplay("/dev/<vgname>/<lvname>") 的结果字典,并附加 "Output from lvcreate" 键(成功为 'Logical volume "<lvname>" created.',失败为 stderr)。

单元测试 test_linux_lvm.py 验证了上述全部参数校验分支(size/extents 互斥、thinvolume/thinpool 互斥、thin volume 不允许 extents、非法 kwargs 不进入命令、无值开关只追加参数名等),可作为函数行为的权威佐证。

lvm.lvremove —— 删除逻辑卷

salt '*' lvm.lvremove lvname vgname force=True

对应源码 lvremove():执行 lvremove <vgname>/<lvname>;成功返回 'Logical volume "<lvname>" successfully removed',失败返回 stderr。

lvm.lvresize —— 调整逻辑卷大小(可缩可扩)

salt '*' lvm.lvresize +12M /dev/mapper/vg1-test
salt '*' lvm.lvresize lvpath=/dev/mapper/vg1-test extents=+100%FREE

对应源码 lvresize():

  • size(-L)与 extents(-l)二选一,同传或都不传时记录错误日志并返回空字典 {};
  • lvpath:逻辑卷设备路径(如 /dev/mapper/vg1-test);
  • force=True 时追加 --force(lvresize 专用,允许缩容),否则 -qq;
  • resizefs=True 时追加 --resizefs,让 LVM 同时使用 fsadm 调整文件系统大小;
  • 成功返回 {"Output from lvresize": 'Logical volume "<lvpath>" successfully resized.'}。

lvm.lvextend —— 扩展逻辑卷

salt '*' lvm.lvextend +12M /dev/mapper/vg1-test
salt '*' lvm.lvextend lvpath=/dev/mapper/vg1-test extents=+100%FREE

对应源码 lvextend():签名与 lvresize 完全一致,区别仅在于 force=True 时追加的是 --yes(而非 --force),成功消息为 'Logical volume "<lvpath>" successfully extended.'。

七、状态模块集成:声明式管理 LVM

除了命令行式执行模块,仓库还提供了配套的 lvm 状态模块,把上述函数封装成语义化的 *_present / *_absent 状态,实现幂等的声明式管理。状态模块与执行模块共享同一虚拟名 lvm,因此调用形式为 lvm.pv_present、lvm.vg_present、lvm.lv_present 等。

状态模块 docstring 中给出的典型 SLS 示例:

/dev/sda:
  lvm.pv_present

my_vg:
  lvm.vg_present:
    - devices: /dev/sda

lvroot:
  lvm.lv_present:
    - vgname: my_vg
    - size: 10G
    - stripes: 5
    - stripesize: 8K

各状态函数与执行函数的对应关系:

状态函数 底层执行函数 说明
lvm.pv_present(name, **kwargs) lvm.pvdisplay / lvm.pvcreate 确保设备已成为 PV,kwargs 透传给 pvcreate
lvm.pv_absent(name) lvm.pvdisplay / lvm.pvremove 确保设备不再是 PV
lvm.vg_present(name, devices=None, **kwargs) lvm.vgdisplay / lvm.vgcreate / lvm.vgextend 卷组已存在时,还会逐个检查 devices 是否已加入该卷组,孤儿 PV(Volume Group Name 为空或 #orphans_lvm2)会自动 vgextend 加入
lvm.vg_absent(name) lvm.vgdisplay / lvm.vgremove 确保卷组被删除
lvm.lv_present(name, vgname, size, extents, snapshot, pv, thinvolume, thinpool, force, resizefs, **kwargs) lvm.lvdisplay / lvm.lvcreate / lvm.lvresize 创建逻辑卷;已存在且大小不一致时自动 resize(自 3002 起支持 force,2018.3 起支持 resizefs)
lvm.lv_absent(name, vgname) lvm.lvdisplay / lvm.lvremove 确保逻辑卷被删除

关于 lv_present 的 resize 逻辑(源码见 lv_present()),有几点值得注意:

  • size 支持带单位后缀(S、M、G、T、P,分别代表 512 字节扇区、MB、GB、TB、PB,大小写不敏感),由内部辅助函数 _convert_to_mb 统一换算为 MB 后与现有大小比较(salt/states/lvm.py);
  • 缩容必须显式设置 force=True,否则状态返回失败并提示 "To reduce a Logical Volume option 'force' must be True.";
  • 若 extents 使用百分比写法(如 100%FREE)且逻辑卷已存在,状态不会执行 resize,避免重复计算导致抖动;
  • 所有状态都遵循 Salt 状态约定:test=True 时返回 result: None 与"is set to be created/removed/resized"注释,不会真正执行变更。

八、测试与验证

仓库针对该模块提供了完整的单元测试,可作为行为契约参考:

  • 执行模块测试 tests/pytests/unit/modules/test_linux_lvm.py:覆盖 version/fullversion 的解析、三个 *display 的字段映射(含 real=True 时的 Real Physical Volume Device 与 os.path.realpath 调用)、quiet=True 时 cmd.run_all 收到 ignore_retcode=True、pvcreate/pvremove/pvresize 的幂等跳过逻辑、vgcreate/vgextend 的成功消息、lvcreate 的全部参数校验分支以及非法 kwargs 被过滤等;
  • 状态模块测试 tests/pytests/unit/states/test_lvm.py:覆盖 pv_present/pv_absent 等状态的 already-present、test 模式(result: None)、成功与失败路径,以及 _convert_to_mb 的单位换算。

在真实环境中验证模块可用性的最小步骤:确保 minion 已安装 lvm2 → salt '<minion>' lvm.version 确认模块已加载 → 用 lvm.pvdisplay/lvm.vgdisplay/lvm.lvdisplay 盘点现状 → 再按需执行创建/扩容操作。

九、注意事项与最佳实践

  1. 加载前提:模块依赖 lvm 二进制,未安装 lvm2 的主机上模块不会加载;不要在未安装的机器上直接调用,否则会得到模块不可用的提示。
  2. 幂等设计:pvcreate/pvremove/pvresize/vg_present 等都会先查询现状再决定是否执行,重复下发不会产生副作用;多设备参数支持逗号分隔字符串,也支持列表。
  3. 错误以字符串返回:与多数 Salt 模块不同,这些函数在参数错误、设备不存在等场景下直接返回错误字符串(如 "Error: at least one device is required"),调用方需自行判断返回类型;而底层命令失败时返回的是 stderr 文本,成功时通常返回 True 或字典。
  4. 缩容有风险:lvresize 缩容逻辑卷默认不强制(force=False 时使用 -qq),状态模块也强制要求 force=True 才允许缩容;生产环境缩容前务必确认文件系统与数据安全,可配合 resizefs=True 让 fsadm 同步调整文件系统。
  5. 安全执行:模块内部全部使用 python_shell=False 直接传参调用 LVM 命令,规避了 shell 注入风险;参数名白名单机制(valid 元组)确保任意 kwargs 不会污染命令构造。
  6. 相关文档:模块 API 引用页位于 doc/ref/modules/all/salt.modules.linux_lvm.rst,通过 Sphinx automodule 指令自动生成;状态模块的用法见 salt/states/lvm.py 顶部示例;执行模块的完整单元测试见 tests/pytests/unit/modules/test_linux_lvm.py。

通过将本文介绍的执行函数与状态模块组合使用,你可以把 LVM 的物理卷初始化、卷组编排、逻辑卷创建与扩容全部纳入 Salt 的声明式配置体系,实现存储层面的自动化交付与一致性管理。

登录后查看全文
salt