4. 项目目录结构设计:模块化设计原则、标准Terraform项目布局、根模块与子模块划分
在事故响应值班机器人项目中,合理的目录结构是基础设施即代码(IaC)长期可维护性的基石。本章将围绕模块化设计原则,构建一套标准 Terraform 项目布局,并明确根模块与子模块的划分边界。
4.1 模块化设计原则
模块化设计的核心目标是复用、隔离与可组合。在值班机器人场景中,我们遵循以下三条原则:
| 原则 | 说明 | 值班机器人示例 |
|---|---|---|
| 单一职责 | 每个模块只负责管理一类资源或一个逻辑单元 | PagerDuty 集成模块只管理集成密钥与服务映射,不混入 Slack 配置 |
| 显式依赖 | 模块间通过输出(output)暴露必要信息,通过变量(variable)接收上游数据 | Lambda 模块输出函数 ARN,API Gateway 模块通过变量接收该 ARN |
| 环境无关 | 模块内部不硬编码环境标识(dev/staging/prod),环境差异由根模块的变量注入 | 模块内不写 environment = "prod",而是通过 var.environment 传入 |
实践建议:每个子模块都应包含独立的
README.md,说明其用途、输入变量、输出值以及使用示例。这能显著降低团队协作时的认知负担。
4.2 标准 Terraform 项目布局
我们采用 HashiCorp 推荐的标准布局,并结合值班机器人的实际需求进行微调。整体结构分为三层:根配置层、环境目录层和共享模块层。
oncall-robot-infra/
├── environments/ # 环境目录层
│ ├── dev/
│ │ ├── main.tf # 根模块入口
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ ├── terraform.tfvars # 环境特定变量
│ │ └── backend.tf # 远程状态配置
│ ├── staging/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ └── prod/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ ├── terraform.tfvars
│ └── backend.tf
├── modules/ # 共享模块层
│ ├── pagerduty_integration/ # PagerDuty 集成模块
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ ├── slack_notifier/ # Slack 通知模块
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ ├── lambda_function/ # Lambda 函数模块
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ └── api_gateway/ # API Gateway 模块
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── README.md
├── global/ # 全局共享资源
│ ├── iam_roles/
│ │ ├── main.tf
│ │ └── variables.tf
│ └── kms_keys/
│ ├── main.tf
│ └── variables.tf
├── scripts/ # 辅助脚本
│ ├── init_workspace.sh
│ └── validate_all.sh
├── .terraform-docs.yml # 文档生成配置
└── terragrunt.hcl # 可选:Terragrunt 编排文件
关键说明:
global/ 目录用于存放跨环境共享的 IAM 角色、KMS 密钥等资源。这些资源通常由平台团队统一管理,各环境通过数据源(data source)引用,而非重复创建。
4.3 根模块与子模块划分
根模块是每个环境目录下的 main.tf,它负责组合子模块并注入环境特定变量。子模块则是 modules/ 下的独立单元,每个子模块封装一类云资源或 SaaS 集成。
以下 SVG 展示了根模块与子模块之间的调用关系:
4.4 子模块内部结构规范
每个子模块遵循统一的内部文件组织方式,确保团队成员可以快速定位关键配置:
| 文件 | 职责 | 是否必须 |
|---|---|---|
main.tf |
资源定义主体,包含所有 resource 和 data 块 |
是 |
variables.tf |
声明所有输入变量,包含类型、描述和默认值 | 是 |
outputs.tf |
暴露模块对外接口,供根模块或其他模块引用 | 是 |
locals.tf |
定义局部变量,用于简化表达式或派生标签 | 推荐 |
versions.tf |
声明 Terraform 版本约束和 Provider 版本约束 | 推荐 |
README.md |
模块使用文档,包含示例和注意事项 | 推荐 |
常见陷阱:不要在子模块的
main.tf 中直接引用根模块的变量名(如 var.environment)。子模块应通过自己的 variables.tf 显式声明所需变量,根模块在调用时通过 module 块传入。这保证了模块的可移植性。
4.5 环境目录与根模块的职责
每个环境目录(如 environments/dev/)下的根模块承担以下职责:
- 组合编排:通过
module块引用子模块,并按环境需求调整参数。 - 环境变量注入:从
terraform.tfvars读取环境特定值(如 Slack Webhook URL、PagerDuty 服务 ID)。 - 远程状态绑定:在
backend.tf中配置 S3 后端和 DynamoDB 锁表,确保状态隔离。 - Provider 配置:设置 AWS Provider 的区域、profile 等全局参数。
以下 SVG 展示了环境目录与子模块之间的部署关系:
4.6 模块版本管理策略
当项目规模增长后,子模块可能被多个环境或不同项目引用。建议采用以下版本管理方式:
- Git 子模块(submodule):将
modules/目录作为一个独立的 Git 仓库,通过子模块方式引入主项目。每个子模块的变更通过 Git 标签(tag)管理版本。 - Terraform Registry(私有):将子模块发布到私有 Terraform Registry,根模块通过
source = "app.terraform.io/example/module/lambda//aws"指定版本。 - 本地路径引用:在项目初期,直接使用
source = "../../modules/lambda_function"相对路径,保持简单。
推荐做法:在项目初期使用本地路径引用,当模块稳定后迁移至 Git 子模块或私有 Registry。这避免了过早引入版本管理复杂性,同时为后续扩展保留空间。