news 2026/9/25 5:08:34

Salt SLS 模板变量完全指南:`sls`、`slspath`、`tpldir` 等上下文变量的使用与原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Salt SLS 模板变量完全指南:`sls`、`slspath`、`tpldir` 等上下文变量的使用与原理
  • 运维
  • 配置管理
  • 后端

【免费下载链接】salt

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

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

Salt(SaltStack)在渲染 SLS 状态文件与文件模板时,会向模板引擎注入一批上下文变量(context variables),例如salt、opts、pillar、grains,以及仅对 SLS 状态文件可见的sls、slspath、tpldir、sls_path等路径派生变量。本篇指南以官方参考文档 SLS Template Variable Reference 为骨架,结合仓库源码(salt/utils/templates.py、salt/utils/jinja.py、salt/renderers/jinja.py)深入剖析每个变量的来源、作用域与底层生成逻辑。读完本文,你将能够在 SLS 中安全地引用这些变量、正确生成基于当前 SLS 目录的相对salt://路径,并规避 Salt 3005 版本对sls_path、tplfile、tpldir行为的破坏性变更。

模板上下文变量从哪里来:渲染管线概览

在深入每个变量之前,先理清 Salt 的模板渲染调用链。SLS 文件首先由 salt/template.py 中的compile_template()按 shebang(如#!jinja|yaml)选出渲染管线(render pipe),随后每个 renderer(如 Jinja)以saltenv、sls等关键字参数被调用:

ret = render(input_data, saltenv, sls, **render_kwargs)

以 Jinja renderer 为例,salt/renderers/jinja.py 的render()会一次性注入salt、grains、opts、pillar、saltenv、sls、proxy、context、tmplpath等上下文,并调用salt.utils.templates.JINJA(即wrap_tmpl_func(render_jinja_tmpl))。而wrap_tmpl_func会进一步调用 generate_sls_context() 生成slspath、sls_path、slsdotpath、slscolonpath、tplpath、tplfile、tpldir、tpldot这一组 SLS 路径变量,并通过context.setdefault(key, value)合入模板上下文(见 salt/utils/templates.py)。

因此,所有变量本质上都是渲染期(render-time)通过上下文字典注入的,下文将按“全局可用”与“仅 SLS 可用”两个维度逐一讲解。

全局模板变量:在任何模板中均可使用

以下变量在所有 Salt 模板(SLS 状态文件、file.managed管理的 Jinja 模板、pillar、top文件等)中均可直接引用。

salt:调用 minion 上所有函数的字典

salt变量抽象了 Salt 库函数,本质是一个 Python 字典,包含运行中 minion 可用的全部执行模块函数,可在所有 Salt 模板中使用。最典型的用途是在模板中动态收集系统信息并生成状态声明:

{% for file in salt'cmd.run'.splitlines() %} /opt/to_remove/{{ file }}: file.absent {% endfor %}

源码细节:在 wrap_tmpl_func() 中,salt会被包装为AliasedLoader,将cmd.run别名到cmd.shell,从而让模板内的命令调用默认以python_shell=True执行(即 shell 语法直接可用)。Jinja renderer 还会通过_split_module_dicts()同时支持salt'cmd.run'与salt.cmd.run(...)两种调用语法(见 salt/renderers/jinja.py)。

opts:minion 配置字典

opts变量将 minion 配置文件(/etc/salt/minion)的内容直接暴露给模板,它是一个字典,在所有模板中可用:

{{ opts['cachedir'] }}

注意,config.get函数也会在opts字典中查找取值,因此opts与salt'config.get'的数据来源是相通的。

pillar:pillar 数据字典

pillar字典可以直接引用,并在所有模板中可用:

{{ pillar['key'] }}

官方更推荐通过salt变量调用pillar.get函数,原因是它可以安全地设置默认值(当 pillar 中不存在该键时不会抛异常),并且可以直接用冒号分隔符遍历嵌套字典:

{{ salt'pillar.get' }} {{ salt'pillar.get' }}

grains:minion 的 grains 字典

grains字典直接暴露 minion 的静态与动态 grains 信息,在所有模板中可用:

{{ grains['os'] }}

同样地,grains.get函数可以深入嵌套结构并设置默认值:

{{ salt'grains.get' }}

saltenv:当前环境(file root)名称

saltenv变量表示当前正在从中获取 SLS 的环境(如base、dev、prod)。它只在从某个环境收集 SLS 文件时可用:

{{ saltenv }}

当不存在saltenv时(例如本地文件模板场景),Jinja renderer 会退化为使用FileSystemLoader而非 Salt 的SaltCacheLoader(见 salt/utils/templates.py)。

仅 SLS 文件可用的变量(SLS Only Variables)

以下变量只在处理 SLS 状态文件时注入。如果你在其他模板(如file.managed的源模板)中也需要这些信息,通常需要把它们作为模板上下文(context或defaults)显式传入。

sls:SLS 引用值

sls变量保存当前 SLS 的引用值(reference value),即你在 top 文件中引用它、或通过include:选项包含它时使用的名称(点分隔形式)。它只在实际的 SLS 文件本身中可用,而不会出现在该 SLS 引用的其他文件(例如被file.managed管理的模板)中:

{{ sls }}

例如top.sls中写- webserver.nginx,那么在渲染webserver/nginx.sls时sls的值就是webserver.nginx。

slspath:当前 SLS 所在目录(斜杠分隔)

slspath包含当前 SLS 文件所在目录的路径。当当前 SLS 位于 file roots 根目录时,其值为""。它的取值与引用方式有关:

  • 对于 Jinja{% include %}(模板级包含):slspath是当前文件所在目录的路径;
  • 对于 Saltinclude:(状态级包含):slspath是被包含文件所在目录的路径。
{{ slspath }}

sls_path:下划线分隔的slspath

sls_path是slspath的变体,用下划线_代替路径分隔符/。例如slspath为path/to/state,则sls_path为path_to_state:

{{ sls_path }}

slsdotpath:点分隔的slspath

slsdotpath用点.代替路径分隔符。例如slspath为path/to/state,则slsdotpath为path.to.state。当sls指向一个目录(即隐式init.sls)时,slsdotpath与sls相同:

{{ slsdotpath }}

slscolonpath:冒号分隔的slspath

slscolonpath用冒号:代替路径分隔符,例如path/to/state对应path:to:state:

{{ slscolonpath }}

tplpath:模板在本地磁盘上的完整路径

tplpath是被处理的 SLS 模板文件在本地磁盘上的完整路径(通常指向缓存目录中的副本,且为操作系统特定格式,Windows 与 POSIX 不同)。官方文档明确提示“最好别用它”(It is probably best not to use this.),因为它的值依赖缓存布局,不具备可移植性:

{{ tplpath }}

tplfile:相对于 file roots 的模板路径

tplfile是当前被处理的 SLS 模板文件相对于 file roots 的路径,例如foo/bar.sls:

{{ tplfile }}

tpldir:当前 SLS 所在目录(相对 file roots)

tpldir是当前 SLS 文件相对于 file roots 的目录。与slspath的区别仅在于:当 SLS 位于 file roots 根目录时,tpldir返回.(点),而slspath返回空字符串""。其余情况下二者通常相同。

tpldir最经典的实战用途是生成相对salt://的源路径,使状态文件可以在不同目录结构间迁移而不写死绝对路径:

my-file: file.managed: source: salt://{{ tpldir }}/files/my-template

tpldot:点分隔的tpldir

tpldot用点代替tpldir中的斜杠;例如tpldir为path/to/state,则tpldot为path.to.state。注意:如果tpldir是.,则tpldot被置为"":

{{ tpldot }}

slspath与tpldir何时被填充:状态编译器 vs 渲染器

文档特别澄清了slspath与tpldir的注入时机。它们是模板渲染期变量,由 Salt 的**状态编译器(state compiler)**在把 SLS 渲染为状态数据时注入。由此可以总结出三条明确的边界规则:

  1. 被填充的场景:所有被当作状态数据渲染的 SLS 文件——包括 top 文件、被include:包含的 SLS、以及正在被 apply 的 SLS 本身——内部都有slspath与tpldir。
  2. 不被填充的场景:通过file.managed+template: jinja渲染的非状态文件模板中,这两个变量不存在。因为此时模板由渲染器子系统(renderer subsystem)渲染,而非状态编译器,状态专属变量不在作用域内。
  3. 需要在非状态模板中取 SLS 路径怎么办:通过defaults或context显式传入。官方文档给出的标准做法是:
configure-app: file.managed: - name: /etc/app.conf - source: salt://app/files/app.conf.j2 - template: jinja - defaults: sls_dir: {{ slspath }}

在app.conf.j2内部即可使用{{ sls_dir }}拿到 SLS 所在目录。

两类 include 的行为差异

  • 在 SLS 文件中使用 Jinja 的{% include %}:被包含的模板继承当前的 SLS 渲染上下文,因此slspath与tpldir仍指向发起包含的那个 SLS。
  • 在 SLS 文件顶部使用 Salt 的include:指令引入其他 SLS:每个 SLS 在自身被渲染期间看到的是各自的slspath。

源码级原理:generate_sls_context()如何推导这些变量

这些路径变量的推导逻辑集中在 salt/utils/templates.py 的generate_sls_context(tmplpath, sls)函数中,关键步骤包括:

  1. 先把点分隔的sls引用值规范化为路径形式并去掉尾部斜杠:
    slspath = sls.replace(".", "/").rstrip("/")
  2. 根据tmplpath与slspath的匹配关系剥离出模板名(处理xxx.sls、xxx/init.sls与无法匹配的非 SLS 文件三种分支,见 salt/utils/templates.py)。
  3. 从剥离后的模板路径中计算tplfile、tpldir(空目录时回退为.)与tpldot。
  4. 最后基于slspath一次性派生四个变体:
    slsdotpath = slspath.replace("/", ".") slscolonpath = slspath.replace("/", ":") sls_path = slspath.replace("/", "_")

单元测试 tests/pytests/unit/utils/templates/test_jinja.py 完整覆盖了这些推导行为,可以直接当作速查表使用:

输入 SLSslsslspathtpldirsls_pathslsdotpathslscolonpathtpldot
foo/bar.slsfoo.barfoofoofoofoofoofoo
foo/init.slsfoofoofoofoofoofoofoo
a/b/c.slsa.b.ca/ba/ba_ba.ba:ba.b
foo.sls(根级)foo"".""""""""

(以上断言对应 test_jinja.py 中的四个用例,根级行为的另一个实现见 test_wrap_tmpl_func.py。)

用户自定义值优先:Issue #68754 的修复语义

在wrap_tmpl_func()中,由generate_sls_context()计算出的变量通过context.setdefault(key, value)合并进上下文(salt/utils/templates.py),这意味着:

调用方显式提供的值(例如file.managed上的defaults/context)优先于从sls推导出的值,不会被静默覆盖。

这是对 Issue #68754 的修复。对应回归测试 test_wrap_tmpl_func.py 验证了:当用户在defaults/context中同时给出sls及其派生变量(slspath、sls_path、slsdotpath、slscolonpath、tpldir等)时,最终渲染上下文中的这些值必须保持为用户提供的内容。这为上文“在file.managed中用defaults传递slspath”的推荐做法提供了实现级保证。

实战组合:构建目录感知的公式(formula)

综合以上变量,可以写出一个“目录感知”的 SLS 与模板协作模式:

# salt://myapp/init.sls {% set config_dir = salt'pillar.get' %} myapp-config: file.managed: - name: /etc/myapp.conf - source: salt://{{ tpldir }}/files/myapp.conf.j2 - template: jinja - defaults: sls_dir: {{ slspath }} app_env: {{ saltenv }} # myapp/files/myapp.conf.j2 内部 base_dir = {{ sls_dir }} environment = {{ app_env }}

这里tpldir、slspath、saltenv都取自当前 SLS,通过defaults传递后,模板内部不再依赖任何 SLS 上下文变量,从而避免了“非状态模板中tpldir未定义”的坑。此外,salt/modules/slsutil.py 提供的slsutil.findup也能借助tplfile在公式目录树中向上查找共享的map.jinja/defaults.yaml:

{% set defaults = salt"slsutil.findup" %}

兼容性警示:3005 版本对sls_path、tplfile、tpldir的改进

官方文档开篇即给出警告:在 Salt 3005 版本中,sls_path、tplfile与tpldir得到了重大改进,这些改进可能破坏依赖旧行为的状态文件。

结合源码可以推断旧行为与新行为的差异点:

  • 3005 之前的实现可能把tplpath(本地磁盘缓存路径)错误地当作tplfile/tpldir的来源,导致 Windows 与 POSIX 行为不一致;
  • 新实现(generate_sls_context())统一以sls引用值为基准推导路径变量,并明确区分“SLS 被渲染为状态数据”与“模板被渲染为非状态文件”两种场景;
  • 根级 SLS 的边界值也被规范化:tpldir = "."、slspath = ""、tpldot = ""。

如果你维护的公式依赖这些变量且尚未适配 3005,建议:

  1. 用salt --versions确认 minion / master 版本;
  2. 在根级 SLS 中不要假设tpldir/slspath有非空值;
  3. 尽量用defaults/context显式传递路径,而非依赖模板内部隐式可见性。

小结

变量作用域含义典型用途
salt所有模板minion 全部执行模块函数的字典动态收集数据、生成声明
opts所有模板minion 配置字典读取cachedir等配置
pillar所有模板pillar 数据字典读取配置数据(推荐pillar.get)
grains所有模板minion grains 字典按系统信息分支(推荐grains.get)
saltenvSLS(从环境获取时)当前环境名称环境相关逻辑
sls仅 SLS 本身SLS 引用值自引用、日志
slspath仅 SLS当前 SLS 目录(斜杠分隔,根级为"")构造相对路径
sls_path仅 SLS下划线分隔的slspath生成唯一标识符
slsdotpath仅 SLS点分隔的slspath与sls引用值互转
slscolonpath仅 SLS冒号分隔的slspath与pillar.get的键格式对齐
tplpath仅 SLS模板本地磁盘完整路径不推荐使用
tplfile仅 SLS相对 file roots 的模板路径配合slsutil.findup查找共享文件
tpldir仅 SLS当前 SLS 目录(根级为.)生成salt://{{ tpldir }}/...相对源
tpldot仅 SLS点分隔的tpldir(根级为"")模板标识

理解这些变量的作用域与推导规则,是编写健壮、可移植 SLS 状态文件的基础。更多官方描述可查阅 doc/ref/states/vars.rst,推导逻辑的实现细节可深入阅读 salt/utils/templates.py、salt/utils/jinja.py 及对应的单元测试 test_jinja.py 与 test_wrap_tmpl_func.py。

  • 运维
  • 配置管理
  • 后端

【免费下载链接】salt

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

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

相关推荐

上一篇:Visdom能源消耗监控:实时数据与优化建议
下一篇:如何轻松备份微信聊天记录:WeChatMsg完整指南让珍贵对话永不丢失

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 5:08:17

AI电影宣传片制作全流程:从分镜脚本到成片输出

1. 项目缘起与整体设计思路1.1 为什么会想做一支AI电影宣传片先说清楚这个项目的来龙去脉。我平时的工作跟影视后期、视觉特效沾边,业余时间喜欢折腾各种生成式AI工具。前段时间《黑寡妇》这个IP的讨论度又起来了,网上关于续集的各种猜测满天飞&#xff…

作者头像 李华
网站建设 2026/9/25 5:08:04

非标定制芯片烧录设备:从需求到量产的全流程实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 5:06:30

动态路由配置

动态路由配置原理RIPOSPF拓扑图示静态ip方式RIP方式要点说明案例OSPF方式要点说明案例ping通测试静态路由配置是动态路由配置的基础,动态路由先配置每一个端口的IP,之后在根据相应的动态路由配置协议进行相关配置原理 RIP RIP是一种基于距离向量&#…

作者头像 李华
网站建设 2026/9/25 5:05:35

yolov7 tensorrt模型加速部署【实战】

TensorRT系列之 Windows10下yolov8 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov8 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov7 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov6 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov5 tensorrt模型加速…

作者头像 李华
网站建设 2026/9/25 5:05:16

Atlas 300V 24G AI推理加速卡部署YOLO完整指南

最近在折腾目标检测的推理加速,不少朋友跑来问我同一个问题:“atlas 300v 24g 是运算加速卡吗”。这个问题也把我拉回了去年第一次接触 Atlas 的场景。我的回答很干脆:它是AI推理加速卡,不是传统意义上的显卡,也不是训…

作者头像 李华