☰
Salt master_tops 的 reclass 适配器:用外部数据源动态生成 Highstate Top 数据
2026/9/25 16:22:20 网站建设 项目流程
  • 运维
  • 配置管理
  • 后端

【免费下载链接】salt

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

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

导读

本文围绕 Salt 内置的master_tops插件reclass_adapter(虚拟模块名reclass)展开,讲解如何将 reclass 外部配置数据库作为 Top 数据的唯一来源,让 Salt master 在每次 highstate 运行时按 minion 动态计算应执行的 state 列表。读完本文,你将掌握master_tops.reclass的完整配置方法、与ext_pillar.reclass复用同一份 reclass 配置的 YAML 锚点技巧、从源码目录运行 reclass 的方式,以及该适配器从加载、参数解析到错误处理的源码级工作原理。

一、背景:master_tops 可插拔系统

Salt 的 state 系统默认通过 Top 文件(top.sls)决定各 minion 需要执行的 state。当需要把 top 数据的决策权交给外部系统时,可以使用master_tops机制。

根据 master 配置文档,master_tops默认值为{},它取代了早期的external_nodes选项,形成一套可插拔的「外部 top 数据生成」体系:

master_tops: ext_nodes: <Shell command which returns yaml>

master_tops下可以同时注册多个后端,例如cobbler、varstack、saltclass,以及本文的主角reclass。所有内置 tops 模块的清单见 doc/ref/tops/all/index.rst,模块源码统一位于 salt/tops/ 目录(cobbler.py、ext_nodes.py、mongo.py、reclass_adapter.py、saltclass.py、varstack_top.py)。

值得注意的两点:

  • 从 3007.0 版本开始,无 master 的 minion(masterless)同样支持 master_tops 模块(见 doc/topics/master_tops/index.rst)。
  • master_tops 返回的数据会与 top 文件中的匹配结果叠加,而不是互相替代;不过 2018.3.0 起可以通过 minion 端配置master_tops_first: True让 minion 把 master_tops 视为首选来源。

二、reclass 适配器最小配置

reclass 是一个外部配置管理数据库,Salt 通过 salt/tops/reclass_adapter.py 这个适配器从中读取 top 数据。该模块的完整使用文档以 docstring 形式保存在源码顶部,并由 doc/ref/tops/all/salt.tops.reclass_adapter.rst 通过automodule指令自动生成。

启用方式很简单:在 master 配置的master_tops列表中注册reclass插件,并用几个选项告诉 reclass 在哪里、以何种格式寻找 inventory:

master_tops: reclass: storage_type: yaml_fs inventory_base_uri: /srv/salt
  • storage_type: yaml_fs:reclass 从 YAML 文件系统读取 inventory。
  • inventory_base_uri: /srv/salt:inventory 的根路径。

配置完成后,reclass 会从/srv/salt/nodes与/srv/salt/classes两个子目录读取 inventory:nodes目录存放每个节点的数据(节点即 minion),classes目录存放可被节点引用/继承的类定义。这也意味着,若要使用该插件,需要按照 reclass 的目录约定在这些位置维护 inventory 文件。

master_tops在 conf/master 示例配置中有注释说明,默认保持注释状态(master_tops: {})。

三、与 ext_pillar 共享同一份 reclass 配置

如果同时把 reclass 用作ext_pillar插件(见 salt/pillar/reclass_adapter.py),避免在两处重复填写相同参数的最优雅做法是利用 YAML 锚点:

reclass: &reclass storage_type: yaml_fs inventory_base_uri: /srv/salt reclass_source_path: ~/code/reclass ext_pillar: - reclass: *reclass master_tops: reclass: *reclass

这里需要特别留意ext_pillar与master_tops两处数据结构类型的差异:

  • ext_pillar是一个列表,其中每一项是「单键哈希」:- reclass: *reclass;
  • master_tops是一个映射:reclass: *reclass。

两者的取值都引用同一个锚点&reclass,因此修改一处即可同时作用于 pillar 与 top 两个数据通道,保证数据源一致性。

四、从源码运行 reclass(不必安装)

