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     = {}
}

变量赋值方式(按优先级从高到低):

  1. 命令行 -var 或 -var-file
  2. 环境变量 TF_VAR_<变量名>
  3. terraform.tfvars 或 *.auto.tfvars 文件
  4. 变量块中的 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 配置文件中各语法元素之间的组织关系,以及变量、局部值、资源、输出之间的数据流。

HCL 语法结构与数据流 HCL 语法结构与数据流 variable 块 类型约束 / 默认值 验证规则 locals 块 表达式计算 中间值定义 data 块 查询已有资源 只读数据 resource 块 参数引用:var.xxx / local.xxx / data.xxx 表达式 / 函数 / 条件 output 块 暴露资源属性给调用者 数据流方向:变量 / 局部值 / 数据源 → 资源 → 输出

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)。