云计算百科
云计算领域专业知识百科平台

【Linux】SaltStack 批量管理服务器

在这里插入图片描述

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕Linux这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!


文章目录

  • SaltStack 批量管理服务器 🖥️⚡
    • 什么是 SaltStack?🔧
    • SaltStack 核心组件 🧱
      • 1. Salt Master
      • 2. Salt Minion
      • 3. Grains
      • 4. Pillar
      • 5. States & SLS 文件
    • 安装与配置 SaltStack 🛠️
      • 环境准备
      • 安装 Salt Master
      • 安装 Salt Minion
      • 接受 Minion 密钥
    • 基础命令实战 🧪
      • 1. 远程执行命令
      • 2. 使用 Grains 筛选目标
      • 3. 使用复合匹配器
      • 4. 批量文件分发
    • 编写第一个 State 文件 🗂️
      • 示例:统一配置时区
    • 使用 Pillar 管理敏感数据 🔐
      • 1. 创建 Pillar 目录结构
      • 2. 编辑主配置文件 `/srv/pillar/top.sls`
      • 3. 创建 `/srv/pillar/webapp.sls`
      • 4. 创建 `/srv/pillar/dbapp.sls`
      • 5. 刷新 Pillar 数据
      • 6. 在 State 中引用 Pillar
    • SaltStack 架构图 📊
    • 通过 Salt-API 实现自动化集成 🤖
      • 1. 安装并启用 salt-api
      • 2. 获取 Token
      • 3. 调用远程命令
    • Java 集成示例 💻☕
      • Maven 依赖
      • Java 代码
      • 输出示例
    • 高级技巧:事件驱动与 Reactor 🔄
      • 1. 启用 Reactor
      • 2. 编写 Reactor 文件
      • 3. 触发自定义事件
    • 批量管理真实案例 🎯
      • 案例一:紧急安全补丁推送
      • 案例二:Java 应用灰度发布
      • 案例三:自动化巡检报告
    • 性能优化与最佳实践 🚀
      • 1. 使用 Syndic 实现横向扩展
      • 2. 启用 Job Cache 清理
      • 3. 使用 Batch 模式减少负载
      • 4. 合理使用 Grains vs Pillar
      • 5. 版本控制你的 States
    • 与其他工具对比 🆚
    • 故障排查与调试 🐞
      • 1. Minion 无法连接 Master
      • 2. State 执行失败
      • 3. API 返回 401 Unauthorized
    • 社区与资源 🌍
    • 结语 🌈

SaltStack 批量管理服务器 🖥️⚡

在现代运维体系中,批量、自动化地管理成百上千台服务器已成为刚需。无论是部署应用、配置系统、监控状态,还是执行命令、分发文件,传统“人肉 SSH”方式早已无法胜任。而 SaltStack 作为一款强大的开源基础设施自动化工具,以其高性能、灵活架构和丰富功能,成为众多企业首选的批量管理解决方案。

本文将从零开始带你深入理解 SaltStack 的核心概念、安装部署、实战用法,并结合 Java 示例展示如何通过 API 与 SaltStack 集成,实现自动化运维闭环。文章最后还将提供多个真实场景案例,帮助你构建属于自己的批量管理系统。


什么是 SaltStack?🔧

SaltStack(简称 Salt)是一个基于 Python 开发的开源配置管理与远程执行系统,支持跨平台(Linux、Windows、macOS 等),采用 C/S 架构,由 Master(控制节点)和 Minion(被控节点)组成。

SaltStack 最大的特点包括:

  • ⚡ 高速通信:使用 ZeroMQ + MsgPack 实现异步、非阻塞通信,可同时管理数千台主机。
  • 🧩 灵活架构:支持 Master-Minion、Masterless、Syndic 多种部署模式。
  • 📦 模块化设计:内置数百个执行模块(execution modules)、状态模块(state modules)、Grains、Pillar 等。
  • 🔐 安全可靠:支持 AES 加密通信、ACL 权限控制、外部认证(如 LDAP、PAM)。
  • 🌐 API 友好:提供 REST API、Python API、CLI 工具,方便与其他系统集成。