文档明确给出了两种让 master 找到 reclass 库的方式:

  1. 通过PYTHONPATH环境变量将 reclass 源码目录加入 Python 搜索路径;
  2. 通过配置选项reclass_source_path指定源码路径,例如上面的~/code/reclass。

在源码层面,salt/utils/reclass.py 的prepend_reclass_source_path()负责把配置中的路径规范化后插入sys.path:

def prepend_reclass_source_path(opts): source_path = opts.get("reclass_source_path") if source_path: source_path = os.path.abspath(os.path.expanduser(source_path)) sys.path.insert(0, source_path)

注意它对路径做了expanduser(展开~)与abspath(转为绝对路径)处理,因此写~/code/reclass是可行的。

五、源码级原理剖析

5.1 虚拟名与延迟加载

由于模块文件名如果叫reclass.py会与第三方包reclass在 import 时互相遮蔽,源码注释专门说明了这一点:真正的插件文件命名为reclass_adapter.py,但通过__virtualname__ = "reclass"让加载器仍以reclass之名注册它(salt/tops/reclass_adapter.py)。

__virtual__()的实现(L67-L78)体现了「先探测、再补救」的策略:

def __virtual__(retry=False): try: import reclass return __virtualname__ except ImportError: if retry: return False opts = __opts__.get("master_tops", {}).get("reclass", {}) prepend_reclass_source_path(opts) return __virtual__(retry=True)
  • 若 reclass 已可导入,直接返回虚拟名reclass,插件被激活;
  • 若首次导入失败,则尝试从master_tops.reclass配置中取出reclass_source_path并把它插入搜索路径,然后递归重试一次;
  • 重试仍失败则返回False,加载器会跳过该插件,而不会让整个加载扫描崩溃。

5.2 top() 的调用链

每个 tops 模块都必须实现top(**kwargs)。reclass 适配器的top()(L81-L143)核心流程如下:

