4.1 Ansible 自动化配置管理入门

预计阅读时间:17 分钟

📖 目录

学习目标

学完本章后,你将能够:

  • 理解 Ansible 无代理架构的设计原理
  • 编写 Inventory 主机清单并执行 Ad-Hoc 命令
  • 编写 Playbook 实现多任务自动化编排
  • 使用 Role 和 Jinja2 模板组织可复用的配置代码
  • 使用 Ansible Vault 管理敏感信息

核心知识

  • Ansible——基于 SSH 的无代理自动化工具,使用 YAML 描述系统状态
  • Inventory(主机清单)——定义被管理主机的列表,支持分组和变量
  • Ad-Hoc 命令——一条命令执行一次操作,适合临时任务
  • Playbook——YAML 格式的自动化剧本,定义一组有序任务
  • Module(模块)——Ansible 的功能单元,每个模块完成一类操作
  • 幂等性(Idempotence)——多次执行同一 Playbook 产生相同结果
  • Role(角色)——Playbook 的复用单元,将任务/模板/变量组织为目录结构
  • Jinja2 模板——Python 模板引擎,Ansible 用其动态生成配置文件
  • Ansible Vault——加密敏感数据(密码、密钥、证书)的机制

知识关联

原理讲解

无代理架构

与传统配置管理工具(Puppet、Chef)需要在每台主机上安装 Agent 不同,Ansible 只在控制节点安装,通过 SSH 协议连接远程主机执行任务。这消除了受管节点上的 Agent 运维成本,也降低了被攻击面。控制节点将 Python 模块通过 SSH 传输到远程主机并执行,执行完毕后自动清理。

幂等性设计

Ansible 模块在设计上遵循幂等性原则:检查当前状态,只有当状态与目标不一致时才执行变更。例如 apt: name=nginx state=present 会先检查 nginx 是否已安装,已安装则跳过。这种"声明式"风格确保 Playbook 可以安全地反复执行。

Jinja2 模板渲染机制

Jinja2 是 Python 生态最流行的模板引擎,Ansible 的 template 模块渲染前会自动收集目标主机的 facts(如 ansible_hostnameansible_processor_vcpusansible_default_ipv4),再结合 Playbook 的 vars、Inventory 的组变量与主机变量、Role 的 defaultsvars,按固定优先级合并后替换 {{ 表达式 }}。渲染产物是纯文本文件,任何配置文件(nginx.conf、systemd unit、crontab)都可以模板化。{{ }} 是变量插值,{% %} 是控制结构(for/if 循环),{# #} 是模板注释。理解这个"变量优先级"是排查模板输出异常的钥匙:同名的变量,命令行 -e 覆盖 Playbook vars,Playbook vars 覆盖 Inventory 组变量,组变量覆盖主机变量,Role defaults 优先级最低。

tag 与 include/import 的定位

Ansible 提供了两套"复用与选择"机制,初学者常混淆。tags 是运行时选择器:给任务打标签后,可以用 --tags 只执行某类任务(如只跑配置下发不重启服务),不修改代码结构。includeinclude_tasks)与 importimport_tasks)是把任务文件拼进 Playbook 的机制:import 在解析阶段静态展开,行为可预测、调试直观;include 在执行阶段动态加载,支持循环、变量文件名和条件判断,但错误定位稍难。经验法则:能用 import 就不用 include,只有需要 loop 或按变量选择文件时才用 include。

Pull vs Push

Ansible 默认采用 Push 模式——控制节点主动连接受管节点触发任务。这适合一次性部署和即时变更。与之对比,Puppet 使用 Pull 模式——Agent 定期从 Master 拉取配置。Push 模式的优势在于即时性和低延迟,劣势是受管节点数量大时需额外调优(如 SSH 连接池)。

为什么 Ansible 用 YAML 而不是 JSON