官方网站:https://saltproject.io


SaltStack 核心组件 🧱

1. Salt Master

Salt Master 是整个系统的“大脑”,负责:

  • 接收来自 Minion 的注册请求并颁发密钥;
  • 下发指令给指定或全部 Minion;
  • 收集执行结果并返回给用户;
  • 存储配置文件、状态文件、Pillar 数据等。

2. Salt Minion

Minion 是安装在目标服务器上的代理程序,负责:

  • 启动时向 Master 注册并建立加密连接;
  • 监听 Master 指令并执行本地操作;
  • 将执行结果返回给 Master;
  • 定期上报自身信息(如 IP、CPU、内存、操作系统等)。

3. Grains

Grains 是 Minion 启动时自动收集的静态系统信息,比如:

os: CentOS
os_family: RedHat
cpuarch: x86_64
fqdn: web01.example.com

可通过 salt '*' grains.items 查看所有 Minion 的 Grains。

4. Pillar

Pillar 是由 Master 控制的动态数据结构,用于向 Minion 传递敏感或个性化配置,比如数据库密码、API 密钥等。与 Grains 不同,Pillar 数据是按需下发、加密传输的。

5. States & SLS 文件

Salt 使用 .sls(SaLt State)文件定义“期望状态”,例如:

# /srv/salt/nginx/init.sls
nginx:
pkg.installed: []
service.running:
enable: True
reload: True
watch:
file: /etc/nginx/nginx.conf

/etc/nginx/nginx.conf:
file.managed:
source: salt://nginx/files/nginx.conf
user: root
group: root
mode: 644

执行 salt '*' state.apply nginx 即可确保所有 Minion 上 Nginx 正确安装并运行。


安装与配置 SaltStack 🛠️

环境准备

我们假设有三台服务器:

主机名IP 地址角色
saltmaster 192.168.1.10 Salt Master
web01 192.168.1.11 Salt Minion
db01 192.168.1.12 Salt Minion

所有机器均为 CentOS 7。

安装 Salt Master

在 saltmaster 上执行:

sudo yum install https://repo.saltproject.io/py3/redhat/salt-py3-repo-latest.el7.noarch.rpm -y
sudo yum install salt-master -y
sudo systemctl enable salt-master
sudo systemctl start salt-master

安装 Salt Minion

在 web01 和 db01 上分别执行:

sudo yum install https://repo.saltproject.io/py3/redhat/salt-py3-repo-latest.el7.noarch.rpm -y
sudo yum install salt-minion -y

编辑 /etc/salt/minion 配置文件,指定 Master 地址:

master: 192.168.1.10
id: web01 # 或 db01,建议设置为有意义的主机名

启动 Minion:

sudo systemctl enable salt-minion
sudo systemctl start salt-minion

接受 Minion 密钥

回到 saltmaster,查看待接受的 Minion:

salt-key -L

输出类似:

Accepted Keys:
Denied Keys:
Unaccepted Keys:
web01
db01
Rejected Keys:

接受所有 Minion:

salt-key -A -y

测试连通性:

salt '*' test.ping

若返回 True,说明通信正常 ✅


基础命令实战 🧪

1. 远程执行命令

# 在所有 Minion 上执行 uptime
salt '*' cmd.run 'uptime'

# 在 web01 上查看磁盘使用情况
salt 'web01' disk.usage

# 在所有 CentOS 主机上安装 vim
salt -G 'os:CentOS' pkg.install vim

2. 使用 Grains 筛选目标

# 查看所有 Minion 的操作系统
salt '*' grains.get os

# 仅对 Ubuntu 系统执行重启(谨慎!)
salt -G 'os:Ubuntu' system.reboot

3. 使用复合匹配器

# 匹配 web01 或 db01
salt -C 'web01 or db01' test.ping

# 匹配以 web 开头且操作系统为 CentOS 的主机
salt -C 'web* and G@os:CentOS' test.ping

