☰
Salt master_tops 外部节点分类器(ext_nodes):从外部系统动态生成 Top 文件数据
2026/9/25 5:15:31 网站建设 项目流程
  • 运维
  • 配置管理
  • 后端

【免费下载链接】salt

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

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

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) ...

执行流程可以拆解为五个步骤:

  1. 参数完整性检查:如果调用参数opts中没有id键(即缺少目标 minion 的 id),直接返回空字典{},不执行外部命令;
  2. 命令构造:使用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 能被安全地作为单个参数传递;
  3. 执行子进程:通过subprocess.run(..., stdout=subprocess.PIPE, check=True)执行命令并捕获 stdout。check=True表示命令返回非零退出码时直接抛出异常;
  4. 解析 YAML:用salt.utils.yaml.safe_load解析子进程的标准输出,得到ndata字典;
  5. 映射转换:按上文所述规则,把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 覆盖了两个核心场景:

  1. 基础场景(test_ext_nodes):外部命令输出classes: [one, two]时,top()返回{"base": ["one", "two"]},并断言subprocess.run以["echo", "foo"], check=True, stdout=-1的方式被调用;
  2. 环境场景(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.

项目地址:https://gitcode.com/gh_mirrors/sa/salt
点击查看免费下载
上一篇:AutoBlue-MS17-010源码解析:exploit函数与SMB协议交互实现
下一篇:ZLMediaKit H265视频转码失败问题分析与解决方案

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

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

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

立即咨询