Ansible与SaltStack配置管理工具的对比

描述

一、概述

1.1 背景介绍

在大规模服务器运维场景中,配置管理工具是基础设施自动化的核心组件。经过多年生产环境实践,Ansible和SaltStack作为两款主流的配置管理工具,各自占据了相当的市场份额。本文基于笔者在多个企业级项目中的实际运维经验,深入对比这两款工具的架构设计、性能表现、易用性和适用场景,为技术选型提供参考依据。

2015年至今,笔者先后在电商、金融、游戏等多个行业的生产环境中部署和使用过这两款工具。管理的服务器规模从几十台到上万台不等,积累了大量一手经验。Ansible以其无代理架构和低学习曲线著称,而SaltStack则以高性能和事件驱动架构见长。两者各有优劣,选择哪一款取决于具体的业务场景和团队技术栈。

1.2 技术特点

Ansible核心特点:

无代理架构(Agentless):通过SSH协议与目标主机通信,无需在被管理节点安装额外软件

声明式配置:使用YAML格式编写Playbook,描述期望达到的状态

幂等性设计:相同的任务多次执行结果一致,避免配置漂移

模块化架构:内置超过3000个模块,覆盖主流云平台和中间件

Push模式:从控制节点主动推送配置到被管理节点

SaltStack核心特点:

代理架构(Agent-based):在被管理节点部署minion进程,与master保持长连接

事件驱动:基于ZeroMQ消息队列实现异步通信,支持实时事件响应

高性能并发:得益于长连接设计,可同时管理数万台服务器

双模式支持:同时支持Push和Pull两种配置下发模式

远程执行引擎:除配置管理外,提供强大的远程命令执行能力

1.3 适用场景

Ansible适用场景:

场景类型 具体应用 推荐理由
中小规模集群 100-500台服务器 无代理架构降低运维复杂度
混合环境管理 物理机+虚拟机+容器 SSH普适性强,兼容性好
网络设备配置 交换机、路由器、防火墙 丰富的网络模块支持
CI/CD流水线 Jenkins、GitLab集成 命令行友好,易于集成
临时任务执行 Ad-hoc命令、应急操作 无需预装代理,即开即用

SaltStack适用场景:

场景类型 具体应用 推荐理由
大规模集群 1000+台服务器 长连接+ZeroMQ高性能通信
实时配置管理 频繁配置变更场景 事件驱动,响应速度快
复杂编排需求 多层级依赖关系 强大的状态依赖管理
云原生环境 Kubernetes、容器编排 Salt-SSH和Salt-Cloud模块
安全合规场景 实时监控配置状态 Beacon和Reactor机制

1.4 环境要求

组件 Ansible SaltStack 说明
控制节点操作系统 CentOS Stream 9/Rocky 9/Ubuntu 24.04 CentOS Stream 9/Rocky 9/Ubuntu 24.04 推荐使用LTS版本
Python版本 3.11+ 3.11+ 控制节点和被管理节点
工具版本 2.16+ 3006+ 当前稳定版本
内存要求(控制节点) 4GB+ 8GB+ Salt Master需要更多内存
网络要求 SSH 22端口 4505/4506端口 防火墙放行
被管理节点 仅需SSH和Python 需安装salt-minion 代理vs无代理

二、详细步骤

2.1 准备工作

2.1.1 实验环境规划

本次对比测试环境采用以下架构:

 

控制节点:
- 主机名:ops-master
- IP地址:192.168.100.10
- 操作系统:Rocky Linux 9.3
- 配置:4核CPU / 16GB内存 / 100GB SSD

被管理节点(10台测试机):
- 主机名:web-node-01 至 web-node-10
- IP地址:192.168.100.11 至 192.168.100.20
- 操作系统:Rocky Linux 9.3
- 配置:2核CPU / 4GB内存 / 50GB SSD

 

2.1.2 Ansible环境部署

步骤一:安装Ansible

 

# Rocky Linux 9 / CentOS Stream 9
sudo dnf install -y epel-release
sudo dnf install -y ansible-core ansible-collection-ansible-posix ansible-collection-community-general

# Ubuntu 24.04
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install -y ansible

# 验证安装
ansible --version
# ansible [core 2.16.3]
#   config file = /etc/ansible/ansible.cfg
#   configured module search path = ['/root/.ansible/plugins/modules']
#   ansible python module location = /usr/lib/python3.11/site-packages/ansible
#   executable location = /usr/bin/ansible
#   python version = 3.11.7

 

步骤二:配置SSH免密登录

 

# 生成SSH密钥对
ssh-keygen -t ed25519 -C "ansible@ops-master" -f ~/.ssh/ansible_key -N ""

# 批量分发公钥到被管理节点
for i in {11..20}; do
    ssh-copy-id -i ~/.ssh/ansible_key.pub root@192.168.100.$i
done

# 验证免密登录
ssh -i ~/.ssh/ansible_key root@192.168.100.11 "hostname"

 

步骤三:创建Ansible目录结构

 

# 创建项目目录
mkdir -p /opt/ansible/{inventory,playbooks,roles,group_vars,host_vars,files,templates}

# 目录结构说明
# /opt/ansible/
# ├── ansible.cfg          # 主配置文件
# ├── inventory/           # 主机清单
# │   ├── hosts.ini        # 静态清单
# │   └── dynamic.py       # 动态清单脚本
# ├── playbooks/           # 剧本文件
# ├── roles/               # 角色目录
# ├── group_vars/          # 组变量
# ├── host_vars/           # 主机变量
# ├── files/               # 静态文件
# └── templates/           # Jinja2模板

 

步骤四:配置ansible.cfg

 

# /opt/ansible/ansible.cfg
[defaults]
# 主机清单路径
inventory = ./inventory/hosts.ini
# 远程用户
remote_user = root
# SSH私钥路径
private_key_file = ~/.ssh/ansible_key
# 禁用主机密钥检查(生产环境建议开启)
host_key_checking = False
# 并发连接数
forks = 50
# 超时时间(秒)
timeout = 30
# 日志路径
log_path = /var/log/ansible/ansible.log
# 重试文件路径
retry_files_save_path = /tmp/ansible-retry
# 角色搜索路径
roles_path = ./roles:/etc/ansible/roles
# 回调插件,美化输出
stdout_callback = yaml
# 显示任务执行时间
callback_whitelist = timer, profile_tasks

[privilege_escalation]
# 提权设置
become = True
become_method = sudo
become_user = root
become_ask_pass = False

[ssh_connection]
# SSH参数优化
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no
# 管道模式,提升性能
pipelining = True
# 控制路径
control_path = /tmp/ansible-ssh-%%h-%%p-%%r

[persistent_connection]
# 持久连接超时
connect_timeout = 30
command_timeout = 30

 

2.1.3 SaltStack环境部署

步骤一:配置Salt仓库

 

# Rocky Linux 9 / CentOS Stream 9
sudo rpm --import https://repo.saltproject.io/salt/py3/redhat/9/x86_64/SALT-PROJECT-GPG-PUBKEY-2023.pub

sudo tee /etc/yum.repos.d/salt.repo << 'EOF'
[salt-repo]
name=Salt repo for RHEL/CentOS 9
baseurl=https://repo.saltproject.io/salt/py3/redhat/9/x86_64/3006
enabled=1
gpgcheck=1
gpgkey=https://repo.saltproject.io/salt/py3/redhat/9/x86_64/SALT-PROJECT-GPG-PUBKEY-2023.pub
EOF

# Ubuntu 24.04
curl -fsSL https://repo.saltproject.io/salt/py3/ubuntu/24.04/amd64/SALT-PROJECT-GPG-PUBKEY-2023.gpg | sudo gpg --dearmor -o /usr/share/keyrings/salt-archive-keyring.gpg

echo"deb [signed-by=/usr/share/keyrings/salt-archive-keyring.gpg] https://repo.saltproject.io/salt/py3/ubuntu/24.04/amd64/3006 noble main" | sudo tee /etc/apt/sources.list.d/salt.list

 

步骤二:安装Salt Master

 

# Rocky Linux 9
sudo dnf install -y salt-master salt-minion salt-ssh salt-api

# Ubuntu 24.04
sudo apt update
sudo apt install -y salt-master salt-minion salt-ssh salt-api

