ansible-playbook
Playbook 是 Ansible 的核心功能,使用 YAML 格式编写,可以定义复杂的工作流程
语法结构
ansible-playbook playbook.yml参数
| 参数 | 简写 | 说明 | 经典案例 |
|---|---|---|---|
--inventory | -i | 指定库存文件路径 | ansible-playbook -i hosts.ini site.yml |
--list-hosts | - | 列出匹配的主机 | ansible-playbook play.yml --list-hosts |
--list-tasks | - | 列出将要执行的任务 | ansible-playbook deploy.yml --list-tasks |
--list-tags | - | 列出可用标签 | ansible-playbook setup.yml --list-tags |
--limit | - | 限制执行到特定主机 | ansible-playbook play.yml --limit web01 |
--start-at-task | - | 从指定任务开始执行 | ansible-playbook rollback.yml --start-at-task="stop service" |
--step | - | 交互式逐步执行 | ansible-playbook critical.yml --step |
--check | -C | 模拟执行(dry-run) | ansible-playbook change.yml --check |
--tags | -t | 只运行指定标签任务 | ansible-playbook deploy.yml --tags=config |
--skip-tags | - | 跳过指定标签任务 | ansible-playbook deploy.yml --skip-tags=test |
--user | -u | 指定SSH用户 | ansible-playbook play.yml -u admin |
--ask-pass | -k | 提示输入SSH密码 | ansible-playbook play.yml -k |
--private-key | - | 指定私钥文件 | ansible-playbook play.yml --private-key=~/.ssh/deploy_key |
--ask-become-pass | -K | 提示输入特权密码 | ansible-playbook play.yml -K |
--become | -b | 启用特权升级 | ansible-playbook play.yml --become |
--become-method | - | 指定特权方法 | ansible-playbook play.yml --become-method=su |
--become-user | - | 指定特权用户 | ansible-playbook play.yml --become-user=oracle |
--verbose | -v | 详细输出模式 | ansible-playbook debug.yml -vvv |
--syntax-check | - | 只检查语法 | ansible-playbook new.yml --syntax-check |
--extra-vars | -e | 传递额外变量 | ansible-playbook deploy.yml --extra-vars "version=2.4" |
--vault-id | - | 指定vault身份 | ansible-playbook secret.yml --vault-id @prompt |
--flush-cache | - | 清除fact缓存 | ansible-playbook play.yml --flush-cache |
--force-handlers | - | 强制运行handlers | ansible-playbook play.yml --force-handlers |
查看详细日志
ansible-playbook playbook.yml -vvv # 查看详细日志Playbook 结构
---
# Playbook 基础定义
- name: Playbook描述 # Playbook名称(必填)
hosts: target_hosts # 目标主机/组(必填)
gather_facts: true # 是否开启内置变量(默认true)
become: yes # 是否提权(默认no)
become_user: root # 提权用户(默认root)
vars: # 定义变量
http_port: 80
db_host: "db01.example.com"
vars_files: # 引用外部变量文件
- vars/common.yml
vars_prompt: # 运行时交互式输入变量
- name: "db_password"
prompt: "Enter DB password"
private: yes
# Pre-tasks(在roles之前执行的任务)
pre_tasks:
- name: 前置任务示例
debug:
msg: "正在执行初始化..."
# Handlers(由notify触发)
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
# Roles 调用
roles:
- role: base_setup # 基础角色
vars: # 角色专用变量
timezone: "Asia/Shanghai"
- { role: web_server, tags: web } # 简写格式
# Tasks(主任务列表)
tasks:
- name: 安装Nginx # 任务名称(必填)
apt: # 模块名称(必填)
name: nginx
state: latest
tags: install # 任务标签
when: ansible_os_family == "Debian" # 条件判断
notify: restart nginx # 触发handler
register: install_result # 注册变量
retries: 3 # 重试次数
delay: 10 # 重试间隔(秒)
ignore_errors: yes # 忽略错误
changed_when: false # 强制不标记为changed
async: 3600 # 异步任务超时时间
poll: 30 # 异步轮询间隔
- name: 使用循环
copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
loop: # 循环结构
- { src: file1, dest: /tmp/ }
- { src: file2, dest: /opt/ }
loop_control: # 循环控制
label: "{{ item.src }}" # 简化输出显示
# Post-tasks(在roles之后执行的任务)
post_tasks:
- name: 清理临时文件
file:
path: "/tmp/{{ app_name }}"
state: absent
# 块结构(错误处理)
tasks:
- block: # 块开始
- name: 高风险操作
shell: /opt/scripts/dangerous.sh
rescue: # 错误处理
- name: 错误恢复
debug:
msg: "操作失败,已回滚"
always: # 始终执行
- name: 清理资源
file:
path: /tmp/lockfile
state: absent
# Tags 全局定义
tags:
- always # 始终执行
- never # 跳过执行(需--skip-tags触发)- 执行顺序:收集facts → pre*tasks → roles → tasks → post*tasks → handlers
- 特殊标记
tags: 允许选择性执行任务(--tags/--skip-tags)when: 条件判断(支持Jinja2表达式)register: 保存任务结果供后续使用block/rescue/always: 异常处理三件套
核心组件说明
| 组件 | 说明 | 示例 |
|---|---|---|
| hosts | 目标主机组 | webservers |
| vars | 变量定义 | http_port: 80 |
| tasks | 任务列表 | 见下文示例 |
| handlers | 触发器 | notify: restart apache |
| roles | 角色引用 | roles: [common, web] |
关键字
Ansible Playbook 使用 YAML 格式编写,包含多个关键字用于定义配置和自动化任务。
核心结构关键字
| 关键字 | 说明 | 示例 |
|---|---|---|
hosts | 指定目标主机或组 | hosts: webservers |
tasks | 定义要执行的任务列表 | tasks: - name: Install package |
vars | 定义变量 | vars: http_port: 80 |
handlers | 定义处理器(触发执行的任务) | handlers: - name: restart nginx |
become | 启用特权升级 | become: yes |
become_user | 指定特权用户 | become_user: root |
become_method | 指定特权升级方法 | become_method: sudo |
任务控制关键字
| 关键字 | 说明 | 示例 |
|---|---|---|
name | 任务或块的描述性名称 | name: Install Nginx |
when | 条件执行 | when: ansible_os_family == "Debian" |
register | 保存任务输出到变量 | register: result |
ignore_errors | 忽略任务错误 | ignore_errors: yes |
changed_when | 自定义"changed"状态 | changed_when: false |
failed_when | 自定义"failed"状态 | failed_when: "'ERROR' in command_result.stdout" |
loop | 循环执行任务 | loop: "" |
until | 重试直到条件满足 | until: result.rc == 0 |
retries | 重试次数 | retries: 3 |
delay | 重试间隔(秒) | delay: 10 |
模块相关关键字
| 关键字 | 说明 | 示例 |
|---|---|---|
action | 指定模块和参数 | action: copy src=a.txt dest=/tmp |
local_action | 在控制节点上执行 | local_action: command echo hello |
delegate_to | 委托任务到特定主机 | delegate_to: 192.168.1.100 |
run_once | 只运行一次 | run_once: yes |
变量和事实关键字
| 关键字 | 说明 | 示例 |
|---|---|---|
set_fact | 设置事实变量 | set_fact: my_var: "value" |
include_vars | 从文件加载变量 | include_vars: file: vars.yml |
vars_files | 包含变量文件 | vars_files: - secrets.yml |
facts | 是否收集事实 | gather_facts: no |
组织和复用关键字
| 关键字 | 说明 | 示例 |
|---|---|---|
include_tasks | 包含任务文件 | include_tasks: setup.yml |
import_tasks | 静态导入任务文件 | import_tasks: install.yml |
roles | 包含角色 | roles: - common - webserver |
include_role | 动态包含角色 | include_role: name: mysql |
import_role | 静态导入角色 | import_role: name: nginx |
block | 任务分组 | block: - name: Task 1 - name: Task 2 |
rescue | 错误处理块 | rescue: - debug: msg: "Recovered from error" |
always | 总是执行的块 | always: - debug: msg: "This always runs" |
高级控制关键字
| 关键字 | 说明 | 示例 |
|---|---|---|
tags | 为任务打标签 | tags: - install - config |
no_log | 隐藏任务输出 | no_log: true |
async | 异步执行 | async: 60 |
poll | 异步轮询间隔 | poll: 5 |
throttle | 限制并发数 | throttle: 5 |
serial | 滚动更新批次大小 | serial: 2 |
任务(Tasks)编写规范
基本结构
tasks:
- name: 描述性任务名称 # 任务描述(必填)
module_name: # 模块名
param1: value1 # 模块参数
param2: value2
register: result_var # 保存任务输出到变量
when: condition # 条件执行
ignore_errors: yes|no # 是否忽略错误
tags: # 任务标签
- tag1
become: yes|no # 是否提权
become_user: root # 提权用户
loop: # 循环
- item1
- item2
retries: 3 # 重试次数
delay: 10 # 重试间隔(秒)变量管理
变量定义方式
vars 自定义变量
vars:
http_port: 80
server_name: "example.com"vars_files 引入外部变量文件
vars_files:
- vars/common.yml
- vars/secrets.yml # 通常用于存放敏感信息在 Inventory 文件中定义
[webservers]
web1.example.com http_port=8080
web2.example.com
[webservers:vars]
server_name="webcluster.example.com"使用 include_vars 动态加载
tasks:
- name: 加载变量文件
include_vars: "vars/{{ env }}.yml"通过命令行传递变量
ansible-playbook playbook.yml --extra-vars "http_port=8080"set_fact 临时变量
tasks:
- name: 设置临时变量
set_fact:
temp_var: "临时值"变量优先级顺序
Ansible 变量遵循特定的优先级顺序(从低到高):
- 命令行值 (
--extra-vars) - 角色默认值 (
roles/role/defaults/main.yml) - Inventory 文件或脚本组变量
- Inventory 文件或脚本主机变量
- Playbook 中的
vars定义 - Playbook 中的
vars_files定义 - 注册变量 (
register) set_fact设置的变量- 角色变量 (
roles/role/vars/main.yml) - 块变量 (
block中的vars) - 任务变量 (
task中的vars)
变量类型
简单变量
app_name: "MyApp"
version: 1.0列表/数组变量
packages:
- nginx
- mysql-server
- php-fpm字典/哈希变量
user_info:
name: "john"
uid: 1001
groups: "admin,wheel"布尔变量
enable_feature: true
debug_mode: no变量使用方式
基本引用
tasks:
- name: 使用变量
debug:
msg: "服务器端口是 {{ http_port }}"访问字典变量
user_name: "{{ user_info.name }}"访问列表变量
first_package: "{{ packages[0] }}"变量过滤器
lowercase_name: "{{ app_name | lower }}"Ansible 内置变量
---
- hosts: 主机群
# 启用ansible 内置变量(setup 模块中的变量名)
gather_facts: True
tasks:
- name: 显示主机IP
debug:
msg: "主机IP是 {{ ansible_default_ipv4.address }}"注册变量 (register)
tasks:
- name: 检查服务状态
command: systemctl status nginx
register: nginx_status # 将执行结果注册到变量中
- name: 显示结果
debug:
var: nginx_status.stdout # 调用变量输出结果完整示例
---
- name: 变量使用示例
hosts: webservers
become: yes
# 启用ansible 内置变量(setup 模块中的变量名)
gather_facts: True
vars:
app_name: "MyWebApp"
app_version: "1.2.3"
ports:
http: 80
https: 443
users:
- name: "admin"
role: "superuser"
- name: "guest"
role: "readonly"
vars_files:
- vars/db_settings.yml
tasks:
- name: 显示应用信息
debug:
msg: "正在部署 {{ app_name }} 版本 {{ app_version }}"
- name: 确保用户存在
user:
name: "{{ item.name }}"
groups: "{{ item.role }}"
loop: "{{ users }}"
- name: 检查数据库连接
command: "mysql -h {{ db_host }} -u {{ db_user }} -p{{ db_password }} -e 'SHOW DATABASES'"
register: db_check
ignore_errors: yes
- name: 显示检查结果
debug:
msg: "数据库连接 {{ '成功' if db_check.rc == 0 else '失败' }}"
- name: 设置动态端口
set_fact:
dynamic_port: "{{ ports.http + 1000 }}"
- name: 使用动态端口
debug:
msg: "动态端口是 {{ dynamic_port | default('未设置') }}"处理程序(Handlers)字段
Handlers 是 Ansible 中一种特殊的任务,用于在 playbook 中实现"触发-执行"机制。它们通常用于服务重启、配置重载等需要在其他任务完成后执行的操作。
基本概念
Handlers 具有以下特点:
- 由普通任务通过
notify触发 - 只在所有普通任务执行完毕后运行
- 即使被多次通知,也只会执行一次
- 按照定义的顺序执行
基本语法
handlers:
- name: <handler名称>
<模块>: <参数>核心关键字
notify - 触发处理程序
在任务中使用 notify 来触发 handler:
tasks:
- name: 复制配置文件
copy:
src: files/nginx.conf
dest: /etc/nginx/nginx.conf
notify: restart nginx # 触发名为"restart nginx"的handler
handlers:
- name: restart nginxlisten - 监听组事件(Ansible 2.2+)
允许多个任务触发同一组 handlers:
handlers:
- name: restart services
listen: "restart web services"
service:
name: "{{ item }}"
state: restarted
loop: [nginx, apache2]触发方式:
tasks:
- name: 更新配置
template:
src: template.j2
dest: /etc/app.conf
notify: "restart web services" # 触发listen名为“restart web services”的handler完整示例
---
- name: Web服务器配置示例
hosts: webservers
become: yes
vars:
app_port: 8080
tasks:
- name: 安装Nginx
apt:
name: nginx
state: latest
notify: enable nginx # 触发enable handler
- name: 配置Nginx
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify:
- reload nginx # 触发reload handler
- "restart web services" # 触发listen组
- name: 配置防火墙
ufw:
rule: allow
port: "{{ app_port }}"
notify: "restart web services"
- name: 确保所有handlers立即执行
meta: flush_handlers
handlers:
- name: enable nginx
service:
name: nginx
enabled: yes
- name: reload nginx
service:
name: nginx
state: reloaded
- name: restart all services
listen: "restart web services"
service:
name: "{{ item }}"
state: restarted
loop:
- nginx
- ufw条件判断
条件判断是 Ansible Playbook 中实现逻辑控制的核心功能,它允许您基于变量、事实或任务执行结果来决定是否执行特定任务或 play。
基础条件判断
when 语句基本用法
tasks:
- name: 只在Ubuntu系统上执行
apt:
name: nginx
state: latest
when: ansible_distribution == "Ubuntu"多条件组合
tasks:
- name: 多条件判断
command: /usr/bin/do_something
when:
- ansible_distribution == "CentOS"
- ansible_distribution_major_version == "7"
- inventory_hostname in groups['web_servers']常用条件表达式
变量存在性检查
when: my_var is defined # 变量已定义
when: my_var is not defined # 变量未定义
when: my_var is none # 变量为null布尔值判断
when: enable_feature # 等同于 == True
when: not disable_service # 否定判断字符串操作
when: "'success' in command_result.stdout" # 包含字符串
when: db_host == 'localhost' # 字符串相等
when: path.startswith('/opt') # 字符串开头数值比较
when: ansible_memtotal_mb > 4096 # 内存大于4GB
when: cpu_cores|int >= 8 # 使用过滤器转换类型列表操作
when: item in list_of_items # 包含检查
when: 80 in ansible_all_ipv4_addresses # 检查IP列表注册变量 + 条件判断
tasks:
- name: 检查文件是否存在
stat:
path: /etc/nginx/nginx.conf
register: nginx_conf
- name: 如果文件存在则备份
copy:
src: /etc/nginx/nginx.conf
dest: /backup/nginx.conf.bak
when: nginx_conf.stat.exists条件与循环结合
tasks:
- name: 只为运行中的容器执行操作
docker_container_info:
name: "{{ item }}"
register: containers
loop: "{{ container_list }}"
- name: 重启运行中的容器
docker_container:
name: "{{ item.item }}"
state: restarted
loop: "{{ containers.results }}"
when: item.State.Status == "running"条件与错误处理
tasks:
- name: 尝试危险操作
command: /usr/bin/risky_command
register: cmd_result
ignore_errors: yes
- name: 失败时执行恢复
command: /usr/bin/recovery
when: cmd_result.rc != 0条件过滤器
default 过滤器
when: port_number | default(8080) > 1024bool 过滤器
when: user_input | bool # 将字符串转为布尔值match 过滤器
when: ansible_hostname is match('web-.*') # 正则匹配特殊条件应用
基于标签的条件执行
tasks:
- name: 生产环境特定任务
command: /usr/bin/prod_only
tags: production
when: "'production' in group_names"基于事实的条件
tasks:
- name: 只在虚拟机上执行
command: /usr/bin/vm_tool
when: ansible_virtualization_type != 'physical'多play条件控制
- hosts: all
tasks: [...]
- hosts: db_servers
when: "'database' in group_names"
tasks: [...]完整示例
---
- name: 条件判断综合示例
hosts: all
vars:
backup_enabled: true
service_ports: [80, 443, 8080]
min_memory: 2048
tasks:
- name: 收集系统信息
setup:
filter: "ansible_*"
tags: always
- name: 检查内存是否充足
debug:
msg: "内存充足 ({{ ansible_memtotal_mb }}MB)"
when: ansible_memtotal_mb >= min_memory
- name: 备份配置文件
copy:
src: "/etc/{{ item }}"
dest: "/backup/{{ item }}"
with_items:
- nginx.conf
- hosts
when:
- backup_enabled | default(false)
- ansible_os_family == "Debian"
- name: 验证端口开放
wait_for:
port: "{{ item }}"
timeout: 2
loop: "{{ service_ports }}"
ignore_errors: yes
register: port_checks
when: "'web_servers' in group_names"
- name: 报告端口状态
debug:
msg: "端口 {{ item.item }} {{ '开放' if item.rc == 0 else '关闭' }}"
loop: "{{ port_checks.results }}"
loop_control:
label: "Port {{ item.item }}"循环控制
基础循环(with_items)
- name: 安装多个软件包
yum:
name: "{{ item }}"
state: present
with_items:
- nginx
- mysql-server
- php-fpm字典列表循环
- name: 创建多个用户
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
groups: "{{ item.groups }}"
with_items:
- { name: 'user1', uid: 1001, groups: 'wheel' }
- { name: 'user2', uid: 1002, groups: 'users' }文件列表循环
- name: 复制多个配置文件
copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: 0644
with_files:
- { src: 'file1.conf', dest: '/etc/file1.conf' }
- { src: 'file2.conf', dest: '/etc/file2.conf' }目录内容循环
- name: 处理目录下所有文件
template:
src: "{{ item }}"
dest: "/etc/{{ item | basename }}"
with_fileglob:
- "templates/*.j2"数字范围循环
- name: 创建序列目录
file:
path: "/data/volume{{ item }}"
state: directory
with_sequence: start=1 end=5条件循环控制
- name: 选择性服务重启
service:
name: "{{ item.name }}"
state: restarted
with_items: "{{ services }}"
when: item.restart_required嵌套循环实现
- name: 配置多应用多环境
template:
src: "{{ item.app }}/{{ item.env }}.j2"
dest: "/etc/{{ item.app }}/config.conf"
with_nested:
- [ 'app1', 'app2' ]
- [ 'dev', 'prod' ]循环结果注册
- name: 批量检查服务状态
command: systemctl status {{ item }}
register: service_status
with_items:
- nginx
- mysql
- redis
- name: 显示检查结果
debug:
msg: "{{ item.item }} 状态: {{ '运行中' if item.rc == 0 else '未运行' }}"
with_items: "{{ service_status.results }}"循环优化技巧
- 使用
loop_control控制输出显示:
loop_control:
label: "Processing {{ item.name }}"- 限制循环并发数:
throttle: 3- 跳过特定项:
when: item != 'skip_this'- 使用
include_tasks实现动态循环:
- name: 动态包含任务文件
include_tasks: process_item.yml
with_items: "{{ dynamic_list }}"错误处理
基础错误忽略
- name: 尝试危险操作(基础忽略)
command: /usr/bin/risky-command
ignore_errors: yes # 忽略所有错误继续执行
register: cmd_result
- name: 显示结果(无论成功失败)
debug:
msg: "命令执行完成,状态码: {{ cmd_result.rc }}"条件错误处理
- name: 检查服务状态
command: systemctl status nginx
register: service_status
failed_when: false # 永不标记为失败
changed_when: false # 永不标记为变更
- name: 根据状态码处理
command: systemctl restart nginx
when: service_status.rc != 0 # 仅当状态检查失败时执行块级错误处理
- block: # 定义可能出错的代码块
- name: 更新关键配置
template:
src: config.j2
dest: /etc/app.conf
- name: 触发配置重载
command: /usr/bin/reload-config
rescue: # 出错时执行的恢复逻辑
- name: 恢复备份配置
copy:
src: /backup/app.conf.bak
dest: /etc/app.conf
- name: 发送告警通知
mail:
subject: "配置更新失败"
body: "主机 {{ inventory_hostname }} 配置更新失败"
always: # 无论成功失败都执行
- name: 记录执行日志
lineinfile:
path: /var/log/ansible.log
line: "配置更新尝试于 {{ ansible_date_time.iso8601 }}"自定义失败条件
- name: 检查磁盘空间
command: df -h /
register: disk_usage
failed_when: # 自定义失败条件
- "'No such file' in disk_usage.stderr" # 命令执行错误
- "'90%' in disk_usage.stdout" # 磁盘使用超过90%
changed_when: false # 纯检查任务不触发变更
- name: 磁盘清理任务
command: /usr/bin/clean-disk
when: disk_usage.failed # 仅在检查失败时执行重试机制实现
- name: 等待服务启动
uri:
url: "http://localhost:8080/health"
method: GET
register: health_check
until: health_check.status == 200 # 成功条件
retries: 5 # 最大重试次数
delay: 10 # 每次间隔秒数
ignore_errors: yes # 最终失败也不终止playbook
- name: 显示最终状态
debug:
msg: "服务{{ '已启动' if health_check.status == 200 else '启动失败' }}"错误传播控制
- name: 多步骤任务组
block:
- name: 步骤一(关键)
command: /bin/step1
- name: 步骤二(可跳过)
command: /bin/step2
ignore_errors: yes
- name: 步骤三(关键)
command: /bin/step3
rescue:
- name: 错误处理
debug:
msg: "关键步骤失败,跳过后续任务"
meta: clear_host_errors # 清除错误状态继续执行其他host综合处理案例
- name: 应用部署流程
hosts: app_servers
vars:
max_attempts: 3
backoff_delay: 5
tasks:
- name: 预检查环境
block:
- name: 验证磁盘空间
command: df -h /
register: disk_check
failed_when: "'90%' in disk_check.stdout"
- name: 验证内存
command: free -m
register: mem_check
failed_when: "'available' not in mem_check.stdout"
rescue:
- name: 环境检查失败处理
debug:
msg: "主机 {{ inventory_hostname }} 不满足部署要求"
meta: end_play # 终止当前play
- name: 分阶段部署
block:
- name: 传输安装包
copy:
src: "/dist/{{ app_package }}"
dest: "/tmp/"
- name: 安装应用
command: "/bin/install-app /tmp/{{ app_package }}"
register: install_result
retries: "{{ max_attempts }}"
delay: "{{ backoff_delay }}"
until: install_result.rc == 0
- name: 验证安装
uri:
url: "http://localhost:8080/status"
method: GET
register: verify_result
failed_when: verify_result.json.status != 'OK'
rescue:
- name: 记录失败信息
lineinfile:
path: "/var/log/deploy_errors.log"
line: "{{ inventory_hostname }} 失败于 {{ ansible_date_time.iso8601 }}"
- name: 执行回滚
include_tasks: rollback.yml
when: "'install_result' in vars" # 仅在安装阶段后失败时执行
always:
- name: 清理临时文件
file:
path: "/tmp/{{ app_package }}"
state: absent包含(Includes)
静态包含(import_tasks)
- name: 主Playbook
hosts: webservers
tasks:
# 静态包含会在解析时立即加载(变量提前解析)
- name: 包含基础配置任务
import_tasks: tasks/base_setup.yml # 文件路径相对playbook所在目录
vars: # 可传递变量到被包含文件
install_dir: /opt/app
tags: # 标签会传递给所有被包含任务
- setup
- base
# tasks/base_setup.yml
- name: 创建安装目录 # 此任务会自动继承tags和vars
file:
path: "{{ install_dir }}"
state: directory动态包含(include_tasks)
- name: 主Playbook
hosts: appservers
tasks:
# 动态包含在运行时加载(变量实时计算)
- name: 包含环境特定任务
include_tasks: "tasks/{{ env_type }}_setup.yml" # 可使用变量指定文件
when: env_type is defined # 条件判断在包含前执行
loop: # 支持循环包含
- dev
- prod
loop_control:
loop_var: env_type # 重定义循环变量名角色包含(import_role/include_role)
- name: 主Playbook
hosts: all
tasks:
# 静态角色导入(解析时加载)
- name: 导入基础角色
import_role:
name: common # 角色名称
tasks_from: install.yml # 指定执行的入口文件
vars:
java_version: 11
# 动态角色包含(运行时加载)
- name: 包含环境角色
include_role:
name: "{{ env_type }}_config" # 支持变量
apply: # 可应用额外参数
tags:
- config
when: not skip_config文件包含(include_vars)
- name: 主Playbook
hosts: dbservers
tasks:
# 包含变量文件(YAML/JSON格式)
- name: 加载数据库配置
include_vars:
file: vars/db_config.yml # 显式指定文件
name: db_config # 将变量存入指定命名空间
depth: 1 # 控制嵌套变量层级
- name: 使用包含的变量
debug:
msg: "DB端口: {{ db_config.port }}"条件包含案例
- name: 智能部署流程
hosts: clusters
vars:
components: ["frontend", "backend", "database"]
tasks:
# 根据组件类型动态包含
- name: 包含组件部署逻辑
include_tasks: "tasks/deploy_{{ item }}.yml"
loop: "{{ components }}"
when: "deploy_{{ item }}" # 检查对应布尔变量
# 错误处理包含
- name: 包含监控部署
block:
- include_tasks: tasks/deploy_monitor.yml
rescue:
- include_tasks: tasks/fallback_monitor.yml嵌套包含案例
# playbooks/main.yml
- name: 基础设施部署
hosts: "{{ target }}"
tasks:
- import_playbook: plays/network.yml # 包含整个playbook
- include_tasks: tasks/security_groups.yml
tags: security
# plays/network.yml
- name: 网络配置
hosts: "{{ target }}"
vars:
network_profile: "default"
tasks:
- include_tasks: "network/{{ network_profile }}.yml"模板与文件管理
Jinja2 模板基础
- name: 生成Nginx配置
template:
src: templates/nginx.conf.j2 # 模板文件路径
dest: /etc/nginx/nginx.conf # 目标路径
owner: root # 文件属主
group: root # 文件属组
mode: '0644' # 文件权限
backup: yes # 修改前自动备份
notify: restart nginx # 触发handler模板变量使用
# templates/db.conf.j2
[database]
host = {{ db_host | default('localhost') }} # 使用带默认值的变量
port = {{ db_port }}
{% if db_ssl %} # 条件判断
ssl = true
ssl_cert = /etc/ssl/{{ cert_file }}
{% endif %}文件内容管理
- name: 确保文件存在
file:
path: /etc/app_config.json
state: touch # 创建空文件
owner: appuser
group: appgroup
- name: 修改文件内容
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?Port' # 匹配行
line: 'Port 2222' # 替换内容
backup: yes # 修改前备份目录管理
- name: 创建日志目录结构
file:
path: "/var/log/{{ app_name }}/{{ item }}"
state: directory # 创建目录
mode: '0755'
with_items: # 循环创建
- archive
- audit
- debug文件分发
- name: 分发静态文件
copy:
src: files/static/ # 源目录
dest: /var/www/static/ # 目标目录
owner: www-data
group: www-data
mode: '0644'
remote_src: no # 从控制节点复制模板条件控制
# templates/hosts.j2
{% for host in groups['web_servers'] %}
{{ hostvars[host].ansible_default_ipv4.address }} {{ hostvars[host].ansible_hostname }}
{% if not loop.last %} # 循环控制
{% endif %}
{% endfor %}文件校验与下载
- name: 下载安全证书
get_url:
url: "https://example.com/certs/{{ cert_name }}"
dest: "/etc/ssl/certs/{{ cert_name }}"
checksum: "sha256:{{ cert_sha256 }}" # 校验文件完整性
mode: '0600'
timeout: 30完整配置案例
- name: 应用部署配置
hosts: app_servers
vars:
app_name: "myapp"
app_port: 8080
features:
- "logging"
- "monitoring"
- "auth"
tasks:
- name: 创建应用目录
file:
path: "/opt/{{ app_name }}"
state: directory
mode: '0755'
- name: 生成主配置文件
template:
src: "templates/app.conf.j2"
dest: "/opt/{{ app_name }}/app.conf"
validate: "/usr/sbin/validator -f %s" # 配置语法校验
- name: 部署初始化脚本
copy:
src: "files/init.sh"
dest: "/opt/{{ app_name }}/bin/init.sh"
mode: '0755'
- name: 配置日志轮转
template:
src: "templates/logrotate.j2"
dest: "/etc/logrotate.d/{{ app_name }}"
owner: root
group: root
mode: '0644'执行控制
任务执行顺序控制
- name: 数据库集群部署
hosts: db_servers
tasks:
- name: 预检查磁盘空间 # 必须首先执行
command: df -h /
register: disk_check
tags: always_first
- name: 安装数据库软件
yum:
name: mysql-server
state: latest
when: disk_check.rc == 0 # 依赖前序任务结果
- name: 初始化集群
command: /usr/bin/mysql-init
when:
- inventory_hostname == groups['db_servers'][0] # 只在第一个节点执行
- "'primary' not in group_names" # 排除特定组滚动更新控制
- name: 零停机应用更新
hosts: app_servers
serial: 2 # 每次更新2个节点
max_fail_percentage: 25 # 允许25%节点失败
strategy: free # 自由策略(不按顺序)
tasks:
- name: 下线节点
uri:
url: "http://{{ inventory_hostname }}:8080/health"
method: DELETE
delegate_to: load_balancer
- name: 更新应用
include_tasks: update_app.yml
- name: 上线节点
uri:
url: "http://{{ inventory_hostname }}:8080/health"
method: POST
delegate_to: load_balancer
async: 60 # 异步操作
poll: 5 # 每5秒检查任务执行策略
- name: 多阶段部署
hosts: all
order: sorted # 按主机名排序执行
strategy: linear # 默认线性策略
tasks:
- name: 阶段一(并行执行)
command: /bin/phase1
async: 120 # 超时120秒
poll: 0 # 不等待结果(完全异步)
- name: 阶段二(串行执行)
command: /bin/phase2
run_once: true # 只在一个节点执行
throttle: 1 # 并发限制为1错误处理控制
- name: 关键业务维护
hosts: production
any_errors_fatal: true # 任何错误立即终止play
tasks:
- name: 创建维护锁
file:
path: /tmp/maintenance.lock
state: touch
- block:
- name: 执行数据迁移
command: /bin/db-migrate
register: migrate_result
until: migrate_result.rc == 0 # 重试直到成功
retries: 5
delay: 10
- name: 验证数据
script: verify_data.sh
ignore_errors: yes # 忽略验证错误
rescue:
- name: 紧急回滚
include_tasks: emergency_rollback.yml
when: migrate_result.failed # 仅迁移失败时执行
always:
- name: 移除维护锁
file:
path: /tmp/maintenance.lock
state: absent资源限制控制
- name: 批量文件处理
hosts: file_servers
tasks:
- name: 压缩日志文件
command: gzip /var/log/{{ item }}
loop: "{{ log_files }}"
throttle: 3 # 并发限制为3
when: item is match('.*\.log$') # 仅处理.log文件
- name: 上传到存储
synchronize:
src: /var/log/
dest: "nfs:/backup/{{ inventory_hostname }}/"
delegate_to: localhost # 在控制节点执行
run_once: true # 只执行一次执行流程调试
- name: 分阶段调试
hosts: test_servers
tasks:
- name: 阶段一检查(标记点)
meta: noop # 空操作
tags: checkpoint1
- name: 交互式确认
pause:
prompt: "请确认阶段一完成,按Enter继续"
seconds: 30 # 超时自动继续
when: interactive_mode # 条件启用
- name: 显示执行路径
debug:
msg: "当前执行路径:{{ play_hosts }}"
changed_when: false # 不触发变更状态
- name: 强制刷新handlers
meta: flush_handlers # 立即执行已通知的handlers最佳实践要点
执行顺序设计:
yaml- name: 关键前置任务 meta: clear_facts # 清理facts缓存 tags: always_run_first资源保护机制:
yaml- name: 保护性检查 assert: that: - ansible_memtotal_mb > 2048 - ansible_processor_vcpus >= 4 fail_msg: "系统资源不满足最低要求" quiet: yes动态执行控制:
yaml- name: 环境自适应执行 include_tasks: "env/{{ deployment_env }}.yml" when: deployment_env is defined性能优化技巧:
yaml- name: 异步批量操作 command: /bin/long-running-task async: 300 # 超时5分钟 poll: 30 # 每30秒检查 throttle: "{{ batch_size | default(5) }}"执行跟踪记录:
yaml- name: 记录执行轨迹 lineinfile: path: /var/log/ansible_trace.log line: "{{ ansible_date_time.iso8601 }} {{ ansible_play_name }} {{ task_name }}" delegate_to: localhost run_once: true
经典案例
部署WEB应用
---
- name: 部署Web应用
hosts: demo
become: yes
vars:
app_version: "v1"
web_root: "/var/www/html"
app_package_path: "/data/html-{{ app_version }}.tar.gz"
tasks:
- name: 安装依赖包
yum:
name: "{{ item }}"
state: present
loop:
- httpd
- wget
ignore_errors: yes # 简化错误处理,实际应根据需要调整
- name: 创建网站目录结构
file:
path: "{{ web_root }}"
state: directory
owner: apache
group: apache
mode: '0755'
recurse: yes
- name: 验证目录权限
shell: |
ls -ld {{ web_root }}
df -h {{ web_root }}
register: dir_check
changed_when: false
failed_when: "'apache apache' not in dir_check.stdout"
- name: 显示目录信息
debug:
var: dir_check.stdout_lines
- name: 检查应用包是否存在(控制机)
local_action:
module: stat
path: "{{ app_package_path }}"
register: app_package
- name: 显示包路径检查结果
debug:
msg: "检查文件路径: {{ app_package_path }},是否存在: {{ app_package.stat.exists }}"
- name: 部署应用代码
unarchive:
src: "{{ app_package_path }}" # 使用变量
dest: "{{ web_root }}"
remote_src: no
owner: apache
group: apache
when: app_package.stat.exists
notify: restart apache
handlers:
- name: restart apache
service:
name: httpd
state: restartedRoles 是 Ansible 中用于组织 Playbook 的高级功能,它提供了一种标准化的方式来结构化自动化内容,使配置管理更加模块化和可重用。
Ansible Roles 组织
Roles 基本概念
什么是 Role
- 预定义的文件和目录结构
- 自包含的自动化单元
- 可重用的功能组件
- 支持参数化和默认值
Role 的优势
- 模块化:将复杂配置分解为独立单元
- 可重用:同一 Role 可被多个 Playbook 使用
- 标准化:统一的项目结构
- 易于维护:逻辑分离,职责明确
Role 标准目录结构
一个完整的 Role 通常包含以下目录结构:
role_name/
├── defaults/ # 默认变量 (最低优先级)
│ └── main.yml
├── vars/ # 角色变量 (较高优先级)
│ └── main.yml
├── tasks/ # 主任务文件
│ └── main.yml
├── handlers/ # 处理器定义
│ └── main.yml
├── templates/ # Jinja2 模板文件
│ └── nginx.conf.j2
├── files/ # 静态文件
│ └── default.html
├── meta/ # 角色依赖关系
│ └── main.yml
└── tests/ # 测试用例
├── inventory
└── test.yml一键创建目录结构
mkdir -p role_name/{defaults,vars,tasks,handlers,templates,files,meta,tests}
touch role_name/{defaults,vars,tasks,handlers,meta}/main.yamlRole 各目录详解
defaults/
优先级最低的变量定义,可被其他变量覆盖
- 必须包含
main.yml - 用途:定义角色默认配置(如软件默认版本)
# defaults/main.yml
nginx_port: 80
app_version: "1.0"vars/
高优先级变量,通常用于不可覆盖的配置
- 必须包含
main.yml - 用途:定义常量或关键配置
- 优先级高于 defaults
# vars/main.yml
security_group: "prod-firewall"
required_dependencies:
- libssl-dev
- python3-piptasks/
角色核心逻辑,包含任务列表
- 必须包含
main.yml(入口文件) - 支持拆分子任务文件并通过
include_tasks引用
# tasks/main.yml
- include_tasks: install.yml
- include_tasks: configure.yml when: install_success
- name: 安装 Nginx
apt:
name: "{{ package_name }}"
state: latest
- name: 配置 Nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginxhandlers/
定义由 notify触发的操作
- 必须包含
main.yml - 典型场景:服务重启/重载配置
- name: restart nginx
service:
name: "{{ service_name }}"
state: restartedtemplates/
Jinja2模板文件存放目录
- 文件扩展名建议为
.j2 - 通过
template模块调用
# templates/nginx.conf.j2
server {
listen {{ nginx_port }};
server_name {{ server_name }};
}files/
静态文件存储目录
- 文件会被直接复制到目标主机
- 通过
copy模块调用
# 任务示例
- copy:
src: files/logo.png
dest: /var/www/static/logo.pngmeta/
角色元数据定义
- 必须包含
main.yml - 用途:声明依赖关系、兼容性等
# meta/main.yml
dependencies:
- role: common
vars:
timezone: "Asia/Shanghai"
- role: nginx
galaxy_info:
author: "Your Name"
min_ansible_version: "2.9"library/ (可选)
存放自定义模块
- 模块可直接在角色中使用
- 文件需为
.py格式
# library/custom_module.py
from ansible.module_utils.basic import *
def main():
# 模块逻辑module_utils/(可选)
存放自定义模块依赖的公共库
- 被
library/中的模块引用 - 需Python标准格式
tests/ (可选)
角色测试目录
- 包含测试Playbook和Inventory
# tests/test.yml
- hosts: localhost
roles:
- role: ../ # 测试当前角色Role 的使用方式
在 Playbook 中调用 Role
- hosts: webservers
roles:
- common
- role: nginx
vars:
http_port: 8080
- { role: mysql, when: "db_required" }条件执行 Role
roles:
- role: nginx
when: ansible_os_family == "Debian"动态导入 Role
tasks:
- name: 包含数据库角色
include_role:
name: "{{ db_type }}"Role 最佳实践
命名规范
- 使用小写字母和下划线
- 如:
nginx,postgresql,web_app
变量管理
- 默认值放在
defaults/main.yml - 强制变量放在
vars/main.yml - 敏感变量使用 Ansible Vault 加密
- 默认值放在
任务组织
- 复杂 Role 可将任务拆分到多个文件:
tasks/ ├── main.yml ├── install.yml └── configure.yml - 在 main.yml 中引入:yaml
- include_tasks: install.yml - include_tasks: configure.yml
- 复杂 Role 可将任务拆分到多个文件:
依赖管理
- 明确声明依赖关系
- 避免循环依赖
文档说明
- 每个 Role 包含 README.md
- 说明变量、用法和示例
经典案例
部署Nginx
目录结构
roles/nginx
├── defaults
│ └── main.yaml
├── files
│ ├── install_nginx.sh
│ └── nginx-1.26.3.tar.gz
├── handlers
│ └── main.yaml
├── meta
│ └── main.yaml
├── tasks
│ └── main.yaml
├── templates
│ └── nginx.conf.j2
├── tests
└── vars
└── main.yaml模板文件初始化
mkdir -p nginx/{defaults,vars,tasks,handlers,templates,files,meta,tests}
touch nginx/{defaults,vars,tasks,handlers,meta}/main.yaml入口文件
nginx.yaml
---
- name: 部署Nginx
hosts: nginx
gather_facts: True # 开启系统内置变量
roles: # 启用roles原型配置
- role: nginx # 执行nginx原型模组
vars:
NGINX_PORT: 8080定义变量
默认变量
nginx/defaults/main.yaml
---
# 定义域名
SERVER_NAME: localhost
# 版本控制
NGINX_VERSION: 1.26.3
# 安装路径
NGINX_PREFIX: /usr/local/nginx
# Nginx 程序用户
NGINX_USER: www
# 解压路径
TAR_DIR: /usr/src/自定义变量
nginx/vars/main.yaml
---
NGINX_CONFIGURE: >-
--prefix={{ NGINX_PREFIX }}
--with-http_ssl_module
--with-http_stub_status_module
--with-http_realip_module
--with-http_gzip_static_module
--with-http_v2_module
--with-pcre
--with-stream
--with-stream_ssl_module
--with-threads
--with-file-aio
--user={{ NGINX_USER }}
--group={{ NGINX_USER }}tasks 任务编排
nginx/tasks/main.yaml
---
- name: 创建用户
user:
name: "{{ NGINX_USER }}"
state: present
shell: /sbin/nologin
- name: 复制安装包并解压
unarchive:
src: "nginx-{{ NGINX_VERSION }}.tar.gz"
dest: "{{ TAR_DIR }}"
owner: "{{ NGINX_USER }}"
group: "{{ NGINX_USER }}"
- name: 安装依赖
yum:
name: "{{ item }}"
state: present
loop:
- gcc
- make
- zlib
- openssl-devel
- zlib-devel
- pcre-devel
- pcre
- name: 复制 install_nginx.sh
template:
src: install_nginx.sh.j2
dest: "/usr/src/nginx-{{ NGINX_VERSION }}/install_nginx.sh"
owner: "{{ NGINX_USER }}"
group: "{{ NGINX_USER }}"
mode: "644"
- name: 安装脚本
shell:
cmd: ./install_nginx.sh
chdir: "/usr/src/nginx-{{ NGINX_VERSION }}"
register: install_result
- name: 查看脚本执行结果
debug:
var: install_result
- name: 复制nginx.conf
template:
src: nginx.conf.j2
dest: "{{ NGINX_PREFIX }}/nginx/conf/nginx.conf"
owner: "{{ NGINX_USER }}" # 文件属主
group: "{{ NGINX_USER }}" # 文件属组
mode: "0644" # 文件权限
backup: yes # 修改前创建备份
notify: start_nginxhandlers 配置
nginx/handlers/main.yaml
---
- name: start_nginx # 启动Nginx
shell: "{{ NGINX_PREFIX }}/sbin/nginx"
notify: check_nginx
- name: stop_nginx # 停止Nginx
shell: "{{ NGINX_PREFIX }}/sbin/nginx -s stop"
- name: reload_nginx # 重启Nginx
shell: "{{ NGINX_PREFIX }}/sbin/nginx -s reload"
- name: check_nginx # 查看启动结果
shell:
cmd: "ss -antup | grep {{ NGINX_PORT }}"Nginx 配置文件
nginx/templates/nginx.conf.j2
user {{ NGINX_USER }};
worker_processes auto;
worker_rlimit_nofile 65535;
events {
multi_accept on;
worker_connections 65535;
}
http {
charset utf-8;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
log_not_found off;
types_hash_max_size 2048;
types_hash_bucket_size 64;
client_max_body_size 16M;
# MIME
include mime.types;
default_type application/octet-stream;
server {
listen {{ NGINX_PORT }};
server_name {{ SERVER_NAME }};
root html;
# index.html fallback
location / {
index index.html
try_files $uri $uri/ /index.html;
}
}
}安装程序脚本
nginx/templates/install_nginx.sh.j2
#!/bin/bash
./configure {{ NGINX_CONFIGURE }}
[ $? -eq 0 ] && echo "预配置成功" || echo " 预配置失败"
make -j$(nproc) && make install
[ $? -eq 0 ] && echo "编译成功" || echo " 编译失败"语法检测
ansible-playbook -C nginx.yaml执行自动化任务
ansible-playbook nginx.yaml