LabHub
学习 学习路径 课程

Ansible 基础

变量优先级与 facts

在 LabHub 中继续学习

一句话总结

在 Ansible 中,所谓‘变量不生效’,多数不是因为变量不存在,而是因为在优先级更高的位置定义的值胜出了

概念图: 在优先级更高的位置定义的值胜出了 · 防止分支不断增加的结构性选择 · 第一,可被覆盖的值放在 defaults 中。 · -e 只用于一次性例外。

为什么需要它

假设要把同一个应用部署到 dev、stage、prod,只有端口、副本数和日志级别不同。如果为此复制三份 playbook,六个月后三个文件就会变成完全不同的东西。把值从代码中抽离并非风格偏好,而是防止分支不断增加的结构性选择

问题在于,可以放置值的位置太多了:play 中的 vars、单独的 vars_filesgroup_vars/host_vars/、inventory 中的变量、命令行 -e、role 的 defaultsvars,以及运行中通过 set_fact 创建的值。每个位置的优先级不同;不了解这个顺序,就会为‘明明改了值却没有生效’浪费一整天。

它如何工作

详细顺序很长,但实务中需要记住的骨架很短。

强度 位置 特性
最弱 role 的 defaults/main.yml ‘该 role 的默认值’——专为被覆盖而设
inventory 组变量 各环境的公共值
中等 inventory 主机变量 仅该服务器使用的例外
play 的 varsvars_files 本次运行使用的值
很强 通过 set_fact 创建的值 运行中计算出的值
最强 命令行 -e (extra vars) 胜过所有其他值

只要遵守两项原则,大多数问题都能解决。**第一,可被覆盖的值放在 defaults 中。第二,-e 只用于一次性例外。**如果习惯性使用 -e,这个值不会记录在任何地方,之后的人便无法复现。

事实(fact)的性质不同。它不是人手填写的值,而是从目标服务器收集到的事实。当 gather_facts: true 时,play 开始时会运行 setup 模块,收集操作系统类型、架构、主机名、网络接口、内存等信息,并放入 ansible_facts。借助事实,可以针对不同目标自动执行‘Ubuntu 用 apt,RHEL 用 dnf’之类的分支。

收集事实是有成本的。主机达到数百台时,仅事实收集就可能耗费数十秒。不需要事实的 play 应以 gather_facts: false 关闭收集;若频繁重复运行,则应考虑事实缓存。

register 是第三种类型。它把任务的完整执行结果(标准输出、退出码、是否 changed)存入变量。常见错误是把整个结果对象原样写入文件,而实际需要的通常只是 .stdout

实际工作中常见的情况

**第一,用 -e 救急后却不把修改落实到代码中。**如果在故障应对时传入 -e replicas=10 恢复了服务,这个值必须提交到仓库。否则下次部署会悄无声息地把它恢复原状。这和 Terraform 中的漂移事故在结构上完全相同。

第二,信任事实,但也要验证。ansible_distribution 等事实通常准确,但在容器或特殊镜像中可能出现意料之外的值。按事实分支时,应为意外值保留默认分支。

如何确认值来自哪里

比背诵优先级更实用的方法,是直接询问该主机此刻的值是什么。推断可能出错,实测不会。

最快的方法是用一个任务打印值。以 ad-hoc 方式运行 debug 输出指定变量,立刻就能看出以该主机为准的最终胜出值。尤其值得做的是一次对多台主机运行。若只有一台的值不同,说明该主机的 host_vars 或 inventory 行中写了例外,这通常就是问题根源。

如果关心的不是值,而是它来自哪里,就逐步缩小范围。暂时删除组变量后值发生变化,说明来源就在这里;若不变,则来自优先级更高的位置。运行时开启详细输出,日志会显示读取了哪些变量文件,从而能立即发现自己一直在修改一个根本未被读取的文件。

命名也会极大影响这个问题。养成为变量名添加前缀的习惯,几乎可以消除冲突。与 port 相比,myapp_port 更好,role 的变量应以 role 名作为前缀。短名称可能被任何 role 使用,一旦另一个 role 使用同名变量,其中一个就会悄无声息地败下阵来。

最后,比背诵优先级更有效的是,把什么内容放在哪个位置制定为团队规则。各环境不同的值放在组变量中,单台服务器的例外放在主机变量中,role 建议的默认值放在 defaults 中。只要遵守这三条,几乎无需使用其他位置;不用,就不会再被优先级吓到。

下一项实验要做什么

分别创建 play 变量、变量文件、组变量和主机变量,亲眼确认哪个会胜出,再观察命令行 -e 如何胜过所有这些值。收集事实并保存为 JSON,再用 registerset_fact 组合值,生成最终摘要文件。