- 运维
- 配置管理
- 后端
【免费下载链接】salt
Software to automate the management and configuration of infrastructure and applications at scale.
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是当前文件所在目录的路径; - 对于 Salt
include:(状态级包含):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-templatetpldot:点分隔的tpldir
tpldot用点代替tpldir中的斜杠;例如tpldir为path/to/state,则tpldot为path.to.state。注意:如果tpldir是.,则tpldot被置为"":
{{ tpldot }}slspath与tpldir何时被填充:状态编译器 vs 渲染器
文档特别澄清了slspath与tpldir的注入时机。它们是模板渲染期变量,由 Salt 的**状态编译器(state compiler)**在把 SLS 渲染为状态数据时注入。由此可以总结出三条明确的边界规则:
- 被填充的场景:所有被当作状态数据渲染的 SLS 文件——包括 top 文件、被
include:包含的 SLS、以及正在被 apply 的 SLS 本身——内部都有slspath与tpldir。 - 不被填充的场景:通过
file.managed+template: jinja渲染的非状态文件模板中,这两个变量不存在。因为此时模板由渲染器子系统(renderer subsystem)渲染,而非状态编译器,状态专属变量不在作用域内。 - 需要在非状态模板中取 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)函数中,关键步骤包括:
- 先把点分隔的
sls引用值规范化为路径形式并去掉尾部斜杠:slspath = sls.replace(".", "/").rstrip("/") - 根据
tmplpath与slspath的匹配关系剥离出模板名(处理xxx.sls、xxx/init.sls与无法匹配的非 SLS 文件三种分支,见 salt/utils/templates.py)。 - 从剥离后的模板路径中计算
tplfile、tpldir(空目录时回退为.)与tpldot。 - 最后基于
slspath一次性派生四个变体:slsdotpath = slspath.replace("/", ".") slscolonpath = slspath.replace("/", ":") sls_path = slspath.replace("/", "_")
单元测试 tests/pytests/unit/utils/templates/test_jinja.py 完整覆盖了这些推导行为,可以直接当作速查表使用:
| 输入 SLS | sls | slspath | tpldir | sls_path | slsdotpath | slscolonpath | tpldot |
|---|---|---|---|---|---|---|---|
foo/bar.sls | foo.bar | foo | foo | foo | foo | foo | foo |
foo/init.sls | foo | foo | foo | foo | foo | foo | foo |
a/b/c.sls | a.b.c | a/b | a/b | a_b | a.b | a:b | a.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,建议:
- 用
salt --versions确认 minion / master 版本; - 在根级 SLS 中不要假设
tpldir/slspath有非空值; - 尽量用
defaults/context显式传递路径,而非依赖模板内部隐式可见性。
小结
| 变量 | 作用域 | 含义 | 典型用途 |
|---|---|---|---|
salt | 所有模板 | minion 全部执行模块函数的字典 | 动态收集数据、生成声明 |
opts | 所有模板 | minion 配置字典 | 读取cachedir等配置 |
pillar | 所有模板 | pillar 数据字典 | 读取配置数据(推荐pillar.get) |
grains | 所有模板 | minion grains 字典 | 按系统信息分支(推荐grains.get) |
saltenv | SLS(从环境获取时) | 当前环境名称 | 环境相关逻辑 |
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.
相关推荐
Salt SLS 模板变量(slspath、tpldir 等)的作用域边界与正确传递方式
Salt SLS 模板变量(slspath、tpldir 等)的作用域边界与正确传递方式 Salt 状态系统内置了一组仅在 SLS 渲染期有效的模板变量( sl
运维配置管理后端Salt 的 Mako 渲染器(salt.renderers.mako)实战指南:模板管线、上下文变量与源码原理
Salt 的 Mako 渲染器(salt.renderers.mako)实战指南:模板管线、上下文变量与源码原理 导读 本文围绕 Salt 官方 API 参考文
运维配置管理后端Salt slsutil 执行模块完全指南:SLS 文件与模板中的数据处理、渲染与文件探测工具
Salt slsutil 执行模块完全指南:SLS 文件与模板中的数据处理、渲染与文件探测工具 本文以 Salt 官方 API 文档 doc/ref/modul
运维配置管理后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考