3. 模块仓库结构规划

模块平台建设到第三阶段,真正决定长期维护成本的往往不是模块本身写得多好,而是仓库结构是否合理。一个企业里通常有几十到几百个 Terraform 模块,如果一开始没有规划好仓库形态、目录层级和文档标准,半年后就会出现“找不到模块、版本对不上、README 形同虚设”的典型困境。本节聚焦三件事:monorepo 与 polyrepo 的取舍、目录与命名规范、README 与 CHANGELOG 标准。

一、monorepo 与 polyrepo 的取舍

monorepo 指所有模块放在同一个仓库中,通过子目录区分;polyrepo 指每个模块(或每组强相关模块)独立成仓。两者没有绝对优劣,关键看团队规模、发布节奏和权限模型。

monorepo 的优势在于:统一 CI 流水线、跨模块重构方便、依赖关系一目了然、新人只需 clone 一个仓库。代价是仓库体积增长快、CI 全量触发成本高、权限粒度粗(无法只让某团队维护某个模块)。polyrepo 的优势是:模块独立版本、独立发布、权限清晰、CI 只跑受影响模块。代价是跨模块变更需要多仓协同、版本对齐困难、平台侧要维护大量仓库配置。

一个可落地的判断依据是:模块数量少于 50、团队少于 5 个、发布节奏接近时优先 monorepo;模块数量超过 100、多团队独立发布、存在强权限隔离需求时优先 polyrepo。很多企业实际采用混合模式:核心基础模块(网络、IAM、标签规范)放 monorepo 统一维护,业务域模块按团队 polyrepo 独立演进,平台通过 registry 统一暴露。

monorepo 与 polyrepo 结构对比与决策路径 monorepo(单仓多模块) terraform-modules/ ├─ modules/network/vpc/ ├─ modules/iam/role/ ├─ modules/tags/standard/ └─ .github/workflows/ci.yml 统一 CI · 跨模块重构 · 权限粗粒度 适合:模块<50 · 团队<5 · 节奏一致 polyrepo(多仓独立) 独立仓库(每模块一仓) tf-aws-vpc/ → v1.4.2 tf-aws-iam-role/ → v2.1.0 tf-tags-standard/ → v1.0.5 各自 .github/workflows/ 独立版本 · 独立发布 · 权限清晰 适合:模块>100 · 多团队 · 强隔离 混合模式(推荐) 核心基础模块 → monorepo 业务域模块 → polyrepo 统一 registry 暴露

二、目录层级规划

无论选哪种仓库形态,目录层级都应遵循“按资源类型分层、按业务域分组”的原则。推荐 monorepo 采用如下结构:

terraform-modules/
├── modules/                  # 可复用模块
│   ├── network/
│   │   ├── vpc/
│   │   │   ├── main.tf
│   │   │   ├── variables.tf
│   │   │   ├── outputs.tf
│   │   │   ├── versions.tf
│   │   │   ├── README.md
│   │   │   └── CHANGELOG.md
│   │   └── subnet/
│   ├── iam/
│   │   └── role/
│   └── tags/
│       └── standard/
├── examples/                 # 每个模块的可运行示例
│   ├── network-vpc-basic/
│   └── iam-role-cross-account/
├── test/                     # 集成测试与合规测试
│   ├── terratest/
│   └── policy/
├── .github/workflows/        # CI/CD
└── docs/                     # 平台使用文档

关键约定:modules/ 下第一层是资源域(network、iam、compute、data),第二层是具体模块名;examples/ 与 modules/ 一一对应,命名采用“域-模块-场景”格式;test/ 与 docs/ 平级,不嵌套进模块目录。这样做的目的是让模块本身保持纯净(只含 Terraform 代码和文档),示例和测试作为独立消费者存在,避免模块目录被测试文件污染。

三、命名规范

命名规范要覆盖仓库名、模块目录名、资源名、变量名和输出名五个层面。推荐规则如下:

  • 仓库名:polyrepo 用 tf-<provider>-<resource>,如 tf-aws-vpc;monorepo 用 terraform-modules。
  • 模块目录名:小写短横线,<域>/<模块>,如 network/vpc、iam/role。
  • 资源名:this 用于单资源模块,多资源用语义名,如 aws_vpc.this、aws_subnet.public。
  • 变量名:小写蛇形,vpc_cidr、enable_flow_log,布尔变量用 enable_ 或 is_ 前缀。
  • 输出名:与变量风格一致,vpc_id、subnet_ids,复数用 _ids 结尾。

命名一旦确定,应通过 tflint 或自定义 conftest 策略在 CI 中强制校验,避免人工评审遗漏。

四、README 与 CHANGELOG 标准

README 是模块的“使用说明书”,必须包含以下固定章节,缺一不可:

  1. 模块简介:一句话说明用途和适用场景。
  2. 使用示例:可直接复制的 module 调用代码块。
  3. 输入变量表:名称、类型、是否必填、默认值、说明。
  4. 输出表:名称、类型、说明。
  5. 依赖与前置条件:Provider 版本、所需权限、外部依赖。
  6. 版本兼容性:支持的 Terraform 版本和 Provider 版本范围。

推荐使用 terraform-docs 自动生成变量表和输出表,通过 pre-commit hook 保证 README 与代码同步。CHANGELOG 遵循 Keep a Changelog 规范,按 Added / Changed / Deprecated / Removed / Fixed / Security 分类,每个版本标注日期和语义化版本号。破坏性变更必须单独用 BREAKING CHANGE 标注,并在升级指南中给出迁移步骤。

实践建议:在 CI 中加入三项检查——README 是否包含必需章节、CHANGELOG 是否随版本更新、模块目录命名是否符合规范。三项检查全部通过才允许合并到主分支,这样文档标准才不会随时间腐化。

仓库结构规划的本质,是把“模块怎么被找到、怎么被使用、怎么被升级”这三件事在物理层面固化下来。monorepo 与 polyrepo 的选择决定了协作边界,目录层级决定了查找效率,命名规范决定了可读性,README 与 CHANGELOG 决定了模块能否被安全消费。四者共同构成模块平台的地基,后续的版本管理、CI/CD 和发布流程都建立在这个地基之上。