分层是为了能改
一份能长期维护的自动化仓库,通常分成几层:清单回答“操作谁”,变量回答“各地差异是什么”,角色回答“这类主机该怎么配”,剧本回答“按什么顺序把哪些角色用到哪些分组上”。分层的价值不在整齐,而在于改动被限制在一层里:换参数不动逻辑,逻辑升级不影响参数,新增一类主机只需组合已有角色。
基本信息
| 清单层 | 主机与分组,以及连接方式;不含业务参数 |
|---|---|
| 变量层 | 分组变量、主机变量、秘密文件;不含流程逻辑 |
| 角色层 | 可复用的配置能力,参数从外部传入,自带默认值 |
| 剧本层 | 编排:哪些分组、按什么顺序、用哪些角色,尽量少写具体步骤 |
| 实现层 | 角色内部的任务文件、模板、处理器,对外只暴露变量接口 |
| 判断标准 | 每项内容只在一个地方定义,改动只影响一层 |
典型使用场景
- 多环境多角色:剧本保持环境无关,环境差异留在清单与变量层
- 能力复用:把“装好并加固某中间件”固化成角色,多个项目直接引用
- 职责划分:平台团队维护角色,业务团队只写编排与参数
- 变更影响评估:知道要改的是哪一层,就能预估影响范围
- 新人上手:从剧本读流程、从角色读实现,路径清晰不跳层
- 逐步重构:把散落在剧本里的实现细节逐步下沉到角色
层与层的调用关系
层与层的调用关系
- name: 剧本层只做编排
hosts: web
roles:
- role: common # 基线:用户、时区、基础包
tags: [common]
- role: nginx # 能力:装好并配置
vars:
nginx_worker_processes: "{{ web_worker_processes }}" # 参数来自变量层
tags: [nginx]
# 角色内部:tasks 是步骤,templates 是内容,defaults 是对外参数
# roles/nginx/defaults/main.yml 对外可见、可覆盖的默认参数
# roles/nginx/vars/main.yml 角色内部使用、通常不对外开放的取值
# roles/nginx/tasks/main.yml 步骤;按需要拆成 install.yml、config.yml
# roles/nginx/handlers/main.yml 变化后要做的动作
# roles/nginx/templates/*.j2 配置文件模板
# 变量层:只放取值,不放逻辑
# group_vars/web/vars.yml
# web_worker_processes: 4内容该放哪一层
| 内容 | 所属层 | 放错的表现 |
|---|---|---|
| 主机与分组 | 清单层 | 把主机名写进剧本,换环境必须改剧本 |
| 环境参数与阈值 | 变量层 | 参数写死在角色里,其它环境只能复制角色 |
| 某个软件的完整配置能力 | 角色层 | 剧本里堆几十个任务,无法被别的项目复用 |
| 跨角色的执行顺序 | 剧本层 | 顺序写进角色内部,角色变得只能用于特定场景 |
| 配置文件内容 | 角色内的模板 | 用多个行编辑任务拼配置,内容难以整体评审 |
| 变化后的动作 | 角色内的处理器 | 把重启动作写进任务,每次执行都可能触发无谓重启 |
角色对外只应暴露变量接口:调用者通过变量控制行为,而不需要关心角色内部任务怎么拆。若某个角色必须靠调用者了解内部细节才能正确使用,说明接口没设计好,或者它本就不该被拆成角色。
同一份取值出现在多层时,最终生效的是优先级更高的那一层,而阅读者往往只看其中一层:这会制造“我明明改了却没生效”的典型问题。任何变量只定义一次,是分层设计里最该守住的纪律。
重构路径
面对一个大剧本,先按“谁、差异、能力、编排”把内容分类,再把能力部分整段搬进角色并把取值改成变量,最后让剧本只留角色调用与顺序。每次只搬一类,搬完做一次幂等验证,避免一次性重构后无法定位问题出在哪个环节。