4. 批量文件分发

创建一个测试文件:

echo "Hello from SaltStack!" > /srv/salt/hello.txt

分发到所有 Minion 的 /tmp/ 目录:

salt '*' cp.get_file salt://hello.txt /tmp/hello.txt

验证:

salt '*' cmd.run 'cat /tmp/hello.txt'


编写第一个 State 文件 🗂️

State 是 Salt 的核心功能之一,它描述了系统“应该处于什么状态”,而不是“如何达到该状态”。Salt 会自动计算差异并执行必要操作。

示例:统一配置时区

创建 /srv/salt/timezone/init.sls:

Asia/Shanghai:
timezone.system:
utc: True

/etc/localtime:
file.symlink:
target: /usr/share/zoneinfo/Asia/Shanghai

执行:

salt '*' state.apply timezone

Salt 会确保所有 Minion 的时区设置为上海,并创建正确的符号链接。


使用 Pillar 管理敏感数据 🔐

假设我们要在每台服务器上部署一个 Java 应用,但每台机器的数据库密码不同。

1. 创建 Pillar 目录结构

mkdir -p /srv/pillar

2. 编辑主配置文件 /srv/pillar/top.sls

base:
'web01':
webapp
'db01':
dbapp

3. 创建 /srv/pillar/webapp.sls

database:
host: 192.168.1.12
port: 3306
username: appuser
password: WebApp@2024!

4. 创建 /srv/pillar/dbapp.sls

database:
host: localhost
port: 3306
username: dbadmin
password: DBAdmin#Secure2024

5. 刷新 Pillar 数据

salt '*' saltutil.refresh_pillar

6. 在 State 中引用 Pillar

修改之前的状态文件 /srv/salt/javaapp/init.sls:

java-app-config:
file.managed:
name: /opt/myapp/config.properties
source: salt://javaapp/files/config.properties.jinja
template: jinja
context:
db_host: {{ pillar['database']['host'] }}
db_port: {{ pillar['database']['port'] }}
db_user: {{ pillar['database']['username'] }}
db_pass: {{ pillar['database']['password'] }}

对应的模板文件 /srv/salt/javaapp/files/config.properties.jinja:

database.host={{ db_host }}
database.port={{ db_port }}
database.username={{ db_user }}
database.password={{ db_pass }}

执行部署:

salt '*' state.apply javaapp

这样,每台 Minion 都会获得专属的配置文件,且密码不会明文暴露在 State 文件中!


SaltStack 架构图 📊

下面是一个典型的 SaltStack 部署架构,包含 Master、Minion、Syndic、API 层等组件:

