Ansible批量管理服务器教程:主机清单、远程执行与配置部署

公司从 10 台服务器扩张到 80 台,靠 for 循环加 SSH 已经撑不住了:改一次 Nginx 配置要写好几段判断、机器分组靠手工维护的文本、执行结果还得自己汇总。这时候就该引入 Ansible。Ansible 最大的优势是无 agent——被管机器上不需要装任何客户端,只要能 SSH 登录、有 Python 就行,控制机通过 SSH 把模块脚本推过去执行再取回结果。它用 YAML 描述"机器应该是什么状态",天然幂等,重复执行不会产生副作用。本文从主机清单(inventory)讲起,到临时命令(ad-hoc),再到 Playbook 的任务、处理器与变量,给你一条可以直接上手的完整路径。

一、先理解 Ansible 的四个核心概念

开始写命令之前,先把几个词对齐,否则看文档会一直懵。

  • 控制节点(Control Node):装了 Ansible 的那台机器,你在这上面执行命令。Windows 不能做控制节点,用 Linux 或 macOS。
  • 受管节点(Managed Node):被管理的服务器,只需 SSH + Python(现代发行版自带 Python 3)。
  • Inventory(主机清单):定义"有哪些机器、如何分组、各有什么变量"的文件,默认 /etc/ansible/hosts。
  • Module(模块):真正干活的最小单元,如 ping、copy、yum、service。Ansible 自带几千个模块,绝大多数需求不用写 Shell。
  • Playbook:把多个模块调用按逻辑组织起来的 YAML 文件,是 Ansible 的主力形态。

与 SaltStack、Puppet 相比,Ansible 的取舍很清楚:优点是零 agent、学习曲线平缓、推送式执行即时生效;缺点是没有常驻 agent 就无法主动感知配置漂移(即有人手工改了机器配置,Ansible 不会自动发现并纠正,需要定时跑 playbook 来"收敛"),且机器数量到数千台时串行 SSH 会比较慢(可通过 forks 参数与 pipelining 优化)。

二、安装与 SSH 前置准备

# 控制机安装(CentOS/RHEL)
yum install -y epel-release && yum install -y ansible
# Debian/Ubuntu
apt-get update && apt-get install -y ansible
# 或使用 pip 安装最新版
pip3 install ansible

ansible --version     # 确认版本与配置文件路径(config file 一行)

# 生成并分发 SSH 密钥
ssh-keygen -t ed25519 -f ~/.ssh/ansible_key -N ''
for ip in 10.0.0.11 10.0.0.12 10.0.0.21; do
  ssh-copy-id -i ~/.ssh/ansible_key.pub root@$ip
done

# 受管机器需要有 Python(Ansible 靠它执行模块)
# CentOS 7 若只有 python2,可临时指定:ansible_python_interpreter=/usr/bin/python2

Ansible 的配置文件查找顺序是:ANSIBLE_CONFIG 环境变量 → 当前目录 ansible.cfg → 家目录 ~/.ansible.cfg → /etc/ansible/ansible.cfg。强烈建议每个项目目录下放一份 ansible.cfg,这样配置随项目走,不会互相干扰。

# 项目目录下的 ansible.cfg
[defaults]
inventory      = ./inventory.ini
remote_user    = root
private_key_file = ~/.ssh/ansible_key
host_key_checking = False      # 关闭首次连接的指纹确认,批量场景必需
forks          = 20            # 并发数,默认 5,机器多时调大
timeout        = 10
log_path       = ./ansible.log

[ssh_connection]
pipelining = True              # 减少 SSH 往返,显著提速
ssh_args = -o ControlMaster=auto -o ControlPersist=60s

三、Inventory 主机清单:分组、变量与范围写法

Inventory 是 Ansible 的"资产表",支持 INI 和 YAML 两种格式,INI 更常用。方括号表示组名,组之间可以嵌套(用 :children)。

# inventory.ini
# 1. 基础写法:IP 或可解析的主机名
10.0.0.11
10.0.0.12

# 2. 分组
[web]
web01 ansible_host=10.0.0.11
web02 ansible_host=10.0.0.12

[db]
db01  ansible_host=10.0.0.21
db02  ansible_host=10.0.0.22

# 3. 范围简写(等价于 10.0.0.11 ~ 10.0.0.20)
[batch]
10.0.0.[11:20]

# 4. 组嵌套:prod 包含 web 与 db 两个组
[prod:children]
web
db

# 5. 组变量:[组名:vars]
[web:vars]
nginx_worker_processes=4
http_port=80

# 6. 主机变量(写在主机行后面)
[cache]
cache01 ansible_host=10.0.0.31 ansible_port=2222 ansible_user=ops

# 7. 特殊连接参数
[jumper]
jump01 ansible_host=10.0.0.99 ansible_python_interpreter=/usr/bin/python3
参数作用常见取值
ansible_host实际连接地址IP 或域名
ansible_portSSH 端口22(默认)、2222 等
ansible_user登录用户root、ops
ansible_ssh_private_key_file私钥路径~/.ssh/ansible_key
ansible_become是否提权true / false
ansible_python_interpreterPython 路径/usr/bin/python3

