CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Terraform 基础设施即代码/课时/HCL 语法
RawBlame
课时目录12 节

Terraform 基础设施即代码

  1. 01Terraform 是什么
  2. 02HCL 语法
  3. 03init 与 Provider
  4. 04plan 与 apply
  5. 05State 管理
  6. 06变量与输出
  7. 07Module 复用
  8. 08AWS 资源示例
  9. 09Terraform 与 K8s
  10. 10CI 中的 Terraform
  11. 11Terraform vs Helm
  12. 12综合练习

HCL 语法

resource、provider、block 结构。

30 分钟

HCL 语法

这一课你将能做到

  1. 区分 HCL 中最常用的五种块:terraform、provider、resource、data、variable
  2. 正确写出资源块并用「类型.名称.属性」的方式跨资源引用
  3. 熟练使用字符串插值、集合类型、条件表达式和常用内置函数

为什么要学

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 分别取键和值。

for 表达式用来「变形」集合:

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 块不创建资源,只读取信息:

data "local_file" "existing" {
  filename = "${path.module}/app.properties"
}

(需先存在该文件,plan 才不会报错。)AWS 中最常见的是用 data 查最新的 Amazon Linux 镜像 ID,第 8 课会用到。

讲清楚要点

  1. resource 和 data 的区别:resource 是「我要创建/管理」,data 是「我只读取已有的」。一个动手,一个只看。
  2. 本地名不影响云端:把 "app" 改成 "web",Terraform 认为是删旧建新(state 地址变了),重命名要配合 terraform state mv,第 5 课讲。
  3. 参数分「参数」和「属性」:等号左边你能填的叫参数(argument);创建后云端返回的叫属性(attribute),只能引用不能赋值,比如 public_ip。
  4. 块(block)与属性(attribute)不能混写:tags = {} 是属性赋值;dynamic "tag" {} 才是嵌套块,嵌套块没有等号。
  5. 表达式只在求值时计算:字符串里少写 ${} 会原样输出,这是初学者最高频的笔误。
  6. 官方文档即字典:每种资源的参数和属性查 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 里引用

卡点急救

  1. 报错 Argument or block definition required:某行少了等号、少了引号或括号不配对,从报错行号往上查最近的资源块。
  2. Reference to undeclared resource:本地名写错了,或资源在另一个未加载的目录。确认「类型.本地名」拼写。
  3. 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 语句。

自测三问

  1. 引用 resource "aws_s3_bucket" "logs" 的 ARN,完整写法是什么?
  2. 下面代码有什么问题:resource "local_file" "f" { for_each = ["a","b"] ... }?
  3. locals 的作用是什么?它和 variable 有什么区别?

小结一句话

HCL 的核心是资源块加引用表达式:「类型.本地名.属性」拿一切,插值、for_each、三元条件和内置函数覆盖日常九成场景。

延伸

  • HCL Configuration Language 语法参考
  • Terraform 内置函数索引
  • 下一课:terraform init 与 Provider——插件、锁文件和初始化的全部细节。
加载本节练习…
← 上一课Terraform 是什么下一课 →init 与 Provider