Ansible 生产级自动化指南:Playbook、Handlers、Jinja2 全解析
一、什么是 Ansible 剧本(Playbook)?
Ansible 剧本(Playbook)是使用 YAML 格式编写的自动化脚本文件,用于定义一系列任务(Tasks),实现对多台主机的批量配置、部署、维护。
与临时命令(Ad-hoc)相比,剧本具有可重复执行、易于维护、支持复杂流程等优势,是企业级自动化的核心工具。
剧本 vs Ad-hoc 命令
特性 | Playbook(剧本) | Ad-hoc 命令(临时命令) |
---|---|---|
可重复性 | 高(文件化,可版本控制) | 低(命令行输入,难追溯) |
复杂任务支持 | 支持多任务、条件判断、循环等 | 仅支持单条命令 |
变量支持 | 支持多种变量定义方式 | 不支持或需命令行传参 |
典型应用场景 | 服务部署、配置管理、CI/CD 流水线 | 临时测试、快速排查、单次操作 |
💡 一句话总结:
Ad-hoc 是“即兴发挥”,Playbook 是“编排好的交响乐”。
二、剧本基础:YAML 语法与执行流程
1. 剧本基本结构
- name: 配置 Web 服务器集群hosts: webserversbecome: yesgather_facts: yestasks:- name: 安装 Nginxapt:name: nginxstate: present- name: 启动并启用 Nginx 服务systemd:name: nginxstate: startedenabled: yes
2. YAML 书写规范(必须遵守)
- 使用 空格缩进(推荐 2 空格),禁止使用 Tab
- 冒号后加空格:
name: Nginx
- 列表项以
-
开头 - 使用
---
开头(YAML 文档分隔符)
⚠️ 常见错误示例:
tasks:
- name: 安装软件包yum: name=httpd state=present # ❌ 错误:未换行,模块参数未对齐
正确写法:
tasks:- name: 安装 Apacheyum:name: httpdstate: present
三、实战案例
案例 1:: 创建目录并分发文件
1.创建目录/server/files/
2.把/etc/hosts文件发送过去/data/files/
- 中间转换步骤: 任务的步骤–>模块(命令行)
1. 创建目录/server/files/
ansible all -m file -a 'path=/data/files/state=directory'2. 分发
ansible all -m copy -a 'src=/etc/hosts dest=/data/files/ '
- 书写剧本
[root@m01 /server/playbooks]# cat dir_copy.yaml
---
- hosts: alltasks:- name: 01.mkdir dirfile:path: /data/filesstate: directory- name: 02.copy filecopy:src: /etc/hostsdest: /server/files/
案例 2:部署 Nginx + 静态网站(多任务编排)
目标:在
webservers
组部署 Nginx,并发布一个简单的前端页面。
# playbooks/deploy_nginx.yml
---
- name: 部署 Nginx 并发布网站hosts: webserversbecome: yesvars:web_root: /var/www/htmlsite_title: "Welcome to My Site"tasks:- name: 确保 Nginx 已安装package:name: nginxstate: present- name: 创建网站根目录file:path: "{{ web_root }}"state: directorymode: '0755'- name: 部署 index.html 页面template:src: templates/index.html.j2dest: "{{ web_root }}/index.html"mode: '0644'- name: 启动并启用 Nginx 服务systemd:name: nginxstate: startedenabled: yes- name: 确保防火墙放行 HTTPfirewalld:service: httppermanent: yesstate: enabledwhen: ansible_os_family == "RedHat"
🔍 说明:
- 使用
template
模块动态生成 HTML 页面(支持变量注入)when
条件判断:仅在 RHEL/CentOS 上配置防火墙vars
定义路径和标题,便于后期修改
案例 3:使用变量文件管理配置(vars_files
)
场景:多个剧本共用数据库连接信息。
# group_vars/all/db_vars.yml
db_host: "db.internal.prod"
db_port: 5432
db_user: "app_user"
db_password: "secure_password_123"
# playbooks/config_app.yml
---
- name: 配置应用服务hosts: app_serversbecome: yesvars_files:- group_vars/all/db_vars.yml # 引入变量文件tasks:- name: 创建应用配置文件template:src: templates/app.conf.j2dest: /opt/app/config/app.conf
# templates/app.conf.j2
[database]
host = {{ db_host }}
port = {{ db_port }}
user = {{ db_user }}
password = {{ db_password }}
优势:配置集中管理,避免硬编码。
案例 4:基于主机组的变量管理(group_vars
)
场景:不同环境(dev/staging/prod)使用不同配置。
group_vars/dev/vars.yml # dev 环境变量staging/vars.yml # staging 环境变量prod/vars.yml # prod 环境变量all/common.yml # 所有环境共用变量(如监控 agent 配置)
# group_vars/prod/vars.yml
app_env: "production"
log_level: "error"
backup_schedule: "0 2 * * *"
# playbooks/deploy_app.yml
---
- name: 部署应用(根据环境自动适配)hosts: allbecome: yestasks:- name: 输出当前环境debug:msg: "当前部署环境:{{ app_env }}"
最佳实践:
group_vars/all/
存放通用配置group_vars/<env>/
存放环境特有配置- 结合
ansible-playbook -i inventory_prod ...
实现多环境部署
案例 5:使用 Facts 变量获取主机信息
场景:生成系统健康报告。
# playbooks/system_report.yml
---
- name: 收集系统信息hosts: allgather_facts: yes # 默认开启tasks:- name: 输出主机信息debug:msg: |主机名: {{ ansible_hostname }}IP 地址: {{ ansible_default_ipv4.address }}操作系统: {{ ansible_distribution }} {{ ansible_distribution_version }}CPU 核心数: {{ ansible_processor_vcpus }}内存总量: {{ ansible_memtotal_mb }} MB磁盘空间: {{ ansible_mounts | selectattr('mount', '==', '/') | map(attribute='size_total') | first | int / 1024 / 1024 / 1024 | round(2) }} GB
🔍 Facts 常用变量:
{{ ansible_hostname }}
:主机名{{ ansible_default_ipv4.address }}
:主 IP{{ ansible_distribution }}
:系统类型{{ ansible_memtotal_mb }}
:内存(MB){{ ansible_processor_vcpus }}
:CPU 核心数
⚠️ 性能提示:若无需主机信息,可设置
gather_facts: no
加速执行。
案例 6:Register 变量 —— 捕获命令输出
场景:检查服务状态并根据结果执行操作。
# playbooks/check_service.yml
---
- name: 检查 Nginx 运行状态hosts: webserversbecome: yestasks:- name: 获取 Nginx 进程信息shell: ps aux | grep nginx | grep -v grepregister: nginx_statusignore_errors: yes # 即使无进程也不报错- name: 输出检查结果debug:msg: "Nginx 正在运行,PID: {{ nginx_status.stdout }}"when: nginx_status.rc == 0- name: 重启 Nginx(如果未运行)systemd:name: nginxstate: restartedwhen: nginx_status.rc != 0
Register 关键字段:
stdout
:标准输出stderr
:错误输出rc
:返回码(0 表示成功)changed
:是否改变了系统状态
四、流程控制:循环、判断与触发器
1. 循环(Loop)
应用场景:批量创建用户、目录、服务管理等。
基本循环(单变量)
- hosts: web01tasks:- name: 重启多个服务systemd:name: "{{ item }}"state: restartedloop:- nginx- mysql- redis
多变量循环(字典列表)
- hosts: web01tasks:- name: 批量创建用户user:name: "{{ item.name }}"uid: "{{ item.uid }}"groups: "{{ item.groups }}"state: presentloop:- { name: 'app_user', uid: '2001', groups: 'www-data' }- { name: 'db_user', uid: '2002', groups: 'mysql' }- { name: 'backup_user', uid: '2003', groups: 'backup' }
文件循环示例
- hosts: app_serverstasks:- name: 创建多个日志目录file:path: "/var/log/{{ item }}"state: directoryowner: rootgroup: rootmode: '0755'loop:- nginx- mysql- redis- application
建议:优先使用
loop
,with_items
已逐步被取代。
2. Handlers 触发器
应用场景:配置文件变更后才重启服务,避免无谓重启。
使用 Handlers 的正确方式
- hosts: web_serverstasks:- name: 配置 Nginxtemplate:src: templates/nginx.conf.j2dest: /etc/nginx/nginx.confbackup: yesnotify: 重新加载 Nginx- name: 配置 SSL 证书copy:src: files/ssl_cert.pemdest: /etc/nginx/ssl/cert.pemnotify: 重新加载 Nginxhandlers:- name: 重新加载 Nginxsystemd:name: nginxstate: reloaded
数据库配置变更示例
- hosts: db_serverstasks:- name: 更新 MySQL 配置template:src: templates/my.cnf.j2dest: /etc/mysql/my.cnfnotify: 重启 MySQL 服务- name: 调整内存参数lineinfile:path: /etc/mysql/my.cnfline: "innodb_buffer_pool_size = {{ db_memory }}G"regexp: "^innodb_buffer_pool_size"notify: 重启 MySQL 服务handlers:- name: 重启 MySQL 服务systemd:name: mysqlstate: restarted
多 Handler 触发示例
- hosts: monitoring_serverstasks:- name: 配置 Prometheustemplate:src: templates/prometheus.yml.j2dest: /etc/prometheus/prometheus.ymlnotify:- 重新加载 Prometheus- 重启 Alertmanager- name: 更新告警规则copy:src: files/alerts.rulesdest: /etc/prometheus/alerts.rulesnotify: 重新加载 Prometheushandlers:- name: 重新加载 Prometheussystemd:name: prometheusstate: reloaded- name: 重启 Alertmanagersystemd:name: alertmanagerstate: restarted
Handlers 最佳实践:
- 有意义的命名:handler 名称应清晰描述其作用
- 单一职责:每个 handler 只负责一个服务的操作
- 条件触发:只有相关配置真正变更时才执行
- 执行顺序:handlers 在 play 末尾按定义顺序执行
高级 Handler 用法
- hosts: alltasks:- name: 配置系统参数template:src: templates/sysctl.conf.j2dest: /etc/sysctl.confnotify: 应用内核参数- name: 配置 limits.conftemplate:src: templates/limits.conf.j2dest: /etc/security/limits.confnotify: 重新登录生效提示handlers:- name: 应用内核参数command: sysctl -plisten: "系统配置变更"- name: 重新登录生效提示debug:msg: "limits.conf 已更新,需要重新登录会话才能生效"listen: "系统配置变更"# 使用 listen 分组多个 handlers
- name: 重启所有服务debug:msg: "执行系统重启前检查"listen: "系统维护"
🔍 关键点:
notify
触发 handler 名称handlers
块必须放在tasks
之后- 只有文件实际变更时才会触发
- 使用
listen
可以实现 handler 分组
3. 条件判断(When)
应用场景:根据系统类型、主机名、变量等条件执行任务。
基于系统类型安装软件
- hosts: alltasks:- name: CentOS/RHEL 安装软件包yum:name: "{{ item }}"state: presentloop:- epel-release- htop- iotopwhen: ansible_os_family == "RedHat"- name: Ubuntu/Debian 安装软件包apt:name: "{{ item }}"state: presentloop:- htop- iotop- net-toolswhen: ansible_os_family == "Debian"
使用正则匹配(match)
- name: 生产环境特殊配置debug:msg: "应用生产环境安全配置"when: ansible_hostname is match("prod.*")- name: 开发环境宽松配置debug:msg: "应用开发环境调试配置"when: ansible_hostname is match("dev.*")
复合条件与逻辑运算
- name: 高内存服务器优化配置template:src: templates/high_memory.j2dest: /etc/systemd/system/custom.servicewhen: - ansible_memtotal_mb > 8192- ansible_processor_vcpus >= 4- deployment_env == "production"- name: 数据库服务器专用配置template:src: templates/db_optimization.j2dest: /etc/security/limits.d/db.confwhen: - "'db' in group_names"- ansible_architecture == "x86_64"- name: 紧急维护模式debug:msg: "进入维护模式,暂停非关键服务"when: maintenance_mode | bool and emergency_maintenance | bool
文件存在性检查
- name: 检查自定义配置是否存在stat:path: /etc/application/custom.confregister: custom_config- name: 应用自定义配置template:src: templates/application.conf.j2dest: /etc/application/application.confwhen: custom_config.stat.exists- name: 使用默认配置copy:src: files/default.confdest: /etc/application/application.confwhen: not custom_config.stat.exists
版本比较条件
- name: 检查内核版本shell: uname -rregister: kernel_version- name: 安装新版驱动yum:name: kmod-special-driverstate: latestwhen: kernel_version.stdout is version('5.0', '>=')- name: 安装旧版驱动yum:name: kmod-special-driver-legacystate: presentwhen: kernel_version.stdout is version('5.0', '<')
常用判断符号:
==
,!=
- 等于/不等于is match("正则")
,is not match("正则")
- 正则匹配>
,>=
,<
,<=
- 数值比较and
,or
- 逻辑组合in
,not in
- 包含判断is version('1.0', '>=')
- 版本比较
⚠️ 注意事项:
- 使用
| bool
过滤器确保布尔值比较正确- 复杂的条件判断建议拆分为多个条件或使用变量预处理
- 条件判断应保持可读性,避免过度复杂
五、Jinja2 模板引擎:动态配置文件
1. 基本使用(变量注入)
# templates/motd.j2
#######################################
欢迎访问 {{ ansible_hostname }}
IP 地址: {{ ansible_default_ipv4.address }}
内存: {{ ansible_memtotal_mb }} MB
CPU: {{ ansible_processor_vcpus }} 核
操作系统: {{ ansible_distribution }} {{ ansible_distribution_version }}
- name: 分发 MOTD 文件template:src: templates/motd.j2dest: /etc/motd
2. 模板中的判断(if/else)
# templates/keepalived.conf.j2
{% if ansible_hostname == "lb01" %}
state MASTER
priority 100
{% elif ansible_hostname == "lb02" %}
state BACKUP
priority 90
{% endif %}
3. 模板中的循环(for)
# 生成 IP 列表
{% for i in range(5, 11) %}
server 10.0.0.{{ i }}:8080;
{% endfor %}# 遍历列表
{% for name in ['oldboy', 'lidao'] %}
user {{ name }};
{% endfor %}
最佳实践:
- 所有需变量替换的文本文件使用
.j2
后缀- 使用
template
模块分发- 二进制或静态文件使用
copy
模块
六、高级技巧与调试指南
1. 调试与语法检查
命令 | 作用 |
---|---|
ansible-playbook --syntax-check playbook.yml | 仅检查语法 |
ansible-playbook -C playbook.yml | 模拟运行(不实际修改) |
ansible-playbook --step playbook.yml | 单步执行,交互式确认 |
2. 使用 Tags 进行任务分类
- name: 安装 Nginxyum:name: nginxstate: presenttags: install- name: 配置 Nginxtemplate:src: nginx.conf.j2dest: /etc/nginx/nginx.conftags: config- name: 重启 Nginxsystemd:name: nginxstate: restartedtags: restart
# 只运行安装任务
ansible-playbook -t install site.yml# 跳过重启任务
ansible-playbook --skip-tags restart site.yml# 查看所有标签
ansible-playbook --list-tags site.yml
3. 忽略非致命错误
- name: 创建可能已存在的目录file:path: /data/logsstate: directoryignore_errors: yes
适用场景:用户已存在、目录已创建等幂等性报错。
七、变量管理最佳实践总结
变量定义方式 | 适用场景 | 推荐指数 |
---|---|---|
vars: 在剧本中定义 | 单个 Play 内部使用,临时变量 | ⭐⭐⭐⭐ |
vars_files: 引入文件 | 多个 Play 共享同一组变量 | ⭐⭐⭐⭐ |
group_vars/ 分组变量 | 多环境、大型项目、团队协作(强烈推荐) | ⭐⭐⭐⭐⭐ |
Facts 变量 | 获取主机信息(IP、OS、硬件等) | ⭐⭐⭐⭐ |
register 注册变量 | 捕获命令输出,用于条件判断 | ⭐⭐⭐⭐ |
八、总结:Ansible 自动化核心原则
- 结构化:使用
group_vars
和host_vars
实现配置分离 - 可维护:为每个任务添加
name
和tags
- 幂等性:确保剧本可重复执行而不产生副作用
- 安全性:敏感信息使用 Ansible Vault 加密
- 测试先行:使用
--check
和--diff
模拟变更
最终建议:
- 剧本即代码,纳入版本控制(Git)
- 使用
pre-commit
钩子自动检查 YAML 语法- 结合 CI/CD 实现自动化部署流水线