- 运维
- 配置管理
- 后端
【免费下载链接】salt
Software to automate the management and configuration of infrastructure and applications at scale.
ext_nodes是 Salt 开源配置管理系统中master_tops子系统的一项经典能力,由 salt/tops/ext_nodes.py 实现。它允许 Salt Master 在每次 highstate 运行时,调用一个外部 Shell 命令(最典型的是 Cobbler 提供的cobbler-ext-nodes)来获取目标节点的分类(classes)与环境信息,并自动将其翻译为 Salt Top 文件(top.sls)能理解的数据结构。读完本文,你将掌握master_tops.ext_nodes的配置方法、返回数据格式约定、源码级的执行原理,以及如何用自定义命令替代cobbler-ext-nodes实现外部节点分类。
背景:master_tops 系统是什么
Salt 的传统状态管理依赖位于 file server 中的top.sls文件,通过匹配规则决定某个 minion 应应用哪些 state。但在 0.10.4 版本中,Salt 引入了 master tops 系统:在 Master 端通过可插拔的子系统动态生成 Top 文件数据,用于 highstate 运行。
关于这一演进,releases/0.10.4.rst 中有明确记载:external_nodes系统被升级为模块化子系统,旧的external_nodes配置项仍可工作,但将被废弃,取而代之的是master_tops配置项。当前仓库中的 doc/ref/configuration/master.rst 同样说明:
The master_tops option replaces the external_nodes option by creating a pluggable system for the generation of external top data. The external_nodes option is deprecated by the master_tops option.
也就是说,本文讨论的ext_nodes是master_tops下的一个具体实现模块,它是对经典 external nodes 方案的现代化封装。除ext_nodes外,master_tops还支持cobbler、mongo、reclass、saltclass、varstack等实现,这些模块共同存放在 salt/tops/ 目录下(对应 API 文档位于 doc/ref/tops/all/)。
配置方法:一行 YAML 接入外部分类命令
ext_nodes的配置非常简单。在 Master 配置文件(默认/etc/salt/master,仓库中对应 conf/master 的模板)中加入:
master_tops: ext_nodes: cobbler-ext-nodes其中cobbler-ext-nodes是一个 Shell 命令。Salt 会在每次需要生成 Top 数据时执行该命令,并把当前 minion 的 id 作为参数追加到命令尾部。例如,当名为web01的 minion 发起 highstate 时,Master 实际执行的命令等价于:
cobbler-ext-nodes web01该命令必须向stdout输出一段符合约定的 YAML 数据(下文详述格式),Salt 将解析这段 YAML 并转换为 Top 文件数据。
配置项说明
| 配置项 | 说明 |
|---|---|
master_tops.ext_nodes | 返回 YAML 的外部 Shell 命令字符串(Master 端配置) |
| 默认值 | 未配置时master_tops默认为{}(见 doc/ref/configuration/master.rst) |
需要强调的是,Salt 并不会直接消化cobbler-ext-nodes的原始输出,而是先把输出转换为 Salt Top 文件(top.sls)所需的信息结构。因此,任何命令都可以替换上面的cobbler-ext-nodes,但当前返回数据的格式必须与标准cobbler-ext-nodes保持一致。
返回数据格式:classes 与环境(environment)
外部命令返回的 YAML 数据约定如下(该示例同样出现在模块源码的 docstring 中,见 salt/tops/ext_nodes.py):
classes: - basepackages - database上面这段数据,本质上等价于一个包含以下内容的 top.sls:
base: '*': - basepackages - database也就是说,ext_nodes命令返回的classes列表,会被映射为某个 environment(默认base)下匹配全部目标('*')的 state 列表。
可选的环境键
如果外部命令额外返回了environment键,Salt 会将其作为这些 classes 所属的环境名:
classes: - one - two environment: dev等价于:
dev: '*': - one - two源码中的解析规则
从 salt/tops/ext_nodes.py 的实现可以看到解析逻辑非常直接:
- 如果返回数据中没有
environment键,默认环境为base; - 如果
classes是列表(list),直接作为该环境下的 state 列表使用; - 如果
classes是字典(dict),取其键列表(list(ndata["classes"]))作为 state 列表使用; - 如果
classes既不是列表也不是字典,或者数据中根本没有classes键,则返回空结果(ret保持为空,仅在日志中记录提示信息)。
当外部命令没有任何输出时,模块会记录一条 info 级日志:master_tops ext_nodes call did not return any data;缺少classes键时记录:master_tops ext_nodes call did not have a dictionary with a "classes" key.。
源码级执行原理
下面结合 salt/tops/ext_nodes.py 逐段剖析它的运行机制。
模块加载条件(__virtual__)
def __virtual__(): if __opts__["master_tops"].get("ext_nodes"): return True return False只有当 Master 配置中的master_tops.ext_nodes存在且非空时,该 tops 模块才会被 loader 加载,否则直接返回False跳过加载。这正是 Salt loader 体系中__virtual__的典型用法:按配置决定模块是否可用。
核心函数top(**kwargs)
def top(**kwargs): if "id" not in kwargs["opts"]: return {} proc = subprocess.run( [ _cmd_quote(part) for part in shlex.split( __opts__["master_tops"]["ext_nodes"], posix=salt.utils.platform.is_windows() is False, ) + [_cmd_quote(kwargs["opts"]["id"])] ], stdout=subprocess.PIPE, check=True, ) ndata = salt.utils.yaml.safe_load(proc.stdout) ...执行流程可以拆解为五个步骤:
- 参数完整性检查:如果调用参数
opts中没有id键(即缺少目标 minion 的 id),直接返回空字典{},不执行外部命令; - 命令构造:使用
shlex.split把配置的命令字符串按 Shell 语法拆分成参数列表(Windows 平台关闭 POSIX 模式,Linux 等平台启用),再逐段使用_cmd_quote进行 Shell 引用转义,最后把当前 minion 的 id 作为最后一个参数追加。_cmd_quote在非 Windows 平台为shlex.quote,在 Windows 平台使用salt.utils.win_functions.escape_argument——这保证了包含空格、特殊字符的命令段和 minion id 能被安全地作为单个参数传递; - 执行子进程:通过
subprocess.run(..., stdout=subprocess.PIPE, check=True)执行命令并捕获 stdout。check=True表示命令返回非零退出码时直接抛出异常; - 解析 YAML:用
salt.utils.yaml.safe_load解析子进程的标准输出,得到ndata字典; - 映射转换:按上文所述规则,把
environment和classes转换为{环境名: [state 列表]}的结构返回。
跨平台细节
模块顶部对 Windows 与非 Windows 做了显式分支:
if salt.utils.platform.is_windows(): from salt.utils.win_functions import escape_argument as _cmd_quote else: _cmd_quote = shlex.quote同时shlex.split的posix参数也根据平台动态设置。这保证了ext_nodes在 Windows Master 与类 Unix Master 上都能正确拼接并执行外部命令。
与 Top 文件的合并机制
ext_nodes返回的数据并不会取代 top.sls,而是与 top.sls 的匹配结果合并。这一合并逻辑位于状态系统的核心文件 salt/state.py:
ext_matches = self._master_tops() for saltenv in ext_matches: top_file_matches = matches.get(saltenv, []) if self.opts.get("master_tops_first"): first = ext_matches[saltenv] second = top_file_matches else: first = top_file_matches second = ext_matches[saltenv] matches[saltenv] = first + [x for x in second if x not in first]要点:
- 默认顺序:top.sls 的匹配结果在前,
ext_nodes的结果在后,且去重(x not in first); master_tops_first:如果 Master 配置了master_tops_first: True,则ext_nodes的结果排在前面;- 按环境合并:
ext_matches中的每个环境(如base、dev)分别与该环境下已有的 top.sls 匹配项合并。
此外,_master_tops()方法会根据file_client判断:masterless(本地文件客户端)模式下走_local_master_tops(),否则通过 master client 的master_tops()获取数据。从 releases/3007.0.md 与 doc/topics/master_tops/index.rst 的说明可知,从 3007.0 版本起 masterless minion 也支持 master tops 模块,因此ext_nodes在 masterless 场景下同样可以生效。在_local_master_tops(salt/state.py)中,还会对 minion id 做salt.utils.verify.valid_id校验,只有通过校验才会加载 tops 模块并合并结果。
自定义外部分类命令
ext_nodes的设计决定了它不绑定 Cobbler:只要你的命令接收一个 minion id 参数并向 stdout 输出约定格式的 YAML,就能无缝替换。一个最小可用的自定义命令示例(Shell 脚本):
#!/bin/sh # 接收 minion id 作为 $1,根据业务数据返回 YAML case "$1" in web*) echo "classes:" echo " - webserver" echo " - common" echo "environment: prod" ;; db*) echo "classes:" echo " - database" echo " - common" echo "environment: prod" ;; *) echo "classes:" echo " - common" ;; esac随后在 Master 配置中指向该脚本:
master_tops: ext_nodes: /usr/local/bin/my-ext-nodes也可以直接使用返回 YAML 的现有工具命令。仓库中的单元测试 tests/pytests/unit/tops/test_ext_nodes.py 就用echo作为测试命令("ext_nodes": "echo"),以验证模块在无真实外部程序的情况下也能正常工作。
数据约定小结
- 命令接收参数:目标 minion 的 id(作为最后一个参数传入);
- 输出要求:向 stdout 输出 YAML;
- 必须包含:
classes键(列表或字典形式); - 可选包含:
environment键(缺省为base)。
验证方式
单元测试
仓库自带的单元测试 tests/pytests/unit/tops/test_ext_nodes.py 覆盖了两个核心场景:
- 基础场景(
test_ext_nodes):外部命令输出classes: [one, two]时,top()返回{"base": ["one", "two"]},并断言subprocess.run以["echo", "foo"], check=True, stdout=-1的方式被调用; - 环境场景(
test_ext_nodes_with_environment):输出中带environment: dev时,返回{"dev": ["one", "two"]},验证 classes 被正确分配到指定环境。
运行时验证
配置完成后,可以用 Salt 自带的命令查看 minion 的 Top 数据是否生效:
salt minion state.show_top如果一切正常,输出中会出现由ext_nodes提供的 state 列表(合并逻辑下与 top.sls 内容一同显示)。
参考资料
- 模块 API 文档:doc/ref/tops/all/salt.tops.ext_nodes.rst
- 模块实现源码:salt/tops/ext_nodes.py
- 单元测试:tests/pytests/unit/tops/test_ext_nodes.py
- Master 配置参考:doc/ref/configuration/master.rst 与 conf/master
- Master Tops 系统总览:doc/topics/master_tops/index.rst
- 状态合并逻辑:salt/state.py
- 功能引入历史:doc/topics/releases/0.10.4.rst
适用说明:以上配置与行为均基于当前仓库源码确认。
ext_nodes依赖 Master 端的master_tops配置;在 masterless 模式下需 3007.0 及以上版本才支持 tops 模块的本地执行。
- 运维
- 配置管理
- 后端
【免费下载链接】salt
Software to automate the management and configuration of infrastructure and applications at scale.
相关推荐
Salt master_tops 的 reclass 适配器:用外部数据源动态生成 Highstate Top 数据
Salt master_tops 的 reclass 适配器:用外部数据源动态生成 Highstate Top 数据 导读 本文围绕 Salt 内置的 mast
运维配置管理后端Salt Master Tops 系统完全指南:用外部数据源动态生成 top 数据
Salt Master Tops 系统完全指南:用外部数据源动态生成 top 数据 Salt 的 Master Tops 是一套可插拔的 master 端子系统
运维配置管理后端Salt 的 Saltclass master_tops 模块:用分层类继承为节点生成 top 与 Pillar
Salt 的 Saltclass master_tops 模块:用分层类继承为节点生成 top 与 Pillar Salt 通过 master_tops 机制允
运维配置管理后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考