YAML 和 JSON 都是数据序列化格式,但 Ansible 选择 YAML 有明确的设计考量:可读性——YAML 用缩进而非花括号表示结构,人类手写和审阅时认知负担更低;注释——YAML 原生支持 # 注释,JSON 不支持(Playbook 需要大量内联说明);多行字符串——YAML 的 |(保留换行)和 >(折叠换行)让内嵌 Shell 脚本和模板片段变得自然,而 JSON 需要转义所有换行符;锚点与合并——YAML 的 &/* 锚点机制可以复用配置片段,减少重复。JSON 的优势在于机器解析简单、与 JavaScript 生态无缝衔接,但配置管理的核心消费者是人而非机器,因此 YAML 是更优选择。

Ansible 如何通过 SSH 执行任务:底层原理

Ansible 执行一条任务的完整链路如下:① 控制节点将 Python 模块源码通过 SSH 的 SFTPSCP 传输到远程主机的 /tmp/.ansible/tmp/ 目录;② 在远程主机上执行 python3 /tmp/.ansible/tmp/xxx.py,模块代码读取 stdin 中的 JSON 参数并返回 JSON 结果;③ 控制节点收集 stdout 中的 JSON 输出,解析执行结果;④ 执行完毕后自动删除临时文件。整个过程无需在受管节点安装任何软件——唯一的前提是远程主机有 Python 3 和 SSH 服务。这也是为什么 Ansible 对 Python 版本敏感:ansible_python_interpreter 变量决定了模块在远程用哪个解释器执行。

Ansible vs Puppet vs SaltStack:方案对比

维度AnsiblePuppetSaltStack
架构无中心节点,SSH 直连Master-Agent(Pull)Master-Minion(Push/Pull 双模)
语言YAML(Playbook)DSL(Manifest)YAML + Jinja2
Agent无(SSH 即通信)需要安装 Puppet Agent需要安装 Salt Minion
通信SSH(标准端口 22)自定义协议(端口 8140)ZeroMQ(端口 4505/4506)
学习曲线低(会 SSH 即可上手)中高(DSL + 证书体系)中(YAML 但架构复杂)
大规模性能中等(SSH 并发瓶颈)高(Agent 缓存+Pull)高(ZeroMQ 高吞吐)
适用场景中小规模、快速上手大规模合规基线大规模+实时命令推送

选型建议:500 台以内、团队不熟悉配置管理 → Ansible;数千台、需要合规审计 → Puppet;需要实时推送+大规模执行 → SaltStack。

为什么推荐用 Playbook 而不是 ad-hoc 命令

Ad-hoc 命令适合一次性临时任务(如"在所有服务器上执行 uptime"),但生产环境中重复执行的任务应该写成 Playbook。原因有三:可审计——Playbook 文件入库后有版本历史,任何人可以追溯"谁改了什么";可复用——通过 Role 和变量机制,一份 Playbook 适配 dev/staging/prod 多个环境;幂等安全——Playbook 的任务声明式描述目标状态,可安全反复执行,而 ad-hoc 命令是过程式脚本,重复执行可能产生副作用。经验法则:执行一次后需要再次执行的任何操作,都应该写成 Playbook。

示例代码

1. 安装与 Inventory 配置

# 安装 Ansible(控制节点)
pip install ansible --user

# 验证安装
ansible --version
# 输出:
# ansible [core 2.17.0]  # 2.14 已于 2024.11 EOL
#   config file = /etc/ansible/ansible.cfg

# 创建主机清单 inventory.ini
cat > inventory.ini << 'EOF'
[webservers]
web1 ansible_host=192.168.1.10 ansible_user=ubuntu
web2 ansible_host=192.168.1.11

[databases]
db1 ansible_host=192.168.1.20

[all:vars]
ansible_python_interpreter=/usr/bin/python3
EOF

# 测试连通性(ping 模块是 ICMP 的隐喻,实际走 SSH 测试)
ansible all -i inventory.ini -m ping
# 输出:
# web1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
# web2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}

2. Ad-Hoc 命令实战

# 查看远程主机磁盘使用
ansible all -i inventory.ini -m shell -a "df -h"

# 安装软件包(需 sudo 权限)
ansible webservers -i inventory.ini -m apt -a "name=nginx state=latest" --become

