介绍
两个团队需要采用相同的小型报告存储设计,但使用不同的名称、归属和报告文本。你将补全已提供模块的接口,从两个独立根目录调用它,并扩展一个使用稳定键的报告集合,同时保留现有成员。
你应已了解本地模块、变量、输出和资源地址。实验提供资源骨架和调用方输入;你将编写它们的接口约定,并使用已准备好的工具,在这个全新环境中完成部署,无需个人 AWS 账户。
与认证考试的关联
| 认证 | 考试任务 | 实践内容 |
|---|---|---|
| CloudOps Engineer – Associate (SOA-C03) | 任务 3.1,技能 3.1.6 | Terraform 基础实践:通过经过审查的配置和状态变更维护基础设施,同时保留正在使用的资源。 |
实验概览

定义带类型的模块接口
在这一步中,补全一个小型可复用存储模块的输入和输出约定。子模块通过接口封装资源;根模块提供具体输入,并拥有自己的状态。
cd /home/labex/project/reusable-reports
cat modules/report-store/main.tf
cat dev/main.tf
提供的资源骨架使用 var.bucket_name、var.owner 和 var.reports。for_each 为映射中的每个键创建一个对象实例,使用 aws_s3_object.reports["daily"] 这样的地址。稳定的命名键让现有对象与新添加的成员保持独立。存储桶的版本管理资源是一个显式依赖,以确保写入报告时获得版本标识。
定义接口。map(object({ body = string })) 接受一个带名称的集合,每个成员都必须提供报告文本。它明确了模块的预期输入,不再依赖未文档化的输入结构:
cat > modules/report-store/variables.tf <<'HCL'
variable "bucket_name" {
type = string
description = "Unique bucket name supplied by this environment root."
}
variable "owner" {
type = string
description = "Owner tag supplied by the caller."
}
variable "reports" {
type = map(object({ body = string }))
description = "Stable report keys and their text content."
}
HCL
输出提供有用的资源信息,使调用方无需访问模块内部。下面的 for 表达式构建了从集合键到对象键的映射:
cat > modules/report-store/outputs.tf <<'HCL'
output "bucket_id" {
value = aws_s3_bucket.store.id
}
output "object_keys" {
value = { for key, report in aws_s3_object.reports : key => report.key }
}
HCL
初始化 dev 根目录并检查接口约定:
terraform -chdir=dev init
terraform -chdir=dev validate
terraform -chdir=dev plan
预期配置有效,并显示 3 to add:存储桶、版本管理配置和 daily 报告对象。在检查调用方的存储桶名称、所有者和报告文本之前,不要应用。独立的财务参考报告与此模块无关,必须保持不变。
从两个独立根目录调用同一模块
在这一步中,通过同一个模块,使用不同输入部署 dev 和 preview。两个根目录共享模块源文件,不共享状态。它们各自的工作目录目前使用独立的本地状态;for_each 只区分同一状态内的资源。
审查并应用 dev 的已保存计划:
terraform -chdir=dev plan -out=dev.tfplan
预期看到 3 to add, 0 to change, 0 to destroy。
terraform -chdir=dev apply dev.tfplan
初始化 preview 之前,先阅读其调用方配置:
cat preview/main.tf
terraform -chdir=preview init
terraform -chdir=preview validate
terraform -chdir=preview plan -out=preview.tfplan
它使用不同的输入,选择 module-preview-reports、所有者 preview 及 preview daily report。预期又是一个创建三个资源的计划,不会更改 dev。
terraform -chdir=preview apply preview.tfplan
检查各根目录的输出和稳定的实例地址:
terraform -chdir=dev output
terraform -chdir=preview output
terraform -chdir=dev state list
terraform -chdir=preview state list
两个根目录都提供 bucket_id 和 object_keys;各自都有键为 "daily" 的报告实例,但这些地址属于不同的本地状态和不同的存储桶。读取实际内容:
aws s3 \
cp s3://module-dev-reports/reports/daily.txt -
aws s3 \
cp s3://module-preview-reports/reports/daily.txt -
预期看到 dev daily report 和 preview daily report。AWS View 显示两套独立存储及财务参考报告。显示 dev 当前 daily 报告的 VersionId,以便扩展集合后进行比较:
aws s3api \
head-object \
--bucket module-dev-reports \
--key reports/daily.txt \
--query VersionId \
--output text
添加使用稳定键的集合成员并保留现有报告
在这一步中,仅扩展 dev 的映射。更改集合的键会改变资源实例标识;保留键 daily 并添加 weekly,应使 daily 对象保持完整不变。
替换 dev 调用方配置,保留原有输入并新增一个成员:
cat > dev/main.tf <<'HCL'
module "report_store" {
source = "../modules/report-store"
bucket_name = "module-dev-reports"
owner = "dev"
reports = {
daily = { body = "dev daily report\n" }
weekly = { body = "dev weekly report\n" }
}
}
output "bucket_id" { value = module.report_store.bucket_id }
output "object_keys" { value = module.report_store.object_keys }
HCL
审查变更:
terraform -chdir=dev plan -out=extra.tfplan
预期看到 1 to add, 0 to change, 0 to destroy,仅新增 module.report_store.aws_s3_object.reports["weekly"]。如果现有 daily 对象或存储桶将被替换,请停止并比较键和应保持不变的输入。
terraform -chdir=dev apply extra.tfplan
将 daily 报告的 VersionId 与上一步输出进行比较:
aws s3api \
head-object \
--bucket module-dev-reports \
--key reports/daily.txt \
--query VersionId \
--output text
它必须仍是同一个原始版本。读取新增成员和独立的 preview 报告:
aws s3 \
cp s3://module-dev-reports/reports/weekly.txt -
aws s3 \
cp s3://module-preview-reports/reports/daily.txt -
预期看到 dev weekly report 和未改变的 preview daily report。后台检查会比较原生的首次写入版本,不会仅因为替换后的对象具有相同字节就接受结果。
terraform -chdir=dev output
terraform -chdir=dev plan
terraform -chdir=preview plan
dev 的输出映射包含两个键;preview 仍只有 daily。两个根目录都应收敛,显示 No changes。保持这些正在使用的存储可用。此模块定义了一个接口和一个资源集合;选择独立的远端后端键是另一项环境隔离决策,将在后续介绍。
有关使用模块输入和输出作为边界的说明,请参阅 HashiCorp 的模块组合指南。
总结
你编写了带类型的模块接口,向两个独立根目录返回有用的输出,并添加了使用稳定键的 for_each 成员,同时保留了原始报告。资源集合与环境状态边界是不同的概念。