# 验证安装
salt --version
# salt 3006.5

 

步骤三:配置Salt Master

 

# /etc/salt/master
# 监听地址
interface:0.0.0.0

# 工作进程数
worker_threads:10

# 文件服务器后端
fileserver_backend:
-roots

# 文件根目录
file_roots:
base:
    -/srv/salt/base
dev:
    -/srv/salt/dev
prod:
    -/srv/salt/prod

# Pillar根目录
pillar_roots:
base:
    -/srv/pillar/base
prod:
    -/srv/pillar/prod

# 自动接受minion密钥(生产环境不建议)
auto_accept:False

# 日志级别
log_level:info
log_file:/var/log/salt/master

# 作业缓存
job_cache:True
keep_jobs:24

# 事件返回器
event_return:mysql

# 超时设置
timeout:30
gather_job_timeout:30

# 启用Reactor
reactor:
-'salt/minion/*/start':
    -/srv/reactor/minion_start.sls

# REST API配置
rest_cherrypy:
port:8000
ssl_crt:/etc/pki/tls/certs/salt-api.crt
ssl_key:/etc/pki/tls/private/salt-api.key

 

步骤四:创建Salt目录结构

 

# 创建目录结构
sudo mkdir -p /srv/{salt,pillar,reactor}/{base,dev,prod}
sudo mkdir -p /var/log/salt

# 目录结构说明
# /srv/
# ├── salt/                # 状态文件目录
# │   ├── base/            # 基础环境
# │   │   ├── top.sls      # 顶层文件
# │   │   └── common/      # 通用配置
# │   ├── dev/             # 开发环境
# │   └── prod/            # 生产环境
# ├── pillar/              # Pillar数据目录
# │   ├── base/
# │   │   └── top.sls
# │   └── prod/
# └── reactor/             # Reactor配置
#     └── base/

 

步骤五:启动Salt Master

 

# 启动服务
sudo systemctl enable --now salt-master
sudo systemctl enable --now salt-api

# 检查服务状态
sudo systemctl status salt-master
sudo salt-master --version

# 开放防火墙端口
sudo firewall-cmd --permanent --add-port=4505/tcp
sudo firewall-cmd --permanent --add-port=4506/tcp
sudo firewall-cmd --permanent --add-port=8000/tcp
sudo firewall-cmd --reload

 

步骤六:部署Salt Minion到被管理节点

 

# 在被管理节点执行
# 安装salt-minion
sudo dnf install -y salt-minion

# 配置minion
sudo tee /etc/salt/minion << 'EOF'
# Master地址
master: 192.168.100.10

# Minion ID(默认使用主机名)
id: web-node-01

# 日志级别
log_level: info

# Grains配置
grains:
  roles:
    - webserver
  environment: production
  datacenter: dc1
EOF

# 启动minion
sudo systemctl enable --now salt-minion

 

步骤七:接受Minion密钥

 

# 在Master上查看待接受的密钥
sudo salt-key -L
# Accepted Keys:
# Denied Keys:
# Unaccepted Keys:
# web-node-01
# web-node-02
# ...
# Rejected Keys:

# 接受所有密钥
sudo salt-key -A -y

# 验证连接
sudo salt '*' test.ping
# web-node-01:
#     True
# web-node-02:
#     True

 

2.2 核心配置

2.2.1 Ansible主机清单配置

静态清单示例:

 

# /opt/ansible/inventory/hosts.ini
[webservers]
web-node-01 ansible_host=192.168.100.11
web-node-02 ansible_host=192.168.100.12
web-node-03 ansible_host=192.168.100.13
web-node-04 ansible_host=192.168.100.14
web-node-05 ansible_host=192.168.100.15

[dbservers]
db-node-01 ansible_host=192.168.100.16
db-node-02 ansible_host=192.168.100.17

[cacheservers]
cache-node-01 ansible_host=192.168.100.18
cache-node-02 ansible_host=192.168.100.19
cache-node-03 ansible_host=192.168.100.20

[webservers:vars]
nginx_port=80
app_env=production

[all:vars]
ansible_python_interpreter=/usr/bin/python3
ansible_ssh_private_key_file=~/.ssh/ansible_key

 

组变量配置:

 

# /opt/ansible/group_vars/webservers.yml
---
# Nginx配置
nginx_version:"1.24"
nginx_worker_processes:auto
nginx_worker_connections:65535
nginx_keepalive_timeout:65
nginx_client_max_body_size:"100m"

# 应用配置
app_name:"myapp"
app_port:8080
app_log_level:"info"

# 系统优化参数
sysctl_settings:
net.core.somaxconn:65535
net.ipv4.tcp_max_syn_backlog:65535
net.ipv4.ip_local_port_range:"1024 65535"
net.ipv4.tcp_tw_reuse:1
vm.swappiness:10

 

2.2.2 SaltStack状态配置

Top文件配置:

 

# /srv/salt/base/top.sls
base:
'*':
    -common.packages
    -common.users
    -common.sshd
    -common.ntp

'web-node-*':
    -nginx
    -php
    -app

'db-node-*':
    -mysql
    -backup

'cache-node-*':
    -redis
    -sentinel

 

通用包管理状态:

 

# /srv/salt/base/common/packages.sls
# 基础软件包安装
base_packages:
pkg.installed:
    -pkgs:
      -vim-enhanced
      -wget
      -curl
      -htop
      -iotop
      -net-tools
      -bind-utils
      -lsof
      -strace
      -tcpdump
      -sysstat
      -tree

# 禁用不需要的服务
disable_postfix:
service.dead:
    -name:postfix
    -enable:False

 

Pillar配置:

 

# /srv/pillar/base/top.sls
base:
'*':
    -common

'web-node-*':
    -nginx
    -app

'db-node-*':
    -mysql

# /srv/pillar/base/common.sls
timezone:Asia/Shanghai
dns_servers:
-223.5.5.5
-223.6.6.6
ntp_servers:
-ntp.aliyun.com
-ntp1.aliyun.com

 

2.3 启动和验证

2.3.1 Ansible验证测试

 

# 测试连通性
ansible all -m ping

# 收集系统信息
ansible all -m setup -a "filter=ansible_distribution*"

# 执行ad-hoc命令
ansible webservers -m shell -a "uptime"

# 运行playbook
ansible-playbook playbooks/site.yml --check --diff

# 限制执行范围
ansible-playbook playbooks/site.yml --limit web-node-01

# 指定标签执行
ansible-playbook playbooks/site.yml --tags "nginx,config"

 

2.3.2 SaltStack验证测试

 

# 测试连通性
salt '*' test.ping

# 收集Grains信息
salt '*' grains.items

# 执行远程命令
salt 'web-node-*' cmd.run 'uptime'

# 应用状态
salt '*' state.apply

# 测试模式运行
salt '*' state.apply test=True

# 高状态应用
salt '*' state.highstate

# 刷新Pillar数据
salt '*' saltutil.refresh_pillar

 

三、示例代码和配置

3.1 完整配置示例

3.1.1 Ansible完整Playbook示例

 

# /opt/ansible/playbooks/deploy_webserver.yml
---
-name:部署Web服务器
hosts:webservers
become:yes
gather_facts:yes

vars:
    nginx_version:"1.24"
    php_version:"8.2"
    app_name:"myapp"
    deploy_user:"deploy"
    deploy_group:"deploy"
    app_root:"/var/www/{{ app_name }}"

pre_tasks:
    -name:更新系统软件包缓存
      ansible.builtin.dnf:
        update_cache:yes
      when:ansible_os_family=="RedHat"

    -name:检查系统内存
      ansible.builtin.fail:
        msg:"系统内存不足,需要至少2GB"
      when:ansible_memtotal_mb<2048

roles:
    -role:common
      tags:common
    -role:nginx
      tags:nginx
    -role:php-fpm
      tags:php
    -role:app
      tags:app

