表达式与函数
掌握 Terraform 的表达式和内置函数,让配置更灵活强大。
一、表达式基础
1.1 字符串插值
locals {
project = "myapp"
env = "prod"
# 基础插值
name = "${local.project}-${local.env}"
# 表达式插值
instance_name = "${local.project}-${local.env}-${count.index + 1}"
# 条件插值
url = "https://${local.env == "prod" ? "www" : local.env}.example.com"
}
1.2 条件表达式
locals {
# 三元运算符
instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"
# 嵌套条件
db_size = (
var.environment == "prod" ? "db.r5.2xlarge" :
var.environment == "staging" ? "db.t3.large" :
"db.t3.micro"
)
# 布尔条件
create_lb = var.instance_count > 1 ? true : false
}
1.3 for 表达式
列表推导:
locals {
# 转换列表
uppercase_names = [for s in var.names : upper(s)]
# 过滤列表
prod_instances = [
for inst in var.instances :
inst if inst.environment == "prod"
]
# 带索引
indexed_items = [
for i, v in var.items :
"${i}: ${v}"
]
}
映射推导:
locals {
# 创建映射
instance_ips = {
for instance in aws_instance.app :
instance.id => instance.private_ip
}
# 分组
instances_by_az = {
for az in var.availability_zones :
az => [
for inst in aws_instance.app :
inst.id if inst.availability_zone == az
]
}
# 转换映射
env_tags = {
for k, v in var.tags :
upper(k) => "${v}-${var.environment}"
}
}
1.4 splat 表达式
# 获取所有实例的 ID
instance_ids = aws_instance.app[*].id
# 等价于
instance_ids = [for inst in aws_instance.app : inst.id]
# 嵌套属性
subnet_cidrs = aws_subnet.public[*].cidr_block
# 条件 splat
available_instances = aws_instance.app[*].id if var.create_instances
二、字符串函数
2.1 格式化函数
locals {
# format - 格式化字符串
name = format("%s-%s-%03d", var.project, var.env, count.index)
# 输出: myapp-prod-001
# join - 连接列表
tags_string = join(",", var.tags)
# 输出: "tag1,tag2,tag3"
# split - 分割字符串
cidr_parts = split("/", "10.0.0.0/16")
# 输出: ["10.0.0.0", "16"]
# trimspace - 移除首尾空格
clean_name = trimspace(" myapp ")
# 输出: "myapp"
}
2.2 大小写函数
locals {
# upper / lower
upper_env = upper(var.environment) # "PROD"
lower_env = lower(var.environment) # "prod"
# title - 首字母大写
title_name = title("hello world") # "Hello World"
# replace - 替换
sanitized = replace(var.bucket_name, "_", "-")
}
2.3 子串函数
locals {
# substr(string, offset, length)
short_id = substr(aws_instance.app.id, 0, 8)
# strrev - 反转字符串
reversed = strrev("terraform") # "mrofrarret"
# trimprefix / trimsuffix
clean_url = trimprefix("https://example.com", "https://")
# 输出: "example.com"
}
三、集合函数
3.1 列表函数
locals {
zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
# length - 长度
zone_count = length(local.zones) # 3
# element - 获取元素(循环)
primary_zone = element(local.zones, 0)
wrapped_zone = element(local.zones, 5) # 循环,返回 zones[2]
# index - 查找索引
zone_index = index(local.zones, "us-west-2b") # 1
# contains - 检查包含
has_zone = contains(local.zones, "us-west-2a") # true
}
3.2 列表操作
locals {
list1 = [1, 2, 3]
list2 = [3, 4, 5]
# concat - 连接列表
combined = concat(local.list1, local.list2)
# 输出: [1, 2, 3, 3, 4, 5]
# distinct - 去重
unique_items = distinct(local.combined)
# 输出: [1, 2, 3, 4, 5]
# flatten - 展平嵌套列表
nested = [[1, 2], [3, 4]]
flat = flatten(local.nested)
# 输出: [1, 2, 3, 4]
# slice - 切片
subset = slice(local.list1, 0, 2)
# 输出: [1, 2]
# reverse - 反转
reversed = reverse(local.list1)
# 输出: [3, 2, 1]
}
3.3 集合操作
locals {
set1 = toset([1, 2, 3])
set2 = toset([2, 3, 4])
# setunion - 并集
union = setunion(local.set1, local.set2)
# 输出: [1, 2, 3, 4]
# setintersection - 交集
intersection = setintersection(local.set1, local.set2)
# 输出: [2, 3]
# setsubtract - 差集
difference = setsubtract(local.set1, local.set2)
# 输出: [1]
}
3.4 映射函数
locals {
tags = {
Name = "MyApp"
Environment = "Prod"
}
# keys - 获取键列表
tag_keys = keys(local.tags)
# 输出: ["Name", "Environment"]
# values - 获取值列表
tag_values = values(local.tags)
# 输出: ["MyApp", "Prod"]
# lookup - 查找值
env = lookup(local.tags, "Environment", "default")
# merge - 合并映射
all_tags = merge(
local.tags,
{ Owner = "Platform" }
)
# zipmap - 创建映射
zone_map = zipmap(
["a", "b", "c"],
["us-west-2a", "us-west-2b", "us-west-2c"]
)
# 输出: {a = "us-west-2a", b = "us-west-2b", c = "us-west-2c"}
}
四、数值和类型函数
4.1 数值函数
locals {
# min / max
minimum = min(5, 12, 9) # 5
maximum = max(5, 12, 9) # 12
# ceil / floor
rounded_up = ceil(4.2) # 5
rounded_down = floor(4.8) # 4
# abs - 绝对值
absolute = abs(-42) # 42
# pow - 幂运算
squared = pow(2, 3) # 8
# log - 对数
logarithm = log(8, 2) # 3
}
4.2 类型转换
locals {
# tobool
is_enabled = tobool("true")
# tonumber
port = tonumber("8080")
# tostring
port_string = tostring(8080)
# tolist
zones_list = tolist(["a", "b"])
# toset
zones_set = toset(["a", "b", "a"]) # 去重
# tomap
config_map = tomap({
key = "value"
})
}
4.3 类型检查
locals {
# can - 测试表达式是否有效
is_valid_cidr = can(cidrhost(var.vpc_cidr, 0))
# try - 尝试多个表达式
instance_ip = try(
aws_instance.app.public_ip,
aws_instance.app.private_ip,
"0.0.0.0"
)
# type - 获取类型
value_type = type(var.instance_count) # "number"
}
五、编码和哈希函数
5.1 编码函数
locals {
# base64encode / base64decode
encoded = base64encode("hello")
decoded = base64decode(local.encoded)
# jsonencode / jsondecode
json_string = jsonencode({
name = "myapp"
port = 8080
})
config = jsondecode(file("config.json"))
# yamlencode / yamldecode
yaml_string = yamlencode({
name = "myapp"
port = 8080
})
}
5.2 哈希函数
locals {
# md5
file_hash = md5(file("script.sh"))
# sha1 / sha256 / sha512
password_hash = sha256(var.password)
# base64sha256 / base64sha512
content_hash = base64sha256(filebase64("archive.zip"))
# uuid / uuidv5
random_id = uuid()
namespace_id = uuidv5("dns", "example.com")
}
六、文件系统函数
6.1 文件操作
locals {
# file - 读取文件内容
script_content = file("${path.module}/scripts/init.sh")
# filebase64 - Base64 编码读取
binary_content = filebase64("image.png")
# templatefile - 模板渲染
user_data = templatefile("${path.module}/user-data.tpl", {
hostname = "web-server"
region = var.aws_region
})
# fileexists - 检查文件是否存在
has_config = fileexists("${path.module}/config.json")
}
6.2 路径函数
locals {
# path.module - 当前模块路径
module_path = path.module
# path.root - 根模块路径
root_path = path.root
# path.cwd - 当前工作目录
current_dir = path.cwd
# basename - 文件名
filename = basename("/path/to/file.txt") # "file.txt"
# dirname - 目录名
directory = dirname("/path/to/file.txt") # "/path/to"
# abspath - 绝对路径
absolute = abspath("./config")
}
七、网络函数
7.1 CIDR 函数
locals {
vpc_cidr = "10.0.0.0/16"
# cidrhost - 计算主机地址
first_ip = cidrhost(local.vpc_cidr, 0) # "10.0.0.0"
gateway = cidrhost(local.vpc_cidr, 1) # "10.0.0.1"
# cidrnetmask - 获取子网掩码
netmask = cidrnetmask(local.vpc_cidr) # "255.255.0.0"
# cidrsubnet - 创建子网
public_subnet_1 = cidrsubnet(local.vpc_cidr, 8, 0) # "10.0.0.0/24"
public_subnet_2 = cidrsubnet(local.vpc_cidr, 8, 1) # "10.0.1.0/24"
private_subnet_1 = cidrsubnet(local.vpc_cidr, 8, 10) # "10.0.10.0/24"
# cidrsubnets - 批量创建子网
subnets = cidrsubnets(local.vpc_cidr, 8, 8, 8, 8)
# 输出: ["10.0.0.0/24", "10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}
7.2 实际应用
# 自动划分子网
locals {
vpc_cidr = "10.0.0.0/16"
az_count = length(var.availability_zones)
# 为每个 AZ 创建公有和私有子网
public_subnets = cidrsubnets(
local.vpc_cidr,
[for i in range(local.az_count) : 8]...
)
private_subnets = cidrsubnets(
cidrsubnet(local.vpc_cidr, 1, 1),
[for i in range(local.az_count) : 8]...
)
}
resource "aws_subnet" "public" {
count = local.az_count
vpc_id = aws_vpc.main.id
cidr_block = local.public_subnets[count.index]
availability_zone = var.availability_zones[count.index]
}
八、日期和时间函数
locals {
# timestamp - 当前时间戳
current_time = timestamp()
# 输出: "2024-01-10T12:00:00Z"
# formatdate - 格式化日期
formatted_date = formatdate("YYYY-MM-DD", timestamp())
# 输出: "2024-01-10"
# timeadd - 时间加法
future_time = timeadd(timestamp(), "24h")
# timecmp - 时间比较
is_after = timecmp(timestamp(), "2024-01-01T00:00:00Z")
# 返回: 1 (当前时间在后), 0 (相等), -1 (在前)
}
九、实用函数组合
9.1 动态标签生成
locals {
base_tags = {
Environment = var.environment
ManagedBy = "Terraform"
CreatedAt = formatdate("YYYY-MM-DD", timestamp())
}
# 为不同资源添加特定标签
instance_tags = merge(
local.base_tags,
{
Name = "${var.project}-${var.environment}-instance"
Type = "Compute"
}
)
database_tags = merge(
local.base_tags,
{
Name = "${var.project}-${var.environment}-db"
Type = "Database"
}
)
}
9.2 配置验证
locals {
# 验证 CIDR 格式
is_valid_cidr = can(cidrhost(var.vpc_cidr, 0))
# 验证端口范围
is_valid_port = var.port >= 1 && var.port <= 65535
# 验证环境名称
is_valid_env = contains(["dev", "staging", "prod"], var.environment)
# 组合验证
validation_errors = concat(
local.is_valid_cidr ? [] : ["Invalid CIDR block"],
local.is_valid_port ? [] : ["Port out of range"],
local.is_valid_env ? [] : ["Invalid environment"]
)
}
# 在 variable 中使用
variable "vpc_cidr" {
type = string
validation {
condition = can(cidrhost(var.vpc_cidr, 0))
error_message = "Must be a valid CIDR block."
}
}
9.3 资源命名标准化
locals {
# 标准化命名函数
resource_name = lower(replace(
"${var.project}-${var.environment}-${var.component}",
"_",
"-"
))
# S3 桶名称(全局唯一)
bucket_name = "${local.resource_name}-${md5(data.aws_caller_identity.current.account_id)}"
# DNS 名称
dns_name = "${local.resource_name}.${var.domain}"
}
十、性能和最佳实践
10.1 避免重复计算
# ❌ 避免
resource "aws_instance" "app" {
count = 3
tags = {
Name = "${var.project}-${upper(var.environment)}-${count.index}"
}
}
# ✅ 推荐
locals {
name_prefix = "${var.project}-${upper(var.environment)}"
}
resource "aws_instance" "app" {
count = 3
tags = {
Name = "${local.name_prefix}-${count.index}"
}
}
10.2 简化复杂表达式
# ❌ 难以理解
instance_type = var.env == "prod" ? (var.size == "large" ? "t3.2xlarge" : "t3.large") : (var.size == "large" ? "t3.large" : "t3.micro")
# ✅ 使用 locals 分解
locals {
size_map = {
prod = {
large = "t3.2xlarge"
medium = "t3.large"
}
dev = {
large = "t3.large"
medium = "t3.micro"
}
}
instance_type = local.size_map[var.env][var.size]
}
10.3 错误处理
locals {
# 使用 try 提供回退值
config = try(
jsondecode(file("config.json")),
{
default = "value"
}
)
# 使用 can 检查有效性
use_custom_ami = can(data.aws_ami.custom[0].id) ? data.aws_ami.custom[0].id : data.aws_ami.default.id
}
小结
Terraform 的表达式和函数体系:
- 表达式: for、条件、splat
- 字符串: format、join、split
- 集合: concat、merge、lookup
- 类型: can、try、type
- 编码: json、yaml、base64
- 网络: cidr 系列函数
- 文件: file、templatefile
掌握这些工具,可以写出更简洁、更强大的 Terraform 配置。