☰
深入解析 Salt 中 SSH 资源的 test 执行模块覆盖:`salt.resources.ssh.modules.test` 与真实的远端连通性检测
2026/9/25 17:36:01 网站建设 项目流程
  • 运维
  • 配置管理
  • 后端

【免费下载链接】salt

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

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

test.ping是 Salt 生态中使用频率最高的连通性探针之一。本文围绕 salt/resources/ssh/modules/test.py 这一针对ssh资源类型(Resource Type)的执行模块覆盖(execution module override),讲解它如何通过__resource_funcs__["ssh.ping"]()委托机制,让test.ping返回的是目标 SSH 主机的真实连通性而非管理 minion 的存活状态,并顺带说明 Salt Resources 框架下 per-type 执行模块的加载、优先级与扩展方式。读者读完后,既能理解salt -C 'T@ssh:web-01' test.ping的完整执行链路,也能掌握为自定义资源类型编写执行模块覆盖的通用模式。

一、背景:Salt Resources 框架中的执行模块覆盖

Salt Resources(资源类型框架)允许把一个 minion 同时"管理"多个外部资源——例如通过 SSH 管理的远程 Linux/Unix 主机。框架为每种资源类型维护独立的执行模块加载器(per-type loader),并通过目录约定实现"插槽覆盖":

  • 资源类型ssh的覆盖模块存放在salt/resources/ssh/modules/目录下;
  • 文件名(去掉.py)即对应执行模块的虚拟名(slot),例如 modules/cmd.py 覆盖cmd、modules/pkg.py 覆盖pkg、modules/state.py 覆盖state、modules/test.py 覆盖test。

这一机制在 doc/topics/resources/authoring/execution_modules.rst 中有完整说明:当一条 job 被派发到某资源类型时,__salt__会先在<rtype>/modules/中查找,找不到的函数才回落到标准的 salt/modules/ 目录。这正是框架设计的核心——为资源类型定制cmd.run、pkg.installed等行为时,无需 fork 标准模块,也无需触碰__virtual__。

二、modules/test.py:一行委托,直通真实 SSH 连通性

salt/resources/ssh/modules/test.py 的源码非常简短,但语义关键。它只定义了一个ping()函数:

def ping(): """ Return ``True`` if the targeted SSH resource is reachable. Delegates to :func:`salt.resources.ssh.ping` via ``__resource_funcs__`` so the result reflects actual SSH connectivity to the remote host rather than the liveness of the managing minion. CLI Example: salt -C 'T@ssh:web-01' test.ping salt -C 'T@ssh' test.ping """ return __resource_funcs__["ssh.ping"]()

三个要点值得展开:

1. 位置即加载门控。该文件位于salt/resources/ssh/modules/这一 per-type 覆盖目录,只会被 ssh 资源的加载器发现(通过salt.loader._module_dirs中 per-type 的目录前缀注入),因此不需要__virtual__或__virtualname__门控。这与salt/modules/test.py中标准test模块的加载方式形成鲜明对比。

2. 目录优先级实现"影子遮蔽"。对派发到 ssh 资源的 job 而言,目录顺序优先级使salt.resources.ssh.modules.test遮蔽了标准salt.modules.test模块;而管理 minion 自身(managing minion)的 job 仍使用标准模块。也就是说:同一个test.ping调用,因目标不同而得到不同的语义。

3. 委托而非复制逻辑。ping()通过__resource_funcs__["ssh.ping"]()调用连接模块(connection module,即 salt/resources/ssh/init.py)中定义的ping()。真正的连接逻辑只维护在连接模块一处,覆盖文件纯粹负责"插槽绑定"。

三、__resource_funcs__dunder:连接模块的命名空间入口

__resource_funcs__是资源类型执行模块专用的 loader dunder,它暴露的是连接模块的函数命名空间,按下标<rtype>.<fname>索引。在 doc/topics/resources/authoring/execution_modules.rst 中明确说明:如果连接模块定义了def ping(),覆盖模块内即可用__resource_funcs__["ssh.ping"]()调用——这是覆盖模块触达连接模块助手的规范方式。

类似的 dunder 还包括:

  • __resource__:{"type": ..., "id": ...},始终在执行模块代码中可用;
  • __grains__:当前资源的 grains(由grains()采集),而非管理 minion 的 grains;
  • __minion__:管理 minion 的标准执行模块 loader,用于覆盖模块确需访问本机资源的情形;
  • __salt__:per-resource 合并 loader(覆盖槽位 + 标准模块兜底)。

