条件分支要有单一判断点
用事实做条件判断的常见退化形态是:同一个判断(例如“是不是这个发行版家族”)在几十个任务的 when 里各写一遍。改一次判断规则要翻遍全剧,还容易漏。合理的设计是先把事实归纳成少量派生变量,在剧本开头算一次,后面所有分支都引用这些派生变量,判断逻辑只有一处。
基本信息
| 数据来源 | 采集到的事实、注册的变量、清单与分组变量 |
|---|---|
| 判断写法 | 任务的 when、块级 when、条件表达式中的比较与测试 |
| 派生变量 | 用 set_fact 把复杂判断收敛成一两个布尔或类别变量 |
| 版本比较 | 版本号不要按字符串比较,用专门的版本比较测试 |
| 兜底处理 | 未覆盖的分支要显式失败,不要安静跳过 |
| 验证方式 | 检查模式预演,并对各分支分别构造一次真实运行 |
典型使用场景
- 跨发行版适配:按家族选择包名、服务名与配置路径
- 虚拟化差异:物理机与虚拟机在磁盘、网卡命名上处理不同
- 容量判断:按内存或 CPU 数量决定工作进程数
- 安全机制差异:是否启用强制访问控制、防火墙管理方式不同
- 新老版本兼容:某参数仅在较新版本可用,需要按版本分支
- 现实条件判断:某挂载点是否存在,决定是否走额外的配置步骤
先归纳再分支
先归纳再分支
- name: 归纳派生变量,判断只写一次
ansible.builtin.set_fact:
is_debian_family: "{{ ansible_os_family == 'Debian' }}"
supports_systemd_unit: "{{ ansible_service_mgr is defined and ansible_service_mgr == 'systemd' }}"
worker_processes: >-
{{ [2, (ansible_processor_vcpus | default(2) * 2)] | min }}
- name: 按派生变量分支,条件保持简单
ansible.builtin.template:
src: app.conf.j2
dest: "{{ conf_dir }}/app.conf"
vars:
conf_dir: "{{ is_debian_family | ternary('/etc/apache2', '/etc/httpd') }}"
- name: 版本判断用版本比较,不用字符串比较
ansible.builtin.debug:
msg: "该版本支持新参数"
when: ansible_distribution_version is version('8.0', '>=')
- name: 未覆盖的情况主动失败
ansible.builtin.fail:
msg: "未支持的发行版家族 {{ ansible_os_family }}"
when: not (is_debian_family or ansible_os_family == 'RedHat')分支设计对照
| 做法 | 优点 | 风险 |
|---|---|---|
| 每个任务各写 when | 改动局部可见 | 规则分散,改一处漏多处 |
| 开头算派生变量 | 判断单一来源,任务可读 | 需确保派生变量先于使用它的任务生成 |
| 块级 when | 整段共享同一条件 | 块内任务无法各自调整条件,需要拆块 |
| 按家族引入不同任务文件 | 差异集中在一个文件里 | 文件数量增加,需明确引入条件 |
| 用映射表取代条件 | 任务里没有分支,最易读 | 表项需覆盖全部目标取值,否则会出现空值 |
派生变量必须在被使用之前生成,且不能依赖尚未采集的事实。检查模式预演能暴露“变量还没定义就被引用”这类顺序问题,这也是它最实用的用途之一。
不要基于易变的事实做长链路决策:可用内存、挂载点在执行过程中可能变化,用它们在剧本开头决定后续所有动作,会在中途环境改变时产生不一致的结果。易变条件应放在使用它的那一步附近判断。
分支数量要控制
每增加一个分支,验证成本近似翻倍:两个条件就是四种组合,实际很难全部覆盖。设计时优先把差异变成数据(映射表),其次是把分支收敛成一个派生变量,最后才考虑在任务里写条件。分支越多,就越需要把兜底失败写清楚。