把“开箱”这件事搬到技术学习里,往往意味着拿到一套还不熟悉的工具链,从安装、配置到跑通第一个例子,把封装在文档背后的细节一层层拆开。本文要拆的,是一个以 salt.niili 为演示代号的环境初始化项目。它本身不是某个商业产品,而是一套围绕 Salt 自动化运维工具搭建的示例工程,用来模拟真实团队中“一批新服务器从裸机到服务上线”的完整过程。
如果你是第一次接触 Salt,或者已经装了 Salt 但一直停留在test.ping阶段,那么这篇文章会很有帮助。下面是完整实战记录,包括架构概念、环境安装、State 编写、Pillar 差异化配置、批量命令执行、定时任务以及高频排错,新手可以顺着走,有基础的选手也可以直接跳到第 5 节之后看代码。
1. 为什么选择 Salt 做自动化配置管理
1.1 配置管理工具要解决什么问题
先看一个很常见的场景:公司新采购了 20 台服务器,需要统一安装 JDK、Nginx、Python 环境和监控 Agent,还要创建业务用户、调整内核参数、写入配置文件。如果靠人工一台台操作,不仅慢,而且容易漏改。更麻烦的是,三个月后想给所有机器统一升级某个软件版本,或者修改其中一项配置,又得重新来一遍。
配置管理工具就是用来解决这类问题的。它把服务器的预期状态用代码描述出来,比如“安装 nginx 并设置为开机启动”“创建名为 deploy 的用户”“确保 /data/apps 目录存在”,然后在目标机器上执行这些描述,并持续保证机器状态与描述一致。Salt 就是这类工具中非常成熟的一员。
Salt 的底层基于 Python 开发,使用 ZeroMQ 作为默认消息传输层,支持大规模的 Master-Minion 架构。也就是说,一台管理机(Master)可以同时管理成千上万台被管机器(Minion),通过消息队列实时下发命令和状态描述。相比 SSH 轮询模式,它在批量执行和实时性上更有优势。
1.2 salt.niili 演示项目定位
salt.niili 这个名字可以理解为一个项目代号。写作本文时,我会用它指代一套完整的 Salt 入门实战环境,包含:
- 1 台 Salt Master,负责下发配置和收集结果;
- 2 台 Salt Minion,分别模拟不同角色的测试服务器;
- 一套 State 目录,用来安装 Nginx、创建用户、推送配置文件;
- 一套 Pillar 目录,用来管理不同环境之间的差异参数。
这个项目最终要达到的效果是:在 Master 上执行一条命令,两台 Minion 就能自动完成从环境初始化到服务启动的全部流程。整个过程可重复、可回滚、可审计,这正是生产环境中最需要的能力。
1.3 适合哪类读者
- 刚接触自动化运维,想找一个比手动敲命令更优雅的方案;
- 已经在用 Ansible,想对比了解 Salt 的架构差异;
- 公司内部准备引入 Salt,需要快速做一个概念验证;
- 对 State、Pillar、Grains 这些术语有印象,但不知道它们之间如何配合。
读完这篇文章,你应该能独立搭建一套最小可用的 Salt 环境,并通过 State 和 Pillar 管理至少一个真实服务的部署。
2. Salt 核心概念速览
2.1 Master 与 Minion
Salt 使用 C/S 架构,核心角色是两个:
- Master:管理节点,保存配置、下发命令、收集执行结果。
- Minion:被管理节点,安装后需要注册到 Master,接收并执行指令。
Minion 启动后会生成一对密钥,并把自己的公钥发送给 Master。管理员在 Master 上接受该公钥后,双方建立可信通道。后续所有命令都通过这个加密通道传输。
那“salt.niili”里的 Master 和 Minion 具体怎么配合?我建议你把它理解成一家公司的运维中心和一线服务器。运维中心只负责制定标准和下发任务,具体操作由每台服务器自己执行。
2.2 State、Pillar、Grains、Modules
这是四个最高频的概念,也是新手最容易混淆的地方。
| 概念 | 作用 | 类比 |
|---|---|---|
| State | 描述目标的最终状态,例如“nginx 已安装并运行” | 需求文档 |
| Pillar | Master 下发给 Minion 的私有配置数据,例如不同环境的端口、账号、密码 | 环境变量 / 参数表 |
| Grains | Minion 启动时收集的静态信息,例如操作系统、CPU 核数、IP 地址 | 服务器身份证 |
| Modules | Salt 内置或自定义的执行模块,例如pkg.install、service.running | 工具箱 |
它们之间的关系可以这样串起来:Grains 告诉你“我是谁”,Pillar 告诉你“我该怎么配置”,Modules 提供“能做什么”,State 则把所有内容组合成“最终应该长什么样”。
2.3 执行模块与 State 模块的区别
新手经常看到类似salt '*' pkg.install nginx和下面这种 State 写法:
install_nginx: pkg.installed: - name: nginx两者有什么区别?前一种是远程命令,执行完就结束了,不关心以后状态是否还会变化。后一种是声明式配置,下次再执行时,如果 nginx 已经安装,则不做任何操作;如果被卸载,则重新安装。这就是“收敛”的含义——系统会自动把实际状态调整到期望状态。
在 salt.niili 项目中,我们会大量使用 State,因为项目目标是“可重复、可维护”,而不是一次性的临时操作。
3. 环境准备与安装
3.1 演示环境说明
版本需要根据你的实际环境调整,本文以常见方式为例。建议使用以下组合:
- 一台 Linux 服务器作为 Master,内存至少 1GB;
- 两台 Linux 服务器作为 Minion,可以用虚拟机、容器或云主机;
- 操作系统建议使用 CentOS 7/8 或 Ubuntu 20.04/22.04;
- Python 环境由 Salt 安装脚本自动处理,无需提前手工安装;
- Salt 版本以官方当前稳定版为准,本文演示时可通过命令自行确认。
为了方便本地测试,你也可以在一台机器上同时安装 Master 和 Minion。虽然不推荐用于生产,但做入门实验完全够用。salt.niili 项目最初验证时也是在一台机器上跑通的,相当于同时扮演管理端和被管理端。
3.2 安装 Salt Master
Salt 官方提供了一个 bootstrap 脚本,可以自动完成大部分安装流程。在 Master 上执行:
curl -L https://bootstrap.saltproject.io -o bootstrap_salt.sh sudo sh bootstrap_salt.sh -M参数-M表示同时安装 Master 和 Minion。如果只需要 Master,可以去掉-M。
安装完成后,设置 Master 开机启动:
sudo systemctl enable salt-master sudo systemctl start salt-master检查服务状态:
sudo systemctl status salt-master3.3 安装 Salt Minion
在每台 Minion 上执行:
curl -L https://bootstrap.saltproject.io -o bootstrap_salt.sh sudo sh bootstrap_salt.sh安装完成后,需要修改 Minion 配置,指定 Master 的地址。编辑/etc/salt/minion:
master: 192.168.1.100 id: minion-web-01其中master是 Master 的 IP 或主机名,id是这台 Minion 的唯一标识。如果没有指定 id,Salt 会使用机器的主机名。建议显式设置,便于后续管理。
启动 Minion:
sudo systemctl enable salt-minion sudo systemctl start salt-minion3.4 接受 Minion 密钥
在 Master 上执行:
sudo salt-key -L你会看到类似下面的输出:
Accepted Keys: Denied Keys: Unaccepted Keys: minion-web-01 minion-web-02 Rejected Keys:表示两台 Minion 已经发起注册请求,等待接受。执行:
sudo salt-key -A -y再查看状态,两台 Minion 就会进入 Accepted Keys 列表。
验证连通性:
sudo salt '*' test.ping预期输出:
minion-web-01: True minion-web-02: True看到 True,说明 Master 与 Minion 之间的加密通道已经建立。到这一步,salt.niili 项目的基础网络环境就准备好了。
4. salt.niili 项目目录规划与基础配置
4.1 目录结构设计
配置管理项目最怕杂乱无章。Salt 默认的配置目录是/srv/salt,Pillar 目录是/srv/pillar。建议在正式编写之前,先规划好目录结构。
下面是一个推荐的 salt.niili 项目结构:
/srv/salt/ ├── top.sls ├── base/ │ ├── init.sls │ ├── user.sls │ ├── nginx.sls │ └── appdir.sls /srv/pillar/ ├── top.sls ├── common.sls └── web.slsState 目录中,top.sls是入口文件,决定哪些 Minion 应用哪些状态;base目录放具体的状态描述。Pillar 目录中,top.sls决定哪些 Minion 吸收哪些配置数据。
这样的结构有足够弹性:新增一台 Minion 时,不需要修改具体业务 State,只调整top.sls中的匹配规则即可。
4.2 编辑 Master 配置
salt.niili 项目需要让 Master 使用自定义的 State 和 Pillar 目录。编辑/etc/salt/master,找到file_roots和pillar_roots配置段,修改为:
file_roots: base: - /srv/salt pillar_roots: base: - /srv/pillarfile_roots是 State 文件的查找路径,pillar_roots是 Pillar 数据的查找路径。修改后重启 Master:
sudo systemctl restart salt-master4.3 编辑 Minion 配置
在每台 Minion 上,需要确保它能正确加载自定义 State 和 Pillar。一般情况下无需修改,因为 Minion 会自动从 Master 拉取。不过,如果你希望 Minion 本地缓存更精确,可以在/etc/salt/minion中设置:
file_client: remoteremote表示所有文件都从 Master 获取。默认值就是 remote,所以这一步通常可以省略。
配置完成后,重启 Minion:
sudo systemctl restart salt-minion4.4 创建基础目录
在 Master 上创建目录:
sudo mkdir -p /srv/salt/base sudo mkdir -p /srv/pillar到这里,环境骨架已经搭好,下一步开始写第一个 State。
5. 编写第一个 State:初始化 Minion
5.1 创建 top.sls
top.sls是所有 State 的入口。新建/srv/salt/top.sls:
base: '*': - base.initbase是环境名称,和环境配置文件中的file_roots对应。'*'表示匹配所有 Minion。base.init表示应用/srv/salt/base/init.sls这个文件。
如果你只想匹配特定 Minion,可以把'*'换成具体的 id:
base: 'minion-web-01': - base.init使用'*'更符合 salt.niili 的定位——所有测试服务器都应该先完成基础初始化。
创建/srv/salt/base/init.sls:
# 基础初始化:确保系统软件包索引是最新的 update_system: pkg.uptodate: - refresh: true这个 State 会更新系统软件包。注意,在生产环境中,pkg.uptodate需要评估风险后再使用,测试环境则没有问题。
5.2 创建一个业务用户
在/srv/salt/base/user.sls中创建一个名为deploy的用户:
deploy_user: user.present: - name: deploy - shell: /bin/bash - home: /home/deploy - createhome: True - groups: - wheeluser.present是 Salt 的用户管理模块,表示“用户必须存在”。如果用户已存在且参数一致,则不做任何操作;如果缺少 group,则自动补齐。
在top.sls中引入这个 State:
base: '*': - base.init - base.user5.3 创建应用目录
在/srv/salt/base/appdir.sls中:
data_directory: file.directory: - name: /data/apps - user: deploy - group: deploy - mode: 755 - makedirs: Truefile.directory确保目录存在,makedirs: True表示上级目录不存在时会自动创建。user和group把目录归属到 deploy 用户,这样后续部署应用时权限不会出错。
将新的 State 添加到top.sls:
base: '*': - base.init - base.user - base.appdir5.4 应用 State
在 Master 上执行:
sudo salt '*' state.apply输出会逐条列出每个 Minion 上执行的结果。如果显示Succeeded: 3,说明三个 State 都执行成功。可以再次执行一次,会发现没有实际变化,这表示系统已经处于期望状态。
这就是 State 的幂等性:同样的描述执行多次,结果一致,不会重复创建用户或目录。
5.5 使用 grains 判断系统类型
salt.niili 项目里可能有不同操作系统的 Minion。如果需要在 CentOS 和 Ubuntu 上执行不同命令,可以用 grains 判断:
{% if grains['os_family'] == 'RedHat' %} install_epel: pkg.installed: - name: epel-release {% endif %}这种 Jinja 模板写法是 Salt State 的常见套路。执行前先看 Minion 的os_family,再决定是否安装 EPEL。这样同一份 State 代码可以在异构环境中安全运行。
6. 安装并配置 Nginx 服务
6.1 使用 pkg 模块安装软件
在/srv/salt/base/nginx.sls中:
install_nginx: pkg.installed: - name: nginx start_nginx_service: service.running: - name: nginx - enable: Truepkg.installed确保 nginx 已安装,service.running确保服务处于运行状态,enable: True设置开机自启。
在top.sls中加入:
base: '*': - base.init - base.user - base.appdir - base.nginx然后:
sudo salt '*' state.apply如果一切顺利,两台 Minion 上都会安装并启动 nginx。用浏览器访问 Minion IP 的 80 端口,能看到 Nginx 欢迎页。
6.2 使用 file.managed 推送配置文件
Salt 的另一个核心能力是文件分发。我们可以在 Master 上维护一份 Nginx 配置模板,然后统一推送到所有 Minion。
先在 Master 上创建/srv/salt/base/files/nginx.conf,内容按你的业务需求编写。然后在nginx.sls中追加:
push_nginx_config: file.managed: - name: /etc/nginx/nginx.conf - source: salt://base/files/nginx.conf - user: root - group: root - mode: 644 - watch_in: - service: start_nginx_servicesource: salt://base/files/nginx.conf表示从 Master 的 State 目录中读取文件。watch_in是关键:当文件内容发生变化时,通知 nginx 服务重载,而不是重启整个机器。
再次执行:
sudo salt '*' state.applyMaster 会对比文件哈希值,如果两端一致,则跳过;不一致,则推送新文件并触发服务重载。
6.3 使用 Jinja 模板渲染配置
直接把一份固定配置推给所有机器没问题,但不同 Minion 可能需要不同的 worker 进程数或 server_name。这时可以改用模板。
把文件后缀改成.jinja,例如/srv/salt/base/files/nginx.conf.jinja:
worker_processes {{ grains['num_cpus'] }}; http { server { listen {{ pillar['nginx_port'] }}; server_name {{ pillar['nginx_server_name'] }}; } }grains['num_cpus']是每台 Minion 自己的 CPU 核数,pillar['nginx_port']是管理员统一配置的端口。
修改nginx.sls:
push_nginx_config: file.managed: - name: /etc/nginx/nginx.conf - source: salt://base/files/nginx.conf.jinja - template: jinja - user: root - group: root - mode: 644 - watch_in: - service: start_nginx_service关键在于template: jinja,它让 Salt 使用 Jinja 引擎渲染模板后再下发。这样每台 Minion 都会拿到一份带有自己 CPU 核数的个性化配置。
7. Pillar:管理环境差异化配置
7.1 为什么需要 Pillar
State 负责描述“做什么”,Pillar 负责提供“做的时候用到的参数”。比如测试环境和生产环境的 Nginx 端口不同,用户名不同,这些都不应该写死在 State 里。Pillar 可以把这些数据统一管理,按 Minion 分配。
7.2 编写 Pillar top.sls
新建/srv/pillar/top.sls:
base: '*': - common 'minion-web-01': - web'*'表示所有 Minion 都加载common.sls,minion-web-01额外加载web.sls。
7.3 编写 Pillar 数据
/srv/pillar/common.sls:
nginx_port: 8080 nginx_server_name: common.example.com/srv/pillar/web.sls:
nginx_port: 9090 nginx_server_name: web.example.com因为web.sls只分配给minion-web-01,所以这台机器的 Nginx 端口会是 9090,另一台则保留 common 里的 8080。
7.4 刷新 Pillar
Pillar 数据按需生成,修改后需要刷新。在 Master 上执行:
sudo salt '*' saltutil.refresh_pillar验证某台 Minion 上的 Pillar 数据:
sudo salt 'minion-web-01' pillar.items输出中包含nginx_port: 9090就说明 Pillar 已经生效。
7.5 在 State 中引用 Pillar
在上面的 Nginx 模板中,我们已经在调用pillar['nginx_port']。接下来执行状态:
sudo salt '*' state.apply两台 Minion 会分别生成不同端口的 Nginx 配置。这种方式非常适合测试环境与生产环境共用一套 State 代码、只替换 Pillar 数据的场景。
8. 批量命令与定时任务
8.1 远程执行命令
Salt 的远程执行能力非常直接。比如批量查看所有 Minion 的内存使用:
sudo salt '*' cmd.run 'free -m'批量安装某个软件:
sudo salt '*' pkg.installed git批量重启某个服务:
sudo salt '*' service.restart nginx这就是第一章提到的 Modules 能力。日常运维中,这类实时查询和临时变更非常高频。
8.2 使用 cron 模块管理定时任务
Salt 还提供cron模块。通过 State 管理定时任务,可以避免手工编辑 crontab 带来的遗漏和格式错误。
新建/srv/salt/base/cron.sls:
clean_logs: cron.present: - name: 'find /data/logs -type f -mtime +30 -exec rm -f {} \;' - minute: '0' - hour: '3'这段 State 会在每天凌晨 3 点执行日志清理命令。cron.present表示该任务必须存在;如果任务已经被删除,下次执行 state.apply 时会自动恢复。
8.3 使用 schedule 实现状态自愈
Salt 的schedule功能可以让 Minion 自己定期检查状态,而不需要一直依赖 Master 下发。比如让 Minion 每隔 5 分钟检查一次 nginx 是否在运行,如果挂了就自动拉起。
在 Minion 配置中追加:
schedule: nginx_healthcheck: function: state.apply args: - base.nginx seconds: 300这样即使 Master 暂时不可用,只要 Minion 的调度器在工作,它也会自己执行 State 收敛,保持服务可用。这就是基础设施自愈能力的雏形。
9. 常见问题与排查思路
9.1 test.ping 返回 False
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
test.ping返回 False | Minion 未启动或网络不通 | 登录 Minion,检查 salt-minion 服务状态与防火墙 |
| 密钥显示在 Unaccepted | 未接受密钥 | 在 Master 上执行salt-key -A -y |
| Minion 无法连接 Master | master 地址配置错误 | 检查/etc/salt/minion中的master配置 |
9.2 state.apply 提示 State 文件找不到
最常见原因是top.sls中引用的文件名和实际文件名不一致。Salt 的规则是,base.init会寻找base/init.sls,base.nginx会寻找base/nginx.sls。如果文件放在别的目录,要在top.sls中写完整路径,例如:
base: '*': - base/nginx/init9.3 Pillar 数据不生效
修改 Pillar 后必须执行:
sudo salt '*' saltutil.refresh_pillar另外,Pillar 与 State 是两套独立的top.sls,不要把 Pillar 内容写到 State 目录下。/srv/pillar/top.sls只负责 Pillar 分发,/srv/salt/top.sls只负责 State 分发。
9.4 Jinja 渲染报错
报错信息通常会指出行号。常见原因:
- Pillar 中变量名不存在;
- 模板中少写了
{%或{{; pillar['xxx']的 key 拼写错误。
排查时可以先用命令查看 Pillar 数据:
sudo salt 'minion-web-01' pillar.get nginx_port确认数据存在后,再回头检查模板语法。
9.5 服务端口没监听
Nginx 配置推送成功后,如果端口未监听,可以先看服务状态:
sudo salt 'minion-web-01' service.status nginx然后登录 Minion 手动执行:
systemctl status nginx journalctl -u nginx -n 50定位是配置语法错误还是端口被占用。配置错误时,Salt 的watch_in会尝试重载服务,如果重载失败,需要先修复配置。
9.6 Grain 值不一致导致行为不同
如果 State 在不同机器上表现不一致,先用grains.items对比:
sudo salt '*' grains.items重点看os、os_family、num_cpus等字段。这些都是 Minion 启动时采集的静态数据,不会主动更新。如果需要强制刷新,可以执行:
sudo salt '*' saltutil.refresh_grains但要注意,os这类系统级 grain 一般不会变,而自定义 grain 需要保证在每台 Minion 上定义正确。
10. 最佳实践与工程建议
10.1 State 拆分要细,匹配要精
不要把几十个操作堆在一个.sls文件里。按职责拆分,例如base/init.sls负责初始化,base/nginx.sls负责 Web 服务,base/user.sls负责账号。top.sls是入口,尽量用明确的 Minion 匹配规则,少用无差别'*'大范围匹配,避免不小心影响非目标机器。
10.2 配置参数进 Pillar,不写死在 State
端口、路径、用户名、密码等环境相关参数都应该放到 Pillar。State 文件做成通用模板,同一套代码可以适配测试、预发、生产多个环境。需要变更时只改 Pillar,减少了误改核心代码的风险。
10.3 敏感信息不要明文入库
Salt 原生支持通过外部 Pillar 或 sdb 对接 Vault、AWS Secrets Manager 等密钥管理工具。即使在小项目中,也建议至少把密码占位成变量,不直接在.sls里写死。任何变更前先在测试环境验证,执行前检查目标范围,避免在全量机器上做危险操作。
10.4 涉及删除或重建操作时谨慎配置
Salt 的file.absent、pkg.removed、user.absent等操作都是不可逆的。在生产环境使用这些模块时必须设置严格的 Minion 匹配规则,并提前备份数据。如果需要批量下线服务,不要直接删除目录,先停流量、备份、观察日志,确认无误后再处理残留文件。
10.5 充分利用 test=True 做预演
执行 State 前,可以先加test=True参数做预演:
sudo salt '*' state.apply test=True该模式只计算差异,不真正执行变更。输出中Changes列会显示预期变化,这样能提前发现误配置,减少线上事故。
10.6 日志与审计
Master 的操作记录保存在/var/log/salt/master,Minion 的本地操作记录在/var/log/salt/minion。日常巡检时建议关注这些日志。重要变更尽量走 Salt 的 event bus,将事件接入监控系统,方便事后追溯。
10.7 版本控制
State 和 Pillar 文件夹本身是纯文本,非常适合接入 Git 管理。每一次变更都可以留痕、回滚。建议每次上线前打 tag,例如v1.2.0,对应发布说明。把基础设施配置纳入版本控制,是团队协作的基础。
11. 总结与下一步学习建议
salt.niili 项目到这里,已经从一块空白服务器变成了一个由 Salt 自动管理的环境:Master 接受密钥、State 安装软件、Pillar 区分参数、文件模板生成配置、定时任务负责周期维护。这套流程和组织生产环境的模样已经非常接近了。
如果你接下来想继续深入,可以从这几个方向入手:
- 学习 Salt 的 Reactor 和 Event 系统,实现“机器上线后自动初始化”的自动化流程;
- 研究 Salt SSH 模式,在无法安装 Minion 的设备的场景下做替代方案;
- 学习 External Pillar,把密钥管理接入 Vault;
- 结合 CI/CD 流水线,让代码提交后自动触发状态变更。
自动化运维并不是把所有服务器变成黑盒,恰恰相反,它是把每一台服务器的预期状态变成可以阅读、可以评审、可以回滚的代码。希望这篇实战记录能帮你迈出第一步,尽快在自己的测试环境里跑通一套属于自己的 salt 管理项目。