Salt 状态系统执行模块 state 全解析:从 highstate 应用到源码级原理
2026/9/23 23:53:17 网站建设 项目流程
  • 运维
  • 配置管理
  • 后端

【免费下载链接】salt

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

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

本篇技术指南以 Salt 仓库中 doc/ref/modules/all/salt.modules.state.rst 所对应的 salt/modules/state.py 为核心,系统讲解 Salt 状态系统在 minion 端的所有执行入口:state.applystate.highstatestate.slsstate.singlestate.top以及队列、暂停、禁用、请求等高级控制能力。读完本文,你将掌握每一个状态执行函数的作用、参数含义与 CLI 用法,并能结合源码理解 Salt 状态系统底层的工作机制。


1. 模块概览:state 模块在 Salt 中的定位

salt.modules.state是 Salt 中"在 minion 上控制系统状态(State System)"的核心执行模块。它负责把 SLS 状态文件编译成可执行的数据,并在目标机器上逐一落实。模块头部 docstring 说明了其关键设计——状态缓存(State Caching)

当一次 highstate 被调用时,minion 会自动缓存一份最近一次的高数据(high data)。如果之后以cache=True运行 highstate,就会使用这份缓存的高数据,除了状态自身内部的salt://链接之外不再访问 fileserver。

从 salt/modules/state.py 的源码可见,该模块通过__virtualname__ = "state"(第 70 行)定义虚拟名,并声明__proxyenabled__ = ["*"](第 48 行),意味着它可以在所有 proxy minion 环境下运行。同时它通过__outputter__(第 50-64 行)把几乎所有执行函数统一映射到highstate输出器,保证返回数据的展示风格一致。

模块还通过__func_alias__ = {"apply_": "apply"}(第 66 行)将内部函数apply_对外暴露为state.apply。与大多数执行模块不同,这里的函数大多直接使用salt.state.HighStatesalt.state.State类(定义于 salt/state.py)来完成从"高数据"编译到"低数据"执行的全过程。

2. 最常用的入口:state.apply 与 state.highstate

2.1 state.apply:统一、直观的入口

state.apply(源码实现为apply_,第 773 行)自 2015.5.0 引入,是官方推荐的日常状态执行入口。它的行为取决于参数:

  • 不传任何 SLS 目标:等价于执行state.highstate,应用top.sls中为该 minion 配置的所有状态。
  • 传入 SLS 文件列表:等价于执行state.sls,只应用指定的 SLS 文件。

源码第 981-983 行清晰地展示了这一分发逻辑:

if mods: return sls(mods, **kwargs) return highstate(**kwargs)

应用 top.sls 中配置的全部状态:

salt '*' state.apply

