SaltStack配置管理实战:从安装认证到State与Pillar应用

发布时间:2026/8/30 3:45:39
SaltStack配置管理实战:从安装认证到State与Pillar应用 在实际服务器维护中很多人会先经历一段混乱期要上线一台新机器就手动登录执行 apt 或 yum要改同一个配置就写一个脚本循环 SSH 到所有服务器上跑等到机器数量超过 20 台脚本里积累的临时命令越来越多谁改过哪台机器的配置、当前环境和期望环境差多少完全变成黑盒。FiNALE盐oωo这个目录名看起来像一个个人练手项目但它代表了一类更合理的做法把服务器配置变成代码交给配置管理工具去执行。这篇文章以FiNALE盐oωo作为示例项目代号完整走一遍 SaltStack 从安装、认证、State 到 Pillar 的落地路径。本文面向已经具备 Linux 命令行基础、但还没有系统使用过配置管理工具的开发者和运维工程师。文章会从三个节点的小实验开始一台作为 Salt Master两台作为 Salt Minion最终达到的效果是在 Master 上执行一条命令Minion 自动完成 Nginx 安装、配置和启动不同环境的差异统一放在 Pillar 中出现认证、端口、包名问题时能根据返回结果和日志快速定位。1. 先理解 SaltStack 的定位为什么需要配置管理工具1.1 “盐”这个代号背后是一套完整的远程执行框架SaltStack 取名自“salt”常被翻译成“盐栈”它的核心思想不是像 CI/CD 工具那样只负责部署某一类应用而是直接管理操作系统层面的目标状态。FiNALE盐oωo这个示例项目里“盐”放在这里并不是随便凑数而是强调这套配置管理链路使用的是 SaltStack。SaltStack 中有几个基础概念必须首先对齐Master中央控制节点保存 State 和 Pillar 文件向 Minion 下发指令。Minion被管节点运行在目标服务器上默认通过 ZeroMQ 与 Master 通信。State描述“目标状态”的 SLS 文件比如“nginx 这个包要安装”“nginx 服务要启动”“这个文件内容要修改”。Pillar面向特定 Minion 的变量数据适合区分生产、测试环境。GrainsMinion 启动时上报的静态信息比如操作系统、CPU 核数、IP 地址。Top File决定哪些 Minion 应用哪些 State 或 Pillar 的匹配规则文件。举一个通俗的例子不使用 SaltStack 时请求是“把这台机器上的 nginx 改成监听 8080 端口”操作方式是自己登录机器、改配置、重启使用 SaltStack 时请求是“这台机器最终要处于 nginx 安装完成、配置文件内容正确、服务处于运行状态”的状态执行命令后由工具自动对比现状和目标只做必要操作。1.2 为什么不建议用“循环 SSH 自定义脚本”代替配置管理很多人觉得机器不多时不需要引入 SaltStack。对于三五台机器这个判断可以成立。但当机器数量增加后基于 SSH 循环的运维脚本会出现几个不容易绕开的问题。第一个问题是幂等性。同样的脚本跑两遍可能出现“包已存在”“服务已运行”的报错或者更严重的——配置文件被重复追加。脚本写成无条件执行时第二次执行往往会破坏系统。SaltStack 的 State 是声明式的CPU 判断包是否已安装、服务是否在运行未变化的部分不会重复执行。第二个问题是差异管理。自定义脚本很难表达“这台机器采用生产环境参数那台机器采用测试环境参数”通常只能写成变量替换加一堆条件判断条件分支多了以后根本没法维护。Pillar 的价值就是把这些差异变量从执行逻辑中拆出来。第三个问题是审计和可视化。SaltStack 执行结束后会返回结构化结果哪些 Minion 成功、哪些失败、失败在哪个步骤一清二楚。脚本循环只能靠 echo 输出判断出了问题很难准确回溯。1.3 看一个最小管理链路再决定适用边界在不考虑高可用和复杂网络的前提下SaltStack 的最小管理链路如下Master 启动 4505 和 4506 端口。4505 用于消息推送4506 用于返回结果收集。Minion 启动后生成密钥对把公钥发给 Master等待 Master 接受。Master 接受 Minion 密钥后通过salt 目标 模块.函数执行命令。State 和 Pillar 通过 top file 匹配目标 Minion然后下发执行。学习阶段建议先在两台或三台独立虚拟机或容器中验证这条链路不要直接在生产机器上实验。生产环境需要额外考虑高可用、密钥管理、文件服务器、事件监控等这些会在后面单独说明。2. 准备实验环境两台虚拟机就能跑通最小闭环2.1 环境要求和端口规划本文实验环境如下实际环境中版本可以略作调整但底层逻辑一致。节点操作系统建议内存角色主机名建议节点一CentOS Stream 9 或 Ubuntu Server 22.042GBSalt Mastersalt-master节点二同 Master 系统1GBSalt Minionweb01节点三同 Master 系统1GBSalt Minionweb02Master 与 Minion 之间需要放行 TCP 4505 和 4506 端口。如果实验环境在云安全组或本机防火墙后面必须同时确认安全组规则和操作系统防火墙配置。端口用途默认监听地址4505Master 消息发布端口0.0.0.04506Minion 返回请求端口0.0.0.0这里有一个容易忽略的点SaltStack 使用 ZeroMQ 通信不是普通 HTTP 请求所以不能只放行 22 端口。很多第一次部署的人把 Master 装好Minion 也配好了就是test.ping不通最后发现是 4505/4506 被防火墙拦截。2.2 安装 Salt Master 和 Salt Minion在 Master 节点上先准备 Salt 软件仓库再安装 Master。以 CentOS Stream 9 为例sudo rpm --import https://repo.saltproject.io/salt/py3/redhat/9/x86_64/latest/SALTSTACK-GPG-KEY.pub sudo curl -fsSL https://repo.saltproject.io/salt/py3/redhat/9/x86_64/latest.repo | sudo tee /etc/yum.repos.d/salt.repo sudo yum clean expire-cache sudo yum install -y salt-master在 Minion 节点上安装 Minionsudo rpm --import https://repo.saltproject.io/salt/py3/redhat/9/x86_64/latest/SALTSTACK-GPG-KEY.pub sudo curl -fsSL https://repo.saltproject.io/salt/py3/redhat/9/x86_64/latest.repo | sudo tee /etc/yum.repos.d/salt.repo sudo yum install -y salt-minionUbuntu 系统的安装路径类似只是包管理器换成 apt。安装之前建议确认系统 Python 版本Salt 3006 以上版本对 Python 版本有要求CentOS 7 自带的 Python 2.7 已不适合新版本踩坑建议优先使用 CentOS Stream 9、Rocky Linux 9 或 Ubuntu 22.04 这类现代系统。注意如果原始材料没有给出明确版本这里安装的是 Salt 3006 系列。落地前要先确认自己的操作系统发行版和官方仓库是否匹配否则容易出现依赖缺失或版本冲突。2.3 配置 Minion 指向 Master 并完成密钥认证编辑每台 Minion 的/etc/salt/minion找到master配置项master: 192.168.56.10 id: web01id是 Minion 在 Salt 体系中的唯一标识默认取自主机名。这里手动指定id的目的是让目标匹配更可读。master地址可以是 IP也可以是域名但学习和测试阶段建议直接用 IP减少 DNS 不确定因素。修改后启动服务sudo systemctl enable --now salt-minion在 Master 上查看待接受的密钥sudo salt-key -L输出类似Accepted Keys: Unaccepted Keys: web01 web02 Rejected Keys:接受密钥sudo salt-key -a web01 sudo salt-key -a web02也可以在确认所有待接受密钥没有异常后批量接受sudo salt-key -A2.4 验证 Master 与 Minion 连通性密钥接受后在 Master 上执行sudo salt * test.ping预期输出web01: True web02: True如果某一台返回Minion did not return. [No response]先不要急着查 State重点检查防火墙、Minion 的master配置、Master 的/var/log/salt/master日志和 Minion 的/var/log/salt/minion日志。这一步是整个实验链路中的第一个检查点。只有test.ping通过了后面 State 和 Pillar 才有意义。3. 用 State 描述目标状态而不是登录机器敲命令3.1 SLS 文件、Top File 和 state.apply 的关系进入到FiNALE盐oωo项目的核心环节。SaltStack 管理配置的基本单位是 SLS 文件默认放在 Master 的/srv/salt/目录下。SLS 文件使用的语法是 YAML但需要在开头加一行特殊声明告诉 Salt 这个文件是 SLS 而不是普通 YAML。一个最小项目结构如下/srv/salt/ ├── top.sls └── nginx.slstop.sls是入口文件它规定哪些 Minion 应用哪些 Statebase: web*: - nginx这个文件的意思是主机名以web开头的 Minion应用nginx.sls中描述的状态。文件名不带.sls后缀这是 Salt 的约定。在浏览器或文档中经常会看到state.highstate和state.apply两种写法。在旧版本中state.highstate表示应用 top.sls 中匹配的所有状态state.apply更灵活可以只指定某个 SLS例如salt web01 state.apply nginx。现代版本中直接执行state.apply也会默认调用 top.sls 匹配到的状态。建议新项目统一使用state.apply语义更清晰。3.2 编写第一个 State安装 Nginx 并启动服务创建/srv/salt/nginx.slsinstall_nginx: pkg.installed: - name: nginx start_nginx: service.running: - name: nginx - enable: True - require: - pkg: install_nginx这段 SLS 表达了两件事名为install_nginx的状态要求 nginx 包已安装。名为start_nginx的状态要求 nginx 服务处于运行状态并且开机自启。require声明了顺序依赖start_nginx必须在install_nginx完成后执行。注意这里的- pkg: install_nginx不是包名而是对状态 IDinstall_nginx的引用。写成- pkg: nginx是错误的因为 Salt 找不到名为 nginx 的状态 ID。这是新手最容易犯的错误之一。3.3 执行 highstate 并理解返回结果在 Master 上对 web01 单独执行sudo salt web01 state.apply正常执行过程中返回内容会按每个状态 ID 展示结果。关键字段包括web01: ---------- ID: install_nginx Function: pkg.installed Name: nginx Comment: All specified packages are already installed. Started: 10:28:31.123456 Duration: 523.123 ms Changes: ---------- ID: start_nginx Function: service.running Name: nginx Comment: Service is already running Started: 10:28:31.654321 Duration: 41.001 ms Changes:Changes为空表示没有发生变更系统已经处于目标状态。如果Changes中出现new和old字段说明该步骤实际对系统做了修改。执行salt * state.apply可以批量为所有 Minion 应用状态。但在学习阶段建议先针对一台机器验证确认无误后再批量执行避免一次性把错误配置扩散到全部机器。3.4 State 的幂等性和执行顺序为什么重要State 与普通 shell 脚本最大的区别是幂等性。脚本执行时如果 nginx 已经安装再次执行yum install -y nginx不会报错但也没有意义如果服务已经运行systemctl restart nginx可能会造成短暂中断。State 则先收集当前系统状态再决定是否执行动作。pkg.installed只保证“包存在”如果包不在才安装service.running只保证“服务运行”如果服务没在运行才启动。这种“期望状态与当前状态对比”的模型是配置管理工具的核心价值。执行顺序同样重要。Salt 默认会解析各状态 ID 之间的依赖关系没有依赖关系的状态会并行执行。使用require或watch可以显式控制顺序。require表示前置依赖watch除了前置依赖外还会在依赖对象发生变更时触发当前状态重新执行。4. 使用 Pillar 区分环境差异避免在 State 里写死参数4.1 Pillar 与 State、Grains 的分工假设现在需要让 web01 的 Nginx 监听 8080 端口而 web02 监听 80 端口。如果把端口写死在nginx.sls中那这个 State 就无法同时适合两台机器。正确做法是把差异数据放入 Pillar。三者的分工可以这样理解State 描述“做什么”是固定的执行模板。Pillar 提供“用什么参数做”按 Minion 的匹配结果提供不同数据。Grains 描述“这台机器本身是什么”比如操作系统类型、硬件信息。Pillar 的数据默认也是从 Master 下发但只会下发给被匹配到的 Minion天然适合存放敏感信息或环境差异。4.2 配置 Pillar 文件创建/srv/pillar/top.slsbase: web01: - nginx web02: - nginx创建/srv/pillar/nginx.slsnginx: listen_port: 8080这个 Pillar 文件只在web01和web02上生效。如果后续增加web03并且它不匹配任何 Pillar top file那么从 Pillar 读取listen_port时会返回NoneState 里如果没有默认值处理就会出现配置错误。4.3 在 State 中引用 Pillar回到/srv/salt/nginx.sls为避免端口写死可以先用一个简单的 Nginx 配置模板来演示。创建/srv/salt/files/nginx.confserver { listen {{ pillar.get(nginx:listen_port, 80) }}; root /usr/share/nginx/html; index index.html index.htm; }然后修改/srv/salt/nginx.slsinstall_nginx: pkg.installed: - name: nginx render_nginx_conf: file.managed: - name: /etc/nginx/conf.d/demo.conf - source: salt://files/nginx.conf - template: jinja - require: - pkg: install_nginx start_nginx: service.running: - name: nginx - enable: True - watch: - file: render_nginx_conf这里的关键点有三个file.managed会把salt://files/nginx.conf文件下发到 Minion 的指定路径。template: jinja表示模板文件使用 Jinja2 渲染渲染时需要读取 Pillar。watch监听配置文件变更一旦模板渲染结果与现有文件不同就会重启 Nginx 服务。执行一次sudo salt web01 state.apply在web01上检查生成的文件cat /etc/nginx/conf.d/demo.conf预期输出server { listen 8080; root /usr/share/nginx/html; index index.html index.htm; }4.4 验证 Pillar 数据是否到达目标 Minion如果配置文件里端口没有变化首先确认 Pillar 是否已经下发。在 Master 上执行sudo salt web01 pillar.items输出中应该包含web01: ---------- nginx: ---------- listen_port: 8080还可以单独取值sudo salt web01 pillar.get nginx:listen_port如果pillar.items为空或缺少对应 key说明 Pillar top file 的匹配表达式写错了或者执行state.apply前没有刷新 Pillar。修改 Pillar 文件后执行sudo salt * saltutil.refresh_pillar再重新运行 state。5. 用 Salt CLI、返回数据和日志完成验证与日常排错5.1 常用命令速查表日常使用中下面这些命令出现频率最高。命令作用适用场景salt * test.ping测试所有 Minion 连通性确认基础通信salt * pillar.items查看所有 Minion 的 Pillar 数据确认差异数据是否下发salt * grains.items查看所有 Minion 的 Grains 信息排查系统差异salt web* state.apply应用 top.sls 匹配的状态发布配置salt web01 state.apply nginx只应用某个指定 SLS单独调试某个配置salt web01 state.show_top查看该 Minion 匹配到哪些状态检查匹配表达式salt web01 state.show_sls nginx查看渲染后的 SLS检查 Jinja 渲染结果salt-run jobs.list_jobs查看任务历史和执行时间审计已执行任务salt web01 cmd.run systemctl status nginx在 Minion 上执行命令临时排查state.apply是最常用的执行命令但排错时建议先运行state.show_top和state.show_sls。前者确认目标匹配后者确认模板渲染后的内容。直接盯着执行结果看遇到语法错误时不容易判断问题出在匹配规则还是模板。5.2 返回数据的结构怎么看Salt 返回结果通常按 Minion ID 分组一个 Minion 对应一个 key。深层字段如下字段含义排错作用comment该状态步骤的解释说明直接判断为何未执行resulttrue/false/null该步骤是否成功changes实际系统变更内容确认修改了什么duration耗时性能排查started执行时间审计定位看到result: false时优先阅读comment。大多数情况下comment 已经给出了明确原因比如“Package is not available”或“The following block is out of sync”。5.3 Master 和 Minion 日志中值得关注的内容Master 日志路径为/var/log/salt/masterMinion 日志路径为/var/log/salt/minion。日志默认等级是 warning出现问题时可以用下面的方式临时提高日志等级sudo salt-master -l debug或者修改/etc/salt/master中的log_level配置。debug 日志会产生大量输出定位到问题后要恢复默认等级。在认证类问题中Master 日志中会看到类似Authentication attempt from的记录Minion 日志中会看到SaltReqTimeoutError或No route to host。这些关键字可以帮助快速定位是网络层问题还是认证层问题。5.4 常见错误排查表问题现象常见原因检查方式处理建议test.ping无响应4505/4506 端口不通ss -lntp查看监听firewall-cmd --list-ports查看防火墙放行端口或关闭实验环境防火墙test.ping返回认证错误Minion 密钥未被接受salt-key -L接受对应密钥Minion 重启后 ID 变化主机名被修改hostname和salt-key -L对比固定/etc/hostname不要随意改主机名找不到 SLS 文件top.sls 文件路径或文件名错误salt web01 state.show_top确认 SLS 在/srv/salt下且文件名与 top 中一致Pillar 值取不到Pillar top file 匹配错误或未刷新salt web01 pillar.items修改后执行saltutil.refresh_pillar包名不适用当前系统不同发行版包名不同salt web01 grains.get os_family用 Pillar 或 Grains 区分系统再安装不同包服务重启失败配置文件模板渲染出错cat /etc/nginx/conf.d/demo.conf先在 Minion 上手动nginx -t6. 实际项目中容易踩到的坑和应对方案6.1 坑一Minion ID 不稳定导致密钥反复变化有些环境的主机名会随云主机名称变化默认情况下 Minion ID 会取主机名。一旦主机名变化Minion 会重新生成密钥Master 上会出现新密钥旧密钥变成 Rejected下一次执行直接失败。推荐在安装完 Minion 后立即在/etc/salt/minion中固定idid: web01-prod-001这个 ID 不要包含临时环境信息也不要随便起名。ID 一旦确定后在生产环境中改起来很麻烦因为密钥和 top file 匹配都和它绑定。6.2 坑二在 State 中写死业务密码很多教程为了演示简单会把数据库密码直接写在 SLS 文件的cmd.run中。这种做法一旦 State 入库密码就相当于公开了所有能访问 Git 仓库的人都能看到。正确做法是使用 Pillar 存储密码同时开启 Pillar 的加密存储方案比如外部 Pillar、sdb 或 Vault。最小示范如下db_password: {{ salt[pillar.get](db:password, ) }}这样密码不会出现在 SLS 中而是来自 Pillar 数据。生产环境还要考虑密钥轮换和访问审计。6.3 坑三直接对全量 Minion 执行 state.applysalt * state.apply在只有两台测试机时很舒服但生产环境一旦有几十台机器其中部分机器还处于兼容性测试阶段直接批量应用会造成大面积故障。推荐的执行策略是分批次发布sudo salt -L web01,web02 state.apply sudo salt -E web(0[3-4]) state.apply先用-L精确指定要发布的 Minion再用-E进行正则匹配。等全部验证通过后再考虑对全量执行。6.4 生产环境还应补齐的检查清单从学习环境切换到大一点的生产环境之前建议准备一份启用检查清单逐项确认而不是在出现问题后再返工。检查项建议方案Minion ID 是否固定安装后立即写入/etc/salt/minionState 是否入库并走版本管理使用 gitfs 或专用仓库保存/srv/salt和/srv/pillar敏感数据是否加密使用外部 Pillar、sdb 或 Vault是否按批次发布分批次state.apply避免全量是否配置回滚路径在 gitfs 中通过版本标签快速切换回上一个 State 版本是否监控任务执行使用事件总线或 Salt Runner 记录任务结果Master 是否单点生产规模扩大后考虑 Multi-Master 或 Salt 高可用方案日志和审计将 Master 和 Minion 日志统一收集方便故障定位以FiNALE盐oωo这类命名为例如果你只是在个人实验环境中维护一个自动化配置项目不涉及模块分拆那么单 Master、固定 ID、State 和 Pillar 分目录管理已经够用。一旦项目要多人协作或纳入正式运维流程命名需要改成更有语义的代号同时把/srv/salt和/srv/pillar纳入 Git 管理。配置管理工具的价值不在于第一次安装能把服务跑起来而在于半年后一台新机器加入集群时只需要一行命令就能让它和现有环境保持一致。

相关新闻