一次编写,随处部署:掌握 Terraform 表达式工具包
Source: Dev.to
这是 AWS 挑战的第 10 天,今天我们要探索将 Terraform 从一个简单的声明式工具转变为灵活、智能的配置强大引擎的特性。昨天的 lifecycle 元参数让我们拥有了外科手术般的控制;今天的表达式则让我们在最大可复用性的前提下实现外科手术般的精准。
我们将一起看看 条件表达式、动态块 和 Splat 表达式——这三者能够消除代码重复,使你的基础设施在不同环境中真正具备适配性。
条件表达式
语法
condition ? true_value : false_value
Terraform 会先评估条件。如果为真,则使用第一个值;否则使用第二个值。
实际案例
resource "aws_instance" "app_server" {
ami = var.ami
instance_type = var.environment == "production" ? "t3.large" : "t3.micro"
monitoring = var.environment == "production" ? true : false
tags = {
Name = "${var.environment}-app-server"
Environment = var.environment
CostCenter = var.environment == "production" ? "prod-ops" : "dev-team"
}
}
生产环境使用更强大的实例并开启监控,而开发环境保持轻量——全部来自同一套配置。
使用场景
- 根据环境选择实例规格
- 仅在生产环境启用昂贵的功能(例如增强监控)
- 按区域选择不同的 AMI
- 设置资源计数(生产规模高于开发)
- 应用环境特定的安全策略
小技巧: 如果发现自己在链式使用多个三元运算符,建议将逻辑抽离到 locals 块中,以提升可读性。
动态块
语法
dynamic "block_name" {
for_each = var.collection
content {
# Configuration using block_name.value
}
}
注意: 在资源上使用
for_each会创建多个 资源。动态块则在单个资源内部创建多个 嵌套块。
实际案例:安全组规则
variable "ingress_rules" {
type = list(object({
port = number
protocol = string
cidr_blocks = list(string)
description = string
}))
default = [
{
port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTP access"
},
{
port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTPS access"
},
{
port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
description = "SSH access"
}
]
}
resource "aws_security_group" "app_sg" {
name = "app-security-group"
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
description = ingress.value.description
}
}
}
为什么这很棒
- 只需更新变量即可新增规则——无需改动代码。
- 通过传入不同的列表实现环境特定的规则集。
- 将列表设为空即可关闭所有外部访问。
- 只需检查单个变量即可审计开放端口。
其他强大用例
- 为 EC2 实例挂载多个 EBS 卷
- 构建 IAM 策略语句
- 填充路由表路由
- 定义 CloudWatch 报警动作
- 任何需要重复且略有差异的嵌套块
重要提示: 动态块仅适用于资源内部的嵌套块。若需创建多个资源,请在资源本身上使用 count 或 for_each。
Splat 表达式
语法
resource_list[*].attribute_name
实际案例
resource "aws_instance" "web_servers" {
count = 3
ami = var.ami_id
instance_type = "t3.micro"
tags = {
Name = "web-server-${count.index}"
}
}
# 一行输出所有实例 ID
output "instance_ids" {
value = aws_instance.web_servers[*].id
}
# 输出所有私有 IP
output "private_ips" {
value = aws_instance.web_servers[*].private_ip
}
# 在另一个资源中使用这些 IP
resource "aws_lb_target_group_attachment" "web" {
count = length(aws_instance.web_servers)
target_group_arn = aws_lb_target_group.web.arn
target_id = aws_instance.web_servers[*].id[count.index]
}
如果没有 Splat 表达式,你需要循环或重复代码;有了它们,只需一行优雅的写法。
常见使用场景
- 从多个 EC2 实例中提取 ID,以供下游资源使用
- 从 VPC 数据源收集子网 ID
- 获取安全组 ID 并附加到资源上
- 收集 IAM 策略的 ARN
- 为输出和依赖构建列表
小技巧: 将 Splat 与 join() 等函数组合使用,威力更大:
output "all_ips_comma_separated" {
value = join(",", aws_instance.web_servers[*].private_ip)
}
综合示例
下面是一段生产级配置,演示了条件表达式、动态块和 Splat 表达式如何相互配合。
variable "environment" {
type = string
}
variable "ingress_rules" {
type = map(object({
port = number
cidr_blocks = list(string)
}))
}
# 条件:根据环境使用不同的实例数量和规格
resource "aws_instance" "app" {
count = var.environment == "production" ? 3 : 1
ami = var.ami_id
instance_type = var.environment == "production" ? "t3.large" : "t3.micro"
vpc_security_group_ids = [aws_security_group.app.id]
tags = {
Name = "${var.environment}-app-${count.index}"
Environment = var.environment
}
}
# 动态:从变量生成安全组规则
resource "aws_security_group" "app" {
name = "${var.environment}-app-sg"
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = "tcp"
cidr_blocks = ingress.value.cidr_blocks
}
}
}
# Splat:提取所有实例 ID 供输出
output "instance_ids" {
description = "IDs of all app instances"
value = aws_instance.app[*].id
}
# Splat:获取所有私有 IP 用于负载均衡配置
output "private_ips" {
description = "Private IPs for load balancer config"
value = aws_instance.app[*].private_ip
}
- 条件表达式 让你在不复制文件的情况下实现环境感知的配置。
- 动态块 消除重复的嵌套块,使安全组和策略能够以数据驱动、易于维护的方式生成。
- Splat 表达式 让数据提取变得轻而易举,免去手动循环的麻烦。
祝你 Terraform 使用愉快! 🚀