#mermaid-svg-WBuPVb4ss5TCcq5J{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WBuPVb4ss5TCcq5J .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WBuPVb4ss5TCcq5J .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WBuPVb4ss5TCcq5J .error-icon{fill:#552222;}#mermaid-svg-WBuPVb4ss5TCcq5J .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WBuPVb4ss5TCcq5J .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WBuPVb4ss5TCcq5J .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WBuPVb4ss5TCcq5J .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WBuPVb4ss5TCcq5J .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WBuPVb4ss5TCcq5J .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WBuPVb4ss5TCcq5J .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WBuPVb4ss5TCcq5J .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WBuPVb4ss5TCcq5J .marker.cross{stroke:#333333;}#mermaid-svg-WBuPVb4ss5TCcq5J svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WBuPVb4ss5TCcq5J p{margin:0;}#mermaid-svg-WBuPVb4ss5TCcq5J .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-WBuPVb4ss5TCcq5J .cluster-label text{fill:#333;}#mermaid-svg-WBuPVb4ss5TCcq5J .cluster-label span{color:#333;}#mermaid-svg-WBuPVb4ss5TCcq5J .cluster-label span p{background-color:transparent;}#mermaid-svg-WBuPVb4ss5TCcq5J .label text,#mermaid-svg-WBuPVb4ss5TCcq5J span{fill:#333;color:#333;}#mermaid-svg-WBuPVb4ss5TCcq5J .node rect,#mermaid-svg-WBuPVb4ss5TCcq5J .node circle,#mermaid-svg-WBuPVb4ss5TCcq5J .node ellipse,#mermaid-svg-WBuPVb4ss5TCcq5J .node polygon,#mermaid-svg-WBuPVb4ss5TCcq5J .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WBuPVb4ss5TCcq5J .rough-node .label text,#mermaid-svg-WBuPVb4ss5TCcq5J .node .label text,#mermaid-svg-WBuPVb4ss5TCcq5J .image-shape .label,#mermaid-svg-WBuPVb4ss5TCcq5J .icon-shape .label{text-anchor:middle;}#mermaid-svg-WBuPVb4ss5TCcq5J .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WBuPVb4ss5TCcq5J .rough-node .label,#mermaid-svg-WBuPVb4ss5TCcq5J .node .label,#mermaid-svg-WBuPVb4ss5TCcq5J .image-shape .label,#mermaid-svg-WBuPVb4ss5TCcq5J .icon-shape .label{text-align:center;}#mermaid-svg-WBuPVb4ss5TCcq5J .node.clickable{cursor:pointer;}#mermaid-svg-WBuPVb4ss5TCcq5J .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WBuPVb4ss5TCcq5J .arrowheadPath{fill:#333333;}#mermaid-svg-WBuPVb4ss5TCcq5J .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WBuPVb4ss5TCcq5J .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WBuPVb4ss5TCcq5J .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WBuPVb4ss5TCcq5J .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WBuPVb4ss5TCcq5J .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WBuPVb4ss5TCcq5J .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WBuPVb4ss5TCcq5J .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WBuPVb4ss5TCcq5J .cluster text{fill:#333;}#mermaid-svg-WBuPVb4ss5TCcq5J .cluster span{color:#333;}#mermaid-svg-WBuPVb4ss5TCcq5J div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WBuPVb4ss5TCcq5J .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WBuPVb4ss5TCcq5J rect.text{fill:none;stroke-width:0;}#mermaid-svg-WBuPVb4ss5TCcq5J .icon-shape,#mermaid-svg-WBuPVb4ss5TCcq5J .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WBuPVb4ss5TCcq5J .icon-shape p,#mermaid-svg-WBuPVb4ss5TCcq5J .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WBuPVb4ss5TCcq5J .icon-shape .label rect,#mermaid-svg-WBuPVb4ss5TCcq5J .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WBuPVb4ss5TCcq5J .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WBuPVb4ss5TCcq5J .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WBuPVb4ss5TCcq5J :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

运维人员

Salt Master

Minion 1

Minion 2

Minion N

Web UI / Java App

Salt-API

Syndic Node

Minion A

Minion B

Minion C

说明:

  • 运维人员 通过 CLI 或 Web 界面操作 Master;
  • Salt-API 提供 REST 接口,供外部系统调用;
  • Syndic 用于多级架构,适合跨数据中心管理;
  • 所有通信默认使用 AES 加密。

通过 Salt-API 实现自动化集成 🤖

SaltStack 提供了完善的 REST API(称为 salt-api),允许你通过 HTTP 请求远程调用 Salt 功能。这对于 Java 应用集成非常有用。

1. 安装并启用 salt-api

在 Master 上安装:

yum install salt-api python3-cherrypy -y

生成自签名证书(生产环境请使用正规证书):

cd /etc/pki/tls/certs
make testcert
cd ../private
openssl rsa -in localhost.key -out localhost_nopass.key

编辑 /etc/salt/master,添加以下内容:

rest_cherrypy:
port: 8000
ssl_crt: /etc/pki/tls/certs/localhost.crt
ssl_key: /etc/pki/tls/private/localhost_nopass.key
disable_ssl: False

external_auth:
pam:
saltapi:
.*
'@wheel'
'@runner'
'@jobs'

创建系统用户用于 API 认证:

useradd saltapi
passwd saltapi # 设置密码,如 Salt@1234

重启服务:

systemctl restart salt-master salt-api

