前言
Ansible 自动化最容易踩的坑,不是模块语法,而是变量覆盖。
同样一个变量 env、nginx_port、repo_url,你在 group_vars/all、inventory、host_vars、playbook vars、命令行 -e 各写一份,到底哪一个生效?很多人靠猜、靠反复调试,线上经常出现"本地测试正常,上生产值被覆盖"的玄学问题。
Ansible 的变量不是简单的"后面定义覆盖前面",它有一套官方固定优先级链。本文从三大变量作用域入手,配合可直接运行的实战工程,把优先级讲清楚,最后给出生产最佳实践。
说明:本文基于 Ansible 2.14+ 稳定版,优先级规则官方长期稳定。
一、Ansible 三大变量作用域
Ansible 变量分为三层作用域:全局作用域、Play 作用域、主机作用域 ,日常工作中最核心的三类:全局变量、组变量、主机变量。
1. 全局变量(Global)
作用范围:本次 Ansible 整个执行流程全局生效
来源:
- 命令行额外传参
-e / --extra-vars(优先级天花板) - 环境变量
ANSIBLE_* - ansible.cfg 配置里的变量
- 角色 defaults(优先级最低,用来做默认兜底)
特点:一次性覆盖,对所有主机、所有 play 生效,命令行 -e 生产调试神器,也是最容易翻车的地方。
2. 组变量(Group Vars)
作用范围:归属该主机组下所有主机
来源:
- inventory 清单内
[group:vars] - 项目目录
group_vars/组名.yml,还有公共全局组group_vars/all.yml(所有主机都会加载) - 支持嵌套组:子组 > 父组,子组变量覆盖父组变量
特点:批量统一配置,区分环境(dev/test/prod)、区分业务组(web/db/redis)首选。
合并顺序:all 组 → 父组 → 子组;同层级组默认按字母顺序合并,后加载覆盖前面。
3. 主机变量(Host Vars)
作用范围:仅针对单台主机生效
来源:
- inventory 清单内主机后面直接写
key=value host_vars/主机名.yml文件set_fact、register注册的主机 fact(属于主机域变量)
特点:单台主机个性化兜底。当大部分机器统一配置,个别机器需要特殊参数时使用。
一句话口诀:主机变量 > 组变量。
项目标准目录结构(规范写法)
ansible-demo/
├── inventory/
│ └── hosts.ini # 主机清单
├── group_vars/
│ ├── all.yml # 全局组变量(所有主机)
│ ├── web.yml # web组变量
│ └── db.yml # db组变量
├── host_vars/
│ ├── web01.yml # web01单主机变量
│ └── db01.yml # db01单主机变量
└── site.yml # 主playbook
二、官方完整优先级(从低 → 高,后面覆盖前面)
重点:越靠下面,优先级越高,会覆盖上面所有同名变量
- Role defaults(roles/*/defaults/main.yml)【最低,兜底默认值】
- inventory 清单内组变量
group_vars/all(inventory目录下)- playbook 目录下
group_vars/all - inventory
group_vars/自定义组 - playbook
group_vars/自定义组 - inventory 清单内主机变量
- playbook
host_vars/主机名 - play 级别 vars / vars_files
- 角色 vars(roles/*/vars/main.yml)
- 主机 facts(ansible_facts,setup采集)
set_fact/register注册变量- 命令行 extra-vars
-e【最高优先级,强覆盖一切】
✅ 极简记忆版(日常够用):
role defaults < group_vars < host_vars < play vars < set_fact < -e
坑点提醒:
group_vars/all本质也是组变量,属于所有主机的公共组;- 同级别组:子组覆盖父组;同级多组按字母序合并;可以用
ansible_group_priority手动调整组合并权重,数值越大优先级越高;- 字典变量默认是合并(merge),不是直接覆盖;数组是直接替换;修改
hash_behaviour=merge可以开启字典合并,生产不推荐开启,容易出现不可预期合并。
三、优先级实战实验(可直接复制运行)
我们构造一套场景,验证覆盖关系:
目标变量:app_port
预期覆盖顺序:group_vars/all → group_vars/web → host_vars/web01 → play vars → -e
Step1:inventory/hosts.ini
ini
[web]
web01 ansible_host=127.0.0.1
web02 ansible_host=127.0.0.1
[db]
db01 ansible_host=127.0.0.1
Step2:group_vars/all.yml(最低公共组)
yaml
---
app_port: 8080
env: public
Step3:group_vars/web.yml(web业务组)
yaml
---
app_port: 8081
env: web-group
Step4:host_vars/web01.yml(web01单主机)
yaml
---
app_port: 8082
env: web01-host
Step5:site.yml(playbook)
yaml
---
- name: Ansible变量优先级测试
hosts: web
gather_facts: false
vars:
app_port: 8083
env: play-vars
tasks:
- name: 打印变量
debug:
msg: "app_port = {{ app_port }}, env = {{ env }}"
测试1:直接执行,不追加-e
bash
ansible-playbook -i inventory/hosts.ini site.yml
输出:
app_port = 8083, env = play-vars
✅ 结论:play 内 vars 覆盖 group_vars 和 host_vars。
测试2:命令行加 -e 传参(最高优先级)
bash
ansible-playbook -i inventory/hosts.ini site.yml -e "app_port=9000 env=cmd-extra"
输出:
app_port = 9000, env = cmd-extra
✅ 结论:-e 直接碾压前面所有定义。
测试3:注释掉 play 里 vars,再执行
注释 site.yml 中 vars 块,再次运行,web01 输出:
app_port = 8082, env = web01-host
✅ 结论:host_vars/web01.yml 覆盖 group_vars/web.yml。
测试4:删掉 host_vars/web01.yml,再执行
输出:
app_port = 8081, env = web-group
✅ 结论:group_vars/web.yml 覆盖 group_vars/all.yml。
整套实验链路完全印证优先级链条。
四、嵌套组变量小实验(父组 vs 子组)
inventory:
ini
[all:children]
prod
[prod:children]
web
[web]
web01
- group_vars/all.yml:
region: global - group_vars/prod.yml:
region: prod - group_vars/web.yml:
region: web
结果:web01 获取 region: web
规则:子组变量 > 父组变量
五、常见踩坑清单
- 同名变量不知道哪里被覆盖
排查方案:开启 debug,打印 vars 或者用 ansible-inventory 查看最终渲染变量
bash
# 查看主机最终合并后的所有变量
ansible-inventory -i inventory/hosts.ini --host web01
这个命令非常关键,可以直接看到合并完成之后的变量,定位覆盖来源。
- group_vars 目录放错位置
很多人把 group_vars 放到 inventory 子目录,又在 playbook 根目录放一份,两份都会加载,playbook目录下 group_vars 优先级更高,不知不觉覆盖。
- 混淆 facts 和普通变量
setup 采集的 facts(ansible_memtotal_mb 这类)优先级高于 host_vars、group_vars,低于 set_fact 和 -e。
- 用 hash_behaviour=merge 字典合并
字典合并会让排障难度指数上升,线上强烈建议保持默认(replace),字典直接覆盖,逻辑清晰可控。
- 生产环境大量使用 -e
-e 优先级最高,适合临时调试;正式交付脚本尽量不要依赖 -e 传参,否则版本管理、变更审计困难。
六、生产环境最佳实践
-
分层规划变量
group_vars/all.yml:全集群公共默认配置(时区、ntp、基础yum源)group_vars/{env}.yml:环境区分 dev/test/prod(数据库地址、域名)group_vars/{biz}.yml:业务组区分 web/db/redishost_vars/主机名.yml:只用来放极少数个性化配置,越少越好,尽量不要泛滥- role defaults:给角色兜底默认值,允许上层覆盖
-
敏感变量不要明文写变量文件
密码、token 使用 ansible-vault 加密 group_vars / host_vars 文件,不要提交明文到 Git。
- 变量命名规范
只使用小写字母、数字、下划线;不要横线、中文、空格;不要和 ansible 内置变量重名(ansible_user、ansible_host)。
- 变更变量前先用 ansible-inventory 预览
上线前先 ansible-inventory --host xxx 核验最终渲染值,避免线上踩变量覆盖坑。
- 尽量扁平化,少多层嵌套组
嵌套组越多,变量合并链路越长,新人维护成本高。
七、总结
Ansible 变量优先级本质是一套分层覆盖模型:
兜底默认值放 role defaults,集群公共配置放 group_vars/all,业务/环境放 group_vars,单台特殊配置收敛到 host_vars,play 级统一覆盖业务组,临时调试用 -e 强制覆盖一切。
排障口诀:查覆盖先跑 ansible-inventory,看最终合并结果,而不是盯着单个 yaml 文件看。