默认是替换不是合并
同名变量出现在多层时,Ansible 默认用高优先级的整个值替换低优先级的值,而不是逐键合并。后果最明显的是字典:高优先级层只写了其中几个键,低优先级层里其余的键会一起消失。想要真正的合并,必须用合并过滤器显式表达。判断口诀很简单——默认永远替换,合并要自己写出来。
基本信息
| 默认行为 | 同名变量整体替换,高优先级覆盖低优先级 |
|---|---|
| 字典合并 | combine 过滤器,嵌套字典可加递归参数 |
| 列表处理 | 合并过滤器不处理列表元素,需要并集运算 |
| 排查手段 | 打印整个字典看最终结构,而不是只看单个键 |
典型使用场景
- 基线配置在分组变量里定义,环境差异只覆盖少数字段:用合并过滤器保住其余键
- 需要追加而不是替换列表项:用并集运算或列表相加
- 多层嵌套配置(数据库、缓存、超时)合并:指定递归合并,避免内层整块被替换
- 排查某个键丢失:把结果字典整体打印出来对照各层定义
- 角色默认值与环境覆盖配合:默认值里放全量键,减少缺键概率
覆盖与合并的差别
覆盖与合并的差别
- name: 默认行为是整块替换 # 本层没有的键会一起消失
ansible.builtin.debug:
msg: "{{ env_over }}"
vars:
base: {timeout: 30, retries: 3}
env_over: {timeout: 60}
- name: 显式合并保留下层键 # 嵌套字典再加递归参数
ansible.builtin.debug:
msg: "{{ base | combine(env_over) }}"
- name: 列表要并集而不是覆盖 # 合并过滤器不管列表元素
ansible.builtin.debug:
msg: "{{ old_list | union(new_list) }}"
vars:
old_list: [80, 443]
new_list: [8080]场景与写法
| 场景 | 默认行为 | 怎么表达想要的效果 |
|---|---|---|
| 字典同名 | 整体替换 | 用合并过滤器,嵌套时加递归参数 |
| 列表同名 | 整体替换 | 用并集运算或列表相加 |
| 标量同名 | 高优先级生效 | 无需额外操作,确认来源即可 |
| 缺键的字典 | 低优先级键消失 | 先补全键再合并,或逐层合并 |
递归参数决定嵌套字典是逐层合并还是整块替换。配置嵌套越深越要显式写明,否则外层合并成功、内层却丢了半截配置,表面上看不出来。
依赖全局合并开关改变默认行为的写法已被废弃:升级后语义会变,别人接手时也看不出合并发生在哪里。把合并写进表达式才是可迁移的做法。
怎么设计变量层次
减少合并需求的做法是让每一层只负责自己的键:分组变量放全量基线,主机变量只放这台机器的差异,环境差异集中在环境目录。键的归属清晰之后,绝大多数情况用不到合并;真正需要合并时,也容易判断哪些键是从下层继承来的。