看到 Salt 这个词,不同背景的人会联想到完全不同的东西。在视频标题里,它可能是一段航海生存冒险;在运维和开发同学的终端里,Salt 更多时候指 SaltStack,一个基于 Python 的自动化配置管理与远程执行工具。这篇内容只讨论后者,聊清楚 SaltStack 怎么搭起来、怎么用 State 管理服务器配置、怎么排查一台 Minion 为什么没执行到期望的结果。适合刚接触自动化运维的开发者,也适合已经会用 Ansible 但想对比和理解 Salt 工作方式的人。读完你会得到一套最小可复现的实验环境,以及一份可以直接搬进项目里的排错清单。
1. 先搞清楚 SaltStack 是什么,以及它和“Salt”这个关键词的关系
1.1 同一个名字,两种语境
“Salt”在中文里最常见的意思是盐,在游戏圈里可能指向某个开放世界生存游戏,在技术圈里则大概率指 SaltStack。很多初学者会因为这个名字产生困惑,搜索时也容易混进大量不相干内容。
实际项目里的 SaltStack 是一套自动化运维工具,核心能力有四个:
- 远程执行:在一台管理机上批量给上千台服务器下发命令。
- 配置管理:用声明式文件描述服务器应该处于什么状态,例如“安装 Nginx”“配置文件内容是……”
- 事件驱动:Salt 内部有事件总线,可以监听服务器上报的事件并触发自动操作。
- 编排与 API:通过 Salt API、reactor 等机制,把“发现配置漂移”和“自动修复”串成流程。
本文约定:后面提到的 Salt,默认都指 SaltStack,不再解释游戏语境。如果你是因为其他语境点进来的,也可以把这篇文章当成一次“自动化配置管理”的技术冒险:从两台虚拟机开始,搭建出一个能管理多个节点的最小系统。
1.2 SaltStack 的核心组件:Master、Minion、Syndic 与 Salt SSH
SaltStack 最常见的部署形态是 Master/Minion 架构。Master 是中心节点,负责下发命令、保存返回结果;Minion 是安装在每台被管理服务器上的轻量代理,主动连接到 Master,并执行 Master 下发的任务。
这种“Minion 主动连接 Master”的设计,和普通主从架构不同。它带来的好处是:即使被管理节点在内网或 NAT 后面,只要它能访问 Master 的端口,就能完成注册和管理,不需要在防火墙上给 Master 开返回通道。
核心组件可以用一张表概括:
| 组件 | 角色 | 说明 |
|---|---|---|
| Salt Master | 中心管控节点 | 调度任务、保存 job 结果、维护 state 和 pillar 数据 |
| Salt Minion | 被管理节点代理 | 接收任务并执行,把结果返回给 Master |
| Salt Syndic | 级联代理 | 支持多层级联,把下层 Master 的 Minion 汇总到上层 Master |
| Salt SSH | 无代理模式 | 不安装 Minion,通过 SSH 批量执行命令和 state |
| Salt API | 对外接口 | 把 Salt 的能力封装成 HTTP 接口,便于集成到运维平台 |
实际使用中,最常见的还是 Master/Minion。下面所有实验都围绕这个模式展开。
1.3 适用场景与选型边界
在选型时,很多人会拿 Salt 和 Ansible 对比。两者都能做配置管理和远程执行,但设计取向不同。
Ansible 默认无代理,依赖 SSH,适合临时批量操作和不喜欢在机器上装额外进程的环境。Salt 默认需要装 Minion Agent,但换来的是更快的通信速度、更精细的事件机制、更灵活的状态收敛能力。
| 对比维度 | SaltStack | Ansible |
|---|---|---|
| 默认模式 | Master/Minion 代理 | SSH 无代理 |
| 通信方式 | ZeroMQ 或 TCP | SSH |
| 大规模并发 | 较强,适合千级节点 | 受 SSH 并发限制,需要调整 forks |
| 配置语言 | YAML + Jinja | YAML + Jinja |
| 学习曲线 | 较陡,概念多 | 相对平缓 |
| 事件驱动 | 内置 event bus | 较弱,依赖回调插件 |
需要注意,Salt 不适合做过于复杂的 workflow 编排。如果你要写“先发布 A 服务,再等健康检查,再发布 B 服务”这种强依赖流程,建议把编排逻辑放到 GitLab CI、Jenkins Pipeline 或专门的流程引擎里,Salt 负责执行单点状态和命令即可。
2. 环境准备:从两台虚拟机开始搭建最小集群
2.1 实验环境规划
学习 Salt 不需要重型集群,两台 Linux 虚拟机就够。一台做 Master,一台做 Minion。
下面以 Rocky Linux 9 为例。如果你使用 Ubuntu/Debian,安装命令和软件源会略有区别,但整体流程相同。
| 主机名 | 角色 | 假设 IP | 建议配置 |
|---|---|---|---|
| salt-master | Master | 192.168.56.10 | 2C4G |
| salt-minion-01 | Minion | 192.168.56.11 | 1C2G |
安装前先确认几点:
- 两台机器都能互相访问 IP。
- 主机名尽量不要带大写和下划线,Minion 默认以主机名作为 id。
- 防火墙端口要提前规划:Master 的 4505 端口用于 Minion 拨号,4506 端口用于任务执行和结果返回。
- 关闭 SELinux 会造成学习环境不验证,最好把 SELinux 调整到 permissive,并确认没有系统安全策略阻断。
注意:生产环境不要轻易关闭 SELinux。遇到连接问题时先看审计日志,确认是状态还是端口策略导致。
2.2 安装 SaltStack Master
SaltStack 官方提供了软件源,安装时建议使用发行版对应的 repo 包。
Rocky Linux 9 上可以这样安装:
# 安装 epel 和 salt 官方仓库 sudo dnf install -y epel-release sudo dnf install -y https://repo.saltproject.io/salt/py3/redhat/9/x86_64/latest/salt-repo-latest.el9.noarch.rpm sudo dnf clean expire-cache # 安装 master sudo dnf install -y salt-master安装完成后,先在主配置里做最小化调整。Salt Master 默认配置文件在/etc/salt/master,常见的几个参数如下:
# /etc/salt/master interface: 0.0.0.0 publish_port: 4505 ret_port: 4506 # 提高 worker 数量,实验环境默认值即可 worker_threads: 5 # 日志级别 log_level: info保存后启动服务:
sudo systemctl enable --now salt-master sudo systemctl status salt-master检查 4505 和 4506 端口是否监听:
sudo ss -lntp | grep salt看到4505和4506处于 LISTEN 状态,说明 Master 已经准备好。
这里要理解一个关键点:4505 端口负责接收 Minion 的长连接,4506 端口负责接收 Minion 返回的执行结果。两个端口职责不同,生产环境配置安全组时不要只放行其中一个。
2.3 安装并注册 Minion
在第二台机器上安装 salt-minion:
sudo dnf install -y epel-release sudo dnf install -y https://repo.saltproject.io/salt/py3/redhat/9/x86_64/latest/salt-repo-latest.el9.noarch.rpm sudo dnf clean expire-cache sudo dnf install -y salt-minion修改 Minion 配置,指定 Master 地址,并给 Minion 设置固定 id。这个 id 一定要稳定,因为之后所有 target 匹配、证书授权都会以它为准。
# /etc/salt/minion master: 192.168.56.10 id: minion-node-01在较新版本中,也可以把配置放在/etc/salt/minion.d/99-salt-minion.conf,这样主配置升级时不容易被覆盖。
# /etc/salt/minion.d/99-salt-minion.conf master: 192.168.56.10 id: minion-node-01启动 Minion:
sudo systemctl enable --now salt-minion sudo systemctl status salt-minion启动后,Minion 会生成密钥对,并把公钥发送给 Master,等待管理员授权。
2.4 确认 Master 与 Minion 通信状态
回到 Master 上查看待授权的 key:
sudo salt-key -L预期输出里有Unaccepted Keys,下面能看到minion-node-01。
接受 key:
sudo salt-key -a minion-node-01再次查看:
sudo salt-key -Lminion-node-01出现在Accepted Keys中,就说明注册完成。
用 test.ping 验证通信:
sudo salt 'minion-node-01' test.ping返回true,说明 Master 与 Minion 之间的网络、密钥、端口全部正常。
这一步最常见的坑是:Minion 启动后没有出现在 Unaccepted Keys 里。原因通常是网络不通,或者 Minion 配置里 master 地址写错。可先在 Minion 上手动执行salt-minion -l debug观察日志,确认它是否在持续尝试连接4505端口。
3. 第一个最小闭环:用 Salt 远程执行命令
3.1 test.ping 为什么是第一个要跑的命令
test.ping 并不是真正去 ping 某个 IP,而是 Master 向目标 Minion 发送一个 UTF-8 字符,Minion 原样返回。它能通过,代表整条链路已经打通:
- Minion 能连接 Master 的 4505 端口。
- Master 的密钥认证通过。
- Minion 能接收任务并返回结果。
所以它是排查一切问题时的“链路自检”。
执行方式:
sudo salt 'minion-node-01' test.ping输出:
minion-node-01: True如果 Minion 数量多,可以一次对所有节点执行:
sudo salt '*' test.ping这个命令会返回所有在线 Minion 的结果。
3.2 远程执行命令的语法与 target 匹配
Salt 远程执行命令的通用格式是:
salt '<target>' <module.function> [参数]target 用来选择要执行任务的 Minion 集合。最常用的匹配方式:
| 匹配方式 | 示例 | 说明 |
|---|---|---|
| 通配符 | salt 'minion*' test.ping | 按 Minion id 通配 |
| 列表 | salt -L 'minion-01,minion-02' test.ping | 指定多个 id |
| 正则 | salt -E '^minion-node-\d+$' test.ping | 按正则匹配 id |
| Grains | salt -G 'os_family:RedHat' test.ping | 按系统属性匹配 |
| Pillar | salt -I 'role:web' test.ping | 按 Pillar 数据匹配 |
| CIDR | salt -S '192.168.56.0/24' test.ping | 按 IP 网段匹配 |
例如批量查看所有 Minion 系统信息:
sudo salt '*' grains.item os os_family oscodename在真实环境中,target 匹配非常关键。如果目标写得太宽,可能会把生产环境所有机器都执行一遍;写得太窄,又可能漏掉该执行的节点。建议先在最小范围用--out=json测试,确认目标节点后再执行。
3.3 把结果看懂:返回结构、jid 与耗时字段
默认输出适合人看,但脚本化处理时最好使用 JSON 输出:
sudo salt '*' test.ping --out=json输出:
{ "minion-node-01": true }执行远程命令时,Master 会给每次任务生成一个 job id,简称 jid。哪怕执行结果返回超时,也可以从 job cache 里找到这次任务日志。
查看已执行的任务:
sudo salt-run jobs.list_jobs这会列出所有历史任务,包括 jid、执行时间、target、执行的函数和参数。当 Minion 掉线导致结果没有返回时,这种查询方式非常有用。
远程执行 cmd.run 时要注意谨慎使用。比如:
sudo salt '*' cmd.run 'uptime'输出:
minion-node-01: 10:00:32 up 1 day, 2:33, 2 users, load average: 0.00, 0.01, 0.05不过,cmd.run 是强命令,生产环境不建议开放给所有用户。如果有可能,优先使用 Salt 提供的模块函数,例如pkg.install、service.start、file.replace,而不是写一段任意 shell。原因是模块函数会校验参数、返回结构化结果,出错信息也更明确。
注意:在批量执行 cmd.run 前,一定要确认目标范围,否则一条 rm 命令可能影响大量节点。
4. 用 State 管理配置:从“执行命令”走向“声明期望状态”
4.1 State 文件、Top File 和 highstate 的关系
只靠远程执行命令,自动化程度还不够。真正有价值的是“声明期望状态”:用文件描述每台机器应该装什么包、配置文件写成什么、服务应该启动。Salt 会负责把当前状态收敛到期望状态。
这里涉及三个概念:
- State 文件(SLS):用 YAML 写的最小状态描述。
- Top File:把不同的 Minion 节点和不同的 State 文件关联起来。
- Highstate:让 Master 根据 Top File 对所有目标 Minion 应用所有状态的过程。
默认目录结构:
/srv/salt/ ├── top.sls └── nginx/ └── init.slstop.sls 示例:
# /srv/salt/top.sls base: '*': - nginx这个文件的意思是:在 base 环境中,匹配到所有节点时,应用nginx这个 State。
4.2 用 file.managed 管理 Nginx 配置
一个 State 文件可以描述多个资源。下面用 Nginx 配置管理作为示例。
先准备一个 Nginx 配置源文件。实际项目中,通常会把文件放在 Salt 根目录的nginx/files/下。
mkdir -p /srv/salt/nginx/files创建一个最小 Nginx 配置:
# /srv/salt/nginx/files/nginx.conf user nginx; worker_processes auto; error_log /var/log/nginx/error.log; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; } } }然后编写 State 文件:
# /srv/salt/nginx/init.sls nginx_config: file.managed: - name: /etc/nginx/nginx.conf - source: salt://nginx/files/nginx.conf - user: root - group: root - mode: '0644' - watch_in: - service: nginx_service解释一下关键参数:
name:目标机器上的最终文件路径。source:文件来源,salt://协议表示从 Master 的/srv/salt目录取文件。mode:文件权限,注意用字符串'0644',避免 YAML 把 0644 解析成数字。watch_in:当文件内容变化时,触发等待的服务。这里关联到nginx_service这个 service 状态。
4.3 用 pkg.installed 和 service.running 写一个完整 state
完整的 Nginx state 应该包含三件事:安装包、下发配置、启动服务。
把init.sls扩展成完整版本:
# /srv/salt/nginx/init.sls nginx_install: pkg.installed: - name: nginx nginx_config: file.managed: - name: /etc/nginx/nginx.conf - source: salt://nginx/files/nginx.conf - user: root - group: root - mode: '0644' - watch_in: - service: nginx_service nginx_service: service.running: - name: nginx - enable: True - require: - pkg: nginx_install这里用到了两个关键顺序:
require:nginx_service依赖nginx_install,保证先安装再启动服务。watch:nginx_config通过watch_in监听服务状态,文件变化后自动重启 Nginx。
在 Salt 里,watch和require区别很大。require只控制顺序,而watch会在被监听对象发生变化时触发行为的额外反应,例如重启服务。服务重启逻辑不放在 shell 脚本里,而是由 State 系统管理,这样更容易追踪变更。
4.4 执行 highstate 与按需应用指定 state
在 Master 上执行:
sudo salt 'minion-node-01' state.apply nginx这条命令表示只对目标节点应用nginx这个 State,不依赖 top.sls。
如果希望按 top.sls 全量应用:
sudo salt 'minion-node-01' state.highstate返回结果会包含三个部分:
pkg_|-nginx_install_|-nginx_|-installedfile_|-nginx_config_|-/etc/nginx/nginx.conf_|-managedservice_|-nginx_service_|-nginx_|-running
每部分都有result: True、changes: {}、comment字段。
第一次执行时changes里会明确写出安装了什么、修改了什么:
nginx_install: ---------- new: nginx old:再次执行时,changes应该为空,说明系统已经处于期望状态,没有配置漂移。这就是状态收敛和幂等性的表现。
5. 深入理解 Pillar、Grains 与 Jinja 模板
5.1 Grains:Minion 的静态属性
Grains 是 Minion 启动时收集的静态信息,包括操作系统、CPU 核数、内存、IP 地址等。它不会频繁变化,适合做条件判断和节点分组。
查看所有 Grains:
sudo salt 'minion-node-01' grains.items只看某几个字段:
sudo salt 'minion-node-01' grains.item os os_family ipv4在 State 文件里,可以用 Grains 做分支判断。例如:
{% if grains['os_family'] == 'RedHat' %} nginx_package: nginx {% elif grains['os_family'] == 'Debian' %} nginx_package: nginx {% endif %}这样同一个 State 就能兼容不同发行版。
5.2 Pillar:敏感的定向配置
Pillar 和 Grains 最大的区别是:Pillar 由 Master 维护,可以按 Minion 定向下发,并且适合存储密码、密钥、差异化配置等数据。Pillar 内容也通过 Salt 加密通道传输,比明文写在 minon 端的文件安全得多。
默认 Pillar 目录是/srv/pillar,需要创建一份 top.sls:
# /srv/pillar/top.sls base: 'minion-node-01': - nginx创建对应的 Pillar 数据文件:
# /srv/pillar/nginx.sls nginx: server_name: www.example.com listen_port: 8080 max_workers: 4查看 Minion 收到的 Pillar:
sudo salt 'minion-node-01' pillar.items输出里会包含nginx下面的所有键值。
Pillar 里的数据可以直接被 Jinja 模板读取,从而让不同节点生成不同的配置文件。注意:不要用 Pillar 暴露不必要的信息到大范围节点上。写入密钥时建议结合 Salt 的外部 Pillar 或 sdb 模块,避免密钥出现在版本控制系统的纯文本文件里。
5.3 Jinja 模板:让 state 不再写死
前面 Nginx 配置是静态文件。在真实场景中,不同服务器往往需要不同的 server_name、端口和 worker 数量,可以把 Nginx 配置改成 Jinja 模板。
先修改 State 文件,把source指向模板文件,并告诉 Salt 这个文件需要模板渲染:
# /srv/salt/nginx/init.sls nginx_config: file.managed: - name: /etc/nginx/nginx.conf - source: salt://nginx/files/nginx.conf.j2 - template: jinja - user: root - group: root - mode: '0644' - watch_in: - service: nginx_service模板文件内容:
# /srv/salt/nginx/files/nginx.conf.j2 user nginx; worker_processes {{ pillar.get('nginx', {}).get('max_workers', 'auto') }}; error_log /var/log/nginx/error.log; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; server { listen {{ pillar.get('nginx', {}).get('listen_port', 80) }}; server_name {{ pillar.get('nginx', {}).get('server_name', 'localhost') }}; location / { root /usr/share/nginx/html; index index.html; } } }这样,修改 Pillar 数据后重新 apply State,配置文件就会按新值渲染出来。
使用 Jinja 模板时,一个常见坑是模板变量取不到值时直接报错。建议所有 Pillar 取值都带上默认值,例如pillar.get('nginx', {}).get('listen_port', 80)。这样即使某台 Minion 没有配置对应 Pillar,也不会导致渲染失败。
5.4 参数速查表
| 数据来源 | 存放位置 | 主要用途 | 敏感信息 | 更新方式 |
|---|---|---|---|---|
| Grains | Minion 端收集 | 系统属性、分组、条件判断 | 不存放 | 重启 Minion 或刷新 |
| Pillar | Master 端维护 | 节点差异化配置、敏感数据 | 适合存放 | 修改后继续 apply 或刷新 |
| State | Master 端维护 | 描述系统期望状态 | 不存放 | 修改 SLS 后 apply |
| GitFS | 外部 Git 仓库 | 存放 State 和 Pillar 文件 | 结合 git roles | 提交代码后 refresh |
生产环境建议把 State 和 Pillar 统一放到 Git 仓库,再通过 GitFS 或 CI 流程同步到 Master。这样配置变更可以走 Code Review,比直接在 master 上改文件更可控。
6. 运行验证与结果分析
6.1 查看执行结果和返回码
Salt 命令本身返回 0 不代表所有 Minion 都成功。看结果要关注每个 Minion 返回体里的result字段。
以 state.apply 为例:
sudo salt 'minion-node-01' state.apply nginx --out=json输出片段:
{ "minion-node-01": { "pkg_|-nginx_install_|-nginx_|-installed": { "comment": "Package nginx is already installed", "result": true, "changes": {} }, "file_|-nginx_config_|-/etc/nginx/nginx.conf_|-managed": { "comment": "File /etc/nginx/nginx.conf is up to date", "result": true, "changes": {} } } }判断执行是否成功,不能只看命令是否返回,也不能只看result: True,还要确认:changes是否符合预期、comment是否合理、有没有某个资源被跳过。
可以写一个简单脚本批量检查所有 Minion 的结果:
sudo salt '*' state.highstate --out=json | jq 'to_entries[] | {name: .key, result: .value.result}'如果result是 false,就用--out=highstate查看更详细的失败信息。
6.2 用 event bus 和 job cache 观察执行过程
Salt Master 内部有事件总线,所有 Minion 的连接、任务执行、返回结果都会以事件形式在总线上流动。
查看实时事件:
sudo salt-run state.event listen执行一条命令,Event Bus 里会出现salt/job/20250301.../new、salt/job/.../ret/minion-node-01等类型的事件。
当任务执行得非常慢,或者有大量 Minion 没有返回时,可以用 job cache 查找:
sudo salt-run jobs.lookup_jid 20250301120000123456把 jid 替换成目标任务的 ID,就能看到该任务在各 Minion 上的返回结果。如果某些 Minion 返回为空,可以判断是执行超时还是 Minion 掉线。
6.3 幂等性验证:再次执行 highstate
Salt 设计上要求 State 是幂等的:同一个状态反复执行,结果不应该产生副作用。
验证方法很简单:
sudo salt 'minion-node-01' state.apply nginx然后再次执行:
sudo salt 'minion-node-01' state.apply nginx第二次执行时,changes应该是空,comment应该是类似 “Package nginx is already installed” 或 “File is up to date” 的信息。
如果第二次执行仍然产生大量 changes,说明 State 写得有问题。常见原因包括:
- 写的不是期望状态,而是每次执行都会追加内容的脚本。
- 模板渲染结果不稳定,每次生成的文件内容都不同。
cmd.run里执行的命令本身每次都会返回变化。
幂等性不是 Salt 自动保证的,而是靠写 State 的人的约束。这是配置管理工具和普通跑脚本之间最大的区别。
7. 常见问题排查
7.1 Minion 一直处于离线状态
现象:salt-key -L里能看到 Minion,但执行salt '*' test.ping时返回Minion did not return. [No response],或者 Minion 显示为离线。
可能原因:
- 4505 或 4506 端口被防火墙拦截。
- Minion 上
master地址配置错误。 - Minion 进程没有启动或启动后退出。
- Master 上的 worker 线程耗尽。
排查步骤:
- 在 Minion 上检查进程:
ps aux | grep salt-minion。 - 查看 Minion 日志:
journalctl -u salt-minion -f。 - 在 Minion 上测试连接:
timeout 5 bash -c '</dev/tcp/192.168.56.10/4505' && echo ok。 - 在 Master 上检查端口:
ss -lntp | grep 4505。 - 查看 Master 日志:
journalctl -u salt-master -f。
处理建议:如果是防火墙问题,放行 4505/4506;如果是配置问题,修正/etc/salt/minion或/etc/salt/minion.d/下的配置;如果是 worker 线程问题,适当调大worker_threads。
7.2 Minion key 认证失败
现象:执行命令时看到类似Minion did not return. [No response],或者日志里出现Authentication attempt of minion-node-01 failed.。
可能原因:
- Minion 被重新安装过,密钥对发生了变化。
- Master 上保留了旧的 Unaccepted/Denied Keys。
- Minion 的 id 被另一台机器抢用。
检查方式:
sudo salt-key -L处理建议:先删除旧 key,再重启 Minion,然后重新接受新 key:
sudo salt-key -d minion-node-01 sudo systemctl restart salt-minion sudo salt-key -a minion-node-01预防方法是:给每台 Minion 设置固定且唯一的 id,不要依赖可能重复的 hostname。
7.3 state 执行报错定位
现象:state.apply nginx后,返回结果里某个资源的result: false。
常见错误:
| 错误现象 | 可能原因 | 处理方式 |
|---|---|---|
Source file salt://nginx/files/nginx.conf not found | state 中的 source 路径错误 | 检查/srv/salt/nginx/files/下是否有文件 |
Data failed to compile: Rendering SLS failed | YAML 缩进或 Jinja 模板语法错误 | 使用python -c "import yaml; yaml.safe_load(open('...'))"或salt-run fileserver.file_list检查 |
Job 20250... failed with return code: 1 | 模块执行失败 | 先手动在 Minion 上执行对应的命令,确认系统情况 |
After rendering, state was shown as {'file': ...} | SLS 结构不完整 | 检查 state 是否包含名称、模块函数和参数 |
排查顺序:
- 先看报错信息里的
comment,它通常已经写明原因。 - 在 Master 上确认文件是否存在:
sudo salt-run fileserver.file_list- 在 Minion 上手动执行模块函数,比如
salt 'minion-node-01' pkg.install nginx或salt 'minion-node-01' service.status nginx。 - 用
--out=highstate输出更完整的结果,而不是只看默认样例。
7.4 配置变更不生效
现象:修改了 State 文件或 Pillar 数据,重新执行state.apply,但目标机器上的配置没有变。
可能原因:
- 修改的是文件,但没有重新 apply。
- top.sls 没有包含这个 State,导致
state.highstate没有执行它。 - 文件权限或 watch 条件没有触发服务重启。
- Minion 和 Master 的 fileserver 缓存没有刷新。
检查方式:
sudo salt 'minion-node-01' file.get_managed /etc/nginx/nginx.conf sudo salt 'minion-node-01' pillar.items处理建议:
- 先执行
salt-run fileserver.clear_cache清理缓存。 - 确认
salt 'minion-node-01' state.apply nginx中file_managed状态是否有 changes。 - 如果
changes为空,说明文件内容没有变化,需要检查模板是否用了正确的变量。 - 检查
watch_in是否挂到了 service 状态上。
从根本上预防,可以把 State 文件、模板文件、Pillar 数据全部纳入 Git 版本库。每次变更都走 Review,并在 CI 里做一次state.apply --test语法检查,能减少大量“改了没生效”的问题。
8. 生产环境最佳实践与扩展方向
8.1 从单 Master 到多 Master 高可用
学习环境用一个 Master 没有问题,生产环境则要考虑单点故障。常见做法有两种:
- 运行多个 Master,Minion 配置里可以指定多个
master地址,自动切换。 - 使用 Salt Syndic 做层级架构,上层 Master 管理多个区域 Master,适合跨机房、跨网络边界的场景。
多 Master 部署的重点不在安装,而在配置同步。所有 Master 上的 state、pillar、配置文件必须保持一致。建议用 Git 作为唯一数据源,通过 CI/CD 推送到各个 Master,避免不同 Master 上配置不一致导致执行结果漂移。
8.2 Salt SSH 和 Salt API 的使用场景
如果目标机器不能安装 Agent,可以使用 Salt SSH。它的用法和 Master/Minion 模式基本一致,只是通过 SSH 执行。
sudo salt-ssh '192.168.56.12' test.pingSalt SSH 适合做临时接管、短期批量命令和硬件原因不方便装 Agent 的场景。但因为每次执行都要走 SSH 握手,并发和速度会比代理模式差。
如果团队想自动化执行 Salt,而不是每个人都能登录 Master,可以提供 Salt API。启用前先配置认证:
# /etc/salt/master.d/api.conf rest_cherrypy: port: 8000 host: 0.0.0.0 debug: False ssl_crt: /etc/pki/tls/certs/localhost.crt ssl_key: /etc/pki/tls/private/localhost.key生产环境使用 Salt API 时,要把端口放在内网,并通过统一认证网关代理,避免直接暴露。API 调用还需要做权限控制,区分哪些用户可以执行只读命令、哪些用户可以执行高权限命令。
8.3 发布前检查清单
在把新 State 发布到生产环境前,建议逐项检查:
- [ ] 所有 State 文件已经过
state.apply语法校验。 - [ ] top.sls 中引用的 SLS 文件都存在。
- [ ] 模板文件中所有变量都有默认值。
- [ ] Pillar 中的敏感信息没有硬编码在普通 State 里。
- [ ] 目标节点范围是否最小化,先灰度一批节点。
- [ ] 执行前是否备份关键配置文件。
- [ ] 是否提前确认 Nginx、MySQL 等服务的 reload 方式。
- [ ] 是否监控执行日志和异常事件。
- [ ] 是否有回滚方案:旧文件版本、旧服务版本、旧 Pillar 数据。
- [ ] 是否记录变更前后差异,便于审计。
8.4 下一步可以扩展的方向
SaltStack 的入门不难,难在把很多基础能力组合成完整的自动化体系。学完 state、pillar、grains、jinja 之后,可以按下面几个方向继续深入:
- 自定义 module 和 state:当内置模块不能满足需求时,用 Python 编写自己的模块。
- event reactor:利用 Salt event bus 监听文件变更、服务异常、Minion 上线等事件,自动执行修复。
- schedulers:让 Minion 或 Master 定时执行任务,例如每小时校验一次 Nginx 配置。
- MySQL 和数据库模块:用它管理系统账号、数据库权限。
- 与 CI/CD 集成:在 GitLab CI 或 Jenkins 中触发 Salt highstate,实现配置变更的自动化发布。
配置管理工具的价值,不在于能执行多少条命令,而在于把服务器最终状态固化成可审查、可回滚、可重复验证的代码。从两台测试机开始,把一条 test.ping、一个 Nginx state、一次 highstate 弄明白,后面再管理几百台机器时,思路会清晰很多。