# 复制文件到远程主机
ansible all -i inventory.ini -m copy -a "src=./app.conf dest=/etc/app/app.conf owner=root mode=0644"

# 重启服务
ansible webservers -i inventory.ini -m service -a "name=nginx state=restarted" --become

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

3. 编写第一个 Playbook

# deploy-nginx.yml
---
- name: 部署 Nginx Web 服务器
  hosts: webservers
  become: yes
  vars:
    nginx_port: 80
    server_name: example.com

  tasks:
    - name: 安装 Nginx
      apt:
        name: nginx
        state: present
        update_cache: yes

    - name: 创建网站目录
      file:
        path: /var/www/{{ server_name }}
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'

    - name: 部署虚拟主机配置
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/sites-available/{{ server_name }}
      notify: restart nginx

    - name: 启用站点
      file:
        src: /etc/nginx/sites-available/{{ server_name }}
        dest: /etc/nginx/sites-enabled/{{ server_name }}
        state: link
      notify: restart nginx

    - name: 部署首页
      copy:
        content: "<h1>Welcome to {{ server_name }}</h1>"
        dest: /var/www/{{ server_name }}/index.html
        owner: www-data
        group: www-data

  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

# 执行 Playbook
ansible-playbook -i inventory.ini deploy-nginx.yml

# 干运行(不执行变更,只报告将要做什么)
ansible-playbook -i inventory.ini deploy-nginx.yml --check

# 语法检查
ansible-playbook -i inventory.ini deploy-nginx.yml --syntax-check

4. Jinja2 模板示例

# templates/nginx.conf.j2
server {
    listen {{ nginx_port }};
    server_name {{ server_name }};

    root /var/www/{{ server_name }};
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    access_log /var/log/nginx/{{ server_name }}_access.log;
    error_log  /var/log/nginx/{{ server_name }}_error.log;
}

# 模板变量也可以在 Playbook 中通过 vars_files 引入:
# vars_files:
#   - vars/{{ env }}.yml

5. Role 组织

# 创建 Role 骨架
ansible-galaxy init roles/nginx
# 自动生成目录结构:
# roles/nginx/
# ├── tasks/main.yml        # 主任务列表
# ├── handlers/main.yml     # 处理器列表
# ├── templates/            # Jinja2 模板
# ├── files/                # 静态文件
# ├── vars/main.yml         # 高优先级变量
# ├── defaults/main.yml     # 默认变量(可被覆盖)
# ├── meta/main.yml         # 依赖和元信息
# └── README.md

# site.yml —— 引用 Role 的 Playbook
---
- name: 全网 Web 服务器部署
  hosts: webservers
  become: yes
  roles:
    - nginx
    - { role: monitoring, when: enable_monitoring | default(false) }

# 执行 site.yml
ansible-playbook -i inventory.ini site.yml

6. Ansible Vault 加密敏感数据

# 创建加密文件
ansible-vault create secrets.yml
# 输入密码后打开编辑器,写入内容:
# db_password: SuperSecret123
# api_key: sk-abc123def456

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

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

# 解密到明文(谨慎!)
ansible-vault decrypt secrets.yml

# 在 Playbook 中使用加密变量
# playbook.yml:
# - name: 使用加密变量
#   hosts: databases
#   vars_files:
#     - secrets.yml
#   tasks:
#     - name: 创建数据库用户
#       mysql_user:
#         name: app
#         password: "{{ db_password }}"
#         state: present

# 执行时传入 vault 密码
ansible-playbook -i inventory.ini playbook.yml --ask-vault-pass

# 或用密码文件(CI/CD 友好)
ansible-playbook -i inventory.ini playbook.yml --vault-password-file .vault_pass

7. Jinja2 进阶:过滤器与条件判断

# templates/app.conf.j2 —— 综合展示变量、过滤器、条件与循环
# 变量插值 + 过滤器(default、int、upper、replace)
server_name={{ server_name | default('localhost') }};
worker_processes={{ ansible_processor_vcpus | int * 2 }};

# 条件判断:不同环境渲染不同配置
{% if env == 'production' %}
keepalive_timeout 120;
{% else %}
keepalive_timeout 30;
{% endif %}

