在日常运维工作中,我们编写的 Ansible Playbook 往往包含数十甚至上百个任务。如果每次都完整执行整个 Playbook,不仅耗时,还可能带来不必要的风险。Ansible 提供了三种强大的任务控制机制------Tag(标签) 、When(条件判断) 和 Loop(循环),帮助我们精细化地控制任务的执行范围、执行条件和执行方式。本文将系统介绍这三种机制的用法、进阶技巧和最佳实践。
一、Tag(标签):精准控制任务执行范围
1.1 什么是 Tag?
Tag 是 Ansible 中用于标记任务、角色或 Play 的标签机制。当 Playbook 变得庞大复杂时,我们往往只需要运行其中一部分任务------比如只更新配置文件,而不重新安装软件包。Tag 就是为解决这个问题而生的。
1.2 基本用法
给单个任务添加标签:
yaml
- name: 安装 Apache
ansible.builtin.yum:
name: httpd
state: present
tags:
- apache
- webserver
- installation
一个任务可以拥有多个标签,执行时只需指定任意一个即可匹配。
给多个任务添加相同标签:
yaml
- name: 安装 NTP
ansible.builtin.yum:
name: ntp
state: present
tags: ntp
- name: 配置 NTP
ansible.builtin.template:
src: ntp.conf.j2
dest: /etc/ntp.conf
tags: ntp
使用 --tags ntp 即可一次性运行所有标记为 ntp 的任务。
给 Play 添加标签(所有任务继承):
yaml
- name: Apache 配置
hosts: webservers
tags:
- apache
- configuration
tasks:
- name: 复制配置文件
ansible.builtin.copy:
src: httpd.conf
dest: /etc/httpd/conf/httpd.conf
- name: 重启 Apache
ansible.builtin.service:
name: httpd
state: restarted
Play 级别的标签会自动继承给该 Play 下的所有任务。
给角色添加标签:
yaml
- name: Web 服务器配置
hosts: webservers
roles:
- role: apache
tags:
- webserver
- apache
角色内所有任务都会继承这些标签。
1.3 执行与跳过
只运行特定标签:
bash
ansible-playbook site.yml --tags "apache,configuration"
这会运行所有标记了 apache 或 configuration 的任务。
跳过特定标签:
bash
ansible-playbook site.yml --skip-tags "database,backup"
这会跳过所有标记了 database 或 backup 的任务。
查看所有可用标签:
bash
ansible-playbook site.yml --list-tags
1.4 特殊标签:always 与 never
Ansible 提供了两个内置特殊标签:
always:标记了always的任务无论如何都会执行(除非被--skip-tags明确跳过)。never:标记了never的任务默认始终被跳过,除非在命令行中明确指定该标签。
yaml
- name: 安全审计(始终执行)
ansible.builtin.script: security_audit.sh
tags: always
- name: 危险操作(默认跳过)
ansible.builtin.command: risky_reset.sh
tags: never
always 标签特别适合用于日志记录、审计检查等必须执行的任务。
1.5 最佳实践
- 建立命名规范 :建议按功能模块命名标签,如
packages、config、service等。 - 合理使用 always :将关键任务(如安全检查、日志记录)标记为
always。 - 避免过度标记:标签太多反而会增加管理负担,保持标签的简洁和语义清晰。
二、When(条件判断):按需执行任务
2.1 什么是 When?
When 是 Ansible 中最常用的条件判断关键字,用于在执行任务前评估条件是否满足。条件为 True 时执行任务,为 False 时跳过。When 条件使用 Jinja2 模板语法编写。
2.2 基于 Facts 的条件判断
Ansible 会自动收集目标主机的 Facts(系统信息),我们可以利用这些信息做条件判断:
yaml
- name: 在 Debian 系主机上安装 Nginx
ansible.builtin.apt:
name: nginx
state: present
when: ansible_os_family == "Debian"
- name: 在 RedHat 系主机上安装 Nginx
ansible.builtin.yum:
name: nginx
state: present
when: ansible_os_family == "RedHat"
2.3 多条件组合
使用 and、or 和括号组合多个条件:
yaml
# 所有条件都满足才执行(and)
- name: 仅在生产环境的 Ubuntu 服务器上执行
ansible.builtin.command: production_setup.sh
when:
- ansible_os_family == "Debian"
- environment == "production"
# 任意条件满足即执行(or)
- name: 在开发或测试环境中启用调试模式
ansible.builtin.debug:
msg: "Debug mode enabled"
when: environment == "development" or environment == "staging"
# 复杂条件组合
- name: 复杂条件判断
ansible.builtin.command: complex_task.sh
when: (ansible_os_family == "RedHat" and ansible_distribution_major_version >= "7") or environment == "production"
多个条件也可以写成列表形式,效果等同于 and。
2.4 基于变量的条件判断
变量是否存在:
yaml
- name: 仅当 new_username 变量存在时创建用户
ansible.builtin.user:
name: "{{ new_username }}"
state: present
when: new_username is defined
变量值比较:
yaml
- name: 仅当版本大于等于 10 时执行
ansible.builtin.command: upgrade.sh
when: app_version | int >= 10
列表成员检查:
yaml
- name: 仅当 app_version 在允许列表中时执行
ansible.builtin.command: deploy.sh
when: app_version in ["v1.0", "v1.1", "v2.0"]
2.5 基于注册变量的条件判断
使用 register 关键字捕获任务输出,然后在后续任务中作为条件判断依据:
yaml
- name: 检查服务状态
ansible.builtin.command: systemctl is-active myservice
register: service_status
ignore_errors: true
changed_when: false
- name: 如果服务未运行则安装
ansible.builtin.yum:
name: myservice
state: present
when: service_status.stdout != 'active'
- name: 如果服务正在运行则重启
ansible.builtin.service:
name: myservice
state: restarted
when: service_status.stdout == 'active'
2.6 注意事项
- 未定义变量会报错 :When 条件中引用的变量如果未定义,Ansible 会抛出致命错误。可以使用
default()过滤器提供默认值:
yaml
when: my_var | default(false) == true
- When 在循环中的行为 :当
when与loop一起使用时,条件会在每次迭代中分别评估。
三、Loop(循环):消除重复代码
3.1 什么是 Loop?
在编写 Playbook 时,经常需要对多个相似项执行相同的任务------比如安装多个软件包、创建多个用户。Loop(循环)机制让我们可以避免编写重复的任务代码。
3.2 基本用法:遍历列表
loop 关键字是 Ansible 推荐的循环方式:
yaml
- name: 安装多个软件包
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- nginx
- postgresql
- python3
每次迭代中,item 变量自动代表当前元素。
3.3 遍历字典
方式一:使用字典列表
yaml
- name: 创建用户账号
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
shell: "{{ item.shell }}"
loop:
- { name: 'john', groups: 'admin', shell: '/bin/bash' }
- { name: 'jane', groups: 'developers', shell: '/bin/zsh' }
- { name: 'bob', groups: 'support', shell: '/bin/bash' }
方式二:使用 dict2items 过滤器遍历字典
yaml
- name: 从字典创建用户
ansible.builtin.user:
name: "{{ item.key }}"
groups: "{{ item.value.groups }}"
shell: "{{ item.value.shell }}"
loop: "{{ users | dict2items }}"
vars:
users:
john:
groups: admin
shell: /bin/bash
jane:
groups: developers
shell: /bin/zsh
dict2items 过滤器将字典转换为键值对列表,item.key 和 item.value 分别代表键和值。
3.4 循环控制:loop_control
获取循环索引:
使用 index_var 获取当前迭代的索引(从 0 开始):
yaml
- name: 创建带编号的文件
ansible.builtin.copy:
content: "这是第 {{ idx }} 个文件"
dest: "/tmp/file_{{ idx }}.txt"
loop: "{{ range(5) | list }}"
loop_control:
index_var: idx
自定义循环变量名:
默认使用 item,可以通过 loop_var 自定义:
yaml
- name: 使用自定义变量名
ansible.builtin.debug:
msg: "当前值:{{ current_item }}"
loop: "{{ my_list }}"
loop_control:
loop_var: current_item
设置循环标签(便于查看输出):
yaml
- name: 配置虚拟主机
ansible.builtin.template:
src: vhost.conf.j2
dest: "/etc/nginx/sites-available/{{ item.domain }}"
loop: "{{ domains }}"
loop_control:
label: "{{ item.domain }}"
3.5 嵌套循环
Ansible 不直接支持在同一个任务中嵌套两个 loop,但可以通过 include_tasks 实现:
main.yml(外层循环):
yaml
- name: 遍历用户列表
ansible.builtin.include_tasks: inner.yml
loop: "{{ users }}"
loop_control:
loop_var: outer_user
inner.yml(内层循环):
yaml
- name: 为用户分配多个组
ansible.builtin.user:
name: "{{ outer_user.name }}"
groups: "{{ item }}"
state: present
loop: "{{ outer_user.groups }}"
3.6 Loop 与 When 的组合
当 loop 与 when 一起使用时,when 会在每次迭代中独立评估:
yaml
- name: 仅安装尚未安装的软件包
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- nginx
- postgresql
when: item not in installed_packages
3.7 旧版语法(with_*)
Ansible 早期版本使用 with_* 系列关键字(如 with_items、with_dict、with_nested 等)。虽然这些语法仍然可用,但官方推荐使用统一的 loop 关键字。
四、三者的协同与对比
4.1 功能对比
| 机制 | 作用 | 适用场景 |
|---|---|---|
| Tag | 控制哪些任务执行 | 调试、分阶段部署、选择性执行 |
| When | 控制是否执行某个任务 | 基于系统状态、环境变量的条件执行 |
| Loop | 控制如何重复执行某个任务 | 批量操作、遍历列表/字典 |
4.2 组合使用示例
三者可以灵活组合,实现精细化的任务控制:
yaml
- name: 根据环境安装软件包
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- nginx
- postgresql
- redis
when: environment == "production"
tags:
- packages
- production
这个例子中:
loop遍历三个软件包when确保仅在 production 环境执行tags允许通过--tags packages或--tags production选择性执行
4.3 注意事项
- Tag 与 When 的区别 :Tag 是运行时的选择器 (由用户在命令行决定),而 When 是运行时的判断器(由系统状态决定)。
- Tag 不继承到 include/import :使用
include_tasks或import_tasks时,标签可能不会自动传递,需要使用apply关键字。 - When 在 Block 上的行为 :在 Block 上使用
when,该条件会继承给 Block 内的所有任务。
五、总结
Ansible 的 Tag、When 和 Loop 是日常自动化运维中最常用的三种任务控制机制:
- Tag 让我们能够精准选择要执行的任务,特别适合大型 Playbook 的调试和分阶段部署。
- When 让我们能够智能判断任务执行条件,实现跨平台、跨环境的自适应运维。
- Loop 让我们能够高效批量执行重复任务,大幅减少代码冗余。
掌握这三种机制并将其灵活组合,是写出高效、可维护的 Ansible Playbook 的关键。在实际项目中,建议根据业务场景建立统一的标签命名规范、条件判断策略和循环使用准则,让自动化运维代码更加清晰、健壮。