常用命令验证清单是否正确:ansible-inventory --list 输出完整结构(JSON);ansible-inventory --graph 以树状展示分组关系;ansible all --list-hosts 列出匹配到的主机。改完清单先跑这三个命令确认,比直接执行任务安全。

四、Ad-hoc 临时命令:最快的上手方式

Ad-hoc 指"临时敲一条命令就执行",适合快速查看状态、做一次性变更。语法是 ansible <目标> -m <模块> -a '<参数>',不写 -m 时默认是 command 模块。

# 连通性测试:Ansible 的"Hello World",成功返回 pong
ansible all -m ping
ansible web -m ping

# 查看所有机器的系统信息(facts)
ansible all -m setup | head -50
ansible web -m setup -a 'filter=ansible_distribution*'

# 远程执行命令:command 模块不经过 shell,不支持管道与变量
ansible all -m command -a 'uptime'

# 需要管道、重定向、变量时用 shell 模块
ansible all -m shell -a 'df -h / | tail -1'
ansible web -m shell -a 'ps aux --sort=-%cpu | head -5'

# 批量分发文件
ansible web -m copy -a 'src=/opt/conf/nginx.conf dest=/etc/nginx/nginx.conf backup=yes'

# 批量装包
ansible web -m yum  -a 'name=sysstat state=present'      # RHEL/CentOS
ansible web -m apt  -a 'name=sysstat state=present'      # Debian/Ubuntu

# 批量管理服务
ansible web -m service -a 'name=nginx state=reloaded'
ansible web -m systemd -a 'name=nginx state=restarted enabled=yes'

# 批量建用户
ansible all -m user -a 'name=deploy shell=/bin/bash groups=wheel append=yes'

# 串行执行,一次只跑 1 台(滚动操作,避免服务同时中断)
ansible web -m shell -a 'systemctl restart nginx' --forks 1

# 只在一台上执行(滚动更新常用)
ansible web -m shell -a 'uptime' --limit web01

command 与 shell 的区别是新手必踩的坑:command 更安全更幂等,但不解析 shell 语法,写 df -h | tail 会报错;shell 走 /bin/sh,支持管道但要注意幂等性。能用专用模块(copy、yum、service、user)就别用 shell,因为专用模块自带幂等判断和变更检测,shell 每次执行都会报告 changed。

五、Playbook:把操作写成可复用的代码

Playbook 是 Ansible 的真正形态。一个 Playbook 包含一到多个 play,每个 play 由 hosts(作用目标)、vars(变量)、tasks(任务列表)、handlers(触发器)组成。下面是一个部署 Nginx 的完整示例。

# site.yml
---
- name: 部署并配置 Nginx
  hosts: web                 # 对应 inventory 里的组
  become: yes                # 提权到 root(等价 sudo)
  gather_facts: yes          # 收集系统信息,后面可用 ansible_distribution 等变量
  serial: 2                  # 每次只在 2 台机器上执行,实现滚动发布
  vars:
    http_port: 80
    worker_processes: 4

  tasks:
    - name: 安装 Nginx
      yum:                   # Debian 系改用 apt
        name: nginx
        state: present
      tags: install

    - name: 分发主配置文件
      template:              # template 支持 Jinja2 变量渲染,copy 不支持
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
        validate: 'nginx -t -c %s'    # 写入前先校验,校验失败不替换
      notify: reload nginx            # 文件发生变更时触发 handler
      tags: config

    - name: 创建站点目录
      file:
        path: /data/www
        state: directory
        owner: nginx
        mode: '0755'

    - name: 启动并设置开机自启
      systemd:
        name: nginx
        state: started
        enabled: yes
      tags: service

    - name: 健康检查
      uri:
        url: "http://{{ inventory_hostname }}:{{ http_port }}/health"
        status_code: 200
        timeout: 5
      register: health
      retries: 3
      delay: 5
      until: health.status == 200

  handlers:
    - name: reload nginx
      systemd:
        name: nginx
        state: reloaded

这个 Playbook 里有几个关键设计值得学习:become: yes 提权;serial: 2 实现滚动,避免一次性重启所有 Web 导致服务中断;validate 参数让配置文件在正式替换前先过一遍语法检查(%s 会被替换成临时文件路径),这是防止"配置写错导致服务起不来"的最佳实践;notify + handlers 保证只有在配置文件真的发生变更时才 reload,没变更就不动——这正是幂等性的体现;until/retries/delay 实现带重试的健康检查。

对应的模板文件 templates/nginx.conf.j2 可以这样写,Jinja2 的 {{ }} 会被变量替换:

user nginx;
worker_processes {{ worker_processes }};
# 也可用 facts 自动适配:worker_processes {{ ansible_processor_vcpus }};

events {
    worker_connections 1024;
}

http {
    server {
        listen {{ http_port }};
        server_name {{ inventory_hostname }};
        root /data/www;
        access_log /var/log/nginx/access.log main;
    }
}

执行与调试命令:

# 语法检查
ansible-playbook site.yml --syntax-check

