Resource 与 Data Sources
High Contrast
Dark Mode
Light Mode
Sepia
Forest
2 min read403 words

Resource 与 Data Sources

核心问题:怎样声明要创建的云资源?怎样引用已存在(不由当前 Terraform 管理)的资源?资源之间怎样互相引用?


Resource 块

resource 是 Terraform 的核心——声明你想要的云资源:

resource "<资源类型>" "<本地名称>" {
<参数> = <值>
...
}
resource "aws_security_group" "web" {
name        = "${local.name_prefix}-web-sg"
description = "Web server security group"
vpc_id      = aws_vpc.main.id   # 引用同模块中其他资源的属性
ingress {
from_port   = 80
to_port     = 80
protocol    = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port   = 443
to_port     = 443
protocol    = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port   = 0
to_port     = 0
protocol    = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = local.common_tags
}

资源依赖

隐式依赖(推荐)

通过引用其他资源的属性自动建立依赖,Terraform 计算依赖图,自动决定创建顺序

resource "aws_instance" "web" {
ami                    = data.aws_ami.ubuntu.id
instance_type          = "t3.medium"
subnet_id              = aws_subnet.public[0].id      # 隐式依赖 aws_subnet
vpc_security_group_ids = [aws_security_group.web.id]  # 隐式依赖 aws_security_group
tags = local.common_tags
}

Terraform 会自动先建 aws_subnetaws_security_group,再建 aws_instance

显式依赖(depends_on

当依赖关系无法通过属性引用表达时使用:

resource "aws_ecs_service" "app" {
name            = "my-app"
cluster         = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
# IAM 策略绑定无法通过属性引用,需要显式声明
depends_on = [
aws_iam_role_policy_attachment.task_execution_policy
]
}

count:创建多个相同资源

resource "aws_instance" "web" {
count         = var.instance_count           # 创建 N 个
ami           = data.aws_ami.ubuntu.id
instance_type = local.instance_type
subnet_id     = aws_subnet.public[count.index % length(aws_subnet.public)].id
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-web-${count.index + 1}"
})
}
# 引用:aws_instance.web[0].public_ip
# 引用所有:aws_instance.web[*].public_ip(splat 表达式)

for_each:按 Map/Set 创建

for_eachcount 更灵活——资源有独立 key,增删某项不影响其他项:

variable "buckets" {
type = map(object({
acl         = string
versioning  = bool
}))
default = {
media   = { acl = "public-read",  versioning = true }
backups = { acl = "private",      versioning = true }
logs    = { acl = "private",      versioning = false }
}
}
resource "aws_s3_bucket" "buckets" {
for_each = var.buckets
bucket   = "${local.name_prefix}-${each.key}"
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-${each.key}"
})
}
resource "aws_s3_bucket_acl" "buckets" {
for_each = var.buckets
bucket   = aws_s3_bucket.buckets[each.key].id
acl      = each.value.acl
}
# 引用:aws_s3_bucket.buckets["media"].bucket

Data Sources

data 块查询已存在的资源(不创建),用于引用: - 不由当前 Terraform 管理的资源(手动创建的 VPC、其他团队的 Terraform State) - 动态查询(最新 AMI、可用区列表)

# 查询最新 Ubuntu 22.04 AMI
data "aws_ami" "ubuntu" {
most_recent = true
owners      = ["099720109477"]   # Canonical 官方账号
filter {
name   = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
filter {
name   = "virtualization-type"
values = ["hvm"]
}
}
# 引用
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id   # 使用查到的 AMI ID
...
}
# 引用另一个 Terraform State 的输出(跨模块协作)
data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "my-tf-state"
key    = "prod/vpc/terraform.tfstate"
region = "ap-southeast-1"
}
}
# 引用其他 State 的输出
resource "aws_instance" "app" {
subnet_id = data.terraform_remote_state.vpc.outputs.private_subnet_ids[0]
}
# 查询可用区
data "aws_availability_zones" "available" {
state = "available"
}
# 查询当前账号信息
data "aws_caller_identity" "current" {}
output "account_id" {
value = data.aws_caller_identity.current.account_id
}
# 查询已有 Route53 Hosted Zone
data "aws_route53_zone" "main" {
name         = "example.com"
private_zone = false
}
resource "aws_route53_record" "api" {
zone_id = data.aws_route53_zone.main.zone_id
name    = "api.example.com"
type    = "A"
alias {
name                   = aws_lb.main.dns_name
zone_id                = aws_lb.main.zone_id
evaluate_target_health = true
}
}

实战:完整 Web 应用基础设施

# 数据源
data "aws_ami" "ubuntu" {
most_recent = true
owners      = ["099720109477"]
filter {
name   = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
# EC2 实例
resource "aws_instance" "web" {
count                  = var.web_instance_count
ami                    = data.aws_ami.ubuntu.id
instance_type          = local.instance_type
subnet_id              = aws_subnet.public[count.index].id
vpc_security_group_ids = [aws_security_group.web.id]
iam_instance_profile   = aws_iam_instance_profile.web.name
key_name               = var.key_name
root_block_device {
volume_type           = "gp3"
volume_size           = 20
delete_on_termination = true
encrypted             = true
}
user_data = base64encode(templatefile("${path.module}/templates/user_data.sh.tpl", {
app_env = var.env
}))
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-web-${count.index + 1}"
Role = "web"
})
lifecycle {
create_before_destroy = true    # 蓝绿替换
ignore_changes        = [ami]   # AMI 更新不自动重建(由 Ansible 管理)
}
}
# ALB
resource "aws_lb" "main" {
name               = "${local.name_prefix}-alb"
internal           = false
load_balancer_type = "application"
security_groups    = [aws_security_group.alb.id]
subnets            = aws_subnet.public[*].id
access_logs {
bucket  = aws_s3_bucket.logs.bucket
prefix  = "alb"
enabled = true
}
tags = local.common_tags
}

lifecycle 元参数

resource "aws_instance" "web" {
...
lifecycle {
create_before_destroy = true   # 先创建新资源再删旧的(零停机替换)
prevent_destroy       = true   # 防止意外 terraform destroy(生产数据库常用)
ignore_changes        = [tags] # 忽略某些属性的外部变更
replace_triggered_by  = [      # 当某依赖变更时强制替换
aws_launch_template.web.id
]
}
}

常见错误

错误 原因 解决
Error: Cycle: A → B → A 资源循环依赖 检查 depends_on 和属性引用,找出环路
count.index 引发索引越界 count 值小于引用的列表长度 用取模:[count.index % length(list)]
for_each 值包含 null null 不可用作 Map key compact(){ for k, v in map : k => v if v != null } 过滤
data source not found 查询条件过严或资源不存在 放宽 filter 条件,或先确认资源存在

下一节State 文件与远程 Backend