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 展示了根模块与子模块之间的调用关系:

根模块与子模块调用关系 根模块 (main.tf) PagerDuty 集成 Slack 通知 Lambda 函数 API Gateway 模块间数据流 • 根模块通过 module.pagerduty 创建集成,输出 integration_key • Lambda 模块接收 integration_key 作为环境变量 • API Gateway 模块接收 Lambda ARN 并创建触发端点

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 展示了环境目录与子模块之间的部署关系:

环境目录与子模块部署关系 environments/dev/ environments/staging/ environments/prod/ 共享模块池 (modules/) PagerDuty Slack Lambda API GW 每个环境目录的根模块引用同一套子模块,通过变量差异化配置实现环境隔离

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。这避免了过早引入版本管理复杂性,同时为后续扩展保留空间。