tasks:
    -name:创建部署用户
      ansible.builtin.user:
        name:"{{ deploy_user }}"
        group:"{{ deploy_group }}"
        shell:/bin/bash
        home:"/home/{{ deploy_user }}"
        create_home:yes
        state:present

    -name:配置sudo权限
      ansible.builtin.lineinfile:
        path:/etc/sudoers.d/{{deploy_user}}
        line:"{{ deploy_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx, /usr/bin/systemctl reload nginx"
        create:yes
        mode:'0440'
        validate:'visudo -cf %s'

    -name:创建应用目录结构
      ansible.builtin.file:
        path:"{{ item }}"
        state:directory
        owner:"{{ deploy_user }}"
        group:"{{ deploy_group }}"
        mode:'0755'
      loop:
        -"{{ app_root }}"
        -"{{ app_root }}/releases"
        -"{{ app_root }}/shared"
        -"{{ app_root }}/shared/logs"
        -"{{ app_root }}/shared/config"

    -name:部署Nginx虚拟主机配置
      ansible.builtin.template:
        src:templates/nginx/vhost.conf.j2
        dest:/etc/nginx/conf.d/{{app_name}}.conf
        owner:root
        group:root
        mode:'0644'
        backup:yes
      notify:重载Nginx配置

    -name:配置PHP-FPM池
      ansible.builtin.template:
        src:templates/php/pool.conf.j2
        dest:/etc/php-fpm.d/{{app_name}}.conf
        owner:root
        group:root
        mode:'0644'
      notify:重启PHP-FPM服务

    -name:配置系统内核参数
      ansible.posix.sysctl:
        name:"{{ item.key }}"
        value:"{{ item.value }}"
        sysctl_file:/etc/sysctl.d/99-app-tuning.conf
        reload:yes
        state:present
      loop:"{{ sysctl_settings | dict2items }}"

    -name:配置文件描述符限制
      ansible.builtin.template:
        src:templates/system/limits.conf.j2
        dest:/etc/security/limits.d/99-app.conf
        owner:root
        group:root
        mode:'0644'

    -name:部署日志切割配置
      ansible.builtin.template:
        src:templates/logrotate/app.j2
        dest:/etc/logrotate.d/{{app_name}}
        owner:root
        group:root
        mode:'0644'

handlers:
    -name:重载Nginx配置
      ansible.builtin.systemd:
        name:nginx
        state:reloaded

    -name:重启PHP-FPM服务
      ansible.builtin.systemd:
        name:php-fpm
        state:restarted

post_tasks:
    -name:健康检查-验证Nginx运行状态
      ansible.builtin.uri:
        url:"http://localhost/health"
        method:GET
        status_code:200
        timeout:10
      register:health_check
      retries:3
      delay:5
      until:health_check.status==200

    -name:发送部署通知
      ansible.builtin.uri:
        url:"{{ notification_webhook }}"
        method:POST
        body_format:json
        body:
          text:"服务器 {{ inventory_hostname }} 部署完成"
          status:"success"
      when:notification_webhookisdefined
      ignore_errors:yes

 

Nginx虚拟主机模板:

 

