3、HCL语法:变量、资源、数据源、输出、模块的基本语法
Terraform 的核心价值在于将云基础设施以声明式代码的形式进行管理,而这一切的基石便是其专用的配置语言——HCL(HashiCorp Configuration Language)。HCL 设计得既对人类友好,又便于机器解析,它不仅是 Terraform 的配置语言,也被用于 Consul、Vault 等 HashiCorp 生态工具。本章将深入剖析 HCL 中最核心的五个语法块:变量(Variable)、资源(Resource)、数据源(Data Source)、输出(Output)和模块(Module)。理解这些基础语法是编写可维护、可复用、生产级 Terraform 代码的必经之路。我们将从每个语法块的核心原理出发,结合实战案例与常见误区,帮助你建立扎实的 HCL 语法功底。
上图清晰地展示了 HCL 中五个核心语法块之间的协作关系。变量作为输入参数,为资源和数据源提供配置值;数据源用于查询外部已有的基础设施信息,供资源引用;资源是 Terraform 管理的核心对象,负责创建、更新和删除云资源;输出则用于对外暴露资源或数据源的属性,方便用户查看或供其他模块调用;模块则是将这些语法块进行封装和复用的高级抽象。接下来,我们将逐一深入解析每个语法块。
3.1 变量(Variable):参数化你的配置
核心原理
变量是 HCL 中实现配置参数化的关键机制。它允许你将硬编码的值提取出来,使得同一份 Terraform 配置可以在不同环境(如开发、测试、生产)中复用,而无需修改代码逻辑。变量在声明时可以不指定值,而在运行 terraform plan 或 terraform apply 时通过命令行、环境变量或 .tfvars 文件传入。
判断方法与操作流程
声明一个变量使用 variable 块,其基本语法结构如下:
variable "instance_type" {
description = "The EC2 instance type"
type = string
default = "t3.micro"
sensitive = true
}
- description:可选,用于描述变量的用途,增强代码可读性。
- type:可选,指定变量的类型,如
string、number、bool、list、map、object等。如果不指定,默认为any。 - default:可选,为变量设置默认值。如果未设置默认值,且未在运行时传入,Terraform 会交互式提示用户输入。
- sensitive:可选,设置为
true后,该变量在 CLI 输出和日志中会被隐藏,适用于密码、密钥等敏感信息。
变量的赋值优先级从高到低为:命令行 -var 参数 > 环境变量 TF_VAR_<变量名> > terraform.tfvars 文件 > terraform.tfvars.json 文件 > *.auto.tfvars 文件 > 变量定义中的 default 值。
案例拆解:多环境部署配置
假设我们需要为开发和生产环境部署不同的 EC2 实例规格。我们可以定义变量 instance_type 和 ami_id,然后创建两个 .tfvars 文件:
# dev.tfvars
instance_type = "t3.micro"
ami_id = "ami-0c55b159cbfafe1f0"
# prod.tfvars
instance_type = "m5.large"
ami_id = "ami-0c55b159cbfafe1f0"
在运行 Terraform 时,通过 -var-file 参数指定对应的文件:
terraform apply -var-file="dev.tfvars"
这种方式使得环境切换变得极其简单,且避免了在代码中硬编码环境差异。
default 值或 .tfvars 文件中并提交到版本控制。应使用环境变量或 Terraform Cloud 的变量管理功能,并配合 sensitive = true 标记。
3.2 资源(Resource):基础设施的核心单元
核心原理
资源是 Terraform 中最核心的语法块,它代表一个具体的云基础设施对象,如一台虚拟机、一个 VPC、一个 DNS 记录等。Terraform 通过资源块来声明期望的最终状态,并负责将当前状态收敛到期望状态。每个资源块都包含资源类型和资源名称,两者组合起来在配置中必须是唯一的。
操作流程与语法
资源的基本语法如下:
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "Web Server"
}
}
- 资源类型(如
aws_instance):由 Provider 提供,决定了资源的种类。 - 资源名称(如
web_server):在同一个 Terraform 配置中,用于唯一标识该资源。在配置内部,可以通过aws_instance.web_server来引用该资源。 - 参数:资源块内部的参数是特定于资源类型的,用于定义资源的属性。这些参数可以是字符串、数字、列表、映射,甚至可以是另一个资源的属性引用。
资源之间可以通过 隐式依赖 和 显式依赖 来构建关系。隐式依赖是指通过引用其他资源的属性自动建立的依赖;显式依赖则通过 depends_on 参数手动指定。
案例拆解:Web 服务器与安全组
我们创建一个安全组和一个 EC2 实例,并让实例引用安全组的 ID:
resource "aws_security_group" "web_sg" {
name_prefix = "web-sg-"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.web_sg.id]
tags = {
Name = "Web Server"
}
}
这里,aws_instance.web_server 引用了 aws_security_group.web_sg.id,Terraform 会自动识别出这种隐式依赖,并在创建实例之前先创建安全组。
depends_on。
3.3 数据源(Data Source):读取外部基础设施信息
核心原理
数据源允许 Terraform 读取在配置文件之外定义的信息,或者由其他 Terraform 配置管理的信息。它执行的是只读操作,不会创建、修改或删除任何资源。数据源非常适合用于获取已有的 AMI ID、VPC ID、子网 ID,或者查询当前 AWS 账号下的可用区列表等。
操作流程与语法
数据源的基本语法与资源类似,但使用 data 关键字:
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
owners = ["099720109477"] # Canonical
}
引用数据源的属性时,使用 data.<数据源类型>.<名称>.<属性> 的格式。例如,要获取上述 AMI 的 ID,可以使用 data.aws_ami.ubuntu.id。
案例拆解:动态获取最新 AMI
在创建 EC2 实例时,我们通常不希望硬编码 AMI ID,因为 AMI 会不断更新。使用数据源可以动态获取最新的 AMI:
data "aws_ami" "amazon_linux_2" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
resource "aws_instance" "web_server" {
ami = data.aws_ami.amazon_linux_2.id
instance_type = "t3.micro"
}
这样,每次执行 terraform apply 时,Terraform 都会查询最新的 Amazon Linux 2 AMI,确保使用的是最新的系统镜像。
terraform plan 阶段就会被获取并缓存。如果数据源查询的结果在 plan 和 apply 之间发生了变化(例如,AMI 被注销),可能会导致 apply 失败。对于这种场景,需要重新运行 terraform plan。
3.4 输出(Output):暴露配置结果
核心原理
输出块用于定义 Terraform 配置执行完成后,需要向用户展示或提供给其他配置使用的值。它类似于编程语言中的返回值。输出值可以是资源属性、数据源属性,也可以是变量或任何有效的 HCL 表达式。
操作流程与语法
输出块的基本语法如下:
output "instance_ip" {
value = aws_instance.web_server.public_ip
description = "The public IP of the web server"
sensitive = false
}
- value:必填,指定要输出的值。
- description:可选,描述输出的用途。
- sensitive:可选,如果设置为
true,该输出值在 CLI 中会被隐藏,但可以通过terraform output命令查看。
执行 terraform apply 后,输出值会显示在 CLI 的末尾。你也可以随时使用 terraform output 命令来查询输出值。
案例拆解:模块间传递信息
输出在模块化设计中扮演着至关重要的角色。当根模块调用子模块时,子模块可以通过输出将内部资源的关键信息(如 ID、IP 地址)暴露给根模块,根模块再将这些信息用于其他资源的配置或作为最终的输出。
# 在子模块中定义输出
output "vpc_id" {
value = aws_vpc.main.id
}
# 在根模块中调用子模块并引用其输出
module "network" {
source = "./modules/network"
}
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
subnet_id = module.network.vpc_id
}
sensitive = true,防止在日志或 CI/CD 系统中泄露。
3.5 模块(Module):封装与复用
核心原理
模块是 Terraform 中实现代码复用和抽象的核心机制。一个模块就是一组放在同一目录下的 .tf 文件,它们共同定义了一组相关的资源。模块可以像资源一样被调用,并且可以接收输入变量(通过 variable 定义)并返回输出值(通过 output 定义)。模块可以来自本地目录、Terraform Registry、GitHub 等。
操作流程与语法
调用模块使用 module 块:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
}
- source:必填,指定模块的来源。可以是本地路径(如
./modules/vpc),也可以是远程地址(如github.com/.../vpc)。 - version:可选,指定模块的版本,仅用于从 Registry 获取的模块。
- 其他参数:传递给模块的输入变量,需要与模块内部定义的
variable块匹配。
案例拆解:构建可复用的网络模块
我们创建一个简单的 VPC 模块,包含 VPC、子网和互联网网关:
# modules/network/main.tf
variable "vpc_cidr" {
type = string
}
variable "subnet_cidr" {
type = string
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
}
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
cidr_block = var.subnet_cidr
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
}
output "vpc_id" {
value = aws_vpc.main.id
}
output "subnet_id" {
value = aws_subnet.main.id
}
在根模块中调用这个模块:
module "network" {
source = "./modules/network"
vpc_cidr = "10.0.0.0/16"
subnet_cidr = "10.0.1.0/24"
}
output "vpc_id" {
value = module.network.vpc_id
}
通过这种方式,我们可以将复杂的网络架构封装成一个可复用的模块,并在多个项目中共享。
source 路径如果使用本地相对路径,必须确保路径正确。在团队协作中,推荐将模块发布到 Terraform Registry 或 Git 仓库,并指定版本号,以保证环境的一致性和可追溯性。
3.6 总结与复盘
| 语法块 | 核心作用 | 关键关键字 | 典型使用场景 |
|---|---|---|---|
| 变量 | 参数化配置,提高复用性 | variable, type, default, sensitive |
环境区分、用户输入、敏感信息管理 |
| 资源 | 声明和管理基础设施对象 | resource, depends_on |
创建 EC2、VPC、RDS 等所有云资源 |
| 数据源 | 只读查询外部信息 | data, filter |
获取最新 AMI、查询现有 VPC、获取可用区 |
| 输出 | 暴露配置结果,供外部使用 | output, value, sensitive |
展示 IP 地址、传递模块间数据 |
| 模块 | 封装和复用配置代码 | module, source, version |
抽象网络、计算、存储等通用架构 |
在本章中,我们系统地学习了 HCL 的五大核心语法块。变量让配置变得灵活,资源是管理的核心,数据源打通了与外部世界的连接,输出提供了结果的可见性,而模块则赋予了代码复用的能力。掌握这些基础语法后,你就能读懂绝大多数 Terraform 配置,并开始编写自己的基础设施代码。在实际项目中,请务必注意敏感信息的处理、依赖关系的正确表达以及模块的版本管理,这些是构建生产级 Terraform 代码的关键要素。