Salt linux_lvm 执行模块完全指南:用 Salt 自动化管理 LVM2 物理卷、卷组与逻辑卷
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 盘点现状 → 再按需执行创建/扩容操作。
九、注意事项与最佳实践
- 加载前提:模块依赖
lvm二进制,未安装lvm2的主机上模块不会加载;不要在未安装的机器上直接调用,否则会得到模块不可用的提示。 - 幂等设计:
pvcreate/pvremove/pvresize/vg_present等都会先查询现状再决定是否执行,重复下发不会产生副作用;多设备参数支持逗号分隔字符串,也支持列表。 - 错误以字符串返回:与多数 Salt 模块不同,这些函数在参数错误、设备不存在等场景下直接返回错误字符串(如
"Error: at least one device is required"),调用方需自行判断返回类型;而底层命令失败时返回的是 stderr 文本,成功时通常返回True或字典。 - 缩容有风险:
lvresize缩容逻辑卷默认不强制(force=False时使用-qq),状态模块也强制要求force=True才允许缩容;生产环境缩容前务必确认文件系统与数据安全,可配合resizefs=True让 fsadm 同步调整文件系统。 - 安全执行:模块内部全部使用
python_shell=False直接传参调用 LVM 命令,规避了 shell 注入风险;参数名白名单机制(valid元组)确保任意kwargs不会污染命令构造。 - 相关文档:模块 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 的声明式配置体系,实现存储层面的自动化交付与一致性管理。