- 运维
- 配置管理
- 后端
【免费下载链接】salt
Software to automate the management and configuration of infrastructure and applications at scale.
导读
本文围绕 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/saltstorage_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 库的方式:
- 通过
PYTHONPATH环境变量将 reclass 源码目录加入 Python 搜索路径; - 通过配置选项
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)- 取配置:从
__opts__["master_tops"]["reclass"]取出插件参数。源码注释提到,Salt 的 tops 接口与 ext_pillar 存在不一致(#5786),因此需要通过解析配置来提取参数,该适配器把这一内部细节隐藏了起来。 - 过滤内部选项:
reclass_source_path只是 Salt 侧用于加载库的路径,reclass 本身不关心,调用filter_out_source_path_option()把它从传给 reclass 的参数中剔除(salt/utils/reclass.py#L17-L20)。 - 默认 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]- 传入 minion id:Salt 要求 top 数据按 minion 过滤,适配器从
kwargs["opts"]["id"]中取出当前 minion 的 ID(源码注释引用 issue #6930 说明这一取法)。 - 委托给 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.
相关推荐
Salt 外部 Pillar 实战:用 PostgreSQL 查询动态生成 Pillar 数据(salt.pillar.postgres 模块深度解析)
Salt 外部 Pillar 实战:用 PostgreSQL 查询动态生成 Pillar 数据(salt.pillar.postgres 模块深度解析) 本文围
运维配置管理后端如何使用Mobiledoc-Kit创建自定义卡片和原子组件:完整指南
如何使用Mobiledoc Kit创建自定义卡片和原子组件:完整指南 Mobiledoc Kit是一个强大的框架无关的WYSIWYG编辑器构建工具包,它通过自定
前端Eidos数据导入:外部数据源集成
Eidos数据导入:外部数据源集成 概述 在现代数据管理场景中,数据导入(Data Import)是连接外部世界与个人知识库的关键桥梁。Eidos作为一个离线的
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考