HCL 语法
这一课你将能做到
- 区分 HCL 中最常用的五种块:terraform、provider、resource、data、variable
- 正确写出资源块并用「类型.名称.属性」的方式跨资源引用
- 熟练使用字符串插值、集合类型、条件表达式和常用内置函数
为什么要学
HCL 是 Terraform 的「Java 语言」——语法不懂,连官方示例都读不下去,更别说自己写。好消息是 HCL 很小:日常 80% 的工作只涉及资源块、属性赋值、字符串插值和少量函数。这一课把这些一次讲透,后面所有课程都是在这套语法上换资源类型。
人话:HCL 像填空题——「我要一个什么(resource 类型),给它起个什么名字(本地名),它的参数分别填什么」。掌握这个形状,AWS 五十多种资源你都能照着文档写出来。
先建立图像
一个资源块的固定形状:
resource "aws_instance" "app" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "springboot-app"
}
}
resource:块类型,关键字,固定
"aws_instance":资源类型,决定这个块创建什么
"app":本地名,只在当前配置里引用用,不影响云上资源名
- 花括号内:参数 = 参数值
引用这个资源创建后产生的属性:
aws_instance.app.id # 资源实例 ID
aws_instance.app.public_ip # 公网 IP
记忆口诀:类型 + 本地名 + 点属性。
跟着做
步骤 1:搭建一个纯本地的练习目录
新建 hcl-practice/main.tf,全程不需要云账号:
terraform {
required_version = ">= 1.7.0"
}
provider "local" {}
# 在本机生成一个文件,验证各种语法的输出
resource "local_file" "greeting" {
filename = "${path.module}/hello.txt"
content = "Hello HCL"
}
执行 terraform init && terraform apply,目录里会出现 hello.txt。
步骤 2:变量、字符串与插值
改写文件,体验三种字符串写法:
variable "project" {
type = string
default = "springboot-demo"
}
resource "local_file" "greeting" {
filename = "${path.module}/hello.txt"
content = "项目:${var.project},模块路径:${path.module}"
}
要点:双引号字符串里用 ${表达式} 插值;path.module 是内置值,表示当前模块目录。
步骤 3:集合类型——list 与 map
locals {
ports = [8080, 8081, 8082] # list:有顺序
tags = { # map:键值对
Project = var.project
Env = "dev"
}
first_port = local.ports[0] # 下标取值
env_name = local.tags.Env # 按键取值
}
output "show" {
value = "第一个端口是 ${local.first_port},环境是 ${local.env_name}"
}
locals 块定义「局部值」,像 Java 里的私有常量,避免重复表达式。
步骤 4:循环 for_each 与 for 表达式
locals {
config_files = {
app = "server.port=8080"
db = "spring.datasource.url=jdbc:mysql://db:3306/demo"
}
}
resource "local_file" "config" {
for_each = local.config_files
filename = "${path.module}/${each.key}.properties"
content = each.value
}
apply 后会生成 app.properties 和 db.properties 两个文件。for_each 遍历 map,each.key/each.value 分别取键和值。
output "upper_names" {
value = [for k in keys(local.config_files) : upper(k)]
}
步骤 5:条件与常用函数
variable "enable_debug" {
type = bool
default = false
}
resource "local_file" "feature" {
count = var.enable_debug ? 1 : 0
filename = "${path.module}/debug.properties"
content = "debug=true"
}
locals {
joined = join(",", ["a", "b", "c"]) # → "a,b,c"
formatted = format("%s-%03d", "app", 7) # → "app-007"
merged = merge(local.tags, { Owner = "team-a" })
}
output "funcs" {
value = "${local.joined} | ${local.formatted}"
}
三元表达式 条件 ? 真值 : 假值 是 HCL 里唯一的 if;count 条件创建是「可选资源」的标准做法。
步骤 6:data 块——查询已存在的东西
data "local_file" "existing" {
filename = "${path.module}/app.properties"
}
(需先存在该文件,plan 才不会报错。)AWS 中最常见的是用 data 查最新的 Amazon Linux 镜像 ID,第 8 课会用到。
讲清楚要点
- resource 和 data 的区别:resource 是「我要创建/管理」,data 是「我只读取已有的」。一个动手,一个只看。
- 本地名不影响云端:把
"app" 改成 "web",Terraform 认为是删旧建新(state 地址变了),重命名要配合 terraform state mv,第 5 课讲。
- 参数分「参数」和「属性」:等号左边你能填的叫参数(argument);创建后云端返回的叫属性(attribute),只能引用不能赋值,比如
public_ip。
- 块(block)与属性(attribute)不能混写:
tags = {} 是属性赋值;dynamic "tag" {} 才是嵌套块,嵌套块没有等号。
- 表达式只在求值时计算:字符串里少写
${} 会原样输出,这是初学者最高频的笔误。
- 官方文档即字典:每种资源的参数和属性查 registry.terraform.io 的对应 provider 页面,不用背。
你可能会懵的地方
-
HCL 和 JSON 是什么关系?
HCL 有 JSON 等价形式(.tf.json),机器生成时用。人手写用 .tf,可读性好得多,不用管 JSON 形式。
-
单引号能用吗?
不能。HCL 字符串只认双引号,单引号会直接报语法错误。
-
count 和 for_each 该用哪个?
资源实例需要「稳定的身份」时用 for_each(用 key 标识,增删中间项不影响其他项);纯粹按数量复制且顺序无关时用 count。生产中 for_each 更安全。
-
= 和 : 有什么区别?
普通参数用 =;for 表达式、对象表达式内部用 : 分隔键值或投影,例如 { for k, v in var.m : k => v }。
-
函数能自己定义吗?
不能写自定义函数。Terraform 内置约百来个函数,复杂逻辑应放到外部脚本或用 provider 能力解决,别把 .tf 写成通用程序。
错法对照
| 错法 | 问题 | 正解 |
|---|
content = "项目:var.project" | 没有插值,原样输出 | "项目:${var.project}" |
for_each = ["a","b"] 用 list | 实例地址不稳定,顺序变动导致重建 | 用 map 或 toset(list) |
资源块内写 id = "xxx" | id 是只读属性,不能赋值 | 导入已有资源用 terraform import |
键名带横杠不加引号 { my-key = 1 } | 旧版本语法错误 | { "my-key" = 1 } |
| 到处复制粘贴同一段表达式 | 难维护 | 抽到 locals 里引用 |
卡点急救
- 报错
Argument or block definition required:某行少了等号、少了引号或括号不配对,从报错行号往上查最近的资源块。
Reference to undeclared resource:本地名写错了,或资源在另一个未加载的目录。确认「类型.本地名」拼写。
- plan 显示要删除/重建一大堆资源:多半是把 count 改成了 for_each,或改了本地名/for_each 的 key。改之前先
terraform state list 核对地址。
面试常问
-
count 和 for_each 的区别?为什么生产推荐 for_each?
count 用数字下标(0、1、2)标识实例,list 中间删除一项会导致后续实例全部「错位」从而被销毁重建;for_each 用 map 的 key 标识,每个实例身份稳定,增删互不影响。
-
resource、data、module 三种块的用途?
resource 创建管理资源;data 读取既有资源信息;module 调用一个可复用的配置包(第 7 课讲)。
-
HCL 怎么做条件判断?
用三元表达式做值选择;用 count = 条件 ? 1 : 0 控制资源是否创建;Terraform 1.4+ 还有 optional 对象属性处理可选项。没有传统的 if 语句。
自测三问
- 引用
resource "aws_s3_bucket" "logs" 的 ARN,完整写法是什么?
- 下面代码有什么问题:
resource "local_file" "f" { for_each = ["a","b"] ... }?
- locals 的作用是什么?它和 variable 有什么区别?
小结一句话
HCL 的核心是资源块加引用表达式:「类型.本地名.属性」拿一切,插值、for_each、三元条件和内置函数覆盖日常九成场景。
延伸