跳到主要内容

Terraform 核心语法与资源管理

· 阅读需 9 分钟

本文将深入介绍 Terraform 的核心语法 HCL(HashiCorp Configuration Language)以及资源管理的核心概念,包括 Resource、Data Source、Provider 等重要组件的使用方法。

HCL 语法基础

HCL(HashiCorp Configuration Language)是 Terraform 使用的声明式配置语言,具有简洁易读的特点。

1. 基本语法结构

# 这是注释
# 块的基本结构: <块类型> "<块标签>" "<名称>" {
# <参数> = <值>
# }

resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1d0"
instance_type = "t2.micro"

tags = {
Name = "HelloWorld"
}
}

2. 数据类型

# 字符串
variable "region" {
type = string
default = "us-west-2"
description = "AWS region"
}

# 数字
variable "instance_count" {
type = number
default = 1
}

# 布尔值
variable "enable_monitoring" {
type = bool
default = true
}

# 列表
variable "availability_zones" {
type = list(string)
default = ["us-west-2a", "us-west-2b", "us-west-2c"]
}

# 映射
variable "tags" {
type = map(string)
default = {
Environment = "dev"
Project = "webapp"
}
}

# 对象
variable "server_config" {
type = object({
name = string
instance_type = string
monitoring = bool
})
default = {
name = "web-server"
instance_type = "t2.micro"
monitoring = true
}
}

3. 表达式和函数

# 字符串插值
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type

tags = {
Name = "${var.project_name}-${var.environment}-web"
}
}

# 条件表达式
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.environment == "prod" ? "t3.medium" : "t2.micro"
monitoring = var.environment == "prod" ? true : false
}

# 循环表达式 - for
locals {
server_names = [for i in range(var.server_count) : "server-${i}"]

uppercase_tags = {
for k, v in var.tags : k => upper(v)
}
}

# 常用函数
locals {
# 字符串函数
formatted_name = format("%s-%s", var.project, var.environment)
upper_env = upper(var.environment)

# 列表函数
zone_count = length(var.availability_zones)
first_zone = element(var.availability_zones, 0)

# 映射函数
merged_tags = merge(var.default_tags, var.custom_tags)

# 文件函数
user_data = file("${path.module}/scripts/init.sh")
}

核心概念详解

1. Provider(提供者)

Provider 是 Terraform 与各种服务提供商交互的插件。

# Provider 配置
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.1"
}
}
required_version = ">= 1.0"
}

# 配置 AWS Provider
provider "aws" {
region = var.aws_region

default_tags {
tags = {
Environment = var.environment
ManagedBy = "Terraform"
}
}
}

# 多个 Provider 实例(别名)
provider "aws" {
alias = "east"
region = "us-east-1"
}

provider "aws" {
alias = "west"
region = "us-west-2"
}

# 使用别名 Provider
resource "aws_instance" "east_server" {
provider = aws.east

ami = "ami-0c55b159cbfafe1d0"
instance_type = "t2.micro"
}

2. Resource(资源)

Resource 是 Terraform 配置的核心,代表基础设施中的一个组件。

# 基本资源定义
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true

tags = {
Name = "${var.project_name}-vpc"
}
}

# 子网资源
resource "aws_subnet" "public" {
count = length(var.availability_zones)

vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 1}.0/24"
availability_zone = var.availability_zones[count.index]

map_public_ip_on_launch = true

tags = {
Name = "${var.project_name}-public-${count.index + 1}"
Type = "Public"
}
}

# 安全组
resource "aws_security_group" "web" {
name_prefix = "${var.project_name}-web-"
vpc_id = aws_vpc.main.id
description = "Security group for web servers"

ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}

tags = {
Name = "${var.project_name}-web-sg"
}
}

3. Data Source(数据源)

Data Source 用于查询和引用现有资源的信息。

# 查询最新的 Amazon Linux AMI
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]

filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}

filter {
name = "virtualization-type"
values = ["hvm"]
}
}

# 查询可用区
data "aws_availability_zones" "available" {
state = "available"
}

# 查询当前 AWS 账户信息
data "aws_caller_identity" "current" {}

# 查询现有 VPC
data "aws_vpc" "existing" {
filter {
name = "tag:Name"
values = ["existing-vpc"]
}
}

# 使用 Data Source 的数据
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
subnet_id = aws_subnet.public[0].id
vpc_security_group_ids = [aws_security_group.web.id]