def top(**kwargs): from reclass.adapters.salt import top as reclass_top from reclass.errors import ReclassException reclass_opts = __opts__["master_tops"]["reclass"] filter_out_source_path_option(reclass_opts) set_inventory_base_uri_default(__opts__, kwargs) minion_id = kwargs["opts"]["id"] return reclass_top(minion_id, **reclass_opts)
  1. 取配置:从__opts__["master_tops"]["reclass"]取出插件参数。源码注释提到,Salt 的 tops 接口与 ext_pillar 存在不一致(#5786),因此需要通过解析配置来提取参数,该适配器把这一内部细节隐藏了起来。
  2. 过滤内部选项:reclass_source_path只是 Salt 侧用于加载库的路径,reclass 本身不关心,调用filter_out_source_path_option()把它从传给 reclass 的参数中剔除(salt/utils/reclass.py#L17-L20)。
  3. 默认 inventory 路径:若用户未指定inventory_base_uri,set_inventory_base_uri_default()会将其初始化为file_roots中base环境的第一个根目录(salt/utils/reclass.py#L23-L29):
def set_inventory_base_uri_default(config, opts): if "inventory_base_uri" in opts: return base_roots = config.get("file_roots", {}).get("base", []) if base_roots: opts["inventory_base_uri"] = base_roots[0]
  1. 传入 minion id:Salt 要求 top 数据按 minion 过滤,适配器从kwargs["opts"]["id"]中取出当前 minion 的 ID(源码注释引用 issue #6930 说明这一取法)。
  2. 委托给 reclass:最终调用reclass.adapters.salt提供的top(minion_id, **reclass_opts)。这里刻意不把__opts__、__salt__、__grains__传给 reclass,保持 reclass 只依赖自己的配置、不猜测 Salt 内部结构。

5.3 面向用户的错误处理

top()把 reclass 抛出的底层异常统一翻译成可读的SaltInvocationError:

  • ImportError(且消息含reclass)→master_tops.reclass: cannot find reclass module in <sys.path>;
  • TypeError(unexpected keyword argument)→master_tops.reclass: unexpected option: <arg>,直接指出是哪个配置项不合法;
  • KeyError(含reclass)→master_tops.reclass: no configuration found in master config,提示缺少master_tops.reclass配置段;
  • reclass 自身的ReclassException→master_tops.reclass: <错误内容>。

六、master 端如何调用 tops 模块

6.1 加载器按白名单加载

master 通过 salt/loader/init.py 的tops()函数加载 tops 模块,并以master_tops配置的键作为白名单:

def tops(opts, loaded_base_name=None): if "master_tops" not in opts: return {} whitelist = list(opts["master_tops"].keys()) ret = LazyLoader( _module_dirs(opts, "tops", "top"), opts, tag="top", whitelist=whitelist, ... )

这意味着只有出现在master_tops:映射中的模块才会被加载,未配置的 tops 模块不会被实例化。

6.2 请求处理与结果合并

当 minion 向 master 请求 top 数据时,masterapi的_master_tops()(salt/daemons/masterapi.py#L592-L627)会遍历所有已配置的 tops 函数:

for fun in self.tops: if fun not in self.opts.get("master_tops", {}): continue ret = salt.utils.dictupdate.merge( ret, self.topsfun, merge_lists=True )

每个 tops 函数接收opts=opts与grains=grains关键字参数——这正是top(**kwargs)中kwargs["opts"]["id"]的来源。单个 tops 函数失败不会中断整体流程,而是记录错误日志后继续。master 端还通过asyncio的run_in_executor将_master_tops这类可能阻塞的同步调用移出事件循环(salt/master.py#L2631-L2648),避免阻塞 master worker。

6.3 与 top 文件的叠加顺序

在 minion 侧,HighState会把 top 文件匹配结果与 master_tops 结果合并(salt/state.py#L4286-L4295):

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 文件匹配结果排在前面,master_tops 的结果附加其后;
  • 当 minion 配置master_tops_first: True(该选项自 2018.3.0 引入,默认False,见 minion 配置文档)时顺序反转,master_tops 成为优先来源,实现「单一事实来源」的效果。

七、验证与测试

7.1 使用state.show_top验证

配置完成后,可用state.show_top(实现见 salt/modules/state.py#L2464)查看 minion 最终会拿到哪些 top 数据:

salt 'minion' state.show_top

返回结果中会出现由 reclass 生成的各 saltenv 及 state 列表,与 top 文件数据合并展示。

7.2 单元测试

仓库在 tests/pytests/unit/pillar/test_reclass_adapter.py 中为 reclass 适配器提供了单元测试(虽然测试文件位于 pillar 目录,但其中部分断言同样约束 tops 适配器依赖的契约),覆盖的关键行为包括:

  • __virtualname__必须等于文档约定的reclass,以保证加载器能把reclass:条目绑定到该适配器;
  • 当第三方reclass包不可导入且未配置源码路径时,__virtual__()应返回False而非抛异常(抛异常会导致加载器扫描崩溃);
  • ext_pillar必须委托给reclass.adapters.salt.ext_pillar,并原样透传 minion id、已有 pillar 以及storage_type、inventory_base_uri等关键字选项。

这些测试同时守护了适配器「clean failure」与「参数透传」两条契约,避免 3008 分支清理社区扩展时再次出现回归。

八、排查要点小结

结合源码与文档,配置master_tops.reclass时最常见的三类问题及其对应报错如下:

问题报错信息原因与解法
reclass 库未安装master_tops.reclass: cannot find reclass module in ...安装 reclass,或通过reclass_source_path/PYTHONPATH指定源码路径
配置项写错master_tops.reclass: unexpected option: <arg>检查master_tops.reclass下的键名拼写
缺少配置段master_tops.reclass: no configuration found in master config确认 master 配置中存在master_tops.reclass:段落

此外,若未显式设置inventory_base_uri,适配器会自动使用file_roots.base的第一个路径作为默认值,因此在大规模多环境部署中建议显式指定,避免隐式依赖 base 环境的文件根路径。

  • 运维
  • 配置管理
  • 后端

【免费下载链接】salt

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

项目地址:https://gitcode.com/gh_mirrors/sa/salt
点击查看免费下载
上一篇:F´ Data Products 子拓扑(DataProducts Subtopology)完全指南:数据产品从分配、写入到优先级下链的复用构建块
下一篇:碧蓝航线自动化脚本AzurLaneAutoScript:一键托管日常与大世界完整教程

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

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

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

立即咨询