大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
Data Sources 数据源与前置断言
这一课你将能做到
- 用
data块只读查询云上已有信息(当前账号、区域、AMI、托管策略、现有网络) - 用动态 AMI 查询替换写死的镜像 ID,让配置在任何区域自动取到正确镜像
- 用
precondition/postcondition在资源变更前后做跨对象断言
为什么要学
前面的 resource 解决「我要创建什么」,但 Terraform 还经常需要「读取我不拥有、不该由我创建的东西」:当前部署用的是哪个账号?Amazon Linux 最新 AMI 的 ID 是多少(AMI ID 每个区域不同、还频繁更新)?公司共享 VPC 的 ID?AWS 托管的 IAM 策略 ARN?这些都不是你要创建的资源,写一个 resource 去管它只会冲突。
data source 就是 Terraform 的「只读查询接口」:它什么都不创建、不修改,只把云平台的现有信息读进配置供引用。善用 data source,配置才不会到处是写死的 ID,跨区域、跨账号复用才真正成立。配合 1.2 引入的 preconditions,还能把「只有 prod 才能开删除保护」「镜像必须是我们自己账号的」这类规则固化下来。
先建立图像
resource:create / update / delete → 会改变世界
data :read only → 只查询、零副作用、不入常规资源计数
一次 plan/apply 的求值顺序中,data 像「提前问云要答案」:
data "aws_ami" "al2023" ──读──▶ AWS API ──▶ ami_id
│
resource "aws_instance" "web" { ◀────────┘ 引用 data.aws_ami.al2023.id
ami = data.aws_ami.al2023.id
}
跟着做
步骤 1:查询身份与区域——防止部署到错账号
最该放在每份配置开头的两个 data source:
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
output "deploy_target" {
value = {
account_id = data.aws_caller_identity.current.account_id
region = data.aws_region.current.name
}
}
它们不接受参数,读的是当前凭证实际连到的账号和区域。典型用途是配合断言(步骤 4)防止「拿着 prod 凭证对 dev 目录 apply」。
步骤 2:动态查 AMI——消灭写死的镜像 ID
把第 4、9 课写死的 AMI 替换掉:
data "aws_ami" "amazon_linux_2023" {
most_recent = true
owners = ["amazon"] # 只认 Amazon 官方账号,防止第三方冒名镜像
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux_2023.id
instance_type = "t3.micro"
tags = {
Name = "coderoadmap-web"
AmiName = data.aws_ami.amazon_linux_2023.name
}
}
关键点:most_recent = true 取最新一个;owners = ["amazon"] 是安全必需——不限制 owner,别人可以发布同名镜像投毒。filter 的写法和 AWS CLI describe-images --filters 完全一致。现在这份配置不用改一个字符就能在任何区域拿到对的 AMI。
步骤 3:查询托管策略、共享网络与远程 state
# AWS 托管 IAM 策略,按名字查 ARN,避免手抄易错
data "aws_iam_policy" "ssm_managed" {
arn = "arn:aws-cn:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
# 读取既有的共享 VPC(由网络团队/另一个仓库管理)
data "aws_vpc" "shared" {
filter {
name = "tag:Name"
values = ["coderoadmap-shared-vpc"]
}
}
data "aws_subnets" "private" {
filter {
name = "vpc-id"
values = [data.aws_vpc.shared.id]
}
tags = { Tier = "private" }
}
跨仓库共享信息的标准做法是远程 state data source——只读另一个 state 的 root outputs,不需要任何资源存在同一个账号外:
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "coderoadmap-tfstate-2026"
key = "global/network/terraform.tfstate"
region = "ap-northeast-1"
}
}
# 引用对方 root outputs:vpc_id、private_subnet_ids
resource "aws_security_group" "app" {
vpc_id = data.terraform_remote_state.network.outputs.vpc_id
}
这比让应用仓库再查一遍云更可靠:输出是网络仓库显式承诺的接口。
步骤 4:precondition 跨对象断言
variable 的 validation 只能看自己;资源的 lifecycle precondition 可以引用其他对象:
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux_2023.id
instance_type = var.env == "prod" ? "m5.large" : "t3.micro"
lifecycle {
precondition {
condition = data.aws_caller_identity.current.account_id == "123456789012"
error_message = "禁止向账号 123456789012 之外的环境部署,请检查凭证。"
}
precondition {
condition = var.env != "prod" || var.instance_count >= 2
error_message = "生产环境至少 2 个实例,禁止单机配置上 prod。"
}
}
}
postcondition 在资源动作完成后校验结果,比如确认桶真的开启了加密:
resource "aws_s3_bucket" "assets" {
bucket = "coderoadmap-assets"
lifecycle {
postcondition {
condition = self.arn != ""
error_message = "桶创建异常。"
}
}
}
条件失败会让 apply 中止且不写 state(precondition)或标记失败(postcondition)。这类断言是把运维经验固化成代码的核心手段。
步骤 5:data source 的刷新行为与 external
默认每次 plan/apply 都会重新读取 data source;想让它只在 apply 时读取(plan 时显示未知),加:
data "aws_ami" "amazon_linux_2023" {
# ...同上
lifecycle {
postcondition {
condition = length(self.id) > 0
error_message = "没找到匹配的 Amazon Linux 2023 镜像。"
}
}
}
实在没有对应 provider 时,external data source 可以执行本地程序并读 JSON 输出:
data "external" "build_info" {
program = ["python3", "${path.module}/scripts/build_info.py"]
}
# data.external.build_info.result.git_sha
它同样是逃生舱:输出必须是 JSON、程序要存在于执行机,能用原生 data 就不要用。
讲清楚要点
- data 不产生资源、不进 add/destroy 计数:state 里有它的缓存记录,但语义是只读视图。
- owner/来源校验是安全问题:查 AMI 必须限定 owners;远程 state、镜像源都要指向可信账号。
- data 让配置「跟随云现实」:AMI 更新、共享资源 ID 变化无需改代码,消除了跨区域复制配置的最大障碍。
- precondition 补 validation 的不足:它在资源图内执行,可以引用 data、其他资源和变量,适合跨对象/跨环境规则。
- terraform_remote_state 是仓库间的只读契约:依赖的是对方 root outputs,网络仓库改输出属于接口变更,要通知下游。
你可能会懵的地方
-
data source 和直接用变量传入有什么区别? 变量值由人提供,可能过时或填错;data 每次从云实时读取,单一真相来源。AM ID、账号、区域这类「云说了算」的值优先 data,人为决策(环境、规格)用变量。
-
plan 时 data 显示
(known after apply)正常吗? 当 data 依赖尚未创建的资源,或读取发生在 apply 期时正常;多数纯查询 data 在 plan 期就能拿到值。 -
data 查不到东西会怎样? 报
no matching ... found并中止。常见于 filter 写错、区域不对、Name 标签不匹配;先用等价 AWS CLI 查询验证过滤条件。 -
precondition 和 custom validation 该用哪个? 只校验本变量值 → variable validation;需要引用其他变量/资源/查询结果 → resource precondition。
-
能不能用 data「读取」一个资源然后修改它? 不能直接改。data 只给你属性值;要管理变更必须用 resource(必要时先 import 绑定)。data 与 resource 可以共存引用同一个现实对象。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| AMI ID 写死在配置里 | 跨区域失效、永远不会收到安全更新 | data aws_ami 动态查询 + owners 限定 |
| data aws_ami 不写 owners | 可能拉到攻击者发布的同名镜像 | owners = ["amazon"] 或自家账号 ID |
| 用 validation 引用其他变量 | 语法不允许,加载即报错 | lifecycle precondition |
| 直接读别的仓库的 state 文件路径 | 绕过锁、耦合内部结构 | terraform_remote_state 读 outputs |
| 给 data 写 count 又当单值引用 | 地址变成列表索引 | count 后用 [0] 或改用 for_each |
卡点急救
Your query returned no results:把 filter 条件翻译成 AWS CLI(aws ec2 describe-images --filters "Name=name,Values=...")在同区域执行,逐步放宽条件定位是哪一个过滤项过窄。- data 在 plan 阶段反复触发 API 限流:Terraform 会缓存同一次执行中的读取;持续限流时合并查询、减少重复 data 块,或在 provider 配置限流参数。
- remote_state 报找不到 key:对方仓库尚未 apply 或 key/区域写错;确认 state 文件真实存在,且本配置角色有该桶的读权限(s3:GetObject)。
面试常问
-
Terraform data source 解决什么问题?和 resource 的区别? data 只读查询云上已有对象、零变更,用于获取动态信息(AMI、账号、共享网络、托管策略);resource 是被管理对象,会产生 create/update/delete。用 data 替代写死 ID 可提升跨区域复用和安全性。
-
如何保证 EC2 始终使用最新且可信的官方镜像?
data "aws_ami"按名称模式过滤、owners = ["amazon"]限定发布者、most_recent = true取最新;可再加 postcondition 断言镜像 ID 非空。实例刷新(launch template + ASG)负责滚动到新镜像。 -
precondition 相比 variable validation 有什么优势? precondition 在资源生命周期求值、可引用 data source、其他资源与多个变量,能表达「prod 必须多实例」「凭证账号必须匹配」这类跨对象规则;validation 仅限单个变量自身。
自测三问
- 把写死 AMI 改成动态查询,最少需要哪几个要素?为什么 owners 不能省?
- 网络在另一个仓库管理,应用层如何拿到 VPC ID?为什么推荐这种方式?
- 「生产禁止单机部署」这条规则应该写在 validation 还是 precondition?为什么?
小结一句话
Data source 是 Terraform 的只读眼睛:用动态查询消灭写死 ID、用身份查询和 precondition 防止错误目标与非法配置、用 remote_state 建立仓库间的输出契约。
延伸
- Data Sources 官方文档
- Custom Conditions:pre/postcondition
- terraform_remote_state 数据源
- 下一课:CI/CD 与工具链——GitLab CI 流水线、OIDC 免密、
terraform test与 Terragrunt 的适用边界。