1. data source 的声明与读取
一句话总结: data source 是「只读查询」,它把已存在的外部事实读进配置,供其他资源引用,但自身不管理任何资源的生命周期。
data source 与 resource 长得几乎一样,区别只在语义:resource 声明「我要管理它」,data 声明「我要读取它」。Terraform 在 refresh 阶段执行 data 查询,结果不进 apply 的变更范围。
# 读取一个已存在的 AMI
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-22.04-amd64-server-*"]
}
owners = ["099720109477"]
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
}
1.1 data 的地址与属性
data 的引用地址是 data.<类型>.<名称>.<属性>。例如上面是 data.aws_ami.ubuntu.id。它的值只依赖外部现状,不依赖本配置管理的历史。
1.2 典型用途
| 用途 | 示例 |
|---|---|
| 动态查现成资源 | AMI、子网、VPC、账号 ID |
| 跨配置共享 | remote state 数据源读取别的 state |
| 环境差异 | 按环境查不同的既有基础设施 |
| 外部系统集成 | 读密钥、DNS 记录、TLS 证书 |
2. remote state 数据源
一句话总结:
terraform_remote_state数据源跨配置读取他人 state 的 output,是实现「基础设施分栈」解耦的官方通道。
当基础设施按团队拆分(网络一个仓库、应用另一个仓库),应用仓库需要网络仓库的 VPC ID 等输出时,就用 remote state 数据源。
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "my-company-terraform-state"
key = "terraform/network/terraform.tfstate"
region = "ap-northeast-1"
}
}
# 使用网络栈的输出
resource "aws_subnet" "app" {
vpc_id = data.terraform_remote_state.network.outputs.vpc_id
cidr_block = "10.0.10.0/24"
}
2.1 依赖关系与解耦
remote state 数据源在 refresh 时拉取目标 state。目标栈必须先 apply 并产出 output,本栈才能读到;否则读取失败或读到旧值。
| 模型 | 说明 |
|---|---|
| 强依赖 | 网络栈先跑,应用栈再跑 |
| 弱解耦 | 用 remote state 读 output,不再重复声明网络资源 |
| 演进 | 目标栈删除后,本栈 data 失效,需改本地值 |
2.2 output 必须是已导出的值
只有目标栈里 output 块导出的值能被 remote state 读到。临时值(locals)与资源属性若不导出,外部读不到。
# 网络栈里显式导出
output "vpc_id" {
value = aws_vpc.main.id
}
output "subnet_ids" {
value = aws_subnet.public[*].id
}
3. external 与 HTTP 外部数据源
一句话总结:
external数据源执行外部程序并把 JSON 当结果,http数据源拉取 URL 内容;它们把 Terraform 连向任何能产生数据的系统。
3.1 external 数据源
external 调用一个外部程序(脚本/二进制),传入 JSON 到 stdin,读取 stdout 的 JSON 作为属性。
data "external" "current_ip" {
program = ["bash", "${path.module}/get_ip.sh"]
}
# get_ip.sh
# echo '{"ip":"203.0.113.10"}'
#!/bin/bash
# 从元数据服务取当前公网 IP
IP=$(curl -s http://169.254.169.254/latest/meta-data/public-ipv4)
echo "{\"ip\":\"${IP}\"}"
外部程序必须输出合法 JSON,且结果字段只能在配置里引用 data.external.xxx.result.字段。
3.2 http 数据源
http 数据源把 HTTP GET 的结果暴露为 body,可配合 jsondecode 解析。
data "http" "github_actions" {
url = "https://api.github.com/repos/hashicorp/terraform/releases/latest"
request_headers = {
Accept = "application/vnd.github+json"
}
}
locals {
latest_version = jsondecode(data.http.github_actions.body).tag_name
}
一句话:external/http 是把 Terraform 世界之外的数据「翻译」进配置的桥,结果越不可控,越要加
try与校验兜底。
4. 依赖顺序与刷新时机
一句话总结: data 在 refresh 阶段读取、按引用建立依赖,其生命周期与「首次读取」的确定性弱,容易在并发或删除场景下读到过期值。
4.1 隐式与显式依赖
data 与 resource 通过引用建立隐式依赖;必要时用 depends_on 声明顺序。
data "aws_vpc" "selected" {
filter {
name = "tag:Name"
values = ["${var.environment}-vpc"]
}
}
resource "aws_subnet" "app" {
vpc_id = data.aws_vpc.selected.id
cidr_block = "10.0.10.0/24"
availability_zone = "ap-northeast-1a"
# 显式声明:先让 VPC 就绪再建子网
depends_on = [aws_vpc.main]
}
4.2 刷新时机
| 场景 | data 何时重读 |
|---|---|
terraform plan/apply | 每次 refresh 阶段重读 |
-refresh=false | 跳过刷新,用 state 缓存 |
| 依赖的资源变更 | 按依赖图重新读取 |
| 并发 | 与资源创建可能交错,注意时序 |
依赖一个「即将由本配置创建」的值时,用 resource 引用而非 data 读取,否则 data 可能读到旧值或查不到。
4.3 data 批量读取
data 也支持 for_each 与 count,批量读取同类外部资源后再组合使用。
data "aws_subnets" "app" {
filter {
name = "vpc-id"
values = [data.aws_vpc.selected.id]
}
}
locals {
# 批量取可用区列表
azs = data.aws_subnets.app.ids
}
5. 本地文件与模板数据
一句话总结:
file/templatefile读取本地文件并渲染模板,local_file把渲染结果写回磁盘,是「模板化配置生成」的常用组合。
# 直接读取文件内容
locals {
policy_json = file("${path.module}/policies/s3-policy.json")
user_data = templatefile("${path.module}/user_data.sh", {
app_name = "web"
region = var.region
})
}
# 把渲染结果作为文件资源管理
resource "local_file" "rendered" {
content = templatefile("${path.module}/config.yaml.tpl", {
replicas = var.replica_count
})
filename = "${path.module}/generated/config.yaml"
}
5.1 templatefile 语法
模板文件里用 ${} 插值、%{ for } 循环、%{ if } 条件,渲染出不同环境的配置文件。
# config.yaml.tpl
replicas: ${replicas}
env: ${environment}
%{ for item in items }
- ${item}
%{ endfor }
6. 数据源与资源的边界
一句话总结: 资源管生命周期,数据源只读现状;能引用资源属性就不绕道 data,data 适合「外部已存在、本配置不管理」的事实。
6.1 何时用 data,何时用 resource
| 情形 | 选择 | 原因 |
|---|---|---|
| 本配置创建的 VPC | 直接引用 aws_vpc.main.id | 资源属性,生命周期已知 |
| 他人/别栈建的 VPC | data 或 remote state | 本配置不管理它 |
| 查最新 AMI | data | 只读,不可声明管理 |
| 需要 create/delete 的对象 | resource | 生命周期在配置内 |
6.2 反模式
- data 读自己刚建的资源:应先建 resource 再引用属性,而不是先 data。
- 用 data 实现「查询后决定创建」:data 在 plan 阶段就执行,分支决策更应交给变量与条件。
- 大量 data 依赖外部不稳定系统:每次 refresh 都打外部 API,慢且易失败。
7. 常见坑
一句话总结: 数据源高发坑集中在 remote state 顺序、external 结果非 JSON、http 结果无缓存、data 读到旧值,四类都源于「数据源是只读快照而非受管状态」。
| 坑 | 现象 | 正确做法 |
|---|---|---|
| remote state 未 apply | 读取失败或读到空 output | 先跑目标栈,再跑本栈 |
| external 输出非 JSON | 报「result could not be parsed」 | 脚本强制输出合法 JSON |
| http 频繁拉取 | 慢、被限流 | 缓存结果或只读稳定 URL |
| data 与新建冲突 | 读到旧值 | 该场景改用 resource 属性 |
| output 未导出 | remote state 读不到 | 在目标栈显式写 output 块 |
7.1 让数据源可调试
# 查看 data 当前值
terraform state show data.aws_ami.ubuntu
# 强制刷新数据源
terraform plan -refresh-only
# 输出调试
terraform console
> data.aws_ami.ubuntu.id
8. 总结
数据源是 Terraform「只读事实层」,让配置既能管理资源,又能感知外部现状:
| 环节 | 要点 |
|---|---|
| 基本用法 | data 声明 + data.<类型>.<名称>.<属性> 引用 |
| remote state | 跨栈共享 output,先 apply 后读取 |
| external/http | 把外部程序/URL 结果翻译进配置 |
| 依赖顺序 | 引用建立隐式依赖,必要时 depends_on |
| 模板 | templatefile 渲染,local_file 落盘 |
| 边界 | 资源管生命周期,data 只读现状 |
| 坑 | remote state 顺序、JSON 输出、旧值缓存 |
一句话收尾:data source 让 Terraform 从「只管理自己创建的东西」升级为「感知并组合整个环境的事实」,跨栈、跨系统、跨事实的编排都靠它打通。下一篇「资源重构与迁移」将讲解 state 与代码大规模调整时的安全手段。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。