十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Salt自动化运维实战:从零搭建配置管理环境

Salt自动化运维实战:从零搭建配置管理环境 把“开箱”这件事搬到技术学习里往往意味着拿到一套还不熟悉的工具链从安装、配置到跑通第一个例子把封装在文档背后的细节一层层拆开。本文要拆的是一个以 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 与 MinionSalt 使用 C/S 架构核心角色是两个Master管理节点保存配置、下发命令、收集执行结果。Minion被管理节点安装后需要注册到 Master接收并执行指令。Minion 启动后会生成一对密钥并把自己的公钥发送给 Master。管理员在 Master 上接受该公钥后双方建立可信通道。后续所有命令都通过这个加密通道传输。那“salt.niili”里的 Master 和 Minion 具体怎么配合我建议你把它理解成一家公司的运维中心和一线服务器。运维中心只负责制定标准和下发任务具体操作由每台服务器自己执行。2.2 State、Pillar、Grains、Modules这是四个最高频的概念也是新手最容易混淆的地方。概念作用类比State描述目标的最终状态例如“nginx 已安装并运行”需求文档PillarMaster 下发给 Minion 的私有配置数据例如不同环境的端口、账号、密码环境变量 / 参数表GrainsMinion 启动时收集的静态信息例如操作系统、CPU 核数、IP 地址服务器身份证ModulesSalt 内置或自定义的执行模块例如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.04Python 环境由 Salt 安装脚本自动处理无需提前手工安装Salt 版本以官方当前稳定版为准本文演示时可通过命令自行确认。为了方便本地测试你也可以在一台机器上同时安装 Master 和 Minion。虽然不推荐用于生产但做入门实验完全够用。salt.niili 项目最初验证时也是在一台机器上跑通的相当于同时扮演管理端和被管理端。3.2 安装 Salt MasterSalt 官方提供了一个 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/minionmaster: 192.168.1.100 id: minion-web-01其中master是 Master 的 IP 或主机名id是这台 Minion 的唯一标识。如果没有指定 idSalt 会使用机器的主机名。建议显式设置便于后续管理。启动 Minionsudo 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/saltPillar 目录是/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 数据的查找路径。修改后重启 Mastersudo systemctl restart salt-master4.3 编辑 Minion 配置在每台 Minion 上需要确保它能正确加载自定义 State 和 Pillar。一般情况下无需修改因为 Minion 会自动从 Master 拉取。不过如果你希望 Minion 本地缓存更精确可以在/etc/salt/minion中设置file_client: remoteremote表示所有文件都从 Master 获取。默认值就是 remote所以这一步通常可以省略。配置完成后重启 Minionsudo systemctl restart salt-minion4.4 创建基础目录在 Master 上创建目录sudo mkdir -p /srv/salt/base sudo mkdir -p /srv/pillar到这里环境骨架已经搭好下一步开始写第一个 State。5. 编写第一个 State初始化 Minion5.1 创建 top.slstop.sls是所有 State 的入口。新建/srv/salt/top.slsbase: *: - base.initbase是环境名称和环境配置文件中的file_roots对应。*表示匹配所有 Minion。base.init表示应用/srv/salt/base/init.sls这个文件。如果你只想匹配特定 Minion可以把*换成具体的 idbase: 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中引入这个 Statebase: *: - 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.slsbase: *: - 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.jinjaworker_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.slspush_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 为什么需要 PillarState 负责描述“做什么”Pillar 负责提供“做的时候用到的参数”。比如测试环境和生产环境的 Nginx 端口不同用户名不同这些都不应该写死在 State 里。Pillar 可以把这些数据统一管理按 Minion 分配。7.2 编写 Pillar top.sls新建/srv/pillar/top.slsbase: *: - common minion-web-01: - web*表示所有 Minion 都加载common.slsminion-web-01额外加载web.sls。7.3 编写 Pillar 数据/srv/pillar/common.slsnginx_port: 8080 nginx_server_name: common.example.com/srv/pillar/web.slsnginx_port: 9090 nginx_server_name: web.example.com因为web.sls只分配给minion-web-01所以这台机器的 Nginx 端口会是 9090另一台则保留 common 里的 8080。7.4 刷新 PillarPillar 数据按需生成修改后需要刷新。在 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.slsclean_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返回 FalseMinion 未启动或网络不通登录 Minion检查 salt-minion 服务状态与防火墙密钥显示在 Unaccepted未接受密钥在 Master 上执行salt-key -A -yMinion 无法连接 Mastermaster 地址配置错误检查/etc/salt/minion中的master配置9.2 state.apply 提示 State 文件找不到最常见原因是top.sls中引用的文件名和实际文件名不一致。Salt 的规则是base.init会寻找base/init.slsbase.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 充分利用 testTrue 做预演执行 State 前可以先加testTrue参数做预演sudo salt * state.apply testTrue该模式只计算差异不真正执行变更。输出中Changes列会显示预期变化这样能提前发现误配置减少线上事故。10.6 日志与审计Master 的操作记录保存在/var/log/salt/masterMinion 的本地操作记录在/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 管理项目。
返回列表