{# /opt/ansible/templates/nginx/vhost.conf.j2 #}
# {{ ansible_managed }}
# 应用名称: {{ app_name }}
# 生成时间: {{ ansible_date_time.iso8601 }}

upstream {{ app_name }}_backend {
    least_conn;
{% for server in upstream_servers | default(['127.0.0.1:9000']) %}
    server {{ server }} weight=1 max_fails=3 fail_timeout=30s;
{% endfor %}
    keepalive 32;
}

server {
    listen 80;
    listen [::]:80;
    server_name {{ server_name | default('_') }};

    root {{ app_root }}/current/public;
    index index.php index.html;

    # 访问日志
    access_log /var/log/nginx/{{ app_name }}_access.log main buffer=64k flush=5s;
    error_log /var/log/nginx/{{ app_name }}_error.log warn;

    # 安全头
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;

    # 静态文件缓存
    location ~* .(jpg|jpeg|png|gif|ico|css|js|pdf|txt)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # 健康检查端点
    location /health {
        access_log off;
        return 200 'OK';
        add_header Content-Type text/plain;
    }

    # PHP处理
    location ~ .php$ {
        try_files $uri =404;
        fastcgi_pass {{ app_name }}_backend;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

        # FastCGI优化
        fastcgi_connect_timeout 60s;
        fastcgi_send_timeout 60s;
        fastcgi_read_timeout 60s;
        fastcgi_buffer_size 64k;
        fastcgi_buffers 16 64k;
    }

    # 默认路由
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # 禁止访问隐藏文件
    location ~ /. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

 

3.1.2 SaltStack完整状态示例

 

# /srv/salt/base/nginx/init.sls
{%setnginx=salt['pillar.get']('nginx',{})%}
{%setnginx_version=nginx.get('version','1.24')%}
{%setworker_processes=nginx.get('worker_processes','auto')%}
{%setworker_connections=nginx.get('worker_connections',65535)%}

# 安装Nginx
nginx_package:
pkg.installed:
    -name:nginx
    -version:"{{ nginx_version }}*"

# 创建必要目录
nginx_directories:
file.directory:
    -names:
      -/etc/nginx/conf.d
      -/etc/nginx/ssl
      -/var/log/nginx
      -/var/cache/nginx
    -user:root
    -group:root
    -mode:755
    -makedirs:True

# 主配置文件
nginx_main_config:
file.managed:
    -name:/etc/nginx/nginx.conf
    -source:salt://nginx/files/nginx.conf.jinja
    -template:jinja
    -user:root
    -group:root
    -mode:644
    -context:
        worker_processes:{{worker_processes}}
        worker_connections:{{worker_connections}}
    -require:
      -pkg:nginx_package
    -watch_in:
      -service:nginx_service

# 默认虚拟主机配置
nginx_default_vhost:
file.managed:
    -name:/etc/nginx/conf.d/default.conf
    -source:salt://nginx/files/default.conf.jinja
    -template:jinja
    -user:root
    -group:root
    -mode:644
    -require:
      -file:nginx_directories
    -watch_in:
      -service:nginx_service

# 删除默认欢迎页
nginx_remove_default_html:
file.absent:
    -name:/usr/share/nginx/html/index.html
    -require:
      -pkg:nginx_package

# 系统内核优化
nginx_sysctl:
sysctl.present:
    -names:
      -net.core.somaxconn:
        -value:65535
      -net.ipv4.tcp_max_syn_backlog:
        -value:65535
      -net.ipv4.ip_local_port_range:
        -value:"1024 65535"
      -net.ipv4.tcp_tw_reuse:
        -value:1

# 文件描述符限制
nginx_limits:
file.managed:
    -name:/etc/security/limits.d/99-nginx.conf
    -contents:|
        nginx soft nofile 65535
        nginx hard nofile 65535
        nginx soft nproc 65535
        nginx hard nproc 65535
    -user:root
    -group:root
    -mode:644

# 日志切割配置
nginx_logrotate:
file.managed:
    -name:/etc/logrotate.d/nginx
    -source:salt://nginx/files/logrotate.conf
    -user:root
    -group:root
    -mode:644

# 开放防火墙端口
nginx_firewall:
firewalld.present:
    -name:public
    -services:
      -http
      -https
    -prune_services:False

# 启动Nginx服务
nginx_service:
service.running:
    -name:nginx
    -enable:True
    -reload:True
    -require:
      -pkg:nginx_package
      -file:nginx_main_config

 

Nginx主配置模板:

 

{# /srv/salt/base/nginx/files/nginx.conf.jinja #}
# Salt管理的配置文件 - 请勿手动修改
# 生成时间: {{ salt['status.time']('%Y-%m-%d %H:%M:%S') }}
# Minion ID: {{ grains['id'] }}

user nginx;
worker_processes {{ worker_processes }};
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;

worker_rlimit_nofile 65535;

events {
    use epoll;
    worker_connections {{ worker_connections }};
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # 日志格式
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for" '
                    'rt=$request_time uct="$upstream_connect_time" '
                    'uht="$upstream_header_time" urt="$upstream_response_time"';

    log_format json escape=json '{'
        '"time":"$time_iso8601",'
        '"remote_addr":"$remote_addr",'
        '"request":"$request",'
        '"status":$status,'
        '"body_bytes_sent":$body_bytes_sent,'
        '"request_time":$request_time,'
        '"upstream_response_time":"$upstream_response_time"'
    '}';

    access_log /var/log/nginx/access.log main buffer=64k flush=5s;

    # 性能优化
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 4096;

    # Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 5;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript
               text/xml application/xml application/xml+rss text/javascript;

    # 安全配置
    server_tokens off;

    # 请求体限制
    client_max_body_size 100m;
    client_body_buffer_size 128k;

    # 代理缓冲配置
    proxy_buffer_size 64k;
    proxy_buffers 16 64k;
    proxy_busy_buffers_size 128k;

    # 包含虚拟主机配置
    include /etc/nginx/conf.d/*.conf;
}

 

3.2 实际应用案例

3.2.1 案例一:多环境配置管理

场景描述: 管理开发、测试、生产三套环境,每套环境配置不同,需要实现配置隔离和统一管理。

Ansible实现方案:

 

# 目录结构
# /opt/ansible/
# ├── inventories/
# │   ├── dev/
# │   │   ├── hosts.ini
# │   │   └── group_vars/
# │   │       └── all.yml
# │   ├── staging/
# │   │   ├── hosts.ini
# │   │   └── group_vars/
# │   │       └── all.yml
# │   └── prod/
# │       ├── hosts.ini
# │       └── group_vars/
# │           └── all.yml

# /opt/ansible/inventories/dev/group_vars/all.yml
---
env_name:development
app_debug:true
log_level:debug
db_host:dev-db.internal
redis_host:dev-redis.internal
api_endpoint:https://api-dev.example.com

# /opt/ansible/inventories/prod/group_vars/all.yml
---
env_name:production
app_debug:false
log_level:warning
db_host:prod-db.internal
redis_host:prod-redis.internal
api_endpoint:https://api.example.com

# 执行命令
# 部署到开发环境
ansible-playbook-iinventories/devplaybooks/deploy.yml

# 部署到生产环境
ansible-playbook-iinventories/prodplaybooks/deploy.yml

 

SaltStack实现方案:

 

# /srv/pillar/base/top.sls
base:
'*':
    -common

dev:
'env:dev':
    -match:grain
    -dev/settings

staging:
'env:staging':
    -match:grain
    -staging/settings

prod:
'env:prod':
    -match:grain
    -prod/settings

# /srv/pillar/dev/settings.sls
env_name:development
app_debug:true
log_level:debug
db_host:dev-db.internal
redis_host:dev-redis.internal
api_endpoint:https://api-dev.example.com

# /srv/pillar/prod/settings.sls
env_name:production
app_debug:false
log_level:warning
db_host:prod-db.internal
redis_host:prod-redis.internal
api_endpoint:https://api.example.com

# 执行命令
# 刷新Pillar
salt'*'saltutil.refresh_pillar

# 应用到开发环境
salt-G'env:dev'state.apply

# 应用到生产环境
salt-G'env:prod'state.apply

 

3.2.2 案例二:滚动更新部署

场景描述: Web集群有20台服务器,需要实现滚动更新,每次更新5台,确保服务不中断。

Ansible实现方案:

 

# /opt/ansible/playbooks/rolling_update.yml
---
-name:滚动更新Web应用
hosts:webservers
become:yes
serial:5# 每批5台
max_fail_percentage:25# 最大失败比例25%

pre_tasks:
    -name:从负载均衡器摘除当前节点
      ansible.builtin.uri:
        url:"http://{{ lb_api }}/api/v1/backends/{{ inventory_hostname }}/disable"
        method:POST
        headers:
          Authorization:"Bearer {{ lb_api_token }}"
        status_code:200
      delegate_to:localhost

    -name:等待当前连接处理完毕
      ansible.builtin.wait_for:
        timeout:30

    -name:健康检查-确认节点已摘除
      ansible.builtin.uri:
        url:"http://{{ lb_api }}/api/v1/backends/{{ inventory_hostname }}/status"
        method:GET
      register:lb_status
      until:lb_status.json.status=="disabled"
      retries:6
      delay:5
      delegate_to:localhost

tasks:
    -name:停止应用服务
      ansible.builtin.systemd:
        name:"{{ app_name }}"
        state:stopped

    -name:备份当前版本
      ansible.builtin.shell:|
        if [ -L {{ app_root }}/current ]; then
          cp -a $(readlink -f {{ app_root }}/current) {{ app_root }}/rollback/$(date +%Y%m%d%H%M%S)
        fi
      args:
        executable:/bin/bash

    -name:清理旧版本(保留最近5个)
      ansible.builtin.shell:|
        cd {{ app_root }}/releases && ls -t | tail -n +6 | xargs -r rm -rf
      args:
        executable:/bin/bash

    -name:下载新版本
      ansible.builtin.unarchive:
        src:"{{ artifact_url }}/{{ app_name }}-{{ app_version }}.tar.gz"
        dest:"{{ app_root }}/releases/"
        remote_src:yes
        owner:"{{ deploy_user }}"
        group:"{{ deploy_group }}"

    -name:更新符号链接
      ansible.builtin.file:
        src:"{{ app_root }}/releases/{{ app_version }}"
        dest:"{{ app_root }}/current"
        state:link
        owner:"{{ deploy_user }}"
        group:"{{ deploy_group }}"

    -name:运行数据库迁移
      ansible.builtin.shell:|
        cd {{ app_root }}/current && ./bin/migrate
      args:
        executable:/bin/bash
      run_once:true
      delegate_to:"{{ groups['webservers'][0] }}"

    -name:启动应用服务
      ansible.builtin.systemd:
        name:"{{ app_name }}"
        state:started

    -name:等待应用启动
      ansible.builtin.uri:
        url:"http://localhost:{{ app_port }}/health"
        status_code:200
      register:health_result
      until:health_result.status==200
      retries:12
      delay:5

post_tasks:
    -name:将节点重新加入负载均衡器
      ansible.builtin.uri:
        url:"http://{{ lb_api }}/api/v1/backends/{{ inventory_hostname }}/enable"
        method:POST
        headers:
          Authorization:"Bearer {{ lb_api_token }}"
        status_code:200
      delegate_to:localhost

    -name:验证节点已恢复服务
      ansible.builtin.uri:
        url:"http://{{ lb_api }}/api/v1/backends/{{ inventory_hostname }}/status"
        method:GET
      register:lb_status
      until:lb_status.json.status=="enabled"
      retries:6
      delay:5
      delegate_to:localhost

 

SaltStack实现方案:

 

# /srv/salt/base/app/rolling_update.sls
{%setbatch_size=salt['pillar.get']('deploy:batch_size',5)%}
{%setapp_version=salt['pillar.get']('deploy:version')%}
{%setapp_root=salt['pillar.get']('app:root','/var/www/app')%}

# 从负载均衡器摘除
remove_from_lb:
http.query:
    -name:http://{{pillar['lb_api']}}/api/v1/backends/{{grains['id']}}/disable
    -method:POST
    -header_dict:
        Authorization:"Bearer {{ pillar['lb_api_token'] }}"

# 等待连接排空
wait_for_drain:
module.run:
    -name:test.sleep
    -length:30
    -require:
      -http:remove_from_lb

# 停止应用
stop_app:
service.dead:
    -name:{{pillar['app_name']}}
    -require:
      -module:wait_for_drain

# 备份当前版本
backup_current:
cmd.run:
    -name:|
        if [ -L {{ app_root }}/current ]; then
          cp -a $(readlink -f {{ app_root }}/current) {{ app_root }}/rollback/$(date +%Y%m%d%H%M%S)
        fi
    -require:
      -service:stop_app

# 下载新版本
download_release:
archive.extracted:
    -name:{{app_root}}/releases/
    -source:{{pillar['artifact_url']}}/{{pillar['app_name']}}-{{app_version}}.tar.gz
    -user:{{pillar['deploy_user']}}
    -group:{{pillar['deploy_group']}}
    -require:
      -cmd:backup_current

# 更新符号链接
update_symlink:
file.symlink:
    -name:{{app_root}}/current
    -target:{{app_root}}/releases/{{app_version}}
    -user:{{pillar['deploy_user']}}
    -group:{{pillar['deploy_group']}}
    -require:
      -archive:download_release

# 启动应用
start_app:
service.running:
    -name:{{pillar['app_name']}}
    -require:
      -file:update_symlink

# 健康检查
health_check:
http.wait_for_successful_query:
    -name:http://localhost:{{pillar['app_port']}}/health
    -status:200
    -wait_for:60
    -require:
      -service:start_app

# 重新加入负载均衡器
add_to_lb:
http.query:
    -name:http://{{pillar['lb_api']}}/api/v1/backends/{{grains['id']}}/enable
    -method:POST
    -header_dict:
        Authorization:"Bearer {{ pillar['lb_api_token'] }}"
    -require:
      -http:health_check
# Salt滚动更新执行命令
salt 'web-node-*' state.apply app.rolling_update batch=5 batch_wait=30

 

3.2.3 案例三:配置合规性检查

场景描述: 定期检查所有服务器的安全配置是否符合基线要求,生成合规报告。

Ansible实现方案:

 

# /opt/ansible/playbooks/compliance_check.yml
---
-name:安全合规性检查
hosts:all
become:yes
gather_facts:yes

vars:
    compliance_rules:
      sshd:
        PermitRootLogin:"no"
        PasswordAuthentication:"no"
        X11Forwarding:"no"
        MaxAuthTries:"3"
        Protocol:"2"
      password_policy:
        PASS_MAX_DAYS:"90"
        PASS_MIN_DAYS:"7"
        PASS_WARN_AGE:"14"

tasks:
    -name:检查SSH配置合规性
      ansible.builtin.shell:|
        grep -E "^{{ item.key }}" /etc/ssh/sshd_config | awk '{print $2}'
      register:sshd_check
      loop:"{{ compliance_rules.sshd | dict2items }}"
      changed_when:false

    -name:记录SSH配置检查结果
      ansible.builtin.set_fact:
        sshd_compliance:"{{ sshd_compliance | default({}) | combine({item.item.key: {'expected': item.item.value, 'actual': item.stdout, 'compliant': item.stdout == item.item.value}}) }}"
      loop:"{{ sshd_check.results }}"

    -name:检查密码策略合规性
      ansible.builtin.shell:|
        grep -E "^{{ item.key }}" /etc/login.defs | awk '{print $2}'
      register:password_check
      loop:"{{ compliance_rules.password_policy | dict2items }}"
      changed_when:false

    -name:检查防火墙状态
      ansible.builtin.systemd:
        name:firewalld
      register:firewall_status

    -name:检查SELinux状态
      ansible.builtin.command:getenforce
      register:selinux_status
      changed_when:false

    -name:检查未授权SUID文件
      ansible.builtin.shell:|
        find /usr /bin /sbin -perm -4000 2>/dev/null | wc -l
      register:suid_files
      changed_when:false

    -name:检查空密码账户
      ansible.builtin.shell:|
        awk -F: '($2 == "" ) { print $1 }' /etc/shadow
      register:empty_password
      changed_when:false

    -name:生成合规性报告
      ansible.builtin.set_fact:
        compliance_report:
          host:"{{ inventory_hostname }}"
          timestamp:"{{ ansible_date_time.iso8601 }}"
          ssh_config:"{{ sshd_compliance }}"
          firewall_enabled:"{{ firewall_status.status.ActiveState == 'active' }}"
          selinux_enforcing:"{{ selinux_status.stdout == 'Enforcing' }}"
          suid_file_count:"{{ suid_files.stdout }}"
          empty_password_users:"{{ empty_password.stdout_lines }}"

    -name:保存合规性报告到本地
      ansible.builtin.copy:
        content:"{{ compliance_report | to_nice_json }}"
        dest:"/tmp/compliance/{{ inventory_hostname }}_{{ ansible_date_time.date }}.json"
      delegate_to:localhost

    -name:汇总合规性报告
      ansible.builtin.template:
        src:templates/compliance_summary.html.j2
        dest:"/tmp/compliance/summary_{{ ansible_date_time.date }}.html"
      delegate_to:localhost
      run_once:true

 

四、最佳实践和注意事项

4.1 最佳实践

4.1.1 性能优化

Ansible性能优化:

 

# /opt/ansible/ansible.cfg 性能优化配置

[defaults]
# 增加并发数,根据控制节点性能调整
forks = 100

# 启用SSH持久连接
[ssh_connection]
# ControlMaster复用SSH连接
ssh_args = -C -o ControlMaster=auto -o ControlPersist=600s -o PreferredAuthentications=publickey

# 开启管道模式,减少SSH连接开销
pipelining = True

# 使用更快的SCP传输
transfer_method = piped
scp_if_ssh = smart
# Playbook层面优化
---
-name:优化后的Playbook
hosts:all
# 关闭自动收集facts,按需手动收集
gather_facts:no

pre_tasks:
    # 仅收集必要的facts
    -name:收集最小化系统信息
      ansible.builtin.setup:
        gather_subset:
          -min
          -network
        gather_timeout:10

tasks:
    # 使用free策略提升并发效率
    -name:并行执行任务
      # ...

# 策略配置
# strategy: free  # 自由策略,任务完成即继续
# strategy: linear  # 线性策略(默认),等待所有主机完成
# 使用Mitogen加速Ansible执行(可选)
pip install mitogen

# ansible.cfg配置
# [defaults]
# strategy_plugins = /path/to/mitogen/ansible_mitogen/plugins/strategy
# strategy = mitogen_linear

 

SaltStack性能优化:

 

# /etc/salt/master 性能优化配置

# 增加工作线程数
worker_threads:20

# ZeroMQ高水位标记
zmq_hwm:10000

# 批量执行返回超时
gather_job_timeout:60

# 事件发布缓存
event_publisher_niceness:0

# 启用缓存
cache:localfs
# 缓存过期时间(秒)
cachedir:/var/cache/salt
cache_job_timeout:3600

# Pillar缓存
pillar_cache:True
pillar_cache_ttl:3600

# Git文件服务器缓存
gitfs_update_interval:60
gitfs_env_whitelist:
-base
-prod

# 返回数据压缩
serial:msgpack
# 使用salt-ssh进行无代理连接测试
salt-ssh '*' test.ping --roster-file=/etc/salt/roster

# 批量执行优化
salt '*' state.apply batch=100 batch_wait=5

# 异步执行长时间任务
salt '*' state.apply --async
salt-run jobs.lookup_jid 

 

4.1.2 安全加固

Ansible安全加固:

 

# 使用Ansible Vault加密敏感数据
# 创建加密文件
# ansible-vault create group_vars/all/vault.yml

# group_vars/all/vault.yml(加密后)
vault_db_password:"SuperSecretPassword123!"
vault_api_key:"sk-xxxxxxxxxxxxxxxxxxxx"
vault_ssl_private_key:|
  -----BEGIN PRIVATE KEY-----
  MIIEvgIBADANBgkqhkiG9w0BAQE...
  -----END PRIVATE KEY-----

# group_vars/all/vars.yml(引用加密变量)
db_password:"{{ vault_db_password }}"
api_key:"{{ vault_api_key }}"
# ansible.cfg 安全配置
[defaults]
# 启用主机密钥检查
host_key_checking = True

# 日志脱敏
no_log = True  # 默认隐藏敏感输出

# 禁用cowsay
nocows = True

[privilege_escalation]
# 使用sudo而非su
become_method = sudo
# 要求sudo密码
become_ask_pass = True
# Playbook安全实践
---
-name:安全配置示例
hosts:all
become:yes

tasks:
    -name:创建数据库用户(隐藏密码输出)
      community.mysql.mysql_user:
        name:app_user
        password:"{{ vault_db_password }}"
        priv:'app_db.*:ALL'
        state:present
      no_log:true# 关键:隐藏任务输出

    -name:部署包含敏感信息的配置文件
      ansible.builtin.template:
        src:config.yml.j2
        dest:/etc/app/config.yml
        owner:app
        group:app
        mode:'0600'# 仅所有者可读写
      no_log:true

 

SaltStack安全加固:

 

# /etc/salt/master 安全配置

# 禁用自动接受密钥
auto_accept:False

# 启用密钥认证
require_auth:True

# 限制命令执行
publisher_acl:
# 运维组可执行所有命令
ops_group:
    -'*'
# 开发组只能执行特定命令
dev_group:
    -'web-*':
      -test.*
      -status.*
      -state.apply
    -'db-*':
      -test.ping

# 启用审计日志
external_auth:
pam:
    ops_group%:
      -'*'
    dev_group%:
      -'web-*':
        -test.ping
        -state.apply

# SSL/TLS配置
ssl:
keyfile:/etc/salt/pki/master/master.pem
certfile:/etc/salt/pki/master/master.crt

# 启用客户端证书验证
verify_master_pubkey_sign:True
# 使用SaltStack Pillar保护敏感数据
# /srv/pillar/base/secrets.sls
#!jinja|gpg
db_password: |
  -----BEGIN PGP MESSAGE-----
  hQEMA...
  -----END PGP MESSAGE-----

api_token: |
  -----BEGIN PGP MESSAGE-----
  hQEMA...
  -----END PGP MESSAGE-----

# 使用GPG加密Pillar数据
# gpg --encrypt --armor --recipient ops@example.com secret.txt

 

4.1.3 高可用架构

Ansible高可用架构:

 

                    ┌─────────────────┐
                    │   GitLab/GitHub │
                    │  (Playbook仓库) │
                    └────────┬────────┘
                             │
              ┌──────────────┼──────────────┐
              │              │              │
              ▼              ▼              ▼
     ┌────────────┐  ┌────────────┐  ┌────────────┐
     │  Ansible   │  │  Ansible   │  │  Ansible   │
     │  Node 01   │  │  Node 02   │  │  Node 03   │
     └──────┬─────┘  └──────┬─────┘  └──────┬─────┘
            │               │               │
            └───────────────┼───────────────┘
                            │
                    ┌───────┴───────┐
                    │    SSH Pool   │
                    └───────┬───────┘
                            │
        ┌───────────────────┼───────────────────┐
        ▼                   ▼                   ▼
   ┌─────────┐         ┌─────────┐         ┌─────────┐
   │ Target  │         │ Target  │         │ Target  │
   │ Group A │         │ Group B │         │ Group C │
   └─────────┘         └─────────┘         └─────────┘
# 使用AWX/Tower实现企业级高可用
# AWX Kubernetes部署配置
# awx-operator/awx.yml
---
apiVersion:awx.ansible.com/v1beta1
kind:AWX
metadata:
name:awx
namespace:awx
spec:
replicas:3
admin_user:admin
admin_password_secret:awx-admin-password

postgres_configuration_secret:awx-postgres-config
postgres_storage_class:managed-premium
postgres_storage_requirements:
    requests:
      storage:50Gi

# 任务执行Pod配置
task_resource_requirements:
    requests:
      cpu:500m
      memory:1Gi
    limits:
      cpu:2000m
      memory:4Gi

 

SaltStack高可用架构:

 

                    ┌─────────────────┐
                    │   Salt Syndic   │
                    │  (Master of     │
                    │   Masters)      │
                    └────────┬────────┘
                             │
              ┌──────────────┼──────────────┐
              │              │              │
              ▼              ▼              ▼
     ┌────────────┐  ┌────────────┐  ┌────────────┐
     │   Master   │  │   Master   │  │   Master   │
     │    DC-A    │  │    DC-B    │  │    DC-C    │
     └──────┬─────┘  └──────┬─────┘  └──────┬─────┘
            │               │               │
            ▼               ▼               ▼
     ┌──────────────────────────────────────────┐
     │             Salt Minions                 │
     │  (多Master故障转移配置)                  │
     └──────────────────────────────────────────┘
# /etc/salt/minion 多Master配置
master:
-salt-master-01.example.com
-salt-master-02.example.com
-salt-master-03.example.com

# Master故障转移
master_type:failover
master_shuffle:True
master_alive_interval:30

# 随机选择Master
random_master:True

# 重试配置
retry_dns:30
retry_dns_count:3
# /etc/salt/master Syndic配置
# Salt Master同时作为Syndic
syndic_master: salt-syndic.example.com
syndic_master_port: 4506
syndic_log_file: /var/log/salt/syndic

# Syndic认证
syndic_wait: 30

 

4.2 注意事项

4.2.1 配置注意

Ansible配置注意事项:

配置项 注意事项 建议值
forks 过高会导致控制节点资源耗尽 CPU核心数 * 5
timeout 过短可能导致任务误判失败 30-60秒
pipelining 需要目标主机禁用requiretty 生产环境建议开启
host_key_checking 生产环境必须开启 True
gather_facts 全量收集影响性能 按需收集
become 最小权限原则 仅必要时使用

SaltStack配置注意事项:

配置项 注意事项 建议值
worker_threads 根据CPU和连接数调整 CPU核心数 * 2
auto_accept 生产环境严禁开启 False
gather_job_timeout 大规模执行需增加 60-120秒
pillar_cache 频繁更新场景需关闭 根据场景
state_verbose 生产环境关闭详细输出 False
file_recv 文件上传功能,有安全风险 False

4.2.2 常见错误

错误类型 错误描述 解决方案
SSH连接超时 UNREACHABLE! SSH timed out 检查网络连通性,增加timeout值
权限拒绝 Permission denied (publickey) 检查SSH密钥配置和权限
模块未找到 MODULE FAILURE 安装对应的Ansible Collection
Python解释器 /usr/bin/python: No such file 指定ansible_python_interpreter
幂等性失败 changed状态不稳定 检查任务设计,使用when条件
变量未定义 undefined variable 使用default过滤器或检查变量定义
Salt Minion离线 Minion did not return 检查minion服务状态和网络
Salt密钥冲突 Key mismatch 删除旧密钥,重新认证
Pillar渲染失败 Pillar render error 检查Jinja语法和变量引用
状态依赖错误 Requisite failed 检查require/watch依赖链

4.2.3 兼容性

操作系统兼容性矩阵:

操作系统 Ansible 2.16+ SaltStack 3006+ 备注
CentOS Stream 9 完全支持 完全支持 推荐
Rocky Linux 9 完全支持 完全支持 推荐
AlmaLinux 9 完全支持 完全支持 推荐
Ubuntu 24.04 LTS 完全支持 完全支持 推荐
Debian 12 完全支持 完全支持 支持
RHEL 9 完全支持 完全支持 企业版
Windows Server 2022 部分支持 完全支持 需WinRM/SSH

Python版本兼容性:

Python版本 Ansible 2.16+ SaltStack 3006+ 状态
3.9 支持 支持 维护中
3.10 支持 支持 推荐
3.11 完全支持 完全支持 推荐
3.12 支持 部分支持 测试中

五、故障排查和监控

5.1 故障排查

5.1.1 日志分析

Ansible日志配置和分析:

 

# 配置详细日志输出
export ANSIBLE_LOG_PATH=/var/log/ansible/ansible.log
export ANSIBLE_DEBUG=True

# 运行时启用调试
ansible-playbook playbook.yml -vvvv

# 日志级别说明
# -v    : 显示任务结果
# -vv   : 显示任务配置和结果
# -vvv  : 显示SSH连接信息
# -vvvv : 显示完整调试信息

# 分析日志常用命令
# 查看失败任务
grep -E "FAILED|UNREACHABLE" /var/log/ansible/ansible.log

# 统计任务执行时间
grep "TASK|PLAY RECAP" /var/log/ansible/ansible.log

# 提取特定主机日志
grep "web-node-01" /var/log/ansible/ansible.log

 

SaltStack日志配置和分析:

 

# Master日志位置
tail -f /var/log/salt/master

# Minion日志位置
tail -f /var/log/salt/minion

# 启用调试日志
salt-master -l debug
salt-minion -l debug

# 日志级别配置
# /etc/salt/master 或 /etc/salt/minion
# log_level: debug
# log_level_logfile: info

# 分析日志
# 查看认证失败
grep -E "Authentication|denied" /var/log/salt/master

# 查看执行错误
grep -E "Error|Exception|Traceback" /var/log/salt/master

# 事件日志
salt-run state.event pretty=True

 

5.1.2 常见问题排查

问题一:Ansible SSH连接失败

 

# 诊断步骤
# 1. 检查SSH连通性
ssh -v -i ~/.ssh/ansible_key root@target_host

# 2. 检查SSH配置
cat ~/.ssh/config
cat /etc/ssh/ssh_config

# 3. 测试Ansible连接
ansible target_host -m ping -vvvv

# 4. 检查Python解释器
ansible target_host -m raw -a "which python3"

# 常见解决方案
# 配置Python解释器
ansible_python_interpreter=/usr/bin/python3

# 禁用主机密钥检查(仅测试环境)
export ANSIBLE_HOST_KEY_CHECKING=False

 

问题二:SaltStack Minion无响应

 

# 诊断步骤
# 1. 检查Minion服务状态
systemctl status salt-minion
journalctl -u salt-minion -f

# 2. 检查Master连接
salt-call --local test.ping
salt-call test.ping -l debug

# 3. 检查网络连通性
nc -zv salt-master 4505
nc -zv salt-master 4506

# 4. 重新生成Minion密钥
rm /etc/salt/pki/minion/minion_master.pub
systemctl restart salt-minion

# 5. 在Master上重新接受密钥
salt-key -d minion-id -y
salt-key -a minion-id -y

 

问题三:状态应用失败

 

# Ansible诊断
# 检查任务详细输出
ansible-playbook playbook.yml --check --diff -v

# 单步执行
ansible-playbook playbook.yml --step

# 从特定任务开始
ansible-playbook playbook.yml --start-at-task="任务名称"

# Salt诊断
# 测试模式运行
salt 'minion-id' state.apply test=True

# 查看状态编译结果
salt 'minion-id' state.show_lowstate

# 查看Pillar数据
salt 'minion-id' pillar.items

 

5.1.3 调试技巧

 

# Ansible调试模块使用
---
-name:调试示例
hosts:all
tasks:
    -name:打印变量值
      ansible.builtin.debug:
        var:my_variable

    -name:打印自定义消息
      ansible.builtin.debug:
        msg:"当前主机: {{ inventory_hostname }}, IP: {{ ansible_host }}"

    -name:条件断点
      ansible.builtin.pause:
        prompt:"检查变量值是否正确,按Enter继续"
      when:debug_mode|default(false)

    -name:保存调试信息到文件
      ansible.builtin.copy:
        content:"{{ hostvars[inventory_hostname] | to_nice_json }}"
        dest:"/tmp/debug_{{ inventory_hostname }}.json"
      delegate_to:localhost
# Salt调试命令
# 查看模块文档
salt '*' sys.doc state.file

# 查看可用模块
salt '*' sys.list_modules

# 查看Grains
salt '*' grains.items

# 查看Pillar
salt '*' pillar.items

# 执行状态调试
salt-call state.apply nginx -l debug --local

# 查看事件总线
salt-run state.event pretty=True tagmatch='salt/job/*'

 

5.2 性能监控

5.2.1 关键指标

Ansible执行性能指标:

指标名称 说明 健康阈值 告警阈值
任务执行时间 单个任务执行耗时 < 30s > 60s
Playbook总时长 整个Playbook执行时间 < 10min > 30min
并发连接数 同时执行的SSH连接 < forks值 = forks值
失败任务比例 失败任务占比 0% > 5%
重试次数 任务重试次数 0 > 3

SaltStack执行性能指标:

指标名称 说明 健康阈值 告警阈值
命令响应时间 从发送到返回 < 5s > 30s
Minion在线率 在线Minion比例 > 99% < 95%
Master CPU使用率 控制节点CPU < 60% > 80%
Master内存使用率 控制节点内存 < 70% > 85%
ZeroMQ队列长度 消息队列积压 < 1000 > 5000
Job完成率 成功完成的任务 > 99% < 95%

5.2.2 监控配置

Ansible监控脚本:

 

#!/usr/bin/env python3
# /opt/ansible/scripts/ansible_metrics.py
# Ansible执行指标收集脚本

import json
import time
import subprocess
from datetime import datetime
from pathlib import Path

class AnsibleMetrics:
    def __init__(self, log_path="/var/log/ansible"):
        self.log_path = Path(log_path)
        self.metrics_file = self.log_path / "metrics.json"

    def collect_execution_metrics(self, playbook_output):
        """解析Playbook执行结果,提取指标"""
        metrics = {
            "timestamp": datetime.now().isoformat(),
            "ok": 0,
            "changed": 0,
            "unreachable": 0,
            "failed": 0,
            "skipped": 0,
            "rescued": 0,
            "ignored": 0
        }

        # 解析PLAY RECAP行
        for line in playbook_output.split('
'):
            if'ok='in line:
                parts = line.split()
                for part in parts:
                    if'='in part:
                        key, value = part.split('=')
                        if key in metrics:
                            metrics[key] = int(value)

        return metrics

    def export_prometheus_metrics(self, metrics):
        """导出Prometheus格式指标"""
        output = []
        output.append("# HELP ansible_task_total Ansible任务执行统计")
        output.append("# TYPE ansible_task_total counter")

        for key, value in metrics.items():
            if key != "timestamp":
                output.append(f'ansible_task_total{{status="{key}"}} {value}')

        return'
'.join(output)

if __name__ == "__main__":
    collector = AnsibleMetrics()
    # 示例用法
    result = subprocess.run(
        ["ansible-playbook", "playbook.yml"],
        capture_output=True,
        text=True
    )
    metrics = collector.collect_execution_metrics(result.stdout)
    print(collector.export_prometheus_metrics(metrics))

 

SaltStack监控配置:

 

# /etc/salt/master.d/monitoring.conf
# Salt Master监控配置

# 启用Salt API用于监控
rest_cherrypy:
port:8000
ssl_crt:/etc/pki/tls/certs/salt-api.crt
ssl_key:/etc/pki/tls/private/salt-api.key

# 事件返回器配置(存储到MySQL)
event_return:mysql
mysql.host:'localhost'
mysql.user:'salt'
mysql.pass:'salt_password'
mysql.db:'salt'

# 返回器配置
return:mysql

# Beacon监控配置示例
beacons:
diskusage:
    -/:90%
    -/var:80%
service:
    -services:
        nginx:
          onchangeonly:True
        salt-minion:
          onchangeonly:True
load:
    -averages:
        1m:
          -2.0
        5m:
          -1.5
        15m:
          -1.0
# /srv/salt/base/monitoring/salt_exporter.sls
# 部署Salt Prometheus Exporter

salt_exporter_download:
file.managed:
    -name:/usr/local/bin/salt_exporter
    -source:https://github.com/example/salt-exporter/releases/latest/salt_exporter
    -mode:755

salt_exporter_service:
file.managed:
    -name:/etc/systemd/system/salt-exporter.service
    -contents:|
        [Unit]
        Description=Salt Prometheus Exporter
        After=network.target

        [Service]
        Type=simple
        ExecStart=/usr/local/bin/salt_exporter--listen-address=:9175
        Restart=always

        [Install]
        WantedBy=multi-user.target
service.running:
    -name:salt-exporter
    -enable:True
    -require:
      -file:salt_exporter_download
      -file:salt_exporter_service

 

5.2.3 告警配置

 

# Prometheus告警规则
# /etc/prometheus/rules/ansible_alerts.yml
groups:
-name:ansible_alerts
    rules:
      -alert:AnsibleHighFailureRate
        expr:|
          sum(rate(ansible_task_total{status="failed"}[5m])) /
          sum(rate(ansible_task_total[5m])) > 0.05
        for:5m
        labels:
          severity:critical
        annotations:
          summary:"Ansible任务失败率过高"
          description:"过去5分钟内Ansible任务失败率超过5%"

      -alert:AnsiblePlaybookTimeout
        expr:ansible_playbook_duration_seconds>1800
        for:0m
        labels:
          severity:warning
        annotations:
          summary:"Ansible Playbook执行超时"
          description:"Playbook执行时间超过30分钟"

-name:salt_alerts
    rules:
      -alert:SaltMinionOffline
        expr:salt_minion_connected==0
        for:5m
        labels:
          severity:critical
        annotations:
          summary:"Salt Minion离线"
          description:"Minion {{ $labels.minion }} 已离线超过5分钟"

      -alert:SaltMasterHighLoad
        expr:salt_master_cpu_usage_percent>80
        for:10m
        labels:
          severity:warning
        annotations:
          summary:"Salt Master负载过高"
          description:"Salt Master CPU使用率超过80%持续10分钟"

 

5.3 备份与恢复

5.3.1 Ansible备份策略

 

#!/bin/bash
# /opt/ansible/scripts/backup.sh
# Ansible配置备份脚本

BACKUP_DIR="/backup/ansible"
DATE=$(date +%Y%m%d_%H%M%S)
ANSIBLE_HOME="/opt/ansible"

# 创建备份目录
mkdir -p "${BACKUP_DIR}/${DATE}"

# 备份配置文件
tar -czf "${BACKUP_DIR}/${DATE}/config.tar.gz" 
    "${ANSIBLE_HOME}/ansible.cfg" 
    "${ANSIBLE_HOME}/inventory/" 
    "${ANSIBLE_HOME}/group_vars/" 
    "${ANSIBLE_HOME}/host_vars/"

# 备份Playbooks和Roles
tar -czf "${BACKUP_DIR}/${DATE}/playbooks.tar.gz" 
    "${ANSIBLE_HOME}/playbooks/" 
    "${ANSIBLE_HOME}/roles/"

# 备份Vault密钥(加密存储)
if [ -f ~/.vault_pass ]; then
    gpg --encrypt --recipient ops@example.com 
        -o "${BACKUP_DIR}/${DATE}/vault_pass.gpg" 
        ~/.vault_pass
fi

# 保留最近30天备份
find "${BACKUP_DIR}" -type d -mtime +30 -exec rm -rf {} ;

echo"备份完成: ${BACKUP_DIR}/${DATE}"

 

5.3.2 SaltStack备份策略

 

#!/bin/bash
# /opt/salt/scripts/backup.sh
# SaltStack配置备份脚本

BACKUP_DIR="/backup/salt"
DATE=$(date +%Y%m%d_%H%M%S)

# 创建备份目录
mkdir -p "${BACKUP_DIR}/${DATE}"

# 备份Master配置
tar -czf "${BACKUP_DIR}/${DATE}/master_config.tar.gz" 
    /etc/salt/master 
    /etc/salt/master.d/

# 备份PKI密钥
tar -czf "${BACKUP_DIR}/${DATE}/pki.tar.gz" 
    /etc/salt/pki/

# 备份Salt状态和Pillar
tar -czf "${BACKUP_DIR}/${DATE}/srv_salt.tar.gz" 
    /srv/salt/ 
    /srv/pillar/ 
    /srv/reactor/

# 备份Minion密钥列表
salt-key -L > "${BACKUP_DIR}/${DATE}/minion_keys.txt"

# 备份Grains数据
salt '*' grains.items --out=json > "${BACKUP_DIR}/${DATE}/grains.json"

# 保留最近30天备份
find "${BACKUP_DIR}" -type d -mtime +30 -exec rm -rf {} ;

echo"备份完成: ${BACKUP_DIR}/${DATE}"
# 灾难恢复Playbook
# /opt/ansible/playbooks/salt_recovery.yml
---
-name:SaltStack灾难恢复
hosts:salt_master
become:yes

vars:
    backup_path:/backup/salt/latest

tasks:
    -name:停止SaltMaster服务
      ansible.builtin.systemd:
        name:salt-master
        state:stopped

    -name:恢复Master配置
      ansible.builtin.unarchive:
        src:"{{ backup_path }}/master_config.tar.gz"
        dest:/
        remote_src:yes

    -name:恢复PKI密钥
      ansible.builtin.unarchive:
        src:"{{ backup_path }}/pki.tar.gz"
        dest:/
        remote_src:yes

    -name:恢复Salt状态文件
      ansible.builtin.unarchive:
        src:"{{ backup_path }}/srv_salt.tar.gz"
        dest:/
        remote_src:yes

    -name:启动SaltMaster服务
      ansible.builtin.systemd:
        name:salt-master
        state:started
        enabled:yes

    -name:验证Minion连接
      ansible.builtin.command:salt'*'test.ping
      register:ping_result

    -name:显示恢复结果
      ansible.builtin.debug:
        var:ping_result.stdout_lines

 

六、总结

6.1 技术要点回顾

通过本文的对比分析,从架构设计、部署方式、性能表现、易用性等多个维度对Ansible和SaltStack进行了全面评估:

架构对比:

对比维度 Ansible SaltStack
通信方式 SSH(无代理) ZeroMQ(代理)
配置语言 YAML + Jinja2 YAML + Jinja2
学习曲线 较低 中等
执行模式 Push Push + Pull
扩展性 Python模块 Python模块 + Reactor
实时性 按需执行 事件驱动

选型建议:

选择Ansible的场景:

中小规模集群(100-500台)

团队配置管理经验较少

混合环境(物理机+虚拟机+网络设备)

CI/CD集成需求强

不希望在目标机器安装代理

选择SaltStack的场景:

大规模集群(1000+台)

需要实时配置管理

复杂的事件驱动自动化

需要高频率配置变更

对执行性能有严格要求

6.2 进阶学习方向

Ansible进阶:

Ansible Tower/AWX企业级部署

自定义模块和插件开发

Ansible Galaxy角色开发和分享

Ansible与Kubernetes集成

Ansible Lint和测试框架

SaltStack进阶:

Salt Syndic分布式架构

Salt Cloud云资源管理

Salt Reactor高级应用

Salt Beacon监控集成

Salt Thorium智能自动化

6.3 参考资料

Ansible官方文档:https://docs.ansible.com/

SaltStack官方文档:https://docs.saltproject.io/

Ansible Galaxy:https://galaxy.ansible.com/

SaltStack Formulas:https://github.com/saltstack-formulas

Red Hat Ansible Automation Platform:https://www.redhat.com/en/technologies/management/ansible

附录

A. 命令速查表

Ansible常用命令:

 

# 连通性测试
ansible all -m ping

# 执行Ad-hoc命令
ansible webservers -m shell -a "df -h"

# 运行Playbook
ansible-playbook playbook.yml

# 检查模式(干运行)
ansible-playbook playbook.yml --check --diff

# 限制执行范围
ansible-playbook playbook.yml --limit web-node-01

# 指定标签
ansible-playbook playbook.yml --tags "nginx,config"

# 跳过标签
ansible-playbook playbook.yml --skip-tags "backup"

# 列出任务
ansible-playbook playbook.yml --list-tasks

# 列出主机
ansible-playbook playbook.yml --list-hosts

# 加密文件
ansible-vault encrypt secrets.yml

# 解密文件
ansible-vault decrypt secrets.yml

# 编辑加密文件
ansible-vault edit secrets.yml

# 查看加密文件
ansible-vault view secrets.yml

 

SaltStack常用命令:

 

# 连通性测试
salt '*' test.ping

# 执行远程命令
salt 'web-*' cmd.run 'uptime'

# 应用状态
salt '*' state.apply

# 高状态应用
salt '*' state.highstate

# 测试模式
salt '*' state.apply test=True

# 刷新Pillar
salt '*' saltutil.refresh_pillar

# 查看Grains
salt '*' grains.items

# 查看Pillar
salt '*' pillar.items

# 密钥管理
salt-key -L          # 列出所有密钥
salt-key -a minion   # 接受密钥
salt-key -d minion   # 删除密钥
salt-key -A          # 接受所有待定密钥

# 批量执行
salt '*' state.apply batch=5

# 异步执行
salt '*' state.apply --async
salt-run jobs.lookup_jid 

 

B. 配置参数详解

Ansible关键配置参数:

参数 默认值 说明
forks 5 并发连接数
timeout 10 SSH连接超时(秒)
remote_user 当前用户 远程登录用户
host_key_checking True 是否检查主机密钥
pipelining False 是否启用管道模式
gathering implicit Facts收集策略
fact_caching memory Facts缓存后端
retry_files_enabled True 是否生成重试文件
stdout_callback default 输出回调插件

SaltStack关键配置参数:

参数 默认值 说明
worker_threads 5 工作线程数
timeout 5 命令超时(秒)
gather_job_timeout 10 任务收集超时(秒)
auto_accept False 自动接受密钥
file_recv False 允许Minion上传文件
pillar_cache False 启用Pillar缓存
state_verbose True 详细状态输出
log_level warning 日志级别

C. 术语表

术语 Ansible SaltStack
控制节点 Control Node Master
被管理节点 Managed Node Minion
配置清单 Inventory Roster/Grains
配置文件 Playbook State
变量数据 Variables Pillar
模块 Module Execution Module
任务 Task State Function
角色 Role Formula
模板 Template (Jinja2) Template (Jinja2)
事件 Callback Event/Reactor
执行结果 Return Value Return

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分