2. 获取 Token

首先通过用户名密码获取登录 Token:

curl -k https://192.168.1.10:8000/login \\
-H "Accept: application/json" \\
-d username='saltapi' \\
-d password='Salt@1234' \\
-d eauth='pam'

响应示例:

{
"return": [
{
"token": "a3f1c2b4d5e6f7g8h9i0j1k2l3m4n5o6p",
"expire": 1717012345.0,
"start": 1717008745.0,
"user": "saltapi",
"perms": [".*", "@wheel", "@runner", "@jobs"]
}
]
}

3. 调用远程命令

使用 Token 执行命令:

curl -k https://192.168.1.10:8000 \\
-H "Accept: application/json" \\
-H "X-Auth-Token: a3f1c2b4d5e6f7g8h9i0j1k2l3m4n5o6p" \\
-d client='local' \\
-d tgt='*' \\
-d fun='test.ping'


Java 集成示例 💻☕

下面我们编写一个 Java 类,封装 Salt-API 调用逻辑,实现:

  • 登录获取 Token;
  • 执行远程命令;
  • 应用 State 配置;
  • 查询 Job 结果。

依赖:我们将使用 HttpClient(Java 11+ 内置)和 Jackson 解析 JSON。

Maven 依赖

<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>

Java 代码

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

public class SaltApiClient {

private static final String SALT_API_URL = "https://192.168.1.10:8000";
private static final ObjectMapper MAPPER = new ObjectMapper();
private String authToken;
private final HttpClient client;

public SaltApiClient() {
this.client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
}

/**
* 登录 Salt-API,获取认证 Token
*/

public void login(String username, String password) throws Exception {
Map<String, String> formData = new HashMap<>();
formData.put("username", username);
formData.put("password", password);
formData.put("eauth", "pam");

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(SALT_API_URL + "/login"))
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(buildFormData(formData)))
.build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

if (response.statusCode() != 200) {
throw new RuntimeException("Login failed: " + response.body());
}

JsonNode root = MAPPER.readTree(response.body());
JsonNode tokenNode = root.path("return").get(0).path("token");
if (tokenNode.isMissingNode()) {
throw new RuntimeException("Token not found in response");
}
this.authToken = tokenNode.asText();
System.out.println("✅ Login successful. Token: " + this.authToken);
}

/**
* 执行远程命令
*/

public String runCommand(String target, String function, String... args) throws Exception {
Map<String, String> formData = new HashMap<>();
formData.put("client", "local");
formData.put("tgt", target);
formData.put("fun", function);
if (args.length > 0) {
formData.put("arg", String.join(",", args));
}

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(SALT_API_URL))
.header("Accept", "application/json")
.header("X-Auth-Token", this.authToken)
.POST(HttpRequest.BodyPublishers.ofString(buildFormData(formData)))
.build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

if (response.statusCode() != 200) {
throw new RuntimeException("Command execution failed: " + response.body());
}

return response.body();
}

/**
* 应用 State
*/

public String applyState(String target, String stateName) throws Exception {
return runCommand(target, "state.apply", stateName);
}

/**
* 查询 Job 状态
*/

public String getJobInfo(String jobId) throws Exception {
return runCommand("*", "jobs.lookup_jid", jobId);
}

/**
* 构建表单数据
*/

private String buildFormData(Map<String, String> data) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : data.entrySet()) {
if (sb.length() > 0) sb.append("&");
sb.append(entry.getKey()).append("=").append(entry.getValue());
}
return sb.toString();
}

// 测试方法
public static void main(String[] args) {
try {
SaltApiClient client = new SaltApiClient();
client.login("saltapi", "Salt@1234");

// 测试连通性
String pingResult = client.runCommand("*", "test.ping");
System.out.println("📡 Ping Result:\\n" + pingResult);

// 部署 Java 应用配置
String stateResult = client.applyState("*", "javaapp");
System.out.println("⚙️ State Apply Result:\\n" + stateResult);

// 查看磁盘空间
String diskResult = client.runCommand("*", "disk.usage");
System.out.println("💾 Disk Usage:\\n" + diskResult);

} catch (Exception e) {
e.printStackTrace();
}
}
}

