- 运维
- 配置管理
- 后端
【免费下载链接】salt
Software to automate the management and configuration of infrastructure and applications at scale.
本篇技术指南以 Salt 仓库中 doc/ref/modules/all/salt.modules.state.rst 所对应的 salt/modules/state.py 为核心,系统讲解 Salt 状态系统在 minion 端的所有执行入口:state.apply、state.highstate、state.sls、state.single、state.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.HighState或salt.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.sls或salt://stuff/init.sls):
salt '*' state.apply stuff应用多个 SLS 文件:
salt '*' state.apply stuff,pkgs应用深层嵌套目录中的 SLS:
salt '*' state.apply my.organized.stuffstate.apply同时接受以下常用参数(完整参数说明见 源码 docstring):
| 参数 | 默认值 | 作用 |
|---|---|---|
test | — | 以 dry-run(测试)模式运行状态,只报告将要做的更改而不实际执行 |
mock | False | 完全不调用任何状态,返回模拟结果,用于校验 requisite 顺序与状态编排(2015.8.4 起) |
pillar | — | 以字典形式传入自定义 Pillar 值,会覆盖pillar_roots或外部 Pillar 源的同名值 |
exclude | — | 排除特定状态,接受 SLS 名列表、逗号分隔字符串或含sls/id键的字典列表,支持 glob 通配 |
queue | False | 当有另一个状态运行在进行时,排队等待而非直接失败(详见第 6 节) |
concurrent | False | 允许状态并发执行(危险,仅用于可安全并行的场景,不可用于性能优化) |
saltenv | base(若未配置) | 指定使用的 fileserver 环境 |
pillarenv | — | 指定 Pillar 环境;minion 配置中的pillarenv也可设置 |
localconfig | — | 使用指定文件中的 minion 配置,并与现有配置合并,可让特定状态用独立的自定义配置运行 |
sync_mods | — | 运行 SLS 前同步指定类型的自定义模块,如sync_mods=states,modules或sync_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实例并进入上下文管理器,随后依次完成:
- 检查 Pillar 渲染错误(
_get_pillar_errors,出错返回EX_PILLAR_FAILURE); st_.push_active()标记自身为活跃状态,防止并发重复执行;- 可选地创建 snapper pre 快照(受
snapper_states配置控制); st_.call_highstate(...)编译并执行全部高数据;- 若配置
state_data: terse或传入terse=True,过滤掉"结果成功且无更改"的条目(_filter_running,第 88 行); _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=devsls执行的关键流程(源码第 1596-1667 行):
render_highstate({opts["saltenv"]: mods})渲染指定 SLS 为高数据;- 若有
exclude,追加到high_["__exclude__"]; st_.state.call_high(high_, orchestration_jid)编译低数据并执行;- 结果写入
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按点号拆成state与fun两部分(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.p。run_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_cacheclear_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"]}}'从源码看,low用salt.state.State实例的verify_data校验数据后调用call;high则校验 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 minion(salt-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 行),支持mods、saltenv、test、exclude、pillar、pillarenv等参数。
11.3 state.pkg:打包状态运行
state.pkg(第 2607 行)执行一个本地 tarball 打包的状态运行,该 tarball 可由 salt-ssh 生成:
salt '*' state.pkg /tmp/salt_state.tgz 760a9353810e36f6d81416366fc426dc md5调用需提供 tarball 路径、哈希值与哈希类型。源码做了多层安全与完整性校验:
- 文件不存在或哈希不匹配直接返回
{}; - 解包前检查每个成员路径,若以
/或../开头、或包含../片段则拒绝解包(防止路径穿越); - 解包后读取
lowstate.json作为低数据、可选的pillar.json作为 Pillar 覆盖、可选的roster_grains.json作为 grains; - 以
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写入),disable与enable修改后都会调用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表示持续监听 |
quiet | False | 不打印到 stdout,仅阻塞 |
sock_dir | — | master 事件 socket 路径 |
pretty | False | False时单行 JSON(便于 shell 处理),True时美化缩进 |
node | minion | 监听 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.pause、state.resume、state.soft_kill |
| 请求-延迟执行 | state.request/state.run_request |
| 禁用/启用状态 | state.disable/state.enable |
所有执行类函数最终都汇聚到salt.state.HighState与salt.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.
相关推荐
使用 Salt State 调用 Ansible:ansiblegate 状态模块深度解析
使用 Salt State 调用 Ansible:ansiblegate 状态模块深度解析 在 Salt 与 Ansible 并存的基础设施中,通过 salt.
运维配置管理后端Salt Windows 用户组管理实战:win_groupadd 执行模块与 group 状态模块全解析
Salt Windows 用户组管理实战:win_groupadd 执行模块与 group 状态模块全解析 导读 本文围绕 Salt 官方文档 salt.mod
运维配置管理后端Kilo VS Code 扩展后台 Agent 可见性:任务卡滚动离屏后的会话级子代理监控方案
Kilo VS Code 扩展后台 Agent 可见性:任务卡滚动离屏后的会话级子代理监控方案 本文基于 Kilo 仓库中 packages/kilo vsco
运维配置管理后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考