2. HCL 基础语法:语言特性、块与参数、表达式与函数、变量与输出
HCL(HashiCorp Configuration Language)是 Terraform 的配置语言,设计目标是在人类可读性与机器解析效率之间取得平衡。理解 HCL 的核心语法是编写模块化、可复用 Terraform 代码的前提。本节将系统梳理 HCL 的语言特性、块结构、表达式系统以及变量与输出机制。
2.1 HCL 语言特性
HCL 并非通用编程语言,而是一种声明式领域特定语言(DSL)。其核心设计原则包括:
- 声明式:用户描述“期望状态”,而非“执行步骤”。Terraform 负责计算如何从当前状态迁移到期望状态。
- 可组合:通过模块(Module)将配置封装为可复用的单元,支持嵌套与参数化。
- 不可变基础设施友好:HCL 天然鼓励创建新资源而非原地修改,降低配置漂移风险。
- 类型系统:支持字符串、数字、布尔、列表、映射、对象等类型,并允许在变量和输出中显式声明类型约束。
实践提示:HCL 文件以
.tf 为扩展名。Terraform 在加载目录时会合并所有 .tf 文件,因此可以将不同资源类型拆分到多个文件中管理,但需注意变量和输出的跨文件引用。
2.2 块(Block)与参数(Argument)
HCL 配置由块和参数构成。块是配置的逻辑单元,参数是块内的键值对。
| 概念 | 语法示例 | 说明 |
|---|---|---|
| 资源块 | resource "aws_instance" "web" { ... } |
声明一个云资源,类型为 aws_instance,本地名称为 web |
| 数据源块 | data "aws_ami" "ubuntu" { ... } |
查询已有资源的信息,不创建新资源 |
| 变量块 | variable "region" { type = string } |
定义输入变量,允许外部传入值 |
| 输出块 | output "instance_ip" { value = ... } |
定义模块或根配置的输出值 |
| 模块块 | module "vpc" { source = "./vpc" } |
引用其他目录或仓库中的模块 |
| 局部值块 | locals { name_prefix = "prod" } |
定义局部变量,仅在当前模块内可见 |
参数使用 = 赋值,支持字符串、数字、布尔、列表、映射等字面量。块内可以嵌套子块,例如 tags 映射或 ingress 规则列表。
resource "aws_security_group" "web_sg" {
name = "web-server-sg"
description = "Security group for web servers"
vpc_id = var.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "web-sg"
Environment = var.environment
}
}
2.3 表达式与函数
HCL 表达式允许在配置中动态计算值。表达式可以引用变量、资源属性、局部值,并支持运算符和函数调用。
常用运算符
- 算术:
+、-、*、/、% - 比较:
==、!=、<、>、<=、>= - 逻辑:
&&、||、! - 条件:
condition ? true_val : false_val - 字符串拼接:
"${var.prefix}-${var.name}"或直接使用format()函数
内置函数
Terraform 提供了超过 150 个内置函数,涵盖字符串处理、集合操作、编码转换、时间日期等。以下为模块化设计中高频使用的函数:
| 函数 | 签名 | 用途 |
|---|---|---|
concat |
concat(list1, list2, ...) |
合并多个列表 |
flatten |
flatten(list_of_lists) |
展平嵌套列表 |
merge |
merge(map1, map2, ...) |
合并多个映射,后覆盖前 |
lookup |
lookup(map, key, default) |
安全地从映射中取值,避免 key 不存在时报错 |
length |
length(value) |
返回字符串、列表或映射的长度 |
element |
element(list, index) |
按索引取列表元素,支持循环取模 |
format |
format(format_string, args...) |
格式化字符串,类似 sprintf |
cidrsubnet |
cidrsubnet(prefix, newbits, netnum) |
计算子网 CIDR 块,网络规划必备 |
# 条件表达式与函数组合示例
locals {
environment_short = var.environment == "production" ? "prod" : "dev"
subnet_ids = concat(module.vpc.public_subnets, module.vpc.private_subnets)
merged_tags = merge(var.default_tags, { Environment = var.environment })
}
避坑提醒:函数
lookup() 的默认值参数在 Terraform 0.12+ 中为可选,但强烈建议始终提供默认值,以避免因映射中缺少 key 导致 apply 阶段报错。在模块化设计中,这种防御性编程能显著提升模块的健壮性。
2.4 变量与输出
变量和输出是模块化设计的核心接口。变量允许外部调用者向模块传递参数,输出则暴露模块内部资源的属性供外部使用。
变量(Variable)
变量块支持类型约束、默认值、描述和验证规则。
variable "instance_type" {
description = "EC2 instance type for web servers"
type = string
default = "t3.micro"
validation {
condition = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
error_message = "Instance type must be one of: t3.micro, t3.small, t3.medium."
}
}
variable "subnet_ids" {
description = "List of subnet IDs for the auto scaling group"
type = list(string)
}
variable "tags" {
description = "Common tags applied to all resources"
type = map(string)
default = {}
}
变量赋值方式(按优先级从高到低):
- 命令行
-var或-var-file - 环境变量
TF_VAR_<变量名> terraform.tfvars或*.auto.tfvars文件- 变量块中的
default值
输出(Output)
输出块用于向调用者暴露模块的计算结果或资源属性。
output "vpc_id" {
description = "The ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = aws_subnet.public[*].id
}
output "database_endpoint" {
description = "RDS instance endpoint"
value = aws_db_instance.main.endpoint
sensitive = true # 在 plan/apply 输出中隐藏具体值
}
模块化设计要点:输出应遵循“最小暴露原则”——只暴露调用者真正需要的属性,避免将整个资源对象作为输出。这有助于隐藏内部实现细节,降低模块间的耦合度。
2.5 HCL 语法结构总览
下图展示了 HCL 配置文件中各语法元素之间的组织关系,以及变量、局部值、资源、输出之间的数据流。
2.6 模块化设计中的变量与输出最佳实践
| 实践 | 说明 | 示例 |
|---|---|---|
| 显式类型约束 | 所有变量必须声明 type,避免使用 any |
type = list(string) |
| 提供有意义的默认值 | 让模块在零配置下也能工作 | default = "t3.micro" |
| 使用验证规则 | 在 plan 阶段捕获非法输入 | validation { condition = ... } |
| 输出最小化 | 只暴露调用者需要的属性 | value = aws_lb.main.dns_name |
| 敏感输出标记 | 密码、密钥等使用 sensitive = true |
sensitive = true |
| 输出描述 | 每个输出附带 description,便于文档生成 |
description = "ALB DNS name" |
进阶技巧:在模块内部,可以使用
try() 函数结合 can() 对可能缺失的属性进行安全访问,避免因资源属性不存在导致 apply 失败。例如:try(aws_instance.web.private_ip, null)。