# 模拟执行(不真正改动,预测会发生什么变化)
ansible-playbook site.yml --check

# 查看会作用于哪些主机
ansible-playbook site.yml --list-hosts
ansible-playbook site.yml --list-tasks

# 只执行打了某个 tag 的任务
ansible-playbook site.yml --tags config

# 从指定任务开始执行(跳过前面的)
ansible-playbook site.yml --start-at-task "启动并设置开机自启"

# 限制只跑一台,用于灰度验证
ansible-playbook site.yml --limit web01

# 交互式确认每一步(高危操作推荐)
ansible-playbook site.yml --step

# 加密敏感变量(如密码)
ansible-vault create secrets.yml
ansible-playbook site.yml --ask-vault-pass

六、常用模块速查

场景模块关键参数
连通测试ping无参数
执行命令command / shellcmd、chdir、creates
分发文件copy / templatesrc、dest、mode、backup
改文件行lineinfilepath、regexp、line
装包yum / apt / dnfname、state=present/latest
服务管理systemd / servicename、state、enabled
用户组user / groupname、shell、groups
定时任务cronname、minute、job
目录文件filepath、state、mode、owner
下载get_url / unarchiveurl、dest
HTTP 探测uriurl、status_code
拉取信息setupfilter

七、变量优先级与常见排错

Ansible 里同一个变量可以在多处定义,优先级从高到低大致是:命令行 --extra-vars > playbook 中的 vars > inventory 主机变量 > inventory 组变量 > group_vars/ 与 host_vars/ 目录文件 > role 的 defaults/。理解优先级能避免"改了变量却不生效"的困惑。推荐的组织方式是:把不常改的默认值放 role 的 defaults/main.yml,把环境相关的差异放 group_vars/prod.yml、group_vars/test.yml,把真正敏感的信息用 ansible-vault 加密存放。用 ansible-inventory --host web01 可以直接查看某台机器最终生效的变量全集,排错时非常有用。

常见报错与处理:

  • UNREACHABLE! Failed to connect:SSH 不通。检查密钥权限(~/.ssh 700、私钥 600)、用户是否存在、端口是否正确。
  • FAILED! => "msg": "Missing sudo password":提权失败。在受管机的 /etc/sudoers 里给该用户加 NOPASSWD:ALL,或用 --ask-become-pass 交互输入。
  • Aborting, target uses selinux but python bindings aren't installed:缺少 libselinux-python,用 raw 模块先装上:ansible all -m raw -a 'yum install -y libselinux-python'。
  • /usr/bin/python: not found:Python 路径不对,在 inventory 里指定 ansible_python_interpreter=/usr/bin/python3。
  • 任务一直 changed 而非 ok:说明用了无幂等判断的模块(典型是 shell),改用专用模块或在命令后加 changed_when 条件。

调试时加上 -v(-vvv 会打印完整 SSH 过程,-vvvv 包含模块内部细节),绝大多数问题在 -vvv 输出里都能找到直接原因。另外 ansible-lint 工具可以静态检查 playbook 的写法规范,在团队协作时值得接入 CI。

八、落地实施步骤

  1. 在控制机安装 Ansible,生成专用 SSH 密钥并分发到所有受管机,确认 ssh root@主机 免密可登。
  2. 建立项目目录,写入 ansible.cfg(含 inventory 路径、private_key_file、host_key_checking=False、forks)。
  3. 编写 inventory.ini,按角色分组(web/db/cache)并做组嵌套,用 ansible-inventory --graph 检查结构。
  4. 跑 ansible all -m ping 确认全绿;不通的机器用 -vvv 看详细 SSH 报错。
  5. 先用 ad-hoc 完成一件小事(如批量安装 sysstat),熟悉模块参数。
  6. 把最高频的操作写成 Playbook,务必加上 validate 校验与 handlers。
  7. 用 --check 和 --limit 单台 验证,确认无误后再全量。
  8. 把整个项目目录提交到 Git,inventory 与 playbook 都纳入版本管理,敏感信息用 ansible-vault 加密。
  9. 配置定时执行(cron 或 AWX)做配置收敛,防止人工改动造成漂移。

小结

Ansible 批量管理服务器的核心就三件事:inventory 定义资产、ad-hoc 处理临时需求、playbook 描述目标状态。上手最快的路径是"先 ansible -m ping 跑通,再用 ad-hoc 做几次批量操作,最后把高频操作沉淀成 playbook"。避坑清单:一是忘记关 host_key_checking 导致新机器首次连接卡住;二是 command 模块里写管道(应改用 shell);三是所有操作都用 shell 而不用专用模块,失去幂等与变更检测;四是配置文件分发不加 validate,一旦写错所有机器同时起不来;五是 playbook 不加 serial,一次性重启全部机器导致业务中断;六是 inventory 和 playbook 不进 Git,几个月后没人知道线上配置究竟是什么。把这六点规避掉,Ansible 就能稳定支撑上百台服务器的日常运维。


关键词:Ansible教程、批量管理服务器、inventory主机清单、ansible -m ping、Playbook配置、handlers