☰
Salt 的 `virtualenv.managed` 状态:Python 虚拟环境创建与 pip 依赖管理的完整指南
2026/9/25 1:22:04 网站建设 项目流程
  • 运维
  • 配置管理
  • 后端

【免费下载链接】salt

Software to automate the management and configuration of infrastructure and applications at scale.

项目地址:https://gitcode.com/gh_mirrors/sa/salt
点击查看免费下载

导读:本文围绕 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_binNone(见下文解析)创建环境的命令名/路径。取值有三类:virtualenv二进制名、特殊值venv(选用 Python 标准库venv模块)、或一个 Python 解释器路径(如/usr/bin/python3.11,此时以<interpreter> -m venv建环境)。也可在 minion 配置中全局设置virtualenv.venv_bin
pythonNone用于构建环境的 Python 可执行文件。当 Salt 以 onedir 包安装时,默认使用 onedir 自带解释器,因此通常应显式指定目标解释器
system_site_packagesFalse是否允许环境访问系统 site-packages(透传给 virtualenv/venv 的--system-site-packages)
distributeFalse透传给 virtualenv 的--distribute。注意 virtualenv ≥ 1.10 已废弃该选项(见下文"版本相关参数处理")
clearFalse若环境已存在则先清空再重建(透传--clear)
extra_search_dirNone额外的搜索目录,可传逗号分隔字符串或列表,逐个转为--extra-search-dir=
never_downloadNone透传--never-download(仅 virtualenv 二进制路径支持)
promptNone环境的 shell 提示符前缀,透传--prompt
userNone以指定用户身份运行 virtualenv 与 pip(即设置环境属主)

第二组:pip 安装参数

参数默认值说明
requirementsNonepip requirements 文件路径;以salt://开头时先从 master 文件服务器缓存到 minion
pip_pkgsNone替代requirements的包列表,直接传给pip.install的pkgs
index_urlNone基础包索引 URL(透传--index-url)
extra_index_urlNone额外包索引 URL(透传--extra-index-url)
pre_releasesFalse是否允许安装预发布版本
no_depsFalse透传--no-deps给 pip install
pip_exists_actionNone路径已存在时 pip 的默认动作:(s)witch / (i)gnore / (w)ipe / (b)ackup
pip_ignore_installedFalse透传--ignore-installed
pip_upgradeFalse透传--upgrade
proxyNone代理地址,透传给 pip install
pip_download/pip_download_cacheNone下载目录 / 下载缓存目录
pip_no_cache_dirFalse透传--no-cache-dir
pip_cache_dirNone自定义 pip 缓存目录
env_varsNone为某些构建注入的环境变量字典
cwdNonepip install执行时的工作目录
use_vtFalse使用 VT 终端模拟(可实时看到安装输出)

第三组:wheel / binary 相关与兜底参数

参数默认值说明
use_wheelFalse优先使用 wheel 归档(需 pip ≥ 1.4)
no_use_wheelFalse强制不使用 wheel(需 pip ≥ 1.4)
no_binaryNone强制不使用二进制包(需 pip ≥ 7.0.0);可传:all:、:none:或包名列表
process_dependency_linksFalse透传--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):

  1. 调用__salt__"cp.is_cached"检查是否已在本地缓存;
  2. 未缓存则调用cp.cache_file从 master 下载;
  3. 用cp.hash_file对比 master 端与本地缓存文件的哈希,若 master 端文件已变更则重新缓存,确保总是使用最新版本;
  4. 若最终缓存失败,状态返回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.

项目地址:https://gitcode.com/gh_mirrors/sa/salt
点击查看免费下载

相关推荐

上一篇:解锁视频时间压缩:掌握HTML5播放速度控制的专业方案
下一篇:Flink CDC Paimon Pipeline Connector 实战指南:MySQL 实时入湖 Paimon 的流水线配置、原理与类型映射

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询