Terraform 核心语法#
Terraform 的配置语言叫 HCL(HashiCorp Configuration Language)。它的设计目标是:人类可读、可编写、易于自动化。相比 JSON,HCL 更简洁;相比 YAML,HCL 支持更丰富的语法特性。
这一篇,我们深入讲解 Terraform 的核心语法。
#基础结构
#单文件示例
main.tf
# 声明 Provider 版本约束
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
}
}
# Provider 配置
provider "aws" {
region = "us-east-1"
}
# 资源定义
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t3.medium"
tags = {
Name = "web-server"
Environment = "prod"
}
}#变量
#变量定义
variables.tf
variable "instance_type" {
description = "EC2 实例类型"
type = string
default = "t3.micro"
validation {
condition = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
error_message = "实例类型必须是 t3.micro、t3.small 或 t3.medium。"
}
}
variable "environment" {
description = "环境名称"
type = string
}
variable "ami_id" {
description = "AMI ID"
type = string
}
variable "tags" {
description = "额外标签"
type = map(string)
default = {}
}#变量类型
types.tf
# 字符串
variable "string_var" {
type = string
default = "hello"
}
# 数字
variable "count" {
type = number
default = 3
}
# 布尔值
variable "enable_monitoring" {
type = bool
default = true
}
# 列表
variable "azs" {
type = list(string)
default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
# 对象
variable "db_config" {
type = object({
engine = string
engine_version = string
instance_class = string
storage_gb = number
})
default = {
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.micro"
storage_gb = 20
}
}
# Map
variable "instance_types" {
type = map(string)
default = {
dev = "t3.micro"
staging = "t3.small"
prod = "t3.medium"
}
}#使用变量
main.tf
resource "aws_instance" "web" {
count = var.count
ami = var.ami_id
instance_type = var.instance_types[var.environment]
tags = merge(
var.tags,
{ Environment = var.environment }
)
}#资源
#资源语法
resource
resource "<resource_type>" "<local_name>" {
# 资源配置
}#资源引用
resources.tf
# 引用本文件或其他文件中的资源
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
# 引用资源的属性
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
}
# 引用被依赖资源的属性
output "vpc_id" {
value = aws_vpc.main.id
}#count 和 for_each
使用
resource "aws_instance" "web" {
count = 3
ami = "ami-12345678"
instance_type = "t3.micro"
subnet_id = aws_subnet.public[count.index].id
tags = {
Name = "web-${count.index}"
}
}
# 引用
aws_instance.web[0].id
aws_instance.web[*].id # 全部使用
resource "aws_security_group_rule" "http" {
for_each = toset(["80", "443"])
type = "ingress"
from_port = each.value
to_port = each.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.web.id
description = "Port ${each.value}"
}
# 引用
keys(aws_security_group_rule.http)
values(aws_security_group_rule.http)count vs for_each
- count:适用于创建相同类型的多个资源,通过索引引用
- for_each:适用于创建需要唯一标识的资源,通过键引用
- 建议:大多数情况下使用 for_each,因为删除中间元素时不会导致索引混乱 :::
#数据源
数据源(Data Sources)允许读取只读信息,不创建资源。
data_sources.tf
# 获取 AWS Account ID
data "aws_caller_identity" "current" {}
output "account_id" {
value = data.aws_caller_identity.current.account_id
}
# 获取 AMI 信息
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# 获取可用区
data "aws_availability_zones" "available" {
state = "available"
}
# 获取 IAM 策略文档
data "aws_iam_policy_document" "ecs_task" {
statement {
sid = "ECSContainerTaskExecution"
effect = "Allow"
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
actions = [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecs:StartTask"
]
resources = ["*"]
}
}#本地值
本地值(Locals)在模块内定义可重用的表达式。
locals.tf
locals {
# 拼接字符串
project_name = "myapp"
# 引用变量
environment = var.environment
# 组合标签
common_tags = {
Project = local.project_name
Environment = local.environment
ManagedBy = "Terraform"
Owner = "platform-team"
}
# 计算值
all_azs = concat(data.aws_availability_zones.available.names, ["us-east-1d"])
# 条件表达式
instance_type = local.environment == "prod" ? "t3.medium" : "t3.micro"
}
# 使用
resource "aws_instance" "web" {
tags = local.common_tags
}#输出
输出值(Outputs)用于公开资源的属性或计算结果。
outputs.tf
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "instance_ips" {
description = "所有实例的公有 IP"
value = aws_instance.web[*].public_ip
}
output "security_group_rules" {
description = "安全组规则"
value = aws_security_group_rule.http
# 敏感输出
sensitive = true
}
output "db_endpoint" {
description = "数据库连接地址"
value = "${aws_db_instance.main.endpoint}:${aws_db_instance.main.port}"
}#条件表达式
conditionals.tf
# 三元表达式
locals {
instance_type = var.environment == "prod" ? "t3.medium" : "t3.micro"
}
# count 控制资源创建
resource "aws_instance" "web" {
count = var.create_production ? 5 : 1
ami = "ami-12345678"
instance_type = "t3.micro"
}
# for_each 条件
resource "aws_security_group_rule" "http" {
for_each = var.enable_https ? toset(["80", "443"]) : toset(["80"])
# ...
}#循环
#表达式循环
for
# 列表推导
locals {
envs = ["dev", "staging", "prod"]
prefixed = [for env in local.envs : "${env}-server"]
# 结果: ["dev-server", "staging-server", "prod-server"]
}
# Map 推导
locals {
config = {
for k, v in var.instance_config : k => upper(v)
}
# 如果 instance_config = {a = "foo", b = "bar"}
# 结果: {a = "FOO", b = "BAR"}
}
# 带过滤的推导
locals {
filtered = [
for name, instance in aws_instance.all :
name if instance.ami != "ami-old"
]
}#动态块
dynamic_blocks.tf
resource "aws_security_group" "web" {
name = "web-sg"
description = "Web 服务器安全组"
# 动态 ingress 规则
dynamic "ingress" {
for_each = var.ingress_rules
iterator = rule
content {
description = rule.value.description
from_port = rule.value.port
to_port = rule.value.port
protocol = rule.value.protocol
cidr_blocks = rule.value.cidr_blocks
}
}
}使用
variable "ingress_rules" {
type = list(object({
description = string
port = number
protocol = string
cidr_blocks = list(string)
}))
default = [
{
description = "HTTP"
port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
description = "HTTPS"
port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
]
}#函数
Terraform 内置了大量函数:
functions.tf
locals {
# 字符串函数
name = lower("WEB-SERVER") # "web-server"
parts = split("-", "web-server") # ["web", "server"]
joined = join("/", ["path", "to", "file"]) # "path/to/file"
# 数值函数
max_val = max(1, 5, 3) # 5
min_val = min(1, 5, 3) # 1
# Map 函数
merged_tags = merge({A = 1}, {B = 2}, {A = 10}) # {A = 10, B = 2}
keys_list = keys({a = 1, b = 2}) # ["a", "b"]
# 文件函数
file_content = file("${path.module}/config.json")
# 时间函数
timestamp_now = timestamp() # 当前时间戳
formatted = formatdate("YYYY-MM-DD", timestamp()) # "2024-01-15"
# Base64 编码
encoded = base64encode("hello") # "aGVsbG8="
# 正则表达式
matched = regex("^([a-z]+)-([a-z]+)$", "web-server")
# 结果: ["web", "server"]
}#模块调用
modules/web_cluster/main.tf
variable "cluster_size" {
description = "集群中实例数量"
type = number
}
variable "instance_type" {
description = "实例类型"
type = string
}
variable "environment" {
description = "环境名称"
type = string
}
resource "aws_instance" "cluster" {
count = var.cluster_size
ami = var.ami_id
instance_type = var.instance_type
tags = {
Name = "cluster-${var.environment}-${count.index}"
}
}
output "instance_ids" {
value = aws_instance.cluster[*].id
}调用模块
module "web_cluster" {
source = "./modules/web_cluster"
cluster_size = 3
instance_type = "t3.medium"
environment = "prod"
ami_id = data.aws_ami.ubuntu.id
}
# 引用模块输出
module.web_cluster.instance_ids#导入现有资源
# 生成 import 指令
terraform import aws_instance.web i-1234567890abcdef0导入目标配置
resource "aws_instance" "web" {
# 必须有资源配置,否则 import 后会显示 "no configuration"
ami = "ami-12345678"
instance_type = "t3.micro"
# Terraform 会填充其他属性
}批量导入
# 创建一个 import.tf 文件
import {
id = "i-1234567890abcdef0"
to = aws_instance.web
}
import {
id = "sg-12345678"
to = aws_security_group.web
}#常见错误
#引用不存在的属性
错误
resource "aws_instance" "web" {
ami = aws_ami.ubuntu.id
}如果 aws_ami.ubuntu 不存在或类型不对,会报错。
#循环依赖
错误
resource "aws_instance" "a" {
depends_on = [aws_instance.b]
}
resource "aws_instance" "b" {
depends_on = [aws_instance.a]
}Terraform 会报错:Cycle: aws_instance.a, aws_instance.b
#类型不匹配
错误
variable "list" {
type = list(string)
}
# 使用时传入 number
instance_type = var.list[0] # 如果 list = [1, 2, 3] 就会报错:::info 下一步
Terraform 状态是核心概念,想深入了解?请阅读 Terraform State 管理。