# for 循环:由变量列表批量生成 location 块
{% for item in extra_locations %}
location /{{ item.name }} {
    proxy_pass http://{{ item.upstream }};
}
{% endfor %}

# Playbook 中注册变量 + when 条件(模板外最常见的条件用法)
- name: 检查磁盘剩余空间
  shell: df -h / | tail -1 | awk '{print $5}' | tr -d '%'
  register: disk_usage

- name: 磁盘使用率告警
  debug:
    msg: "磁盘使用率 {{ disk_usage.stdout }}% 已超阈值"
  when: disk_usage.stdout | int > 80

# 模板渲染预览(--check 只报告不写入,--diff 显示差异)
ansible all -i inventory.ini -m template \
  -a "src=templates/app.conf.j2 dest=/etc/app.conf" --check --diff

# 调试变量值(临时任务)
ansible all -i inventory.ini -m debug -a "msg={{ ansible_processor_vcpus }}"

8. tag 与 include/import 实战

# tag:给任务打标,运行时按需挑选子集
- name: 全量部署
  hosts: webservers
  become: yes
  tasks:
    - name: 安装软件
      apt: name=nginx state=present
      tags: [install]
    - name: 下发配置
      template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
      tags: [config]
      notify: restart nginx
    - name: 重启服务
      service: name=nginx state=restarted
      tags: [restart]

# 只执行 install 标签的任务
ansible-playbook site.yml --tags install
# 跳过危险任务(如 restart)
ansible-playbook site.yml --skip-tags restart
# 查看所有可用标签
ansible-playbook site.yml --list-tags

# include(动态):支持循环与变量文件名
- name: 按主机角色加载任务
  include_tasks: "tasks/{{ role_item }}.yml"
  loop:
    - setup_nginx
    - setup_monitor
  when: ansible_os_family == "Debian"

# import(静态):解析期展开,行为可预测
- import_tasks: tasks/security.yml
# 区别总结:
# import 不支持 loop/when 包裹但调试友好,优先使用;
# include 支持动态场景(变量文件名/循环),用后注意用 -v 观察展开

9. 执行优化:forks、pipelining 与串行发布

# ansible.cfg —— 大批量主机执行优化
[defaults]
forks = 20               # 默认 5;每个 fork 一条并发通道,机器多时线性提速
gathering = smart        # 已缓存 facts 的主机跳过重新收集
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts_cache
fact_caching_timeout = 3600

[ssh_connection]
pipelining = True        # 减少 SSH 往返(官方推荐,需 sudo 免密配合)
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
# ControlPersist 复用已建立的 SSH 连接,百台规模提速可达 10 倍

# 时间对比验证
time ansible-playbook -i inventory.ini site.yml              # 默认配置
time ansible-playbook -i inventory.ini site.yml --forks=20   # 调优后

# 串行滚动:分批重启,避免全网同时中断
- name: 分批滚动发布
  hosts: webservers
  serial: 2             # 每批 2 台,全部成功才进入下一批
  tasks:
    - service: name=nginx state=restarted

# 单台卡住超时控制
ansible-playbook site.yml --forks=20 --timeout=15

常见错误

错误表现根因正确做法
FAILED! => {"msg": "Using a SSH password instead of a key is ..."}Ansible 默认要求 SSH 密钥认证,未配置密钥或未指定 -k配置 SSH 密钥对;或加 -k 参数交互式输入密码
FAILED! => {"msg": "sudo: a password is required"}远程用户需要 sudo 密码,但未传 --ask-become-pass配置 sudo NOPASSWD,或执行时加 --ask-become-pass
FAILED! => {"changed": false, "msg": "Aborted..."}YAML 缩进错误,Ansible 对缩进极其敏感用空格(2 格)而非 Tab;用 --syntax-check 验证
ERROR! 'template' is not a valid attribute for a Play将模块写在了 Play 级别而非 tasks 内部模块必须放在 tasks:handlers:
Playbook 执行成功但远程主机没有变化--check 模式仅报告不执行,或幂等性判定已是最新状态确认不是 --check 模式;用 -v 增加详细输出分析
Playbook 重复执行每次都报 changed使用 shell/command 模块执行了非幂等命令(如无判断地追加配置、执行安装脚本)优先用 apt/file/lineinfile 等声明式模块;必须用 shell 时用 creates:/removes: 参数或先检查状态
notify 了 handler 却不触发handler 名称与 notify 名称不一致,或前面的任务失败中断了通知链notify 与 handler 名称必须逐字一致;用 ansible-playbook -v 观察 RUNNING HANDLER 日志
模板渲染结果与预期不符(变量空/旧值)同名变量被不同来源覆盖,未理解变量优先级ansible -m debug -a "msg={{ var }}" 逐层验证;命令行 -e 优先级最高