输出示例

运行上述代码后,你将看到类似如下输出:

✅ Login successful. Token: a3f1c2b4d5e6f7g8h9i0j1k2l3m4n5o6p
📡 Ping Result:
{"return": [{"web01": true, "db01": true}]}
⚙️ State Apply Result:
{
"return": [
{
"web01": {
"file_|-java-app-config_|-/opt/myapp/config.properties_|-managed": {
"result": true,
"comment": "File /opt/myapp/config.properties updated"
}
},
"db01": {
"file_|-java-app-config_|-/opt/myapp/config.properties_|-managed": {
"result": true,
"comment": "File /opt/myapp/config.properties is in the correct state"
}
}
}
]
}
💾 Disk Usage:
{
"return": [
{
"web01": { "/": { "1K-blocks": "52403200", "used": "12345678", … } },
"db01": { "/": { "1K-blocks": "52403200", "used": "23456789", … } }
}
]
}


高级技巧:事件驱动与 Reactor 🔄

SaltStack 支持事件驱动架构。当 Minion 发生特定事件(如服务崩溃、文件变更、定时任务触发),Master 可自动执行预设动作。

1. 启用 Reactor

编辑 /etc/salt/master:

reactor:
'salt/minion/*/start':
/srv/reactor/start.sls
'myapp/deploy/finished':
/srv/reactor/notify.sls

2. 编写 Reactor 文件

/srv/reactor/start.sls:

sync_grains_on_start:
local.saltutil.sync_grains:
tgt: {{ data['id'] }}

highstate_on_first_boot:
local.state.apply:
tgt: {{ data['id'] }}
arg:
highstate

/srv/reactor/notify.sls:

send_slack_notification:
runner.slack.post_message:
channel: '#deployments'
message: '🎉 Deployment finished on {{ data["minion"] }}'
api_key: 'xoxb-your-slack-token'

3. 触发自定义事件

在 Minion 上执行:

salt-call event.send 'myapp/deploy/finished' '{"minion": "web01", "status": "success"}'

Master 会自动调用 notify.sls,发送 Slack 通知。


批量管理真实案例 🎯

案例一:紧急安全补丁推送

某日发现 OpenSSL 存在高危漏洞,需立即在所有服务器上升级。

# 1. 更新包缓存
salt '*' pkg.refresh_db

# 2. 升级 openssl
salt '*' pkg.upgrade openssl

# 3. 重启相关服务(如 nginx, sshd)
salt -G 'kernel:Linux' service.restart nginx
salt -G 'kernel:Linux' service.restart sshd

# 4. 验证版本
salt '*' cmd.run 'openssl version'

案例二:Java 应用灰度发布

假设你有 100 台应用服务器,希望分批发布新版本:

# 第一批:10 台
salt -L 'app01,app02,app03,app04,app05,app06,app07,app08,app09,app10' state.apply myapp.deploy

# 等待 5 分钟,检查健康状态
sleep 300
salt -L 'app01-app10' http.query http://localhost:8080/health method=GET

# 第二批:30 台
salt -C 'app[11-40]' state.apply myapp.deploy

# 最后一批:剩余 60 台
salt -C 'app[41-100]' state.apply myapp.deploy

案例三:自动化巡检报告

每天凌晨 2 点,收集所有服务器的关键指标:

# /srv/salt/_schedule/nightly_check.sls
nightly_system_check:
schedule.present:
function: state.apply
job_args:
monitoring.report
when: 2:00am
returner: smtp_return
return_kwargs:
recipients: ops@example.com
subject: '🌙 Nightly System Report'

对应的状态文件 /srv/salt/monitoring/report.sls:

collect_disk_usage:
module.run:
name: disk.usage
register: disk_info

collect_memory_info:
module.run:
name: status.meminfo
register: mem_info

