一、什么是 Ansible 剧本(Playbook)?
Ansible 剧本(Playbook)是使用 YAML 格式 编写的自动化脚本文件,用于定义一系列任务(Tasks),实现对多台主机的批量配置、部署、维护。
与临时命令(Ad-hoc)相比,剧本具有可重复执行、易于维护、支持复杂流程等优势,是企业级自动化的核心工具。
剧本 vs Ad-hoc 命令
特性 | Playbook(剧本) | Ad-hoc 命令(临时命令) |
---|---|---|
可重复性 | 高(文件化,可版本控制) | 低(命令行输入,难追溯) |
复杂任务支持 | 支持多任务、条件判断、循环等 | 仅支持单条命令 |
变量支持 | 支持多种变量定义方式 | 不支持或需命令行传参 |
典型应用场景 | 服务部署、配置管理、CI/CD 流水线 | 临时测试、快速排查、单次操作 |
💡 一句话总结 :
Ad-hoc 是"即兴发挥",Playbook 是"编排好的交响乐"。
二、剧本基础:YAML 语法与执行流程
1. 剧本基本结构
yaml
- name: 配置 Web 服务器集群
hosts: webservers
become: yes
gather_facts: yes
tasks:
- name: 安装 Nginx
apt:
name: nginx
state: present
- name: 启动并启用 Nginx 服务
systemd:
name: nginx
state: started
enabled: yes
2. YAML 书写规范(必须遵守)
- 使用 空格缩进 (推荐 2 空格),禁止使用 Tab
- 冒号后加空格:
name: Nginx
- 列表项以
-
开头 - 使用
---
开头(YAML 文档分隔符)
⚠️ 常见错误示例:
yaml
tasks:
- name: 安装软件包
yum: name=httpd state=present # ❌ 错误:未换行,模块参数未对齐
正确写法:
yaml
tasks:
- name: 安装 Apache
yum:
name: httpd
state: present
三、实战案例
案例 1:: 创建目录并分发文件
1.创建目录/server/files/
2.把/etc/hosts文件发送过去/data/files/
- 中间转换步骤: 任务的步骤-->模块(命令行)
sh
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/ '
- 书写剧本
sh
[root@m01 /server/playbooks]# cat dir_copy.yaml
---
- hosts: all
tasks:
- name: 01.mkdir dir
file:
path: /data/files
state: directory
- name: 02.copy file
copy:
src: /etc/hosts
dest: /server/files/
案例 2:部署 Nginx + 静态网站(多任务编排)
目标 :在
webservers
组部署 Nginx,并发布一个简单的前端页面。
yaml
# playbooks/deploy_nginx.yml
---
- name: 部署 Nginx 并发布网站
hosts: webservers
become: yes
vars:
web_root: /var/www/html
site_title: "Welcome to My Site"
tasks:
- name: 确保 Nginx 已安装
package:
name: nginx
state: present
- name: 创建网站根目录
file:
path: "{{ web_root }}"
state: directory
mode: '0755'
- name: 部署 index.html 页面
template:
src: templates/index.html.j2
dest: "{{ web_root }}/index.html"
mode: '0644'
- name: 启动并启用 Nginx 服务
systemd:
name: nginx
state: started
enabled: yes
- name: 确保防火墙放行 HTTP
firewalld:
service: http
permanent: yes
state: enabled
when: ansible_os_family == "RedHat"
🔍 说明:
- 使用
template
模块动态生成 HTML 页面(支持变量注入)when
条件判断:仅在 RHEL/CentOS 上配置防火墙vars
定义路径和标题,便于后期修改
案例 3:使用变量文件管理配置(vars_files
)
场景:多个剧本共用数据库连接信息。
yaml
# group_vars/all/db_vars.yml
db_host: "db.internal.prod"
db_port: 5432
db_user: "app_user"
db_password: "secure_password_123"
yaml
# playbooks/config_app.yml
---
- name: 配置应用服务
hosts: app_servers
become: yes
vars_files:
- group_vars/all/db_vars.yml # 引入变量文件
tasks:
- name: 创建应用配置文件
template:
src: templates/app.conf.j2
dest: /opt/app/config/app.conf
jinja2
# 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 配置)
yaml
# group_vars/prod/vars.yml
app_env: "production"
log_level: "error"
backup_schedule: "0 2 * * *"
yaml
# playbooks/deploy_app.yml
---
- name: 部署应用(根据环境自动适配)
hosts: all
become: yes
tasks:
- name: 输出当前环境
debug:
msg: "当前部署环境:{{ app_env }}"
最佳实践:
group_vars/all/
存放通用配置group_vars/<env>/
存放环境特有配置- 结合
ansible-playbook -i inventory_prod ...
实现多环境部署
案例 5:使用 Facts 变量获取主机信息
场景:生成系统健康报告。
yaml
# playbooks/system_report.yml
---
- name: 收集系统信息
hosts: all
gather_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 变量 ------ 捕获命令输出
场景:检查服务状态并根据结果执行操作。
yaml
# playbooks/check_service.yml
---
- name: 检查 Nginx 运行状态
hosts: webservers
become: yes
tasks:
- name: 获取 Nginx 进程信息
shell: ps aux | grep nginx | grep -v grep
register: nginx_status
ignore_errors: yes # 即使无进程也不报错
- name: 输出检查结果
debug:
msg: "Nginx 正在运行,PID: {{ nginx_status.stdout }}"
when: nginx_status.rc == 0
- name: 重启 Nginx(如果未运行)
systemd:
name: nginx
state: restarted
when: nginx_status.rc != 0
Register 关键字段:
stdout
:标准输出stderr
:错误输出rc
:返回码(0 表示成功)changed
:是否改变了系统状态
四、流程控制:循环、判断与触发器
1. 循环(Loop)
应用场景:批量创建用户、目录、服务管理等。
基本循环(单变量)
yaml
- hosts: web01
tasks:
- name: 重启多个服务
systemd:
name: "{{ item }}"
state: restarted
loop:
- nginx
- mysql
- redis
多变量循环(字典列表)
yaml
- hosts: web01
tasks:
- name: 批量创建用户
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
groups: "{{ item.groups }}"
state: present
loop:
- { name: 'app_user', uid: '2001', groups: 'www-data' }
- { name: 'db_user', uid: '2002', groups: 'mysql' }
- { name: 'backup_user', uid: '2003', groups: 'backup' }
文件循环示例
yaml
- hosts: app_servers
tasks:
- name: 创建多个日志目录
file:
path: "/var/log/{{ item }}"
state: directory
owner: root
group: root
mode: '0755'
loop:
- nginx
- mysql
- redis
- application
建议 :优先使用
loop
,with_items
已逐步被取代。
2. Handlers 触发器
应用场景:配置文件变更后才重启服务,避免无谓重启。
使用 Handlers 的正确方式
yaml
- hosts: web_servers
tasks:
- name: 配置 Nginx
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
backup: yes
notify: 重新加载 Nginx
- name: 配置 SSL 证书
copy:
src: files/ssl_cert.pem
dest: /etc/nginx/ssl/cert.pem
notify: 重新加载 Nginx
handlers:
- name: 重新加载 Nginx
systemd:
name: nginx
state: reloaded
数据库配置变更示例
yaml
- hosts: db_servers
tasks:
- name: 更新 MySQL 配置
template:
src: templates/my.cnf.j2
dest: /etc/mysql/my.cnf
notify: 重启 MySQL 服务
- name: 调整内存参数
lineinfile:
path: /etc/mysql/my.cnf
line: "innodb_buffer_pool_size = {{ db_memory }}G"
regexp: "^innodb_buffer_pool_size"
notify: 重启 MySQL 服务
handlers:
- name: 重启 MySQL 服务
systemd:
name: mysql
state: restarted
多 Handler 触发示例
yaml
- hosts: monitoring_servers
tasks:
- name: 配置 Prometheus
template:
src: templates/prometheus.yml.j2
dest: /etc/prometheus/prometheus.yml
notify:
- 重新加载 Prometheus
- 重启 Alertmanager
- name: 更新告警规则
copy:
src: files/alerts.rules
dest: /etc/prometheus/alerts.rules
notify: 重新加载 Prometheus
handlers:
- name: 重新加载 Prometheus
systemd:
name: prometheus
state: reloaded
- name: 重启 Alertmanager
systemd:
name: alertmanager
state: restarted
Handlers 最佳实践:
- 有意义的命名:handler 名称应清晰描述其作用
- 单一职责:每个 handler 只负责一个服务的操作
- 条件触发:只有相关配置真正变更时才执行
- 执行顺序:handlers 在 play 末尾按定义顺序执行
高级 Handler 用法
yaml
- hosts: all
tasks:
- name: 配置系统参数
template:
src: templates/sysctl.conf.j2
dest: /etc/sysctl.conf
notify: 应用内核参数
- name: 配置 limits.conf
template:
src: templates/limits.conf.j2
dest: /etc/security/limits.conf
notify: 重新登录生效提示
handlers:
- name: 应用内核参数
command: sysctl -p
listen: "系统配置变更"
- name: 重新登录生效提示
debug:
msg: "limits.conf 已更新,需要重新登录会话才能生效"
listen: "系统配置变更"
# 使用 listen 分组多个 handlers
- name: 重启所有服务
debug:
msg: "执行系统重启前检查"
listen: "系统维护"
🔍 关键点:
notify
触发 handler 名称handlers
块必须放在tasks
之后- 只有文件实际变更时才会触发
- 使用
listen
可以实现 handler 分组
3. 条件判断(When)
应用场景:根据系统类型、主机名、变量等条件执行任务。
基于系统类型安装软件
yaml
- hosts: all
tasks:
- name: CentOS/RHEL 安装软件包
yum:
name: "{{ item }}"
state: present
loop:
- epel-release
- htop
- iotop
when: ansible_os_family == "RedHat"
- name: Ubuntu/Debian 安装软件包
apt:
name: "{{ item }}"
state: present
loop:
- htop
- iotop
- net-tools
when: ansible_os_family == "Debian"
使用正则匹配(match)
yaml
- name: 生产环境特殊配置
debug:
msg: "应用生产环境安全配置"
when: ansible_hostname is match("prod.*")
- name: 开发环境宽松配置
debug:
msg: "应用开发环境调试配置"
when: ansible_hostname is match("dev.*")
复合条件与逻辑运算
yaml
- name: 高内存服务器优化配置
template:
src: templates/high_memory.j2
dest: /etc/systemd/system/custom.service
when:
- ansible_memtotal_mb > 8192
- ansible_processor_vcpus >= 4
- deployment_env == "production"
- name: 数据库服务器专用配置
template:
src: templates/db_optimization.j2
dest: /etc/security/limits.d/db.conf
when:
- "'db' in group_names"
- ansible_architecture == "x86_64"
- name: 紧急维护模式
debug:
msg: "进入维护模式,暂停非关键服务"
when: maintenance_mode | bool and emergency_maintenance | bool
文件存在性检查
yaml
- name: 检查自定义配置是否存在
stat:
path: /etc/application/custom.conf
register: custom_config
- name: 应用自定义配置
template:
src: templates/application.conf.j2
dest: /etc/application/application.conf
when: custom_config.stat.exists
- name: 使用默认配置
copy:
src: files/default.conf
dest: /etc/application/application.conf
when: not custom_config.stat.exists
版本比较条件
yaml
- name: 检查内核版本
shell: uname -r
register: kernel_version
- name: 安装新版驱动
yum:
name: kmod-special-driver
state: latest
when: kernel_version.stdout is version('5.0', '>=')
- name: 安装旧版驱动
yum:
name: kmod-special-driver-legacy
state: present
when: kernel_version.stdout is version('5.0', '<')
常用判断符号:
==
,!=
- 等于/不等于
is match("正则")
,is not match("正则")
- 正则匹配
>
,>=
,<
,<=
- 数值比较
and
,or
- 逻辑组合
in
,not in
- 包含判断
is version('1.0', '>=')
- 版本比较
⚠️ 注意事项:使用
| bool
过滤器确保布尔值比较正确复杂的条件判断建议拆分为多个条件或使用变量预处理
条件判断应保持可读性,避免过度复杂
五、Jinja2 模板引擎:动态配置文件
1. 基本使用(变量注入)
jinja2
# templates/motd.j2
#######################################
欢迎访问 {{ ansible_hostname }}
IP 地址: {{ ansible_default_ipv4.address }}
内存: {{ ansible_memtotal_mb }} MB
CPU: {{ ansible_processor_vcpus }} 核
操作系统: {{ ansible_distribution }} {{ ansible_distribution_version }}
yaml
- name: 分发 MOTD 文件
template:
src: templates/motd.j2
dest: /etc/motd
2. 模板中的判断(if/else)
jinja2
# templates/keepalived.conf.j2
{% if ansible_hostname == "lb01" %}
state MASTER
priority 100
{% elif ansible_hostname == "lb02" %}
state BACKUP
priority 90
{% endif %}
3. 模板中的循环(for)
jinja2
# 生成 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 进行任务分类
yaml
- name: 安装 Nginx
yum:
name: nginx
state: present
tags: install
- name: 配置 Nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
tags: config
- name: 重启 Nginx
systemd:
name: nginx
state: restarted
tags: restart
bash
# 只运行安装任务
ansible-playbook -t install site.yml
# 跳过重启任务
ansible-playbook --skip-tags restart site.yml
# 查看所有标签
ansible-playbook --list-tags site.yml
3. 忽略非致命错误
yaml
- name: 创建可能已存在的目录
file:
path: /data/logs
state: directory
ignore_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 实现自动化部署流水线