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——加密敏感数据(密码、密钥、证书)的机制
知识关联
- 前置知识:4.5:网络故障排查 网络故障排查、2.1:Shell 脚本入门 Shell 脚本入门、2.11:SSH 深入 SSH 深入
- 后续影响:Ansible 是 4.9:CI/CD 持续部署 CI/CD 持续部署流程的核心配置管理工具
- 对比技术:Terraform(基础设施编排)、Puppet/SaltStack(同级竞品)
原理讲解
无代理架构
与传统配置管理工具(Puppet、Chef)需要在每台主机上安装 Agent 不同,Ansible 只在控制节点安装,通过 SSH 协议连接远程主机执行任务。这消除了受管节点上的 Agent 运维成本,也降低了被攻击面。控制节点将 Python 模块通过 SSH 传输到远程主机并执行,执行完毕后自动清理。
幂等性设计
Ansible 模块在设计上遵循幂等性原则:检查当前状态,只有当状态与目标不一致时才执行变更。例如 apt: name=nginx state=present 会先检查 nginx 是否已安装,已安装则跳过。这种"声明式"风格确保 Playbook 可以安全地反复执行。
Jinja2 模板渲染机制
Jinja2 是 Python 生态最流行的模板引擎,Ansible 的 template 模块渲染前会自动收集目标主机的 facts(如 ansible_hostname、ansible_processor_vcpus、ansible_default_ipv4),再结合 Playbook 的 vars、Inventory 的组变量与主机变量、Role 的 defaults 与 vars,按固定优先级合并后替换 {{ 表达式 }}。渲染产物是纯文本文件,任何配置文件(nginx.conf、systemd unit、crontab)都可以模板化。{{ }} 是变量插值,{% %} 是控制结构(for/if 循环),{# #} 是模板注释。理解这个"变量优先级"是排查模板输出异常的钥匙:同名的变量,命令行 -e 覆盖 Playbook vars,Playbook vars 覆盖 Inventory 组变量,组变量覆盖主机变量,Role defaults 优先级最低。
tag 与 include/import 的定位
Ansible 提供了两套"复用与选择"机制,初学者常混淆。tags 是运行时选择器:给任务打标签后,可以用 --tags 只执行某类任务(如只跑配置下发不重启服务),不修改代码结构。include(include_tasks)与 import(import_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 的 SFTP 或 SCP 传输到远程主机的 /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:方案对比
| 维度 | Ansible | Puppet | SaltStack |
|---|---|---|---|
| 架构 | 无中心节点,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 握手开销,大批量主机时可提速 10x | ansible.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 |
练习题
- (概念)什么是 Ansible 的幂等性?为什么它是自动化配置管理的重要特性?
- (概念)Ansible 的 Push 模式与 Puppet 的 Pull 模式有何区别?各适合什么场景?
- (实操)安装 Ansible,编写一个 Inventory 包含两个组:
webservers(web1, web2)和databases(db1)。使用 Ad-Hoc 命令对所有主机执行uptime。 - (实操)创建一个 Playbook,在 webservers 组上执行以下操作:安装 nginx、启动并启用服务、部署一个自定义 index.html、确保防火墙开放 80/tcp。使用
--check验证后再真正执行。 - (🔍 挑战)使用
ansible-galaxy init创建一个名为motd的 Role,在每台主机上生成包含主机名、IP 地址和当前时间的/etc/motd文件。将 Playbook 应用到一组测试主机,验证 SSH 登录时显示的 Message of the Day 已更新。
点击查看答案
- (概念)幂等性指同一 Playbook 执行多次的结果和执行一次相同。在自动化配置管理中重要,因为 Playbook 可能被反复执行(定期一致性检查、恢复后重新应用等)——非幂等的操作会导致每次执行都产生副作用(如重复添加同一行配置、重复重启服务)。
- (概念)Ansible Push 模式由控制节点主动 SSH 连接到被管理节点执行任务,适合临时任务和批量命令;Puppet Pull 模式由代理定期从服务器拉取配置,适合长期、大规模的基础设施一致性管理。Push 适合灵活多变的运维操作,Pull 适合规模化的合规基线管理。
- (实操)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 版本不兼容。 - (实操)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 会尽力模拟执行结果。 - (🔍 挑战)
ansible-galaxy init roles/motd生成 Role 目录结构。在tasks/main.yml中使用template模块:- name: deploy motd template: src=motd.j2 dest=/etc/motd。motd.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 官方文档
- Playbook 编写指南
- 内置模块列表
- 推荐书籍:《Ansible 权威指南》