ansible —— 无 agent 的批量配置管理

运维工具手册 07-自动化与版本 8.8 KB 原文

ansible —— 无 agent 的批量配置管理

分类:07 自动化与版本 适用:Ansible 2.9 / 2.10+(collections 拆分后)/ ansible-core 2.1x 一句话:基于 SSH,不用在目标机装 agent,就能对上百台机器批量执行命令、下发配置。

1. 它解决什么问题

  • 100 台机器要同时改一个参数、装一个包、重启一个服务。
  • 配置要可重复执行(幂等),而不是"跑一次脚本,第二次报错"。
  • 变更要有记录、能灰度、能回滚。
  • 临时排查:一次性在所有 web 节点上执行某条命令。

2. 安装与最小结构

# 控制机(不装在被管节点)
pip install ansible
# 或
apt install -y ansible
dnf install -y ansible
project/
├── ansible.cfg
├── inventory/
│   ├── hosts.ini
│   └── group_vars/
├── playbooks/
│   └── site.yml
└── roles/

ansible.cfg:

[defaults]
inventory = ./inventory/hosts.ini
host_key_checking = False
forks = 20
timeout = 30
stdout_callback = yaml
retry_files_enabled = False
interpreter_python = auto_silent

[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

3. Inventory

# inventory/hosts.ini
[web]
web01 ansible_host=10.0.0.11
web02 ansible_host=10.0.0.12

[db]
db01 ansible_host=10.0.0.21 ansible_user=dba

[all:vars]
ansible_user=ops
ansible_ssh_private_key_file=~/.ssh/id_ed25519

[web:vars]
nginx_worker=4

YAML 格式(更适合动态):

all:
  children:
    web:
      hosts:
        web01: { ansible_host: 10.0.0.11 }
        web02: { ansible_host: 10.0.0.12 }
      vars:
        nginx_worker: 4

动态 inventory(云环境):

ansible-inventory -i inventory/ --graph      # 查看解析结果
ansible-inventory --list                     # JSON 输出

4. Ad-hoc(一次性命令)

ansible all -m ping                                   # 连通性
ansible web -m command -a 'uptime'
ansible web -m shell -a 'df -h | grep /$'             # 支持管道
ansible web -m command -a 'systemctl status nginx'
ansible web -m setup -a 'filter=ansible_memory_mb'    # 取 facts
ansible web -a 'hostname'                             # 默认 command
ansible all --list-hosts

常用参数:

-i inventory/hosts.ini     # 指定 inventory
-u ops                     # 用户
-b / --become              # sudo
-K                         # 询问 sudo 密码
-l web01                   # 限制主机
--limit 'web:!web02'       # 排除
-f 20                      # 并发
-o                         # 单行输出
-v / -vvv                  # 详细
--check                    # 预演(dry-run)
--diff                     # 显示文件差异

command vs shell

模块 特点
command 不经过 shell,不支持管道/重定向/变量展开,更安全
shell 经过 /bin/sh,支持管道与通配
raw 不依赖 Python,用于目标机无 Python 的场景

5. Playbook

---
- name: 部署 nginx
  hosts: web
  become: true
  vars:
    nginx_port: 8080
  tasks:
    - name: 安装 nginx
      ansible.builtin.package:
        name: nginx
        state: present

    - name: 下发配置
      ansible.builtin.template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: "0644"
        backup: true
      notify: reload nginx

    - name: 启动并开机自启
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

  handlers:
    - name: reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded

常用模块

模块 作用 示例
ping 连通性 ansible.builtin.ping
copy 拷文件 src/dest/mode
template Jinja2 模板 src=*.j2
file 目录/权限/软链 state: directory
lineinfile 改一行 regexp + line
blockinfile 改一段 marker
replace 正则替换 regexp/replace
package / yum / apt 装包 state: present
service / systemd 服务 state/enabled/daemon_reload
user / group 用户 name/uid/shell
cron 定时任务 minute/job
get_url 下载 url/dest/checksum
unarchive 解压 src/dest/remote_src
stat 判断存在 register + when
find 查找 patterns/age
synchronize rsync 需装 rsync
command / shell 执行 配合 creates/changed_when
uri HTTP 请求 url/status_code
docker_container 容器 需 collection
setup 收集 facts gather_facts

变量与条件

- name: 只在 Debian 上执行
  ansible.builtin.apt:
    name: nginx
    state: present
  when: ansible_os_family == "Debian"

- name: 注册结果
  ansible.builtin.command: /usr/bin/check_health
  register: health
  changed_when: false
  failed_when: health.rc not in [0, 2]

- name: 条件执行
  ansible.builtin.debug:
    msg: "服务异常"
  when: health.rc != 0

- name: 循环
  ansible.builtin.user:
    name: "{{ item }}"
    state: present
  loop:
    - alice
    - bob

幂等写法

# 不要这样(每次都 changed)
- ansible.builtin.shell: echo "1" >> /tmp/f

# 应该这样
- ansible.builtin.lineinfile:
    path: /tmp/f
    line: "1"

command/shell 加 creates 或 changed_when 控制幂等:

- ansible.builtin.command: /opt/app/init.sh
  args:
    creates: /opt/app/.initialized

6. 执行控制

ansible-playbook -i inventory/hosts.ini playbooks/site.yml
ansible-playbook site.yml --check --diff          # 预演 + 差异
ansible-playbook site.yml --limit web01           # 只对一台
ansible-playbook site.yml --tags config           # 只跑指定 tag
ansible-playbook site.yml --skip-tags slow
ansible-playbook site.yml -e "version=1.2.3"      # 传变量
ansible-playbook site.yml -e @vars.yml
ansible-playbook site.yml --syntax-check
ansible-playbook site.yml --list-tasks
ansible-playbook site.yml -f 50 -vv

灰度与并发

- hosts: web
  serial: 2                 # 每批 2 台
  max_fail_percentage: 30   # 失败超 30% 停止
serial:
  - 1        # 先 1 台
  - 5        # 再 5 台
  - "30%"    # 最后 30%

委派与一次性

- name: 在负载均衡上摘除
  ansible.builtin.command: "lb remove {{ inventory_hostname }}"
  delegate_to: lb01
  run_once: true

7. Vault(加密敏感变量)

ansible-vault create group_vars/prod/vault.yml
ansible-vault edit group_vars/prod/vault.yml
ansible-vault encrypt secrets.yml
ansible-vault decrypt secrets.yml
ansible-vault rekey secrets.yml
ansible-vault view secrets.yml

# 使用
ansible-playbook site.yml --ask-vault-pass
ansible-playbook site.yml --vault-password-file ~/.vault_pass

~/.vault_pass 权限设 600,不要提交到 git。

8. 实战场景

8.1 批量巡检

ansible all -m shell -a 'echo "== $(hostname)"; uptime; df -h / | tail -1; free -m | grep Mem' -o

8.2 批量下发 hosts 文件

- hosts: all
  become: true
  tasks:
    - name: 生成 hosts
      ansible.builtin.template:
        src: hosts.j2
        dest: /etc/hosts
        backup: true
      notify: flush dns
  handlers:
    - name: flush dns
      ansible.builtin.command: systemctl restart systemd-resolved
      when: ansible_os_family == "Debian"

模板里可直接用 inventory 主机名:

{% for h in groups['all'] %}
{{ hostvars[h]['ansible_host'] }} {{ h }}
{% endfor %}

8.3 灰度发布

- hosts: web
  serial: 1
  max_fail_percentage: 0
  tasks:
    - name: 摘流量
      ...
    - name: 更新二进制
      ansible.builtin.copy:
        src: "app-{{ version }}"
        dest: /opt/app/app
        mode: "0755"
    - name: 重启
      ansible.builtin.service:
        name: app
        state: restarted
    - name: 健康检查
      ansible.builtin.uri:
        url: http://127.0.0.1:8080/health
        status_code: 200
      retries: 5
      delay: 3

8.4 只改配置不重启(用 handler)

handler 只在任务 changed 时触发,天然避免无谓重启。

8.5 排查连接问题

ansible web -m ping -vvv
ansible web -m setup -a 'filter=ansible_python*'
# 常见:python 路径、sudo 需要密码、密钥权限

9. 常见坑

  1. 目标机没 Python:报 Missing sudo password 之外的 python not found,用 raw 模块先装,或 ansible_python_interpreter 指定路径。
  2. command 不支持管道:需要管道用 shell,但注意幂等性。
  3. shell 每次都 changed:加 creates 或 changed_when: false。
  4. become 需要密码:-K 或配 NOPASSWD sudo。
  5. host key 校验失败:首次连接交互确认,host_key_checking = False 或预置 known_hosts。
  6. forks 太小导致慢:默认 5,调到 20~50。
  7. 变量优先级混乱:命令行 -e > play vars > host_vars > group_vars > role defaults。记住这个顺序。
  8. group_vars 目录名必须与组名一致。
  9. template 的 Jinja2 未定义变量静默为空:加 -e 或用 | default()。
  10. lineinfile 重复添加:regexp 写不准会每次追加,用 insertafter/insertbefore 配合。
  11. handler 不执行:任务没 changed 就不触发,或 handler 名拼错。
  12. CentOS 7 的 Python 2:ansible-core 2.17+ 已不支持 Python 2 目标机,老系统需用 ansible 2.9。
  13. collection 未安装:community.docker、ansible.posix 要 ansible-galaxy collection install。
  14. synchronize 方向:默认从控制机推到目标,拉取要 mode: pull。
  15. --check 不是万能的:command/shell 在 check 模式下默认跳过,会给出误导性的"无变化"。

10. 速查表

ansible all -m ping
ansible web -m shell -a 'uptime' -o
ansible web -m setup -a 'filter=ansible_*'
ansible-inventory --graph
ansible-doc -l | grep <kw>
ansible-doc <module>

ansible-playbook site.yml
ansible-playbook site.yml --check --diff
ansible-playbook site.yml --limit web01
ansible-playbook site.yml --tags config --skip-tags slow
ansible-playbook site.yml -e "v=1" -e @vars.yml
ansible-playbook site.yml --syntax-check --list-tasks

ansible-vault create/edit/view/encrypt/decrypt/rekey

# 核心模块
copy / template / file / lineinfile / blockinfile / replace
package / service / user / cron / get_url / unarchive
command(creates) / shell(changed_when) / uri / stat / find