modules/test.py顶部的注释还特意强调:这些 dunder 由 per-type loader 在运行时注入(# pylint: disable=undefined-variable),因此不能在模块导入期捕获,只能在函数体内使用。

四、底层实现:ssh.ping()如何判定连通性

连接模块 salt/resources/ssh/init.py 中的ping()(ssh_resource ping())定义如下逻辑:

def ping(): resource_id = _resource_id() try: shell = _make_shell(resource_id, cfg_override={"timeout": 10}) stdout, _stderr, retcode = shell.exec_cmd("echo ping") return retcode == 0 and "ping" in stdout except Exception as exc: log.warning("ssh resource ping() failed for %s: %s", resource_id, exc) return False

要点:

  • 通过_make_shell()构造salt.client.ssh.shell.Shell实例,并在本次调用中把连接超时强制设为 10 秒(cfg_override={"timeout": 10}),避免 ping 探测长时间挂起;
  • 在远端执行echo ping,以退出码为 0 且输出包含 "ping"作为连通性判据——这是对 SSH 会话可用的最小、最可靠验证;
  • 任何异常(连接失败、认证失败、超时)都会捕获并返回False。

也就是说,ssh 资源上的test.ping反映的是"SSH 传输层是否健康",而不是管理 minion 的进程存活状态。这正是modules/test.py模块 docstring 中强调的语义差异。

五、实战:用复合定位符-C对 SSH 资源发起 ping

根据modules/test.py的 CLI 示例,对 ssh 资源执行 ping 的完整命令为:

salt -C 'T@ssh:web-01' test.ping # 单个 ssh 资源 salt -C 'T@ssh' test.ping # 该 minion 管理的全部 ssh 资源

其中:

  • -C使用复合定位符(compound matcher);
  • T@ssh:web-01是资源定位表达式:T@前缀表示按资源类型(type)匹配,ssh为资源类型,web-01为资源 ID(即 Pillar 中hosts下的键);
  • T@ssh则匹配该 minion 下所有 ssh 资源。

资源 ID 由连接模块的discover()返回——即 Pillar 中ssh.hosts的键集合(详见 salt/resources/ssh/init.py 的discover())。向该 Pillar 键增删主机后执行saltutil.refresh_resources,即可热更新 Master 的 Resource Registry,无需重启进程。

六、连接配置:Pillar 中的 ssh 资源声明

ssh 资源的连接参数全部来自 Pillar(顶层键默认为resources,可通过 minion 选项resource_pillar_key覆盖)。在 salt/resources/ssh/init.py 的模块 docstring 中给出了完整示例:

resources: ssh: hosts: web-01: host: 192.168.1.10 user: root priv: /etc/salt/ssh_keys/web-01 web-02: host: 192.168.1.11 user: admin passwd: secretpassword no_host_keys: true

每个主机可配置的参数及默认值如下(与_make_shell()/_make_single()的 kwargs 一一对应):

参数说明默认值
host远端主机名或 IP(必填)—
userSSH 登录用户root
portSSH 端口22
privSSH 私钥文件路径;与passwd互斥语义,但两者可同时给出,设priv时走密钥认证选项串无
passwdSSH 密码;生产环境建议优先使用密钥认证无
priv_passwd私钥保护口令无
sudo是否通过 sudo 以 root 执行命令False
timeoutSSH 连接超时(秒)30
identities_only传-o IdentitiesOnly=yes,防止 SSH agent 提供无关密钥False
no_host_keys完全关闭主机密钥校验(同时置StrictHostKeyChecking=no与UserKnownHostsFile=/dev/null)False
ignore_host_keys传-o StrictHostKeyChecking=no,但不丢弃 known_hosts 数据库False
known_hosts_file该主机的自定义 known_hosts 文件路径无
ssh_options原样传给 ssh 二进制的附加-o Key=Value选项列表无
keepalive启用 TCP keepaliveTrue
keepalive_intervalServerAliveInterval(秒)Salt opts 或60
keepalive_count_maxServerAliveCountMaxSalt opts 或3

底层实现细节:_shell_opts()会把ignore_host_keys、no_host_keys、known_hosts_file逐主机覆盖到__opts__副本之上(Shell是从 opts 字典而非构造 kwargs 读取这些项的),并保证_ssh_version始终存在(_passwd_opts()以[]下标访问该键,缺失会抛KeyError)。ssh 版本在资源类型init()时预解析并缓存到__context__["ssh_resource"],确保 job 执行期间不跑子进程。

七、同类覆盖模块与整体扩展模式

modules/test.py不是孤例,同一目录下的兄弟模块展示了完整的覆盖谱系:

  • modules/cmd.py:提供cmd.run、cmd.run_all、cmd.retcode,全部委托__resource_funcs__["ssh.cmd_run"]。cmd.run返回 stdout 字符串;cmd.run_all返回{stdout, stderr, retcode}字典;cmd.retcode只返回退出码。这是 SSH 资源执行模块的基础构建块(相当于 proxy 模型中__proxy__["ssh_sample.cmd"]()的角色)。对应 CLI:salt -C 'T@ssh:web-01' cmd.run 'uptime'、salt -C 'T@ssh' cmd.run 'df -h' timeout=60。
  • modules/pkg.py:实现pkg.install、pkg.remove、pkg.version、pkg.list_pkgs,根据远端os_familygrain 自动选择apt-get(Debian/Ubuntu)或yum(RedHat/CentOS/Fedora/SUSE),并透传timeout。
  • modules/state.py:实现state.highstate、state.sls、state.apply,在管理 minion 进程内复刻 salt-ssh 的状态执行管线(编译 →prep_trans_tar打包 → SCP 传输 tar → 经 salt-thin 执行state.pkg),通过__func_alias__将apply_映射为apply。

从框架层面看(doc/topics/resources/authoring/execution_modules.rst),为资源类型新增覆盖模块有三种典型模式:

  1. 只覆盖标准模块中的个别函数——在modules/pkg.py里只写需要的函数,其余pkg.*槽位继续由标准模块(如salt.modules.aptpkg)填充;优先级是逐函数的;
  2. 重导出标准模块——用salt.utils.functools.namespaced_function把state.sls等函数复制进覆盖模块 globals,使__salt__等 dunder 在调用期解析到 per-resource loader(modules/state.py即官方示例);
  3. 纯委托连接模块——如modules/test.py这样的一行转发,连接逻辑集中在__init__.py,覆盖文件只做槽位绑定。

同时要避免三类常见错误:在模块导入期抓取 dunder(NamedLoaderContext 代理仅在函数体内有效);在覆盖模块中重定义__virtualname__(槽位由文件位置决定);绕过 loader 直接import salt.modules.cmdmod调用(会跳过 dunder 注入与 per-resource 上下文)。

八、实现证据与测试佐证

modules/test.py的存在与语义均有明确源码依据:

  • 加载门控:模块 docstring 明言"其位于 per-type 覆盖目录,只会被 ssh 资源加载器发现(经由_module_dirs的 per-type 前缀),因此无需__virtual__门控;派发到 ssh 资源的 job 中,标准salt.modules.test被目录顺序优先级隐藏,管理 minion 的 job 继续使用标准模块";
  • 委托语义:ping()一行代码直接转发__resource_funcs__["ssh.ping"]();
  • 底层连通性判定:ssh.ping()在 salt/resources/ssh/init.py 中通过Shell.exec_cmd("echo ping")与 10 秒调用级超时实现;
  • 文档注册:该模块被登记在 doc/ref/resources/all/index.rst 的资源类型子模块目录树中(与salt.resources.ssh.modules.cmd、pkg、state并列),对应的 API 参考页为 doc/ref/resources/all/salt.resources.ssh.modules.test.rst。

仓库内针对 SSH 资源的集成测试(tests/pytests/integration/resources_ssh/ 与 tests/pytests/unit/resources/test_ssh_resource.py)覆盖了资源注册、连接参数解析等链路,可作为进一步阅读与验证的入口。需要说明的是,ssh 资源类型的完整运行依赖 minion 上存在ssh二进制(连接模块通过salt.utils.path.which("ssh")做加载门控),本文所述机制均以当前仓库源码为基准。

小结

salt.resources.ssh.modules.test是理解 Salt Resources 执行模块覆盖机制的最小、最完整的标本:一个文件、一个函数、一行委托,背后却是"目录位置 = 加载门控"、"目录优先级 = 槽位遮蔽"、"__resource_funcs__= 连接模块命名空间"三大框架设计。掌握了它,你就能以此为模板,为自己的资源类型快速定制test、cmd、pkg、state乃至任意执行模块的远端语义。

  • 运维
  • 配置管理
  • 后端

【免费下载链接】salt

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

项目地址:https://gitcode.com/gh_mirrors/sa/salt
点击查看免费下载
上一篇:基于 Python Gen AI SDK 与原生 JavaScript 构建 Gemini Live 实时多模态语音应用:plain-js-python-sdk-demo-app 全解析
下一篇:Bokeh 入门第一步:用 Python 绘制交互式折线图(figure / show 完整实战)

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

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

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

立即咨询