应用单个 SLS 文件(salt://stuff.slssalt://stuff/init.sls):

salt '*' state.apply stuff

应用多个 SLS 文件:

salt '*' state.apply stuff,pkgs

应用深层嵌套目录中的 SLS:

salt '*' state.apply my.organized.stuff

state.apply同时接受以下常用参数(完整参数说明见 源码 docstring):

参数默认值作用
test以 dry-run(测试)模式运行状态,只报告将要做的更改而不实际执行
mockFalse完全不调用任何状态,返回模拟结果,用于校验 requisite 顺序与状态编排(2015.8.4 起)
pillar以字典形式传入自定义 Pillar 值,会覆盖pillar_roots或外部 Pillar 源的同名值
exclude排除特定状态,接受 SLS 名列表、逗号分隔字符串或含sls/id键的字典列表,支持 glob 通配
queueFalse当有另一个状态运行在进行时,排队等待而非直接失败(详见第 6 节)
concurrentFalse允许状态并发执行(危险,仅用于可安全并行的场景,不可用于性能优化)
saltenvbase(若未配置)指定使用的 fileserver 环境
pillarenv指定 Pillar 环境;minion 配置中的pillarenv也可设置
localconfig使用指定文件中的 minion 配置,并与现有配置合并,可让特定状态用独立的自定义配置运行
sync_mods运行 SLS 前同步指定类型的自定义模块,如sync_mods=states,modulessync_mods=all(2017.7.8 起)
state_events状态运行中每个函数完成时发送进度事件(3006.0 起)

2.2 state.highstate:执行完整状态编排

state.highstate(第 1138 行)从 master 检索该 minion 的状态数据并执行——即应用top.sls中匹配该 minion 的所有状态。

# 应用 top.sls 中配置的全部状态 salt '*' state.highstate # 只运行白名单中的 SLS salt '*' state.highstate whitelist=sls1_to_run,sls2_to_run # 排除指定 SLS salt '*' state.highstate exclude=sls_to_exclude # 传递自定义 Pillar salt '*' state.highstate pillar="{foo: 'Foo!', bar: 'Bar!'}"

执行流程(源码第 1324-1358 行)highstate会创建salt.state.HighState实例并进入上下文管理器,随后依次完成:

  1. 检查 Pillar 渲染错误(_get_pillar_errors,出错返回EX_PILLAR_FAILURE);
  2. st_.push_active()标记自身为活跃状态,防止并发重复执行;
  3. 可选地创建 snapper pre 快照(受snapper_states配置控制);
  4. st_.call_highstate(...)编译并执行全部高数据;
  5. 若配置state_data: terse或传入terse=True,过滤掉"结果成功且无更改"的条目(_filter_running,第 88 行);
  6. _set_retcode根据结果设置退出码,最后创建 snapper post 快照。

state.highstate支持exclude的三种写法(与state.apply一致):

salt '*' state.highstate exclude=bar,baz salt '*' state.highstate exclude=foo* salt '*' state.highstate exclude="[{'id': 'id_to_exclude'}, {'sls': 'sls_to_exclude'}]"

从源码看,exclude最终以high_["__exclude__"]的形式注入高数据(见 sls 函数第 1624-1629 行),在编译阶段被剔除。

2.3 防呆助手:state.test

state.test(第 986 行,2017.7/3001 起)是state.apply的别名,但强制test=True,避免手误漏写test=True造成意外变更:

salt '*' state.test

源码实现非常简单——把kwargs["test"] = True后直接转调apply_

3. 精确执行:state.sls、state.sls_id 与 state.single

3.1 state.sls:执行指定的 SLS 文件

state.sls(第 1361 行)执行一个或多个指定的 SLS 文件:

# 执行 salt://core.sls(或 salt://core/init.sls)与 salt://edit/vim.sls salt '*' state.sls core,edit.vim # 执行深层嵌套的 SLS salt '*' state.sls my.nested.state # 指定 saltenv 与自定义 Pillar salt '*' state.sls myslsfile pillar="{foo: 'Foo!', bar: 'Bar!'}" saltenv=dev

sls执行的关键流程(源码第 1596-1667 行):

  1. render_highstate({opts["saltenv"]: mods})渲染指定 SLS 为高数据;
  2. 若有exclude,追加到high_["__exclude__"]
  3. st_.state.call_high(high_, orchestration_jid)编译低数据并执行;
  4. 结果写入cachedir/sls.p,同时把高数据缓存到cachedir/<cache_name>.cache.p(默认highstate.cache.p)供后续cache=True运行复用。

此外,sls会在执行前处理sync_mods参数(源码第 1555-1570 行):把输入拆分为列表、去重为["all"]后逐个调用saltutil.sync_<type>,实现运行前同步自定义模块。

3.2 state.sls_id:只执行某个 ID

state.sls_id(第 2036 行,2014.7.0 起)从指定的一个或多个 SLS 模块中调用单个状态 ID,并处理其全部 requisite。注意命令行中ID 在模块名之前

# 只执行 my_module 中 id 为 my_state 的状态(含其 requisite) salt '*' state.sls_id my_state my_module # 在多个模块中查找该 ID salt '*' state.sls_id my_state my_module,a_common_module

若指定 ID 未在 SLS 中找到,函数会抛出SaltInvocationError,提示"No matches for ID 'my_state' found in SLS '...' within saltenv '...'"(源码第 2160-2165 行)。

3.3 state.single:单条命令式状态执行

state.single(第 2506 行)直接执行单个状态函数,不需要任何 SLS 文件。kwargs 默认按 YAML 解析,也支持 JSON 格式:

salt '*' state.single pkg.installed name=vim

源码内部会把fun按点号拆成statefun两部分(comps = fun.split(".")),组装出{"state": "pkg", "fun": "installed", "__id__": "vim", "name": "vim"}这样的低数据块,然后交给st_.verify_data(kwargs)校验、st_.call(kwargs)执行。若传入的函数名不含点号(少于两段),直接返回"Invalid function passed"

4. 预览与调试:show_* 系列函数

Salt 提供一组以show_开头的函数,用于在不实际执行的情况下查看将要应用的状态数据,是排查 SLS 编写问题的重要工具。

函数作用示例
state.show_highstate显示该 minion 在 highstate 下将应用的全部高数据salt '*' state.show_highstate
state.show_lowstate列出将应用到该 minion 的低数据(low data)列表salt '*' state.show_lowstate
state.show_sls显示指定 SLS 的状态数据(不支持 top 文件)salt '*' state.show_sls core,edit.vim saltenv=dev
state.show_low_sls显示指定 SLS 编译后的低数据salt '*' state.show_low_sls foo saltenv=dev
state.show_top返回 highstate 将使用的 top 数据(即 top.sls 中匹配的条目)salt '*' state.show_top
state.show_states返回 highstate 将应用的状态文件列表(2019.2.0 起)salt '*' state.show_states
state.show_state_usage分析 highstate 数据中"已使用/未使用"的状态salt '*' state.show_state_usage

其中show_highstate调用st_.compile_highstate()show_lowstate调用st_.compile_low_chunks()show_states则基于compile_low_chunks()的结果收集每个 chunk 的__sls__字段并去重(源码第 2014-2033 行)。它们都不会触发任何实际的状态变更,适合在发布前做静态预览。

与预览配套的还有两个存在性检测函数(均 2019.2.0 起):

# 检查 SLS 文件是否存在 salt '*' state.sls_exists core,edit.vim saltenv=dev # 检查指定 ID 是否存在于指定 SLS 中 salt '*' state.id_exists create_myfile,update_template filestate saltenv=dev

从源码看,sls_exists实现为"show_sls的返回值是否为 dict"(第 2434 行),id_exists则把show_low_sls返回的所有__id__收集成集合,判断目标 ID 集合是否是其子集(第 2456-2461 行)。

5. 依赖图:state.graph 与 state.graph_highstate

Salt 可以把状态的依赖关系渲染为DOT 格式的依赖图,便于直观检查 requisite 编排:

# 显示单个/多个 SLS 的依赖图(DOT 格式) salt '*' state.graph core,edit.vim saltenv=dev # 显示整个 highstate 的依赖图 salt '*' state.graph_highstate

两者都可以用 Graphviz 渲染成图片:

salt '*' state.graph_highstate --out=txt | dot -Tpng -o highstate.png salt '*' state.graph core --out=txt | dot -Tpng -o state_graph.png

源码层面,graph先复用show_sls取得高数据,再通过st_.compile_high_data(high)构建st_.dependency_dag并输出to_dot()(第 2393-2412 行);graph_highstate则先compile_highstate()再走相同路径(第 1839-1908 行)。注意:非 dict 的返回(如编译错误列表)会原样返回,不会尝试生成图。

6. 并发控制:queue、running 与并发防护

6.1 冲突检测与 state.running

Salt 默认串行执行状态,防止同一 minion 上多个状态任务互相干扰。state.running(第 378 行)返回当前正在运行的状态信息:

salt '*' state.running

源码实现通过saltutil.is_running("state.*")获取活跃任务,并跳过"当前 JID"(__opts__.get("jid"))以避免把自身误判为冲突——这个细节避免了state.apply(queue=False)时因进程表中的占位符而产生误报(源码第 395-398 行的注释专门说明了这一点)。

6.2 队列机制:queue 参数与 state_queue 配置

当有状态运行在进行时,默认行为是直接失败。设置queue=True则会把新任务排队,等前一个完成后自动开始:

salt '*' state.apply stuff queue=True

从源码(_check_queue,第 474 行)可以看到队列是基于磁盘的 FIFO 队列:冲突时把任务信息(fun、arg、tgt、jid、user、kwarg 等)序列化写入state_queue_dir下的queued_<微秒时间戳>_<jid>.p文件,文件名的时间戳保证 FIFO 顺序,由后台线程取出执行;排队中的任务执行时绕过process_count_max限制,避免被其他负载饿死。

自 3006.0 起,该参数还能通过 minion 配置 conf/minion 中的state_queue统一设置,且可传入整数表示允许排队的最大任务数,超出则拒绝入队,防止无限开启新线程拖垮系统(相关说明见 conf/minion 第 610-618 行):

# conf/minion 中的默认配置(默认关闭) # state_queue: False

需要提醒的是:concurrent=True是另一条路径——它直接跳过冲突检查允许并发,但官方在 docstring 中明确警告该标志"potentially dangerous",只应在多个状态运行可以安全并行的场景使用,不要用它做性能优化。

队列的配套函数还有_wait(第 130 行),当queue为整数(即max_queue)时保留阻塞式等待行为:在队列锁保护下轮询_prior_running_states,直至先前任务全部结束或达到最大队列深度。

7. 运行中控制:pause、resume、soft_kill

Salt 支持对正在运行的状态任务进行暂停、恢复和"软终止"。这些函数需要传入运行中的 JID:

# 暂停整个状态运行 salt '*' state.pause 20171130110407769519 # 暂停到指定 state_id 之前(例如 vim) salt '*' state.pause 20171130110407769519 vim # 带时长暂停(20 秒) salt '*' state.pause 20171130110407769519 vim 20 # 恢复 salt '*' state.resume 20171130110407769519 salt '*' state.resume 20171130110407769519 vim # 软终止:在执行到给定 state_id 前安全退出 salt '*' state.soft_kill 20171130110407769519 salt '*' state.soft_kill 20171130110407769519 vim # 查看当前所有暂停任务 salt '*' state.get_pauses

注意 state_id 是指 SLS 中定义的 ID,例如:

vim: pkg.installed: []

这里传给pause/soft_kill的 state_id 就是vim

实现上,这些控制基于cachedir/state_pause/<jid>目录下的 msgpack 数据文件(_get_pause,第 186 行)。soft_kill写入kill: True标记,pause写入可选的duration秒数,resume则弹出对应 state_id 的条目。get_pauses(第 208 行)在列出暂停信息时会先检查任务是否仍在运行,已结束任务的暂停文件会被自动清理。

8. 请求执行:state.request 与 run_request

Salt 支持"先请求、后执行"的异步管理模式(2015.5.0 起):

# 在 minion 上"请求"执行(以 test 模式预演,并把请求写入缓存) salt '*' state.request salt '*' state.request stuff salt '*' state.request stuff,pkgs # 查看当前挂起的请求 salt '*' state.check_request # 执行挂起的请求(执行后请求文件被清除) salt '*' state.run_request # 清除请求而不执行 salt '*' state.clear_request

从源码看,request(第 1001 行)先以test=True调用apply_得到预演结果,然后把{"name": {"test_run": ret, "mods": mods, "kwargs": kwargs}}序列化写入cachedir/req_state.prun_request(第 1107 行)读取该文件、去掉test标记后真正执行,成功后删除请求文件。这一机制适用于由本地管理员稍后通过salt-call state.run_request触发执行的场景。

9. 状态缓存:cache 参数与 clear_cache

前文提到,执行 highstate 或 sls 时 minion 会把高数据缓存到cachedir/*.cache.p文件。配合cache=True运行可以完全绕过 fileserver(salt://链接除外):

# 使用缓存的 highstate 数据执行(不访问 fileserver) salt '*' state.highstate cache=True # 强制清理所有状态缓存文件 salt '*' state.clear_cache

clear_cache(第 2582 行)会遍历cachedir,删除所有以.cache.p结尾的文件并返回被删除的文件名列表。需要注意的是:默认状态下该缓存功能是完全禁用的,只有在调用状态时显式传cache=True才会写入并复用缓存。从 sls 函数第 1603-1608 行的代码可以看到,命中缓存时直接call_high返回,不再渲染 SLS。

10. 模板与底层测试接口:template、low、high

以下函数面向模板渲染与系统自测场景:

  • state.template(第 693 行):直接处理minion 本地路径上的模板文件,不经过 master。注意路径无需(也不应)以salt://开头,且函数内部会自动补.sls后缀:
salt '*' state.template '<Path to template on the minion>'
  • state.template_str(第 746 行):执行内嵌在字符串中的 SLS 模板:
salt '*' state.template_str '<Template String>'
  • state.low(第 591 行)与state.high(第 640 行):分别执行单条低数据调用和一组高数据调用,docstring 明确说明它们"主要用于测试状态系统,日常使用中不太可能用到":
salt '*' state.low '{"state": "pkg", "fun": "installed", "name": "vi"}' salt '*' state.high '{"vim": {"pkg": ["installed"]}}'

从源码看,lowsalt.state.State实例的verify_data校验数据后调用callhigh则校验 pillar 类型后调用call_high。这两个函数与single一样,是状态系统核心执行链路的底层测试入口。

11. 特殊执行方式:top、orchestrate 与 pkg

11.1 state.top:执行指定的 top 文件

state.top(第 1670 行)执行一个指定的 top 文件而非默认的top.sls,适合在不修改默认 top 的情况下切换 dev/prod 等不同环境:

salt '*' state.top reverse_top.sls salt '*' state.top prod_top.sls exclude=sls_to_exclude salt '*' state.top dev_top.sls exclude="[{'id': 'id_to_exclude'}, {'sls': 'sls_to_exclude'}]"

实现上,它把st_.opts["state_top"]设置为传入的 top 文件 URL,并支持通过saltenv指定 top 文件所在的 fileserver 环境(st_.opts["state_top_saltenv"]),随后走与 highstate 相同的call_highstate路径。

11.2 state.orchestrate:masterless 编排

state.orchestrate(第 347 行,2016.11.0 起)允许在masterless minionsalt-call --local)上执行原本属于 runner 的 orchestrate 编排:

salt-call --local state.orchestrate webserver salt-call --local state.orchestrate webserver saltenv=dev test=True salt-call --local state.orchestrate webserver saltenv=dev pillarenv=aws

它本质上是对salt.runners.state.orchestrate的包装:模块通过salt.utils.functools.namespaced_function把 runner 中的orchestrate函数引入当前命名空间(见__virtual__,第 73-85 行),支持modssaltenvtestexcludepillarpillarenv等参数。

11.3 state.pkg:打包状态运行

state.pkg(第 2607 行)执行一个本地 tarball 打包的状态运行,该 tarball 可由 salt-ssh 生成:

salt '*' state.pkg /tmp/salt_state.tgz 760a9353810e36f6d81416366fc426dc md5

调用需提供 tarball 路径、哈希值与哈希类型。源码做了多层安全与完整性校验:

  1. 文件不存在或哈希不匹配直接返回{}
  2. 解包前检查每个成员路径,若以/../开头、或包含../片段则拒绝解包(防止路径穿越);
  3. 解包后读取lowstate.json作为低数据、可选的pillar.json作为 Pillar 覆盖、可选的roster_grains.json作为 grains;
  4. fileclient = "local"、临时目录为file_roots的方式构造salt.state.State执行。

12. 状态禁用:disable、enable 与 list_disabled

Salt 支持动态禁用/启用状态运行(包括整个 highstate 或某个 SLS):

# 禁用 highstate salt '*' state.disable highstate # 禁用多个状态 salt '*' state.disable highstate,test.succeed_without_changes # 按 SLS 名禁用(与 state.sls 传参一致) salt '*' state.disable bind.config # 重新启用 salt '*' state.enable highstate salt '*' state.enable test.succeed_without_changes # 列出当前禁用的状态 salt '*' state.list_disabled

实现要点:禁用状态列表持久化在 grainstate_runs_disabled中(grains.setval写入),disableenable修改后都会调用saltutil.refresh_modules()刷新模块。内部辅助函数_disabled(第 2796 行)支持xxx.*前缀通配匹配——例如禁用bind.*会拦截所有以bind.开头的 SLS。当 highstate 被禁用时,state.highstate会直接返回一个result: "False"comment: "Disabled"的结果,并在日志中提示"To re-enable, run state.enable highstate"(源码第 1256-1268 行)。

13. 事件监听:state.event

state.event(第 2851 行,2016.3.0 起)阻塞监听 Salt 事件总线,直到匹配指定 tag:

salt-call --local state.event pretty=True

主要参数(源码 docstring 与签名):

参数默认值作用
tagmatch*事件 tag 的 glob 或正则(2019.2.0 起支持正则)
count-1匹配次数计数,-1表示持续监听
quietFalse不打印到 stdout,仅阻塞
sock_dirmaster 事件 socket 路径
prettyFalseFalse时单行 JSON(便于 shell 处理),True时美化缩进
nodeminion监听 minion 侧或 master 侧事件总线

实现上通过salt.utils.event.get_event(..., listen=True, auto_reconnect=True)获取事件流,用expr_match匹配 tag;匹配到的事件以tag<TAB>json格式输出。值得注意的一个实现细节:事件的 JSON 序列化使用了_json_safe(第 2837 行)作为default回调——当事件载荷中混有原始bytes(例如x509.sign_remote_certificate返回的 DER 编码证书,不是合法 UTF-8)时,会转成 base64 的 ASCII 字符串输出,保证事件流始终是合法 JSON。tests/pytests/unit/modules/test_state.py中的test_event_handles_binary_payload(第 346 行)正是对这一行为的回归测试。

14. 测试与质量保障

模块的核心行为在单元测试 tests/pytests/unit/modules/test_state.py 中有系统覆盖,可作为理解语义的补充证据:

  • test_get_initial_pillar(第 30 行)与test_check_test_value_is_boolean(第 44 行)验证 Pillar 快照与test标志的解析逻辑;
  • test_check_queue_*系列(第 59、142、210、269、299 行)覆盖队列冲突检测:其中test_check_queue_preserves_master_jid_69386验证排队任务保留 master 分配的 JID(对应源码中 issue #69386 的修复),test_check_queue_mints_jid_for_saltcall_when_no_pub_jid验证本地调用场景下才生成新 JID——这保证了 master 端的任务追踪(returners、jobs runner、syndic 转发)始终以 master 发布的 JID 为键,不会被 minion 端的新 JID 打断;
  • test_event_handles_binary_payload验证事件输出的二进制安全。

15. 总结:如何选择正确的执行入口

综合以上分析,可以从"执行粒度"维度做如下选择:

需求推荐入口
应用 top.sls 中全部状态state.apply(无参数)/state.highstate
应用指定 SLS 文件state.apply <sls>/state.sls
只执行某个 ID(含 requisite)state.sls_id
不写 SLS,直接执行单个状态函数state.single
执行指定 top 文件state.top
预览将应用的数据state.show_*系列
查看依赖图state.graph/state.graph_highstate
排队/暂停/恢复/终止运行中任务queue参数、state.pausestate.resumestate.soft_kill
请求-延迟执行state.request/state.run_request
禁用/启用状态state.disable/state.enable

所有执行类函数最终都汇聚到salt.state.HighStatesalt.state.State两个核心类(见 salt/state.py):HighState负责从 master/fileserver 获取并渲染 SLS 为高数据,State负责把高数据编译成低数据(low chunks)并逐条执行、处理 requisite 与监听器。理解这一分层后,无论通过哪个入口调用,都能清楚地预判状态系统在 minion 端的行为路径。

  • 运维
  • 配置管理
  • 后端

【免费下载链接】salt

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

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

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

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

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

立即咨询