generate_report_file:
file.managed:
name: /tmp/report.txt
contents: |
Generated at: {{ salt['cmd.run']('date') }}
=== DISK ===
{{ disk_info.result }}
=== MEMORY ===
Total: {{ mem_info.result.MemTotal }}
Free: {{ mem_info.result.MemFree }}

makedirs: True


性能优化与最佳实践 🚀

1. 使用 Syndic 实现横向扩展

当 Minion 数量超过 5000 台时,单 Master 可能成为瓶颈。此时可部署 Syndic 节点:

# 在 Syndic 节点安装
yum install salt-syndic -y
systemctl enable salt-syndic
systemctl start salt-syndic

Master 配置中启用 Syndic:

order_masters: True

2. 启用 Job Cache 清理

避免 Job 数据无限增长:

keep_jobs: 24 # 保留最近 24 小时的 Job

3. 使用 Batch 模式减少负载

对大量 Minion 执行命令时,使用 -b 参数分批执行:

salt -b 10 '*' pkg.upgrade # 每次只处理 10 台

4. 合理使用 Grains vs Pillar

  • ✅ Grains:用于筛选、分类,存储静态属性;
  • ✅ Pillar:用于个性化配置、敏感数据,动态下发。

5. 版本控制你的 States

强烈建议将 /srv/salt 和 /srv/pillar 纳入 Git 管理:

cd /srv/salt
git init
git add .
git commit -m "Initial commit"

配合 CI/CD 实现自动化测试与部署。


与其他工具对比 🆚

工具语言通信协议速度学习曲线适用场景
SaltStack Python ZeroMQ ⚡⚡⚡⚡⚡ 中等 大规模、复杂环境
Ansible Python SSH ⚡⚡ 简单 中小规模、无 Agent
Puppet Ruby HTTPS ⚡⚡ 陡峭 配置即代码、强一致性
Chef Ruby HTTPS ⚡⚡ 陡峭 DevOps 流程集成
Fabric Python SSH 简单 简单脚本、小团队

更多对比参考:https://stackshare.io/saltstack


故障排查与调试 🐞

1. Minion 无法连接 Master

  • 检查网络连通性:telnet 192.168.1.10 4505 4506
  • 检查防火墙:firewall-cmd –list-ports
  • 查看 Minion 日志:journalctl -u salt-minion -f

2. State 执行失败

增加调试输出:

salt '*' state.apply myapp -l debug

或在 SLS 文件中加入 test=True 预演:

salt '*' state.apply myapp test=True

3. API 返回 401 Unauthorized

  • 检查用户名密码是否正确;
  • 检查 external_auth 配置是否包含所需权限;
  • Token 是否过期(默认 12 小时)。

社区与资源 🌍

  • 官方文档:https://docs.saltproject.io
  • 公式仓库(Formula):https://github.com/saltstack-formulas (注:此处仅为示意,实际不提供 GitHub 链接)
  • Slack 社区:加入 SaltStack 官方 Slack 频道交流经验;
  • 视频教程:YouTube 上搜索 “SaltStack Tutorial” 有大量免费资源。

结语 🌈

SaltStack 不仅仅是一个“批量执行工具”,它是一个完整的基础设施自动化平台。通过本文的学习,你应该已经掌握了:

  • SaltStack 的基本架构与组件;
  • 如何安装部署 Master/Minion;
  • 编写 State 和 Pillar 实现配置管理;
  • 通过 REST API 与 Java 应用集成;
  • 应对真实运维场景的实战技巧。

无论你是 DevOps 工程师、系统管理员,还是 Java 开发者,掌握 SaltStack 都将极大提升你的自动化能力,让你从繁琐的手动操作中解放出来,专注于更高价值的工作。

现在就开始你的 SaltStack 之旅吧!🚀

“Automate Everything That Moves.” —— SaltStack 哲学


🙌 感谢你读到这里! 🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。 💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友! 💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿 🔔 关注我,不错过下一篇干货!我们下期再见!✨

赞(0)
未经允许不得转载:网硕互联帮助中心 » 【Linux】SaltStack 批量管理服务器
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!