写 Terraform 配置的时候,你肯定遇到过这些场景:
- 想把几个字符串拼起来
- 想从一个列表里取第一个元素
- 想根据条件选不同的值
- 想读取一个 JSON 配置文件
Terraform 内置了 100+ 个函数,覆盖了字符串处理、集合操作、类型转换、编码解码等各种需求。掌握它们,你的 HCL 代码会从"能跑"变成"优雅"。
本文挑出最常用的 20 个函数,每个都配上可直接运行的 demo,最后附上最佳实践。
1. 字符串函数
1.1. format() — 格式化字符串
类似其他语言的 sprintf,用占位符拼接字符串。
locals {
env = "prod"
region = "cn-hangzhou"
# 输出: "prod-cn-hangzhou-01"
name = format("%s-%s-01", local.env, local.region)
}
1.2. join() — 用分隔符拼接列表
locals {
tags = ["web", "nginx", "frontend"]
# 输出: "web,nginx,frontend"
csv = join(",", local.tags)
}
1.3. split() — 按分隔符拆分字符串
locals {
str = "10.0.0.1,10.0.0.2,10.0.0.3"
# 输出: ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
ips = split(",", local.str)
}
1.4. replace() — 替换子串
locals {
name = "my-project-dev"
# 输出: "my-project-prod"
new = replace(local.name, "dev", "prod")
}
1.5. trimprefix() / trimsuffix() — 去掉前缀/后缀
locals {
domain = "www.example.com"
# 输出: "example.com"
base = trimprefix(local.domain, "www.")
}
1.6. upper() / lower() / title() — 大小写转换
locals {
env = "production"
# 输出: "PRODUCTION"
tag = upper(local.env)
}
2. 集合函数(列表 / 映射 / 集合)
2.1. length() — 获取长度
locals {
zones = ["cn-hangzhou-a", "cn-hangzhou-b", "cn-hangzhou-c"]
count = length(local.zones) # 输出: 3
}
2.2. element() — 按索引取值(会循环)
locals {
zones = ["a", "b"]
# 索引 2 超出长度,自动取模 → 回到索引 0
# 输出: "a"
zone = element(local.zones, 2)
}
💡 和直接
local.zones[2]不同,element()不会报错,适合循环创建资源时分配可用区。
2.3. lookup() — 从 map 中安全取值(带默认值)
locals {
sizes = {
small = "ecs.t6-c1m1.large"
medium = "ecs.g7.large"
}
# 如果 key 不存在,返回默认值
# 输出: "ecs.t6-c1m1.large"
type = lookup(local.sizes, "small", "ecs.t6-c1m1.large")
# 输出: "ecs.t6-c1m1.large"(默认值兜底)
type2 = lookup(local.sizes, "huge", "ecs.t6-c1m1.large")
}
2.4. merge() — 合并多个 map
locals {
base_tags = {
Project = "MyApp"
Env = "dev"
}
extra_tags = {
Team = "Backend"
}
# 输出: { Project = "MyApp", Env = "dev", Team = "Backend" }
all_tags = merge(local.base_tags, local.extra_tags)
}
2.5. concat() — 合并多个列表
locals {
base_sg = ["sg-001"]
extra_sg = ["sg-002", "sg-003"]
# 输出: ["sg-001", "sg-002", "sg-003"]
all_sg = concat(local.base_sg, local.extra_sg)
}
2.6. flatten() — 展平嵌套列表
locals {
nested = [["a", "b"], ["c"], ["d", "e"]]
# 输出: ["a", "b", "c", "d", "e"]
flat = flatten(local.nested)
}
2.7. distinct() — 去重
locals {
list = ["a", "b", "a", "c", "b"]
# 输出: ["a", "b", "c"]
uniq = distinct(local.list)
}
2.8. keys() / values() — 取 map 的键 / 值
locals {
m = { a = 1, b = 2, c = 3 }
k = keys(local.m) # ["a", "b", "c"]
v = values(local.m) # [1, 2, 3]
}
3. 条件与类型转换
3.1. 条件表达式(三元运算符)
locals {
env = "prod"
# 输出: 4
instance_count = local.env == "prod" ? 4 : 1
}
3.2. try() — 安全取值,失败返回默认值
locals {
# 如果 var.config 里没有 "timeout" 这个 key,返回 30
timeout = try(var.config.timeout, 30)
}
3.3. can() — 判断表达式是否能成功求值
locals {
has_timeout = can(var.config.timeout) # true / false
}
3.4. tonumber() / tostring() / tobool() / tolist() / tomap()
locals {
str_num = "42"
num = tonumber(local.str_num) # 42 (number)
}
4. 编码与文件函数
4.1. jsonencode() / jsondecode() — JSON 编解码
locals {
policy = jsonencode({
Version = "1"
Statement = [{
Effect = "Allow"
Action = ["oss:GetObject"]
Resource = ["acs:oss:*:*:my-bucket/*"]
}]
})
}
# 读取外部 JSON 文件
locals {
config = jsondecode(file("${path.module}/config.json"))
}
4.2. file() / templatefile() — 读取文件 / 模板渲染
# 直接读取文件内容
locals {
script = file("${path.module}/scripts/init.sh")
}
# 模板渲染(支持变量替换)
locals {
user_data = templatefile("${path.module}/templates/cloud-init.tftpl", {
hostname = "web-01"
env = "prod"
})
}
templates/cloud-init.tftpl 内容:
#!/bin/bash
hostnamectl set-hostname ${hostname}
echo "ENV=${env}" >> /etc/environment
5. 实战 Demo:一个完整的例子
把上面的函数组合起来,看一个贴近真实场景的写法:
variable "env" {
type = string
default = "prod"
}
variable "zones" {
type = list(string)
default = ["cn-hangzhou-a", "cn-hangzhou-b", "cn-hangzhou-c"]
}
locals {
# 拼接项目前缀
prefix = format("myapp-%s", var.env)
# 根据环境决定实例数量
instance_count = var.env == "prod" ? 3 : 1
# 合并标签
common_tags = merge(
{
Project = "MyApp"
Env = upper(var.env)
},
{
ManagedBy = "Terraform"
}
)
# 安全取值,不存在就用默认
instance_type = lookup(
{ prod = "ecs.g7.xlarge", dev = "ecs.t6-c1m1.large" },
var.env,
"ecs.t6-c1m1.large"
)
}
resource "alicloud_instance" "web" {
count = local.instance_count
instance_name = format("%s-web-%02d", local.prefix, count.index + 1)
instance_type = local.instance_type
image_id = "aliyun_3_x64_20G_alibase_20240528.vhd"
# 用 element() 循环分配可用区
availability_zone = element(var.zones, count.index)
tags = local.common_tags
}
output "instance_names" {
value = alicloud_instance.web[*].instance_name
}
6. 最佳实践
✅ 该做的
| 实践 | 说明 |
|---|---|
用 local 封装复杂表达式 | 把 format()、merge() 等结果存到 locals 里,resource 里直接引用,可读性大幅提升 |
用 try() 替代深层嵌套取值 | try(var.a.b.c, "default") 比写三层 lookup() 清爽得多 |
用 templatefile() 管理多行脚本 | 别在 HCL 里用 <<-EOF 写 50 行 bash,抽成 .tftpl 文件 |
用 jsonencode() 写策略 | 比手写 JSON 字符串安全,Terraform 会帮你校验语法 |
用 element() 做循环分配 | 创建多台 ECS 分配可用区、子网时,不用操心索引越界 |
❌ 不该做的
| 反模式 | 为什么不好 |
|---|---|
| 在 resource 里写一长串嵌套函数 | format("%s-%s", lookup(merge(...), ...)) 没人看得懂,先存到 locals |
用 file() 读取每次构建都变的文件 | 会导致每次都触发 diff,考虑用 templatefile() 或变量传入 |
滥用 jsondecode() 解析复杂结构 | 如果结构固定,直接用 HCL 的 object() 类型约束更安全 |
| 在函数里硬编码环境名 | var.env == "prod" ? ... : ... 可以,但别写 var.env == "production" ? ... 然后别处又用 "prod",统一变量 |
🔧 调试技巧
# 在 terraform console 里交互式测试函数
terraform console
> format("%s-%02d", "web", 3)
"web-03"
> join(",", ["a", "b", "c"])
"a,b,c"
> lookup({a=1, b=2}, "c", 99)
99
不用 apply,打开 terraform console 就能立刻验证函数行为,写配置时非常顺手。
7. 速查表
| 分类 | 常用函数 |
|---|---|
| 字符串 | format join split replace trimprefix trimsuffix upper lower title substr regex |
| 集合 | length element lookup merge concat flatten distinct keys values contains index |
| 条件 | 三元表达式 try can coalesce nonsensitive |
| 类型转换 | tonumber tostring tobool tolist tomap toset |
| 编码 | jsonencode jsondecode base64encode base64decode urlencode csvdecode |
| 文件 | file fileexists templatefile pathexpand basename dirname |
| IP/网络 | cidrsubnet cidrhost cidrnetmask |
| 哈希/加密 | md5 sha1 sha256 sha512 uuid |