- 运维
- 配置管理
- 后端
【免费下载链接】salt
Software to automate the management and configuration of infrastructure and applications at scale.
导读:本文围绕 Salt 状态系统(State System)中的
virtualenv.managed状态展开,讲解如何在 SaltStack 中以声明式 SLS 配置创建 Python 虚拟环境、安装 pip 依赖并做幂等变更管理。通过阅读本文,你将掌握virtualenv状态的虚拟名加载机制、managed状态的全部参数语义、底层virtualenv.create执行模块的命令拼装原理,以及如何利用salt://requirements 文件、clear重建、env_vars编译变量等实战技巧。文档实体为 salt.states.virtualenv 参考页,其技术内容完全由 状态模块源码 的 docstring 与实现承载,本文结合 执行模块源码 与 单元测试、功能测试 进行深度佐证。
模块概览:状态如何挂载到virtualenv虚拟名
virtualenv状态从 Salt0.17.0版本开始引入,用于创建 Python 虚拟环境(virtualenv sandbox),并在创建的同时通过 pip 安装依赖包。其状态模块文件名为salt/states/virtualenv_mod.py,但通过__virtualname__ = "virtualenv"与__virtual__()函数注册为虚拟名:
__virtualname__ = "virtualenv" def __virtual__(): if "virtualenv.create" in __salt__: return __virtualname__ return (False, "virtualenv module could not be loaded")这意味着在 SLS 文件中你必须使用virtualenv.managed(而非virtualenv_mod.managed);同时,状态的可用性取决于 minion 上是否加载了virtualenv.create执行函数(由salt/modules/virtualenv_mod.py提供)。若加载失败,状态直接返回result: False与提示信息 "Virtualenv was not detected on this system"(状态源码)。
版本说明:从源码的
versionchanged标注可以看到,当前仓库代码已演进到支持把 Python 解释器直接作为venv_bin传入(3006.28变更),并允许在venv_bin: venv场景下用python参数指定解释器版本,这些细节会在下文逐一展开。
快速上手:一个最小可运行的 SLS 示例
virtualenv.managed状态的核心用法是:给定虚拟环境的目标路径(name),确保该环境存在,并可选地通过 pip 安装依赖。状态模块 docstring 中的标准示例为:
/var/www/myvirtualenv.com: virtualenv.managed: - system_site_packages: False - requirements: salt://REQUIREMENTS.txt - env_vars: PATH_VAR: '/usr/local/bin/'该示例同时展示了三个高频选项:
system_site_packages: False:环境默认不继承系统全局 site-packages;requirements: salt://REQUIREMENTS.txt:从 Salt master 的文件服务器拉取 requirements 文件;env_vars:为 pip 安装过程注入编译期环境变量(例如某个 Python C 扩展的 Makefile 需要INCLUDE_PATH指向头文件目录)。
apply 该状态后,Salt 会在目标路径创建虚拟环境并执行pip install -r REQUIREMENTS.txt。状态返回的changes中会记录新建/清除环境的 Python 版本以及本次安装的包列表,便于审计。
参数总览与完整语义
managed的签名与 docstring 位于 salt/states/virtualenv_mod.py#L34-L167。除name外,所有参数均有默认值,绝大多数为None或False,即"不传即为默认行为"。下面分三组整理。
第一组:环境创建参数(透传给virtualenv.create)
| 参数 | 默认值 | 说明 |
|---|---|---|
venv_bin | None(见下文解析) | 创建环境的命令名/路径。取值有三类:virtualenv二进制名、特殊值venv(选用 Python 标准库venv模块)、或一个 Python 解释器路径(如/usr/bin/python3.11,此时以<interpreter> -m venv建环境)。也可在 minion 配置中全局设置virtualenv.venv_bin |
python | None | 用于构建环境的 Python 可执行文件。当 Salt 以 onedir 包安装时,默认使用 onedir 自带解释器,因此通常应显式指定目标解释器 |
system_site_packages | False | 是否允许环境访问系统 site-packages(透传给 virtualenv/venv 的--system-site-packages) |
distribute | False | 透传给 virtualenv 的--distribute。注意 virtualenv ≥ 1.10 已废弃该选项(见下文"版本相关参数处理") |
clear | False | 若环境已存在则先清空再重建(透传--clear) |
extra_search_dir | None | 额外的搜索目录,可传逗号分隔字符串或列表,逐个转为--extra-search-dir= |
never_download | None | 透传--never-download(仅 virtualenv 二进制路径支持) |
prompt | None | 环境的 shell 提示符前缀,透传--prompt |
user | None | 以指定用户身份运行 virtualenv 与 pip(即设置环境属主) |
第二组:pip 安装参数
| 参数 | 默认值 | 说明 |
|---|---|---|
requirements | None | pip requirements 文件路径;以salt://开头时先从 master 文件服务器缓存到 minion |
pip_pkgs | None | 替代requirements的包列表,直接传给pip.install的pkgs |
index_url | None | 基础包索引 URL(透传--index-url) |
extra_index_url | None | 额外包索引 URL(透传--extra-index-url) |
pre_releases | False | 是否允许安装预发布版本 |
no_deps | False | 透传--no-deps给 pip install |
pip_exists_action | None | 路径已存在时 pip 的默认动作:(s)witch / (i)gnore / (w)ipe / (b)ackup |
pip_ignore_installed | False | 透传--ignore-installed |
pip_upgrade | False | 透传--upgrade |
proxy | None | 代理地址,透传给 pip install |
pip_download/pip_download_cache | None | 下载目录 / 下载缓存目录 |
pip_no_cache_dir | False | 透传--no-cache-dir |
pip_cache_dir | None | 自定义 pip 缓存目录 |
env_vars | None | 为某些构建注入的环境变量字典 |
cwd | None | pip install执行时的工作目录 |
use_vt | False | 使用 VT 终端模拟(可实时看到安装输出) |
第三组:wheel / binary 相关与兜底参数
| 参数 | 默认值 | 说明 |
|---|---|---|
use_wheel | False | 优先使用 wheel 归档(需 pip ≥ 1.4) |
no_use_wheel | False | 强制不使用 wheel(需 pip ≥ 1.4) |
no_binary | None | 强制不使用二进制包(需 pip ≥ 7.0.0);可传:all:、:none:或包名列表 |
process_dependency_links | False | 透传--process-dependency-links(2017.7.0新增) |
除上述命名参数外,managed还接受virtualenv.create支持的任何其他关键字参数(含use_vt、saltenv等)。docstring 特别提醒:部分 kwargs(如pip选项)要求搭配distribute: True才生效(状态源码)。
从salt://拉取 requirements 文件:状态层的缓存流程
当requirements以salt://开头时,状态会在创建环境之前先完成文件落地,这是状态模块中一个值得注意的细节(salt/states/virtualenv_mod.py#L182-L203):
- 调用
__salt__"cp.is_cached"检查是否已在本地缓存; - 未缓存则调用
cp.cache_file从 master 下载; - 用
cp.hash_file对比 master 端与本地缓存文件的哈希,若 master 端文件已变更则重新缓存,确保总是使用最新版本; - 若最终缓存失败,状态返回
result: False并提示pip requirements file '<path>' not found。
该设计保证salt://requirements 文件的变更能被状态感知,而不是永远命中旧缓存。
底层执行:virtualenv.create如何拼装命令
状态层的环境创建最终委托给 salt/modules/virtualenv_mod.py 的create()函数(签名见 create() 定义)。该函数对venv_bin的值做了三路分支(命令构建逻辑):
if venv_bin == "venv": # <sys.executable 或 python 参数指定的解释器> -m venv elif _is_python_binary(venv_bin): # <解释器> -m venv(python 参数若同时给出会报"Ambiguous"冲突) else: # 直接运行 virtualenv 二进制其中_is_python_binary用正则(python|pypy)[0-9.]*(\.exe)?识别python3、/usr/bin/python3.11、pypy3、python.exe等形态(源码),识别为解释器的输入一律走<interpreter> -m venv路径。
virtualenv 二进制路径下的参数处理
当使用 virtualenv 二进制时,create()会做如下处理:
distribute:解析 virtualenv 版本号,若版本 ≥(1, 10)则只打日志提示该选项已废弃、不再追加--distribute;否则追加(源码)。单元测试 test_issue_6029_deprecated_distribute 精确验证了 virtualenv 1.9.1 追加--distribute、1.10 之后不再追加的行为;python:校验解释器存在于 PATH 后追加--python=<path>(test_python_argument);extra_search_dir:字符串按逗号拆分后逐个追加--extra-search-dir=,列表则直接遍历(test_issue_6031_multiple_extra_search_dirs);never_download:virtualenv 1.10~14.0 之间该选项被废弃,代码仅记日志;never_download=True且版本 < 1.10 或 ≥ 14.0 时追加--never-download;never_download=False且版本 ≥ 20 时反而追加--download(源码);prompt:以--prompt='<value>'形式追加(test_prompt_argument)。
venv 模块路径下的参数处理
当venv_bin为venv或解释器路径时,走 Python 标准库venv模块分支:
upgrade、symlinks分别追加--upgrade、--symlinks(test_venv_module_option_ordering 验证了选项的稳定追加顺序);prompt以--prompt <value>形式追加(venv 模块自 Python 3.6 起支持);extra_search_dir、never_download在 venv 路径下明确拒绝,直接抛CommandExecutionError,避免把 virtualenv 专属参数误传给-m venv(源码)。
两条路径共用的公共选项
无论哪条路径,clear: True追加--clear、system_site_packages: True追加--system-site-packages,最后拼接目标路径(源码)。创建命令通过__salt__"cmd.run_all"执行。
失败清理与 pip/setuptools 引导
create()有一个重要的健壮性设计:若创建命令返回非零退出码,且目标路径在命令执行前并不存在,则用shutil.rmtree删除这个半成品目录,防止后续的virtualenv.managed(其存在性判断以bin/python是否在为准)把残缺环境误判为可用(源码)。test_venv_failure_removes_partial_env 与 test_venv_failure_keeps_preexisting_path 分别覆盖了"删除半成品"与"不删除预先存在的路径"两种情形。
创建成功后,若传了pip或distribute,且不是 venv 模块环境,还会通过_install_script依次引导ez_setup.py与get-pip.py(bootstrap 脚本经cp.cache_file缓存后执行);venv 模块环境则由ensurepip自带 pip,跳过此步骤(源码,相关断言见 test_venv_skips_setuptools_bootstrap)。
幂等性、test 模式与changes报告
virtualenv.managed是一个幂等状态,其执行逻辑在 salt/states/virtualenv_mod.py#L176-L378:
- 存在性判断:Windows 上以
Scripts/python.exe、其他平台以bin/python是否存在作为环境是否已创建的判据(源码); - test 模式(
test=True):环境不存在则返回result: None、comment: "Virtualenv <name> is set to be created";已存在但clear: True则返回"Virtualenv <name> is set to be cleared";已存在且不清理则直接返回"Virtualenv <name> is already created"(源码); - 创建/清理:仅当环境不存在、或存在且
clear时调用virtualenv.create;返回码非 0 时状态失败并把 stdout/stderr 拼入 comment。创建成功后记录changes["new"] = <python -V 输出>;clear模式下还会在创建前记录changes["cleared_packages"] = pip.freeze 结果与changes["old"] = 旧 Python 版本(源码); - 依赖变更检测:安装依赖前用
pip.freeze(bin_env=name)快照现有包集合,安装后再次pip.freeze做差集,将新增/移除的包写入changes["packages"]["new"/"old"](源码)。这样每次 apply 后都能清楚看到环境里发生了什么变化。
pip 版本门槛:use_wheel/no_use_wheel/no_binary
状态层对 wheel 相关参数做了显式版本校验,而不是盲目透传(salt/states/virtualenv_mod.py#L260-L314):
use_wheel与no_use_wheel仅支持 pip1.4 ~ 9.0.3区间,超出则状态失败并提示检测到的 pip 版本;no_binary要求 pip ≥7.0.0,否则状态失败。
版本通过__salt__"pip.version"获取(即环境内 pip 的版本),并使用salt.utils.versions.compare比较。这意味着如果你在较新 pip(>9.0.3)环境下仍开启use_wheel,状态会明确报错而非静默失效。
实战组合示例
示例一:指定解释器 + 编译期环境变量 + 私有索引
针对 onedir 安装的 Salt minion,docstring 特别强调默认会使用 onedir 自带解释器建环境,因此通常应显式提供目标解释器:
/srv/venvs/app: virtualenv.managed: - python: /usr/bin/python3.11 - venv_bin: venv - system_site_packages: False - pip_pkgs: - requests - pyyaml - index_url: https://pypi.example.com/simple - env_vars: INCLUDE_PATH: '/usr/local/include/' LD_LIBRARY_PATH: '/usr/local/lib/' - user: appuser其中python与venv_bin: venv的组合(3006.28起支持)让create()以<python> -m venv建环境,适用于 virtualenv 二进制过旧的发行版(如 EL8)为任意已装解释器建环境(create() docstring)。对应的功能测试 test_create_venv_module_with_python 与 test_create_venv_interpreter_as_venv_bin 验证了这两种写法的产物与pyvenv.cfg。
示例二:requirements 文件 + 重建场景
/var/www/myvirtualenv.com: virtualenv.managed: - requirements: salt://REQUIREMENTS.txt - clear: True # 环境已存在时先清空再重建 - use_vt: True # 实时观察 pip 输出 - pip_upgrade: True # 每次 install 都 --upgrade功能测试 test_clear 展示了clear=True的真实语义:先在环境内安装pep8,再以clear=True重建后,pip.list中已不再包含该包,证明环境被整体清空重建。
示例三:仅创建环境、不装依赖
managed不需要任何依赖参数——只给name即可"确保环境存在":
/srv/venvs/tooling: virtualenv.managed: - venv_bin: /usr/bin/python3.11此时若环境已存在,状态返回成功且comment为"virtualenv exists",不做任何多余操作,完美契合幂等目标。
配套能力与延伸阅读
- 底层 CLI:执行模块同样可直接在命令行使用,如
salt '*' virtualenv.create /path/to/new/virtualenv、salt '*' virtualenv.get_site_packages /path/to/my/venv、salt '*' virtualenv.get_distribution_path <venv> <dist>(后者自2016.3.0提供,返回某发行版在环境内的安装路径)。 - 全局默认值:
venv_bin未显式给出时的解析顺序为"Pillarvenv_bin→ minion 配置virtualenv.venv_bin→ PATH 中第一个 virtualenv 二进制 → 回退venv"(create() 解析),对应单测 test_default_resolution_pillar_overrides_opts。 - 与 pip 模块的衔接:
managed的 pip 相关参数最终汇入 salt/modules/pip.py 的install()(签名见 install() 定义),其中index_url/extra_index_url会先经salt.utils.url.validate校验协议合法性再拼入--index-url/--extra-index-url,no_deps、pre_releases等同样有对应命令行参数映射。 - 相关状态:若你只需要"确保某 Python 包被安装/移除"而无需管理整个虚拟环境,可参考
pip_state(salt/states/pip_state.py);虚拟环境的系统级解释器选择则与 grains 中的 Python 信息联动。
小结
virtualenv.managed是 Salt 中管理 Python 环境的一体化状态:它把"创建虚拟环境"与"安装 pip 依赖"两个动作收敛为一条幂等声明,并通过changes输出、test 模式、salt:// 文件缓存与 pip 版本门槛等机制保证了可预测、可审计、可回放。理解其背后virtualenv.create对 virtualenv 二进制与标准库venv模块的差异化命令拼装,能帮助你在混合解释器版本、onedir 安装、老旧发行版等真实环境中准确配置参数,避免"环境建好了但解释器不对"或"pip 选项静默失效"这类隐蔽问题。
- 运维
- 配置管理
- 后端
【免费下载链接】salt
Software to automate the management and configuration of infrastructure and applications at scale.
相关推荐
Python venv 虚拟环境与 pip 包管理完全指南:创建、激活与依赖隔离
Python venv 虚拟环境与 pip 包管理完全指南:创建、激活与依赖隔离 导读 venv 是 Python 标准库中用于创建"虚拟环境"(virtual
编程语言语言运行时解释器标准库NoteDiscovery核心功能详解:如何高效组织和管理你的笔记与文档
NoteDiscovery核心功能详解:如何高效组织和管理你的笔记与文档 NoteDiscovery是一款功能强大的自托管知识管理工具,专为需要完全掌控自己数据
后端前端知识管理MCP 服务如何快速掌握Python虚拟环境:venv与依赖管理完整指南
如何快速掌握Python虚拟环境:venv与依赖管理完整指南 学习Python编程时,虚拟环境管理是每个开发者必须掌握的核心技能。本文将为您提供Python虚拟
示例工程教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考