tags = {
Name = "${var.project_name}-web"
AMI = data.aws_ami.amazon_linux.name
}
}

资源依赖关系

1. 隐式依赖

Terraform 会自动检测资源之间的依赖关系。

# VPC 必须先创建
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}

# 子网依赖于 VPC(隐式依赖)
resource "aws_subnet" "web" {
vpc_id = aws_vpc.main.id # 这里引用了 VPC 的 ID
cidr_block = "10.0.1.0/24"
}

# 实例依赖于子网(隐式依赖)
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1d0"
subnet_id = aws_subnet.web.id # 这里引用了子网的 ID
}

2. 显式依赖

使用 depends_on 明确指定依赖关系。

# S3 存储桶
resource "aws_s3_bucket" "logs" {
bucket = "${var.project_name}-logs-${random_id.bucket_suffix.hex}"
}

# 随机 ID 生成器
resource "random_id" "bucket_suffix" {
byte_length = 4
}

# EC2 实例显式依赖 S3 存储桶
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t2.micro"

# 显式依赖:确保 S3 存储桶创建完成后再创建实例
depends_on = [
aws_s3_bucket.logs,
aws_security_group.web
]

user_data = <<-EOF
#!/bin/bash
aws s3 cp /var/log/messages s3://${aws_s3_bucket.logs.bucket}/
EOF
}

生命周期管理

1. 生命周期规则

resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type

# 生命周期配置
lifecycle {
# 创建新资源后再销毁旧资源
create_before_destroy = true

# 防止资源被销毁
prevent_destroy = false

# 忽略特定属性的变化
ignore_changes = [
tags,
user_data,
]
}

tags = {
Name = "web-server"
}
}

# 防止关键资源被意外删除
resource "aws_s3_bucket" "important_data" {
bucket = "critical-data-bucket"

lifecycle {
prevent_destroy = true
}
}

2. 替换触发器

# 使用 replace_triggered_by 触发替换
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type

lifecycle {
replace_triggered_by = [
null_resource.force_replacement
]
}
}

# 触发器资源
resource "null_resource" "force_replacement" {
triggers = {
deployment_version = var.deployment_version
}
}

实例:创建完整的 Web 服务器环境

1. AWS EC2 实例部署

# variables.tf
variable "project_name" {
description = "项目名称"
type = string
default = "webapp"
}

variable "environment" {
description = "环境名称"
type = string
default = "dev"
}

variable "instance_type" {
description = "EC2 实例类型"
type = string
default = "t2.micro"
}

# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}

provider "aws" {
region = "us-west-2"

default_tags {
tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "Terraform"
}
}
}

# 数据源:获取最新的 Amazon Linux AMI
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]

filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}

# 数据源:获取可用区
data "aws_availability_zones" "available" {
state = "available"
}

# VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true

tags = {
Name = "${var.project_name}-${var.environment}-vpc"
}
}

# 互联网网关
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id

tags = {
Name = "${var.project_name}-${var.environment}-igw"
}
}

# 公共子网
resource "aws_subnet" "public" {
count = 2

vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 1}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true

tags = {
Name = "${var.project_name}-${var.environment}-public-${count.index + 1}"
Type = "Public"
}
}

# 路由表
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id

route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}

tags = {
Name = "${var.project_name}-${var.environment}-public-rt"
}
}

# 路由表关联
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)

subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}

# 安全组
resource "aws_security_group" "web" {
name_prefix = "${var.project_name}-${var.environment}-web-"
vpc_id = aws_vpc.main.id
description = "Security group for web servers"

# HTTP
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

# HTTPS
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

# SSH
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # 生产环境中应该限制为特定 IP
}

# 出站流量
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}

tags = {
Name = "${var.project_name}-${var.environment}-web-sg"
}

lifecycle {
create_before_destroy = true
}
}

# 密钥对
resource "aws_key_pair" "main" {
key_name = "${var.project_name}-${var.environment}-key"
public_key = file("~/.ssh/id_rsa.pub") # 请确保公钥文件存在
}