最佳实践

实践原理示例
始终使用 --check--diff 预览变更在真正执行前了解 Playbook 将改动什么ansible-playbook site.yml --check --diff
将敏感数据放入 Vault 加密的变量文件防止密码/密钥在代码仓库中明文暴露ansible-vault create group_vars/all/vault.yml
为不同环境(dev/staging/prod)分离 Inventory避免操作错误的服务器组使用 -i inventory/production 明确指定
用 Role 组织可复用逻辑避免 Copy-Paste,提高代码复用率ansible-galaxy init roles/nginx 标准化结构
配置 SSH 控制主机持久连接降低重复 SSH 握手开销,大批量主机时可提速 10xansible.cfg 中设置 ssh_args = -o ControlMaster=auto -o ControlPersist=60s
lineinfile/blockinfile 代替 shell 追加这两个模块自带幂等检查,多次执行结果一致lineinfile: path=/etc/hosts line="10.0.0.5 db01"
为任务打 tag,按运维阶段挑选执行发布时只想执行 install 或 config 子集,避免全量重跑ansible-playbook site.yml --tags deploy --skip-tags restart
开启 fact caching 与 pipelining减少重复收集 facts 和 SSH 往返次数,百台集群提速明显gathering = smart + pipelining = True

练习题

  1. (概念)什么是 Ansible 的幂等性?为什么它是自动化配置管理的重要特性?
  2. (概念)Ansible 的 Push 模式与 Puppet 的 Pull 模式有何区别?各适合什么场景?
  3. (实操)安装 Ansible,编写一个 Inventory 包含两个组:webservers(web1, web2)和 databases(db1)。使用 Ad-Hoc 命令对所有主机执行 uptime
  4. (实操)创建一个 Playbook,在 webservers 组上执行以下操作:安装 nginx、启动并启用服务、部署一个自定义 index.html、确保防火墙开放 80/tcp。使用 --check 验证后再真正执行。
  5. (🔍 挑战)使用 ansible-galaxy init 创建一个名为 motd 的 Role,在每台主机上生成包含主机名、IP 地址和当前时间的 /etc/motd 文件。将 Playbook 应用到一组测试主机,验证 SSH 登录时显示的 Message of the Day 已更新。
点击查看答案
  1. (概念)幂等性指同一 Playbook 执行多次的结果和执行一次相同。在自动化配置管理中重要,因为 Playbook 可能被反复执行(定期一致性检查、恢复后重新应用等)——非幂等的操作会导致每次执行都产生副作用(如重复添加同一行配置、重复重启服务)。
  2. (概念)Ansible Push 模式由控制节点主动 SSH 连接到被管理节点执行任务,适合临时任务和批量命令;Puppet Pull 模式由代理定期从服务器拉取配置,适合长期、大规模的基础设施一致性管理。Push 适合灵活多变的运维操作,Pull 适合规模化的合规基线管理。
  3. (实操)Inventory 文件:[webservers] web1 ansible_host=10.0.0.1 web2 ansible_host=10.0.0.2 [databases] db1 ansible_host=10.0.0.3。Ad-Hoc 命令:ansible all -i inventory -m shell -a "uptime"。常遇到的坑:SSH 密钥认证未配置需要 -k 参数交互式输入密码,或被管理节点的 Python 版本不兼容。
  4. (实操)Playbook 要点:tasks: - name: install nginx apt: name=nginx state=present - name: start and enable systemd: name=nginx state=started enabled=yes - name: deploy index.html copy: src=index.html dest=/var/www/html/index.html - name: allow 80 ufw: rule=allow port=80 proto=tcp。先 --syntax-check--check 验证。--check 模式不会实际执行,但 Ansible 会尽力模拟执行结果。
  5. (🔍 挑战)ansible-galaxy init roles/motd 生成 Role 目录结构。在 tasks/main.yml 中使用 template 模块:- name: deploy motd template: src=motd.j2 dest=/etc/motdmotd.j2 模板内容:Welcome to {{ ansible_hostname }} IP: {{ ansible_default_ipv4.address }} Time: {{ ansible_date_time.iso8601 }}。Playbook 应用后 SSH 登录目标机器验证 /etc/motd 内容。注意 gather_facts: yes(默认)才能使用 ansible_* 变量。

