Ansible Playbook 进阶 #
Blocks #
什么是 Blocks? #
Blocks 允许将多个任务分组,可以统一应用条件、错误处理等属性。
yaml
---
- name: Use blocks
hosts: webservers
become: yes
tasks:
- name: Install and configure Nginx
block:
- name: Install Nginx
apt:
name: nginx
state: present
- name: Copy configuration
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Restart Nginx
- name: Start Nginx
service:
name: nginx
state: started
enabled: yes
when: ansible_facts['os_family'] == "Debian"
become: yes
tags: nginx
Block 结构 #
yaml
tasks:
- name: Block example
block:
# 主要任务
- name: Task 1
...
- name: Task 2
...
rescue:
# 错误处理任务(当 block 中任务失败时执行)
- name: Rescue task 1
...
- name: Rescue task 2
...
always:
# 总是执行的任务(无论成功或失败)
- name: Always task
...
错误处理 #
yaml
---
- name: Error handling with blocks
hosts: webservers
become: yes
tasks:
- name: Handle deployment errors
block:
- name: Stop application
service:
name: myapp
state: stopped
- name: Update application
git:
repo: https://github.com/user/app.git
dest: /var/www/app
version: main
- name: Run migrations
command: /var/www/app/migrate.sh
register: migration_result
failed_when: "'ERROR' in migration_result.stderr"
- name: Start application
service:
name: myapp
state: started
rescue:
- name: Rollback to previous version
command: /var/www/app/rollback.sh
- name: Start application with old version
service:
name: myapp
state: started
- name: Send failure notification
mail:
to: admin@example.com
subject: "Deployment failed on {{ inventory_hostname }}"
body: "Deployment failed. Rollback completed."
always:
- name: Clean up temporary files
file:
path: /tmp/deploy_temp
state: absent
- name: Log deployment result
lineinfile:
path: /var/log/deploy.log
line: "{{ ansible_date_time.iso8601 }} - Deployment {{ 'succeeded' if ansible_failed_task is undefined else 'failed' }}"
create: yes
错误处理 #
ignore_errors #
忽略任务错误继续执行:
yaml
tasks:
- name: This task might fail
command: /usr/bin/some_command
ignore_errors: yes
- name: This task will still run
debug:
msg: "Previous task failed but we continue"
failed_when #
自定义失败条件:
yaml
tasks:
- name: Run a script
command: /usr/local/bin/myscript.sh
register: script_result
- name: Check script result
debug:
msg: "Script output: {{ script_result.stdout }}"
- name: Fail if error in output
fail:
msg: "Script failed with error: {{ script_result.stderr }}"
when: "'ERROR' in script_result.stderr"
# 或者直接在任务中定义
- name: Run another script
command: /usr/local/bin/another_script.sh
register: result
failed_when: "'FAILURE' in result.stdout or result.rc != 0"
changed_when #
自定义变更条件:
yaml
tasks:
- name: Run command that doesn't change anything
command: /usr/bin/check_status.sh
register: status_result
changed_when: false # 永远不会标记为 changed
- name: Run command with custom change condition
command: /usr/local/bin/update_config.sh
register: update_result
changed_when: "'updated' in update_result.stdout"
- name: Restart service only if config changed
service:
name: myapp
state: restarted
when: update_result.changed
block/rescue/always 完整示例 #
yaml
---
- name: Complete error handling example
hosts: databases
become: yes
tasks:
- name: Database backup and restore
block:
- name: Create backup directory
file:
path: /backup/{{ ansible_date_time.date }}
state: directory
- name: Backup database
command: >
mysqldump -u root -p{{ mysql_root_password }}
--all-databases > /backup/{{ ansible_date_time.date }}/all.sql
no_log: true # 不记录敏感信息
- name: Stop database
service:
name: mysql
state: stopped
- name: Upgrade database package
apt:
name: mysql-server
state: latest
- name: Start database
service:
name: mysql
state: started
- name: Verify database is running
command: mysqladmin ping
register: db_ping
retries: 5
delay: 10
until: db_ping.rc == 0
rescue:
- name: Restore from backup
command: >
mysql -u root -p{{ mysql_root_password }}
< /backup/{{ ansible_date_time.date }}/all.sql
no_log: true
- name: Start database
service:
name: mysql
state: started
- name: Alert team
slack:
token: "{{ slack_token }}"
msg: "Database upgrade failed on {{ inventory_hostname }}, rollback completed"
always:
- name: Clean up backup
file:
path: /backup/{{ ansible_date_time.date }}
state: absent
when: cleanup_backup | default(false)
异步任务 #
异步执行 #
长时间运行的任务可以使用异步模式:
yaml
tasks:
# 同步执行(默认,会等待完成)
- name: Run long task synchronously
command: /usr/bin/long_running_task.sh
# 会阻塞直到完成
# 异步执行
- name: Run long task asynchronously
command: /usr/bin/long_running_task.sh
async: 3600 # 最大运行时间(秒)
poll: 0 # 不轮询检查状态
register: long_task
# 继续执行其他任务
- name: Do other work
debug:
msg: "Doing other work while long task runs..."
# 稍后检查异步任务状态
- name: Check async task status
async_status:
jid: "{{ long_task.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 60
delay: 60
轮询检查 #
yaml
tasks:
- name: Run task with polling
command: /usr/bin/long_task.sh
async: 300 # 5分钟超时
poll: 10 # 每10秒检查一次
register: task_result
- name: Check result
debug:
msg: "Task completed with output: {{ task_result.stdout }}"
并行执行多个异步任务 #
yaml
tasks:
- name: Start multiple async tasks
command: "/usr/bin/process_item.sh {{ item }}"
async: 600
poll: 0
loop:
- item1
- item2
- item3
register: async_tasks
- name: Wait for all tasks to complete
async_status:
jid: "{{ item.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 60
delay: 10
loop: "{{ async_tasks.results }}"
委托 #
delegate_to #
在特定主机上执行任务:
yaml
---
- name: Delegation example
hosts: webservers
tasks:
# 在本地执行
- name: Run task on localhost
command: /usr/local/bin/local_script.sh
delegate_to: localhost
# 在特定主机执行
- name: Run task on specific host
command: /usr/local/bin/check_lb.sh
delegate_to: loadbalancer1
# 在主机本身执行(默认行为)
- name: Normal task
command: hostname
delegate_facts #
委托收集 Facts:
yaml
tasks:
- name: Gather facts from another host
setup:
delegate_to: "{{ item }}"
delegate_facts: True
loop:
- web1
- web2
local_action #
在本地执行任务的简写:
yaml
tasks:
# 使用 delegate_to
- name: Run on localhost
command: /usr/local/bin/local_script.sh
delegate_to: localhost
# 使用 local_action(等价)
- name: Run on localhost
local_action: command /usr/local/bin/local_script.sh
实际应用场景 #
yaml
---
- name: Deployment with delegation
hosts: webservers
serial: 1 # 逐个主机执行
tasks:
- name: Remove from load balancer
haproxy:
backend: web_servers
host: "{{ inventory_hostname }}"
state: disabled
delegate_to: loadbalancer
- name: Update application
git:
repo: https://github.com/user/app.git
dest: /var/www/app
version: main
- name: Restart application
service:
name: myapp
state: restarted
- name: Wait for application to start
wait_for:
port: 8080
delay: 5
- name: Add back to load balancer
haproxy:
backend: web_servers
host: "{{ inventory_hostname }}"
state: enabled
delegate_to: loadbalancer
执行策略 #
策略类型 #
text
┌─────────────────────────────────────────────────────────────┐
│ 执行策略类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ linear(默认) free │
│ ───────────── ────── │
│ 逐个主机执行任务 主机独立执行 │
│ 等待所有主机完成 不等待其他主机 │
│ 适合大多数场景 适合独立任务 │
│ │
│ host_pinned │
│ ──────────── │
│ 固定主机顺序 │
│ 按主机顺序执行完整 Play │
│ │
└─────────────────────────────────────────────────────────────┘
linear 策略 #
yaml
---
- name: Linear strategy (default)
hosts: webservers
strategy: linear # 默认策略
tasks:
- name: Task 1
debug:
msg: "Task 1 on {{ inventory_hostname }}"
- name: Task 2
debug:
msg: "Task 2 on {{ inventory_hostname }}"
# 执行顺序:
# web1: Task 1 -> web2: Task 1 -> web3: Task 1
# web1: Task 2 -> web2: Task 2 -> web3: Task 2
free 策略 #
yaml
---
- name: Free strategy
hosts: webservers
strategy: free # 自由策略
tasks:
- name: Task 1
debug:
msg: "Task 1 on {{ inventory_hostname }}"
- name: Task 2
debug:
msg: "Task 2 on {{ inventory_hostname }}"
# 执行顺序:
# 每个主机独立执行,不等待其他主机
# web1: Task 1 -> Task 2
# web2: Task 1 -> Task 2
# web3: Task 1 -> Task 2
# 可能同时进行
serial 控制 #
控制每次处理的主机数量:
yaml
---
- name: Serial execution
hosts: webservers
serial: 2 # 每次处理 2 台主机
tasks:
- name: Update application
debug:
msg: "Updating {{ inventory_hostname }}"
- name: Restart service
debug:
msg: "Restarting on {{ inventory_hostname }}"
# 执行顺序:
# 批次 1: web1, web2
# 批次 2: web3, web4
# 批次 3: web5
百分比控制 #
yaml
---
- name: Percentage serial
hosts: webservers
serial: "25%" # 每次处理 25% 的主机
tasks:
- name: Update
debug:
msg: "Updating {{ inventory_hostname }}"
最大失败比例 #
yaml
---
- name: Control failure threshold
hosts: webservers
max_fail_percentage: 25 # 超过 25% 失败则停止
tasks:
- name: Critical task
command: /usr/bin/critical_operation.sh
循环进阶 #
until 循环 #
等待条件满足:
yaml
tasks:
- name: Wait for service to be ready
uri:
url: http://localhost:8080/health
return_content: yes
register: health_check
until: "'healthy' in health_check.content"
retries: 10
delay: 5
- name: Wait for file to exist
stat:
path: /tmp/ready
register: file_check
until: file_check.stat.exists
retries: 20
delay: 3
循环控制 #
yaml
tasks:
- name: Loop with control
debug:
msg: "{{ item }}"
loop:
- a
- b
- c
loop_control:
pause: 3 # 每次循环暂停 3 秒
index_var: index # 索引变量
label: "{{ item }}" # 输出标签
- name: Loop with index
debug:
msg: "Item {{ index }} is {{ item }}"
loop:
- first
- second
- third
loop_control:
index_var: index
嵌套循环 #
yaml
tasks:
- name: Nested loops
debug:
msg: "User {{ item[0] }} has permission {{ item[1] }}"
loop: "{{ users | product(permissions) | list }}"
vars:
users:
- alice
- bob
permissions:
- read
- write
- execute
条件进阶 #
组合条件 #
yaml
tasks:
- name: Complex conditions
debug:
msg: "Complex condition met"
when: >
(ansible_facts['distribution'] == 'Ubuntu' and
ansible_facts['distribution_version'] == '20.04') or
(ansible_facts['distribution'] == 'CentOS' and
ansible_facts['distribution_major_version'] == '8')
- name: Multiple conditions (AND)
debug:
msg: "All conditions met"
when:
- condition1
- condition2
- condition3
- name: Multiple conditions (OR)
debug:
msg: "At least one condition met"
when: condition1 or condition2 or condition3
条件测试 #
yaml
tasks:
- name: Test if variable is defined
debug:
msg: "Variable is defined"
when: my_var is defined
- name: Test if variable is undefined
debug:
msg: "Variable is undefined"
when: my_var is undefined
- name: Test if variable is string
debug:
msg: "Variable is a string"
when: my_var is string
- name: Test if variable is number
debug:
msg: "Variable is a number"
when: my_var is number
- name: Test if variable is boolean
debug:
msg: "Variable is a boolean"
when: my_var is boolean
- name: Test if list contains item
debug:
msg: "List contains item"
when: item in my_list
- name: Test if string matches regex
debug:
msg: "String matches pattern"
when: my_string is match("^start.*end$")
- name: Test if file exists
debug:
msg: "File exists"
when: my_file is exists
- name: Test if directory exists
debug:
msg: "Directory exists"
when: my_dir is directory
- name: Test if path is file
debug:
msg: "Path is a file"
when: my_path is file
检查模式 #
检查模式 #
bash
# 检查模式(模拟执行)
ansible-playbook site.yml --check
# 检查模式 + 差异显示
ansible-playbook site.yml --check --diff
任务级别的检查模式控制 #
yaml
tasks:
# 总是在检查模式下运行
- name: Always run in check mode
command: /usr/bin/check_script.sh
check_mode: no # 即使在检查模式也实际执行
# 从不在检查模式下运行
- name: Never run in check mode
command: /usr/bin/actual_script.sh
check_mode: yes # 在检查模式跳过
# 模拟变更
- name: Simulate change
command: /usr/bin/simulate.sh
diff: yes
完整示例 #
yaml
---
- name: Advanced deployment playbook
hosts: webservers
become: yes
strategy: free
serial: "25%"
max_fail_percentage: 30
vars:
app_name: myapp
app_port: 8080
health_check_retries: 10
health_check_delay: 5
tasks:
- name: Remove from load balancer
block:
- name: Disable in HAProxy
haproxy:
backend: web_servers
host: "{{ inventory_hostname }}"
state: disabled
delegate_to: loadbalancer
notify: Wait for connections to drain
rescue:
- name: Log load balancer error
debug:
msg: "Failed to remove from load balancer, continuing anyway"
ignore_errors: yes
- name: Deploy application
block:
- name: Create backup
archive:
path: /var/www/{{ app_name }}
dest: /backup/{{ app_name }}_{{ ansible_date_time.iso8601 }}.tar.gz
when: ansible_facts['mounts'] | selectattr('mount', 'equalto', '/var/www') | list | length > 0
- name: Pull latest code
git:
repo: "{{ app_repo }}"
dest: /var/www/{{ app_name }}
version: "{{ app_version | default('main') }}"
register: git_result
async: 300
poll: 10
- name: Install dependencies
command: npm install --production
args:
chdir: /var/www/{{ app_name }}
when: git_result.changed
- name: Run database migrations
command: npm run migrate
args:
chdir: /var/www/{{ app_name }}
register: migration_result
changed_when: "'Migration completed' in migration_result.stdout"
when: git_result.changed
- name: Restart application
systemd:
name: "{{ app_name }}"
state: restarted
daemon_reload: yes
when: git_result.changed
- name: Wait for application to start
uri:
url: "http://localhost:{{ app_port }}/health"
return_content: yes
register: health_check
until: "'healthy' in health_check.content"
retries: "{{ health_check_retries }}"
delay: "{{ health_check_delay }}"
rescue:
- name: Rollback to previous version
debug:
msg: "Rolling back..."
- name: Restore from backup
unarchive:
src: /backup/{{ app_name }}_{{ ansible_date_time.iso8601 }}.tar.gz
dest: /var/www/
remote_src: yes
- name: Restart application
systemd:
name: "{{ app_name }}"
state: restarted
- name: Alert on failure
slack:
token: "{{ slack_token }}"
msg: "Deployment failed on {{ inventory_hostname }}"
delegate_to: localhost
ignore_errors: yes
always:
- name: Clean up old backups
shell: find /backup -name "{{ app_name }}_*.tar.gz" -mtime +7 -delete
changed_when: false
- name: Add back to load balancer
haproxy:
backend: web_servers
host: "{{ inventory_hostname }}"
state: enabled
delegate_to: loadbalancer
handlers:
- name: Wait for connections to drain
pause:
seconds: 30
下一步 #
现在你已经掌握了 Playbook 的高级技巧,接下来学习 变量管理 深入了解 Ansible 的变量系统!
最后更新:2026-03-29