「数据源与远程数据读取」

讲解 Terraform 数据源:data source 的声明与读取、remote state 数据源跨配置共享、external/HTTP 外部数据源、依赖顺序与刷新时机,以及数据源与资源的使用边界与常见坑。

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资源属性,生命周期已知
他人/别栈建的 VPCdata 或 remote state本配置不管理它
查最新 AMIdata只读,不可声明管理
需要 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 与代码大规模调整时的安全手段。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「terraform」更多文章

  1. 「漂移检测与收敛」
  2. 「资源重构与迁移」
  3. 「Kubernetes Provider 编排」