1. 数据平台的资源版图
一句话总结: 数据平台的 IaC 边界比普通应用广得多——从对象存储、消息队列、ETL 作业、数据仓库到权限与目录,全都需要声明式管理。
典型组件按层划分:采集层(Kinesis / Kafka / DMS / Firehose)、存储层(S3 的原始区 / 清洗区 / 集市区)、计算层(Glue / EMR / Databricks / Flink)、编排层(Airflow / Step Functions)、仓库层(Redshift / Snowflake / BigQuery)、目录层(Glue Catalog / Hive Metastore)、权限层(Lake Formation / IAM / 行列级策略)。
为什么必须用 IaC 管理:
- 权限是核心风险面:谁能读哪张表、哪个分区,必须可审计、可评审。
- 环境差异导致「测试通过、生产失败」:Catalog、桶名、权限模型在 dev/prod 不一致时,作业迁移必然翻车。
- 成本巨大且易失控:一个没配生命周期的桶、一个没限制的扫描量,账单能翻十倍。
- 血缘与目录需要版本化:表结构、分区定义的变更应当走 PR。
目录结构上,把「一个存储区」「一个作业」封装成模块,环境目录只做参数组合:
data-platform/
├── modules/{lake-zone,glue-job,lake-permission}/
└── envs/{dev,prod}/
这与模块设计的一般原则一致,但数据平台的模块接口要额外暴露数据契约(表名、分区键、schema 版本)。
2. 数据湖与存储分层
一句话总结: 数据湖用「原始区 / 清洗区 / 集市区」三层划分职责,每层的生命周期、加密、权限都不同,Terraform 模块应把这三层固化下来。
2.1 三层划分
| 层 | 别名 | 内容 | 保留期 | 谁写 |
|---|---|---|---|---|
| Raw | Bronze | 原始数据,不可变 | 长期/永久 | 采集管道 |
| Cleaned | Silver | 去重、规范化、格式统一 | 中长 | ETL 作业 |
| Curated | Gold | 聚合、面向分析 | 按需 | 数仓/BI |
不可变原则:Raw 层只追加不修改,任何清洗都在 Silver 层完成。这样出错时可以重放,而不必重新采集。
2.2 模块化存储区定义
# modules/lake-zone/main.tf
variable "name" { type = string }
variable "env" { type = string }
variable "retention_days" { type = number }
variable "kms_key_arn" { type = string }
resource "aws_s3_bucket" "zone" {
bucket = "${var.env}-lake-${var.name}"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "zone" {
bucket = aws_s3_bucket.zone.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = var.kms_key_arn
}
bucket_key_enabled = true # 降低 KMS 调用成本
}
}
resource "aws_s3_bucket_versioning" "zone" {
bucket = aws_s3_bucket.zone.id
versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_public_access_block" "zone" {
bucket = aws_s3_bucket.zone.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
(加密、版本控制、公共访问阻断在 Terraform 里都是独立资源,与 CloudFormation 的内联写法不同。)
2.3 生命周期策略
数据湖的成本大头在存储与请求,生命周期规则是控成本的第一手段:
resource "aws_s3_bucket_lifecycle_configuration" "zone" {
bucket = aws_s3_bucket.zone.id
rule {
id = "tiering"
status = "Enabled"
filter { prefix = "data/" }
transition { days = 30, storage_class = "STANDARD_IA" } # 30 天转低频
transition { days = 90, storage_class = "GLACIER_IR" } # 90 天转归档
expiration { days = var.retention_days }
noncurrent_version_expiration { noncurrent_days = 30 } # 旧版本 30 天清理
abort_incomplete_multipart_upload { days_after_initiation = 7 }
}
}
注意后两条:开了版本控制后,旧版本和失败的分片上传会悄悄吃掉大量存储,它们几乎是必须的。
2.4 表格式与目录
现代数据湖普遍用表格式(Iceberg / Delta / Hudi)管理元数据。Terraform 侧需要管理 Catalog、表定义与底层存储路径的对应关系;表格式的选型与治理参见 数据湖技术 ,Terraform 负责把选定的方案落地成资源。
resource "aws_glue_catalog_database" "silver" {
name = "${var.env}_silver"
}
resource "aws_glue_catalog_table" "events" {
database_name = aws_glue_catalog_database.silver.name
name = "events"
table_type = "EXTERNAL_TABLE"
storage_descriptor {
location = "s3://${var.env}-lake-silver/data/events/"
input_format = "org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat"
output_format = "org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat"
columns { name = "event_id" type = "string" }
columns { name = "ts" type = "timestamp" }
}
partition_keys { name = "dt" type = "string" }
}
一句话: 分区键的设计直接决定查询成本,它应当与「最常见的过滤条件」一致,且必须在 Terraform 里显式声明。
3. ETL 编排资源
一句话总结: 编排工具本身、作业定义、调度、依赖都应声明化,让「谁在什么时候跑什么」成为可评审的代码。
3.1 Glue 作业
resource "aws_glue_job" "clean_events" {
name = "${var.env}-clean-events"
role_arn = aws_iam_role.glue.arn
command {
script_location = "s3://${var.artifacts_bucket}/scripts/clean_events.py"
python_version = "3"
}
glue_version = "4.0"
worker_type = var.glue_worker_type
number_of_workers = var.glue_workers # 成本与性能旋钮,按环境参数化
default_arguments = {
"--enable-job-insights" = "true"
"--SOURCE_PATH" = "s3://${var.env}-lake-raw/data/events/"
"--TARGET_PATH" = "s3://${var.env}-lake-silver/data/events/"
}
execution_property { max_concurrent_runs = 3 }
}
number_of_workers 与 worker_type 应当参数化而非硬编码,让不同环境用不同规格(dev 用 G.1X 小规格,prod 用 G.2X)。
3.2 调度与依赖
resource "aws_glue_trigger" "daily" {
name = "${var.env}-daily-clean"
type = "SCHEDULED"
schedule = "cron(0 2 * * ? *)" # 每天 02:00 UTC
actions { job_name = aws_glue_job.clean_events.name }
}
# 用 Workflow 表达多作业依赖
resource "aws_glue_workflow" "pipeline" { name = "${var.env}-lake-pipeline" }
resource "aws_glue_trigger" "on_success" {
name = "${var.env}-after-clean"
type = "CONDITIONAL"
workflow_name = aws_glue_workflow.pipeline.name
predicate { conditions { job_name = aws_glue_job.clean_events.name, state = "SUCCEEDED" } }
actions { job_name = aws_glue_job.aggregate_events.name }
}
3.3 Airflow 的声明式管理
若用 MWAA 或自建 Airflow,基础设施(环境、网络、执行角色)由 Terraform 管,DAG 由代码仓库管——不要把 DAG 内容塞进 Terraform,那会让每次 DAG 变更都触发基础设施流水线。
resource "aws_mwaa_environment" "airflow" {
name = "${var.env}-airflow"
airflow_version = "2.9.2"
environment_class = "mw1.medium"
execution_role_arn = aws_iam_role.mwaa.arn
source_bucket_arn = aws_s3_bucket.dags.arn
dag_s3_path = "dags/"
webserver_access_mode = "PRIVATE_ONLY"
max_workers = 10
min_workers = 1
}
(网络配置通过 network_configuration 块传入安全组与私有子网 ID,此处从略。)
分界线:Terraform 管「环境的形状」,DAG 仓库管「环境里跑什么」。
4. 数据仓库与权限
一句话总结: 数据仓库的 IaC 重点不在建集群(那部分与托管数据库 类似),而在「库/表/角色的权限模型」。
resource "aws_redshift_cluster" "warehouse" {
cluster_identifier = "${var.env}-warehouse"
node_type = var.redshift_node_type
number_of_nodes = var.redshift_nodes
database_name = "analytics"
master_username = "admin"
manage_master_user_password = true # 交给 Secrets Manager 托管并轮换
encrypted = true
publicly_accessible = false
final_snapshot_identifier = "${var.env}-warehouse-final"
}
manage_master_user_password = true 让密码由 Secrets Manager 自动轮换,避免在 state 里出现明文口令——这与 密钥与 Vault
的整体思路一致。
4.1 Lake Formation 细粒度权限
Lake Formation 是数据湖细粒度权限的主流方案,按列授权与按行过滤都可完全用 Terraform 表达:
resource "aws_lakeformation_permissions" "analyst_select" {
principal = aws_iam_role.analyst.arn
permissions = ["SELECT", "DESCRIBE"]
table_with_columns {
database_name = aws_glue_catalog_database.silver.name
name = aws_glue_catalog_table.events.name
column_names = ["event_id", "ts", "event_type"] # 列级授权,排除 payload
}
}
# 行级过滤让多租户共享一张表成为可能
resource "aws_lakeformation_data_cells_filter" "tenant_isolation" {
table_data {
database_name = aws_glue_catalog_database.silver.name
table_name = aws_glue_catalog_table.events.name
name = "tenant_a_only"
column_names = ["event_id", "ts", "tenant_id"]
row_filter { filter_expression = "tenant_id = 'tenant-a'" }
}
}
4.2 权限模型的组织
推荐「角色分层」:raw_reader(工程师排查)、silver_reader(分析师)、gold_reader(BI)、etl_writer(作业角色)、platform_admin(平台团队,数量极少)。每个角色是一组 IAM Role + Lake Formation 授权,用 for_each 批量生成:
locals {
analyst_tables = { events = ["event_id", "ts"], sessions = ["session_id", "user_id"] }
}
resource "aws_lakeformation_permissions" "analyst" {
for_each = local.analyst_tables
principal = aws_iam_role.analyst.arn
permissions = ["SELECT"]
table_with_columns {
database_name = aws_glue_catalog_database.silver.name
name = each.key
column_names = each.value
}
}
5. 环境隔离
一句话总结: 数据平台的环境隔离比应用更严格——不只是资源隔离,还包括数据隔离与权限隔离,dev 绝不能读到 prod 的原始数据。
| 级别 | 实现 | 隔离强度 | 成本 |
|---|---|---|---|
| 命名前缀 | 同账号同桶,靠前缀 | 弱 | 最低 |
| 独立桶 + 独立 Catalog | 同账号,资源分离 | 中 | 低 |
| 独立账号 | 跨账号,IAM 边界隔离 | 强 | 中高 |
数据平台推荐至少第二级,涉及敏感数据(PII)时用第三级。环境目录只做参数组合,关键差异用变量表达,不用 if/else:
# envs/prod/main.tf
module "raw_zone" {
source = "../../modules/lake-zone"
name = "raw"
env = "prod"
retention_days = 3650
kms_key_arn = module.kms.prod_key_arn
}
module "silver_zone" {
source = "../../modules/lake-zone"
name = "silver"
env = "prod"
retention_days = 1095
kms_key_arn = module.kms.prod_key_arn
}
5.1 跨环境数据流与最小权限
若确实需要「prod 脱敏后给 dev」,不要直接给桶权限,而是建一条显式的脱敏管道:prod-raw ──脱敏作业──► prod-anonymized ──复制──► dev-lake,每一步都是 Terraform 资源,权限边界清晰且可审计。
在数据平台里,显式的 Deny 比隐式的「没授权」更可靠——IAM 策略会叠加,显式拒绝能兜住意外的授权:
data "aws_iam_policy_document" "analyst" {
statement {
sid = "ReadGoldOnly"
actions = ["s3:GetObject", "s3:ListBucket"]
resources = [aws_s3_bucket.gold.arn, "${aws_s3_bucket.gold.arn}/data/*"]
}
statement {
sid = "DenyOtherZones"
effect = "Deny"
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.raw.arn}/*", "${aws_s3_bucket.silver.arn}/*"]
}
}
6. 成本治理
一句话总结: 数据平台的成本由「存储量、请求数、扫描量、计算时长」四项驱动,Terraform 侧能控制的是生命周期、分区、计算规格与调度频率。
| 因素 | 控制手段 |
|---|---|
| 存储量 | 生命周期分层、旧版本清理、压缩格式(Parquet/ZSTD) |
| 请求数 | 小文件合并(compaction)、避免高频 list |
| 扫描量 | 分区裁剪、列式存储、只选需要的列 |
| 计算时长 | 合理 worker 规格、自动伸缩、按需 vs 预留 |
# 1. 小文件合并:用定时作业而不是手工
resource "aws_glue_trigger" "compaction" {
name = "${var.env}-compaction"
type = "SCHEDULED"
schedule = "cron(0 3 * * ? *)"
actions { job_name = aws_glue_job.compact.name }
}
# 2. 预算告警
resource "aws_budgets_budget" "data_platform" {
name = "${var.env}-data-platform"
budget_type = "COST"
limit_amount = var.monthly_budget
limit_unit = "USD"
time_unit = "MONTHLY"
notification {
comparison_operator = "GREATER_THAN"
threshold = 80
threshold_type = "PERCENTAGE"
notification_type = "FORECASTED"
subscriber_sns_topic_arns = [aws_sns_topic.budget.arn]
}
}
6.1 标签策略
数据平台的成本分摊依赖标签,用 default_tags 统一注入,避免每个资源手写:
provider "aws" {
region = var.region
default_tags {
tags = {
Environment = var.env
Platform = "data"
CostCenter = var.cost_center
ManagedBy = "terraform"
}
}
}
按 CostCenter 分摊后,能清楚看到「哪个业务的数据管道最贵」,这是优化的起点。更多通用手段参见 成本优化
。
7. 实战与排错
一句话总结: 数据平台的 Terraform 排错集中在「权限不足、目录不同步、资源被外部修改」三类,且很多问题表现为作业运行失败而非 apply 失败。
| 现象 | 原因 | 处理 |
|---|---|---|
作业 AccessDenied | 执行角色缺权限 | 查 CloudTrail 的 denied 事件,补策略 |
| 分析师看不到表 | Lake Formation 未授权 | 补 aws_lakeformation_permissions |
| 能读表但读不了某列 | 列级授权未包含该列 | 扩展 column_names |
| Terraform 报「不能修改 LF 权限」 | 表由非 LF 管理 | 统一 LF 管理,避免混用 IAM 与 LF 授权 |
表在 Catalog 里但 Terraform 不认识时,用 terraform import aws_glue_catalog_table.events "123456789012:${ENV}_silver:events" 纳管;表被控制台改了,用 terraform plan -refresh-only 看漂移。作业会动态创建分区,因此这些字段要用 lifecycle { ignore_changes = [partition_index, parameters] } 收敛。
注意:修改 Glue 作业的 number_of_workers 会触发作业定义更新,正在运行的实例不受影响,下一次调度才用新规格,因此成本相关的变更不必等窗口期。
生产清单:
- Raw 层不可变,仅追加;清洗逻辑只在 Silver 层。
- 每个桶都有生命周期、版本控制、KMS 加密、公共访问阻断。
- 分区键与常用过滤条件一致,并在 Terraform 中声明。
- 权限按角色分层,敏感列用列级授权,多租户用行级过滤。
- 敏感环境用独立账号隔离,跨环境数据流经显式脱敏管道。
-
default_tags注入成本分摊标签,预算告警已配置。 - 动态创建的分区/参数用
ignore_changes收敛,避免 plan 噪声。 - Terraform 管「环境形状」,DAG/脚本由代码仓库管,边界清晰。
一句话收尾: 数据平台的 IaC 难点不在「建资源」,而在「管契约」——表结构、分区、权限、生命周期都是长期演进的契约。把这些契约写进 Terraform 并走评审,数据平台才从「一堆脚本和手工配置」变成可治理的工程资产。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。