写 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