# EC2 实例
resource "aws_instance" "web" {
count = 2

ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
key_name = aws_key_pair.main.key_name
subnet_id = aws_subnet.public[count.index].id
vpc_security_group_ids = [aws_security_group.web.id]

user_data = <<-EOF
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Web Server ${count.index + 1}</h1>" > /var/www/html/index.html
echo "<p>Deployed with Terraform</p>" >> /var/www/html/index.html
EOF

tags = {
Name = "${var.project_name}-${var.environment}-web-${count.index + 1}"
Type = "WebServer"
}

lifecycle {
create_before_destroy = true
}
}

# 应用负载均衡器
resource "aws_lb" "main" {
name = "${var.project_name}-${var.environment}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.web.id]
subnets = aws_subnet.public[*].id

enable_deletion_protection = false

tags = {
Name = "${var.project_name}-${var.environment}-alb"
}
}

# 目标组
resource "aws_lb_target_group" "web" {
name = "${var.project_name}-${var.environment}-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id

health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = "/"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 2
}

tags = {
Name = "${var.project_name}-${var.environment}-tg"
}
}

# 目标组附件
resource "aws_lb_target_group_attachment" "web" {
count = length(aws_instance.web)

target_group_arn = aws_lb_target_group.web.arn
target_id = aws_instance.web[count.index].id
port = 80
}

# 负载均衡器监听器
resource "aws_lb_listener" "web" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"

default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web.arn
}
}

2. 输出配置

# outputs.tf
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}

output "public_subnet_ids" {
description = "公共子网 ID 列表"
value = aws_subnet.public[*].id
}

output "instance_ids" {
description = "EC2 实例 ID 列表"
value = aws_instance.web[*].id
}

output "instance_public_ips" {
description = "EC2 实例公共 IP 列表"
value = aws_instance.web[*].public_ip
}

output "load_balancer_dns" {
description = "负载均衡器 DNS 名称"
value = aws_lb.main.dns_name
}

output "load_balancer_url" {
description = "负载均衡器访问 URL"
value = "http://${aws_lb.main.dns_name}"
}

3. 部署步骤

# 1. 初始化 Terraform
terraform init

# 2. 验证配置
terraform validate

# 3. 查看执行计划
terraform plan

# 4. 应用配置
terraform apply

# 5. 查看输出
terraform output

# 6. 访问应用
curl $(terraform output -raw load_balancer_url)

最佳实践

1. 代码组织

# 项目结构
project/
├── main.tf # 主要资源定义
├── variables.tf # 变量定义
├── outputs.tf # 输出定义
├── terraform.tfvars # 变量值
├── versions.tf # Provider 版本约束
└── modules/ # 自定义模块
└── vpc/
├── main.tf
├── variables.tf
└── outputs.tf

2. 变量验证

variable "environment" {
description = "环境名称"
type = string

validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "环境必须是 dev、staging 或 prod 之一。"
}
}

variable "instance_type" {
description = "EC2 实例类型"
type = string
default = "t2.micro"

validation {
condition = can(regex("^t[2-3]\\.(micro|small|medium)$", var.instance_type))
error_message = "实例类型必须是 t2 或 t3 系列的 micro、small 或 medium。"
}
}

3. 本地值

locals {
# 通用标签
common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "Terraform"
CreatedAt = timestamp()
}

# 资源命名
name_prefix = "${var.project_name}-${var.environment}"

# 条件逻辑
is_production = var.environment == "prod"
instance_count = local.is_production ? 3 : 1
}

resource "aws_instance" "web" {
count = local.instance_count

ami = data.aws_ami.amazon_linux.id
instance_type = local.is_production ? "t3.medium" : "t2.micro"

tags = merge(local.common_tags, {
Name = "${local.name_prefix}-web-${count.index + 1}"
Type = "WebServer"
})
}

总结

通过本文,我们学习了:

  1. HCL 语法基础:掌握了 Terraform 配置语言的核心语法
  2. 核心概念:理解了 Provider、Resource、Data Source 的作用和用法
  3. 依赖关系:学会了如何管理资源之间的依赖关系
  4. 生命周期管理:了解了如何控制资源的创建、更新和销毁
  5. 实际应用:通过完整的 AWS EC2 部署示例,掌握了实际项目中的配置方法

接下来建议:

  1. 深入学习状态管理:了解 Terraform 状态文件的管理和远程存储
  2. 模块化开发:学习如何创建和使用 Terraform 模块提高代码复用性
  3. 多环境管理:掌握如何管理开发、测试、生产等多个环境
  4. 自动化集成:将 Terraform 集成到 CI/CD 流水线中