
接手一批新服务器发现上面跑的软件版本乱七八糟配置改起来全靠人肉 SSH 登录一台台敲命令改错了也没人知道。这是我很多年前第一次接触配置管理工具时的真实状态。如果你也正在被这种重复劳动折磨又嫌 Puppet 语法太绕、Ansible 对批量推送的响应速度不够爽快那 Saltstack 确实值得花一晚上认真了解一下。Saltstack也叫 Salt是一个基于 Python 的配置管理与远程执行工具。它最让我服气的地方有两点一是执行速度极快二是管理几百台机器和管三台机器的复杂度没什么区别。它走的是 master-minion 的代理模式master 端下发指令minion 端即时同步返回结果。你不需要像 Ansible 那样在每台机器上配 SSH 账号也不需要在客户端写复杂的 agent 逻辑装上一个 minion 进程就能被批量管理。这篇文章的内容很直白从零开始把这套工具跑起来。我不谈高深架构只讲我实际验证过的安装步骤、最常用的命令、最容易让人卡住的坑以及一个能立即拿走的自动化配置示例。适合刚接触运维自动化的开发、测试、以及被海量服务器压得喘不过气的运维新手。你不需要对 Salt 有任何基础只需要有一台能联网的 Linux 机器跟着操作就能完成极速入门。1. 内容整体设计与思路拆解1.1 为什么我不推荐你一上来就啃官方文档Saltstack 官方文档写得很全但问题恰恰是太全了。它默认你已经有明确的架构规划和业务场景然后像一本字典一样把所有 API 摊在你面前。刚入门的时候你根本不知道该先看哪一章也不知道哪些功能是核心必须掌握的、哪些是极端 edge case 才需要的。看了两天文档脑袋里只剩下“state”、“pillar”、“jinja”这些名词实际问题一个也没解决。我的建议非常明确先跑通最基础的东西再回来读文档。你只需要明白三个核心概念。第一个是 master。它是一台控制中枢负责存配置、分任务、收集结果。你所有的操作命令都在 master 上敲。第二个是 minion。它是被管理端每台需要被控制的机器上装一个安装后主动去连 master然后一直保持长连接。第三个是执行模块。你在 master 上敲的每条命令本质上都是在调用 minion 上的一段 Python 函数。比如salt web* cmd.run uptime意思就是让所有主机名以 web 开头的 minion 执行uptime这个 Shell 命令。当你把这三个概念在脑子里串起来后面所有的操作都是在这条主线上做花样。命令、状态管理、批量部署、配置下发说到底都是在“master 告诉 minion 做什么minion 做完回报结果”这个大框架里转。1.2 Salt 和 Ansible、Puppet 放在一起怎么选选型问题几乎每次分享都会有人问我觉得可以把话说得更直白一些。Puppet 强在声明式模型和多年积累的模块生态适合大型企业把服务器状态管得很死的场景但它的学习曲线是真的陡Ruby 语法让很多人扭头就走。Ansible 的优势是无需 agent通过 SSH 协议执行安全性上天然亲和网络隔离环境但在上千台机器规模下每次执行都要建立 SSH 连接性能消耗会明显放大。Salt 走的是长连接加消息推送的路线一次握手之后保持会话执行命令不需要反复重新连接所以大批量场景下速度优势非常明显。它还自带 event bus 和 reactor 联动机制可以做到监控告警后自动触发修复动作。这是另外两个工具想做但都做得不如 Salt 顺手的地方。代价呢就是你必须多维护一个 minion 进程多一层 agent 的运维成本。但对于绝大多数业务这个成本换来的是极高的执行效率和自动化扩展能力值。1.3 我推荐的入门路径倒着学给完全零基础的人一条直接能走的路先装环境、再跑远程命令、然后写一个最简单的 state 文件、最后做一次完整的配置下发。整个过程不需要超过三个小时。为什么倒着学因为大多数配置管理工具的教学都从“怎么写配置”开始结果新手还没体验到工具的爽感就先被 YAML 缩进和 Jinja 模板语法折磨了一遍。我的思路正好反过来先把工具当成人肉 SSH 的替代品用cmd.run感受一下批量下发命令带来的效率震撼。这种正反馈建立起来之后你自然就有动力去研究 state 模块的细节了。后面我在第三节也会严格遵循这个路径带着你一步步走一遍。2. 核心架构与通信原理理解了这些后面才不会踩坑2.1 master 和 minion 到底是怎么聊天的很多人装完 Salt 之后遇到的第一类问题就是 minion 连不上 master报错信息也看不懂。这背后的通信链路其实并不复杂。Master 启动后会监听两个端口4505 和 4506。4505 是 PUB 端口专门用于广播任务给所有在线 minion4506 是 REP 端口用于接收 minion 返回的执行结果。Minion 启动后会先向 master 发起一个请求把自己的身份信息发过去。第一次通信时身份还是未认证状态必须由管理员在 master 上手动接受这个 minion 的密钥。用salt-key -A命令一次性接受所有待认证的 minion或者用salt-key -a minion_id单独接受某台。这一步很多新手会忘记导致跑命令的时候一直报Minion did not return. No response.但排查了网络发现机器明明能通。再说说 minion id 的生成规则。默认情况下 Salt 会用系统的主机名作为 minion id这在实际生产环境里会埋很大隐患。比如你批量上架了一批机器云平台分配给它们的 hostname 可能是iZbp1abcd2efg3hi这样的随机字符串用这个去匹配执行命令就太别扭了。我现在的习惯是在安装 minion 之前就通过修改/etc/salt/minion配置文件里的id字段把这个机器在业务中的角色名称写进去比如web-node-01、redis-node-02这种。配置文件里加上一行id: web-node-01启动后 master 上看到的就是这个清晰的名字。2.2 认识 Salt 的内置认证机制Salt 默认使用 AES 加密通信不仅数据在网络上传输时是密文每次 minion 连接 master 时还会有一次双向认证握手。在/etc/salt/pki/master/目录下存放的是 master 的公私钥在/etc/salt/pki/minion/目录下存放的是每台 minion 的密钥。当你接受了一台 minion 的密钥master 就会把该 minion 的公钥记录在 trusted 列表里。这里有一个安全细节值得强调生产环境千万不能养成salt-key -A一键全部接受的惰性习惯。正确做法是新环境下先执行salt-key -L检查组待认证的密钥指纹是否对得上。Minion 在启动日志中会打印自己的指纹master 上也有一份两个进行比对一致后再去接受密钥。在早期我接手的一批机器里网络里还真出现过伪装 minion 的情况。不是吓唬你把这套认证流程重视起来能省去后续很多麻烦。2.3 多环境隔离一主多仆的标配思路如果你要管理超过五十台机器我强烈建议你从安装 Salt 的第一天就开始规划多环境目录结构。所谓多环境就是让同一个 master 上跑多套独立的配置集合最常见的是 dev、test、prod 三套环境。通过 minion 配置文件里定义的 grain 值或环境变量把不同角色的机器各自匹配到对应的配置树上。这样做的好处很直接你在 dev 环境调试一个部署脚本绝对不会误伤生产环境的机器。很多团队在 Salt 使用初期都是一套配置打天下跑state.apply的时候所有 minion 全部应用同一份配置等业务规模大了之后想拆分环境发现配置互相耦合拆起来痛不欲生。所以我的建议是哪怕你目前只有十台机器也把目录结构按环境分开建好这属于一次投入长期受益的规划。3. 五分钟快速搭建 Salt 环境3.1 master 端的安装与初始化这里以 CentOS 7/8 或者 Rocky Linux 为例。我用官方 bootstrap 脚本装省去手动配源的过程。你需要有一台能联通外网的机器作为 master。curl -L https://bootstrap.saltproject.io -o bootstrap-salt.sh sudo sh bootstrap-salt.sh -M -N stable解释一下这两个关键参数。-M表示安装 master-N表示不要同时安装 minion。如果你在 master 机器上也想作为一台被管理的 minion后续可以手动补装但入门阶段尽量让 master 保持纯粹。安装完成后检查 master 进程是否正常监听。用netstat -tlnp | grep -E 4505|4506能看到两个端口在 LISTEN 状态。然后修改 master 的核心配置配置文件路径是/etc/salt/master。最早期需要动的不多我就手动追加或者修改以下几个参数# 自动接收所有 minion 密钥测试环境可开生产环境不建议 auto_accept: False # master 运行的用户默认就是 root非 root 会导致权限问题 user: root # 配置根目录后面所有 state 文件都放在这里 file_roots: base: - /srv/salt修改完重启服务systemctl restart salt-master。此刻 master 端已经就绪不需要再做其他多余操作。3.2 minion 端的安装与连接接下来在任意一台需要被管理的机器上安装 minion。curl -L https://bootstrap.saltproject.io -o bootstrap-salt.sh sudo sh bootstrap-salt.sh -M stable注意这里我把-M去掉只保留stable因为这台机器只作为 minion 存在。安装后编辑/etc/salt/minion文件重点改两个配置。第一是master参数改成 master 机器的 IP 或域名第二是id参数改成这台机器的业务名。改完重启服务systemctl restart salt-minion回到 master 上执行salt-key -L你应该能看到 minion 出现在 Unaccepted Keys 列表里。执行salt-key -a minion_id完成认证再跑salt minion_id test.ping返回True就代表整套环境已经通了。3.3 验证安装是否成功的三个关键命令环境搭建完成后我建议用三条命令做一次从通信到执行再到状态的链路验证。salt minion_id test.ping salt minion_id cmd.run uname -a salt minion_id grains.item osfinger第一条验证最基本的认证通信第二条验证远程命令是否能够正常反射第三条验证 grain 数据是否能正常读取这决定了后面写匹配规则时能否准确按操作系统类型做分支。三条全部通过说明这台 minion 已经可以被你掌控后面的所有玩法都建立在这个基础之上。4. 远程执行命令的花式玩法与参数详解4.1 目标匹配怎么只操作我想要的机器Salt 最体贴的功能之一就是目标匹配。你可以按多种方式挑选要执行的机器而不是每次都把所有 minion 都跑一遍。最简单的就是精确匹配 minion id 和通配符匹配。# 精确匹配单台 salt web-node-01 test.ping # 通配符批量匹配* 表示所有web* 表示以 web 开头的所有 minion salt web* test.ping # 列表匹配 salt -L web-node-01,web-node-02,db-node-01 test.ping除了通配符还可以用正则表达式。用法是加-E参数比如salt -E ^(web|api)-node-.* test.ping这适合你的主机命名规则比较复杂的时候。再进阶一点的是 grains 匹配。Grains 是 minion 启动时收集的静态信息包括 CPU 型号、内存大小、操作系统、IP 地址等。执行salt minion_id grains.ls能看到这台机器上所有 grain 字段名salt minion_id grains.item osfinger能看到指定字段的值。实际使用中我最常用的匹配场景是按操作系统类型做差异化操作比如一次针对所有 CentOS 7 机器做内核参数调优或者针对所有 Ubuntu 机器跑 apt 升级。命令可以这么写salt -G osfinger:CentOS Linux-7 cmd.run yum -y update salt -G os:Ubuntu cmd.run apt-get update apt-get -y upgrade这比先查列表、再写脚本、再跑批量要卷得舒服得多。4.2 cmd.run 模块人肉 SSH 的直接替代品cmd.run是入门期使用频率最高的执行模块格式是salt 目标 cmd.run 命令。它做的事情非常干净在目标 minion 上调用系统 Shell 执行你传入的命令字符串然后把标准输出返回给 master。有个小坑得说一下cmd.run 默认在 minion 的 HOME 目录下执行命令如果你需要切换到某个特定的工作目录或者设置环境变量必须借助cwd和env参数。举个例子我想在/opt/app目录下执行脚本并且设置JAVA_HOME环境变量salt app-node-01 cmd.run pwd ./deploy.sh cwd/opt/app env{JAVA_HOME: /usr/local/java}如果你要控制的进程需要长时间后台运行用默认方式执行会因为 Salt 的同步机制而一直卡住。我的做法是借助nohup和输出重定向让任务脱离 Salt 会话转而在系统层独立运行。salt app-node-01 cmd.run nohup /opt/app/start.sh /var/log/app.log 21 还有一个细节值得提默认cmd.run要求命令执行返回码是 0如果返回非零会把错误信息传回来并判定任务失败。有些场景下你并不关心返回码只想知道输出结果那就加ignore_retcodeTruesalt app-node-01 cmd.run grep error /var/log/app.log | tail -20 ignore_retcodeTrue4.3 同时操作多台的并发控制避免雪崩当目标机器数量达到上百台的时候直接执行命令很容易把所有机器同时打满 CPU或者把外部依赖的服务比如数据库冲垮。我习惯用两个参数控制并发一个是-t指定超时秒数另一个是batch指定每批执行的机器数量。# 每批只有 10% 的机器执行一批跑完再跑下一批 salt *.worker cmd.run systemctl restart worker-service --batch-size10% # 设置单次命令的超时时间为 60 秒 salt -t 60 web* cmd.run uptimebatch-size这个参数在版本较新的 Salt 里可以写百分比也可以写固定数字。做批量重启类操作的时候我的习惯分两步先在一个小范围内执行比如先对三台机器操作确认结果正常后再扩大到全部机器。4.4 结果返回处理机器多了得学会看报告默认情况下 Salt 命令执行完会把每台机器的返回结果直接铺在终端里。机器少没问题机器一多几百行输出交错在一起根本没法人工阅读。推荐先加上输出器语法比如--outjson或者--outyaml可以和salt命令配套使用salt web* cmd.run uptime --outjson如果你需要把结果持久化交给后续的自动化流程处理可以这样salt web* cmd.run uptime --outjson /tmp/salt_result.json它还支持--static参数意思是等所有 minion 都返回之后再统一定格输出而不是边跑边打。这个功能在多台执行时体验差异极大推荐测试一下。5. State 状态管理入门从“命令执行”升级到“状态维护”5.1 state 文件和 cmd.run 的本质区别远程命令虽然爽快但它是一种“一次性”的临时交互。前一次执行的结果不会自动保留下来下一次执行时依然要重新检查、重新配置。而 state 管理是一种声明式机制你写清楚目标状态Salt 自动判断当前状态与目标状态之间的差异只执行必要的变更步骤。举个例子。你要在 100 台机器上安装 Nginx。用远程命令的话你得先确认哪些机器已经装过哪些没装然后分成两批去执行。用 state 文件的话你只需要声明一句“确保 Nginx 已安装、已启动、开机自启”剩下的判断交给 Salt。State 的核心目录是/srv/salt/里面放的是.sls文件。一个.sls文件描述一个或一组状态目标。通过salt minion state.apply 名称把这个文件推送到目标 minion 并执行。5.2 手写第一个 state 文件部署 Nginx 并保持运行我带你创建一个名叫webserver.sls的文件路径是/srv/salt/webserver.sls。内容如下nginx-install: pkg.installed: - name: nginx nginx-service: service.running: - name: nginx - enable: True - require: - pkg: nginx-install解释一下里面的逻辑。nginx-install是这个 state 的唯一 ID它调用了pkg.installed模块表示“确保 nginx 这个软件包处于已安装状态”。nginx-service调用service.running模块表示“确保 nginx 服务处于运行状态并且开机自动启动”。require是一个依赖声明保证只有在 nginx 安装成功之后才会去启动服务。在 master 上执行salt web-node-01 state.apply webserver第一次执行会返回一堆带Succeeded和Changed的状态信息。执行完之后再用salt web-node-01 cmd.run systemctl status nginx去验证。之后再重复执行同样的 state.apply可以看到结果中Succeeded: 2但Changed: 0这就是状态管理的精髓不需要重复劳动一切以目标状态为准。5.3 文件分发与模板一键下发配置的核心State 管理中最实用的模块之一就是file.managed它的作用是确保某台机器上的文件内容与 master 端指定的源文件保持一致。配合模板引擎可以实现不同机器使用同一份模板但渲染出不同的最终配置。我先写一个最简单的文件分发示例。创建目录/srv/salt/files/在里面放一个文本文件motd.txt内容是欢迎语。然后增加一个 state 文件motd.slsmotd-config: file.managed: - name: /etc/motd - source: salt://files/motd.txt - user: root - group: root - mode: 644执行salt * state.apply motd所有 minion 上的/etc/motd文件就会被统一替换成 master 上的内容。这比人肉拷贝强太多了尤其是多台机器内容不一致时这个操作直接消除配置漂移。如果你需要每台机器显示不同的内容就引入 Jinja 模板。把文件后缀改成.jinja并在 state 文件里指定template: jinja。模板内可以引用 grains 数据比如抓取 IP 地址Welcome to {{ grains[host] }} (IP: {{ grains[ipv4][0] }})这样推下去之后每台机器看到的内容都带有自己的信息。5.4 pillar 的用法敏感信息别硬编码在 state 里State 文件往往需要根据不同的机器生成不同的配置比如数据库密码、应用端口、认证令牌。很多新手习惯把这些信息直接写在.sls文件里。这是我在生产环境里坚决反对的做法因为 state 文件可能会被团队内多人查看也可能被加入版本控制系统后被外部看到。更专业的做法是用 pillar。Pillar 是 Salt 提供的一个加密分发通道master 可以把特定的数据推送给特定的 minion。你可以在/srv/pillar/目录下定义 pillar 数据再通过配置让不同机器获得不同的数据内容。State 文件里通过 Jinja 语法引用pillar[变量名]去获取数据。简单的理解pillar 是“给特定 minion 看的私有配置”state 文件是“大家都看的公共规则”。两者搭配使用结构上才完整。5.5 状态执行的回执结果看懂 Changes 和 Summary每次 state.apply 执行完终端都会打印一段结果摘要。新手容易忽略这段输出直接看最后一行“Succeeded”就完事。但我建议你养成习惯每次执行完重点看Changes列。Succeeded: 2表示有两个 state 执行成功Changed: 1表示有一个 state 产生了实际变更另一个 state 因为已经是目标状态而没有执行任何操作。生产环境下出现大量Changed结果往往意味着你的配置在持续漂移或者初始化操作没有收敛。把Changed0作为一个稳定的日常基准对判断系统是否处于预期状态非常有帮助。6. 金丝雀发布与灰度操作把 Salt 用出生产级水平6.1 一次完整的配置变更流程应该怎么走Salt 环境搭建好之后真正有挑战的不是运行单个命令而是形成一套规范的变更流程。我现在的标准流程是这样的先准备一台 test 环境机器做小范围验证确认 state 逻辑没有问题之后再用 batch-size 分批滚动到预生产最后再全量对生产机器执行。每一步都会结合回滚方案。具体操作中结合state.apply和--batch-size参数可以轻松完成灰度。举一个实际的例子给所有机器更新一个受控的 sysctl 配置。我先写好 state 文件然后在生产机器小批量执行salt prod-node* state.apply sysctl-setting --batch-size10%这样每次只有 10% 的机器会加载新配置。如果第一批执行后收集到的日志和指标一切正常就继续按 10%、20%、50% 的批次推进。如果发现异常立刻停止后续批次回到 master 检查问题所在。6.2 自动发现与批量操作结合Salt 的 grains 信息可以做非常细致的分组。你可以按内存大小、CPU 核数、机房位置、操作系统版本区分机器然后针对每一类做不同的操作。这种“按属性分组”的执行方式比人肉维护一份 IP 列表可强多了。# 只对物理内存大于等于 8GB 的机器执行 salt -G mem_total 8192 cmd.run cat /proc/meminfo | head -5 # 对 CPU 核数等于 8 的机器做压力测试脚本 salt -G num_cpus:8 cmd.run bash /tmp/cpu_test.sh结合 cron 定时任务还能实现每天固定时间自动对一批机器做状态巡检然后把结果输出到日志文件里。这一步算是 Salt 在生产力层面的核心收益了。6.3 salt schedule 的妙用让机器自己管自己另一个我很推荐的生产级玩法是配置 schedule定时任务。它是 master 下发到 minion 的周期性任务配置可以在 minion 侧自行执行。比如我们可以让 minion 每五分钟执行一次磁盘空间检查空间不足时自动清理临时文件或者把告警发到消息队列。不用额外写复杂的 cron 脚本直接在 state 文件里声明即可。只要 master 上的 state 配置没有变化minion 就会稳定地按照节奏执行下去。这将是一个完全自动化的自愈流程也是 Salt 在配置管理工具中独有的能力。7. 常见问题与排查技巧实录7.1 minion 一直报 No response怎么排查这是入门阶段出现次数最多的问题。Minion did not return. No response.这个提示背后隐藏了多种可能原因。我整理一份速查表按优先级排序检查排查项检查方法解决办法密钥是否接受salt-key -L查看 Unaccepted 列表salt-key -a minion_id接受master 端口是否可达telnet master_ip 4505检查防火墙放行 4505/4506minion 配置文件 master 地址查看/etc/salt/minion中的 master 字段改为正确地址后重启 salt-minionminion 进程是否运行systemctl status salt-minion启动或重启服务master 和 minion 时间偏差date对比两台机器时间同步 NTP 时间偏差过大会认证失败master 日志报错内容journalctl -u salt-master -f根据具体报错关键字处理7.2 执行结果太快导致文件还未落盘日常运维中我踩过一个比较隐蔽的坑用cmd.run执行一条命令去修改某个文件紧接着再用state.apply去基于修改后的文件做验证结果状态还是旧的。原因是 Salt 的远程执行链路是异步的命令执行完成并不代表文件内容已经同步完成有些进程写入文件是缓冲式的。解决办法很简单在执行命令之后加一个短暂延时或者用cmd.run的wait状态模块确保前一个命令的落的文件完全落盘后再进入下一步。生产环境里如果这种时序问题导致误判那是相当闹心的。7.3 YAML 语法缩进错误入门第一大杀手.sls文件本质上是 YAML抓到一个缩进错误就容易让整个 state 报错。新手经常犯的错是一个 key 下面有三个子项有的对齐了有的没对齐。最常见的报错信息类似mapping values are not allowed in this context看起来像天书其实根本原因就是缩进位置不对。我的建议是写.sls文件统一用两个空格做缩进不要使用 Tab。在提交前可以用python -c import yaml; yaml.load(open(/srv/salt/webserver.sls), Loaderyaml.FullLoader)做一次语法校验能提前发现大部分语法问题。7.4 高可用与权限问题的一些叮嘱最后补充几个实战提醒。第一生产环境的 master 不要只部署单点至少做一台冷备 master备份好/etc/salt/pki目录否则 master 挂了密钥丢了几百台 minion 全部需要重新认证。第二minion 运行用户建议就是 root否则很多需要写系统目录的操作会因权限不足而失败。第三生产环境操作前先跑salt * test.ping --outjson把所有 minion 的在线状态摸底一遍避开网络断连的机器再执行重要任务。我自己在实际运维中最受益的习惯是每条重要变更命令执行前先思考回滚方案。Salt 的执行能力很强正因为强才更需要谨慎使用。一个误操作可能瞬间推送到全量机器所以对目标匹配做双重确认永远不嫌多。这套工具入门真的不难难的是在使用中建立自己的操作流程和规范。如果你只是把它当成一个批量执行命令的加速器那它发挥出的价值不到三层。给它配上 state 文件管理配置状态接上 pillar 管理敏感信息再规划出分阶段的发布流程这才是 Saltstack 真正能帮你解放生产力的形态。