学习检查点

学完本章后,请检验自己是否掌握以下内容:

检查项自测问题验证方法
概念理解能用自己的话解释 Ansible 的架构和工作原理尝试向他人讲解
命令操作能不查文档完成 Inventory 配置、Playbook 编写、模块调用在终端实际执行
原理掌握能说出 Ansible 的 SSH 连接机制和幂等性原理画出流程图
故障排查能独立排查连接错误、Playbook 执行失败、权限问题模拟故障并修复
最佳实践能说明为什么需要使用变量、循环和条件判断对比不同方案

本章总结

Ansible 以其无代理架构和声明式 YAML 语法,成为 Linux 运维领域最流行的自动化配置管理工具。核心工作流包括:定义 Inventory(管哪些机器)、编写 Playbook(做什么)、选择 Module(怎么做)、组织 Role(怎么复用)。幂等性设计让 Playbook 可以安全重复执行。Jinja2 模板实现配置动态化,Vault 保障敏感信息安全。

速查表

命令/操作用途
ansible all -m ping -i inventory测试 SSH 连通性
ansible group -m apt -a "name=pkg state=present" --become远程安装软件包
ansible group -m copy -a "src=... dest=..."远程复制文件
ansible-playbook site.yml执行 Playbook
ansible-playbook site.yml --check干运行
ansible-playbook site.yml --syntax-check语法检查
ansible-galaxy init roles/nginx创建 Role 骨架
ansible-vault create secrets.yml创建加密变量文件
ansible-vault edit secrets.yml编辑加密变量

学习路径建议

  • 学完本章后建议阅读 4.9:CI/CD 持续部署 CI/CD 持续部署(将 Playbook 集成到流水线)
  • 学完本章后建议阅读 ansible01 Ansible 生产级实战(Role 设计、执行策略与大规模管理)
  • 进阶可学习 ansible-lint 代码规范化和 Molecule 测试框架
  • 容器场景可对比了解 Docker Compose 和 Kubernetes 的声明式配置模型

延伸阅读

常见问题

Ansible 执行速度慢怎么办?
开启 pipelining:在 ansible.cfg 中设置 pipelining = True,减少 SSH 连接次数。调整 forks(并行数)到 20-50。关闭 gather_facts 如果不需要收集系统信息。使用 mitogen 插件(第三方)可大幅加速(已随 Ansible 6+ 弃用)。如果 hosts 很多,考虑分批滚动执行。
Ansible Vault 密码怎么管理?
不要硬编码密码到文件或仓库。推荐方法:使用 --vault-password-file 指向一个不在版本控制中的脚本;用 ansible-vault encrypt_string 加密单值;与密码管理器集成如 1password/hashicorp vault;CI/CD 中通过环境变量传递密码(用 --vault-password-file 脚本读取 $VAULT_PASS)。
Ansible Role 和 Playbook 的区别?
Playbook 是编排文件(剧本),定义"在哪些主机上执行什么任务"。Role 是结构化组织(角色),将 tasks、handlers、vars、template 按约定目录组织。Role 是可复用的组件单元,Playbook 是调用这些单元的场景。类似面向对象编程中 Class vs Main function。
↑ 回到顶部