Terraform 深度指南
Terraform 深度指南
概述
本文是对 Terraform 基础设施即代码 和 Terraform 生产级实践 的深度补充。前两篇覆盖了基础语法、State 管理、Module、CI/CD 集成和 Terragrunt。本文聚焦 HCL 高级语法、资源生命周期、Provider 架构、函数全集、模块开发、原生测试、性能调优、调试技巧 等深度主题——这些是从”会用 Terraform”到”精通 Terraform”的分水岭。
一句话:Terraform 的 80% 时间花在 State 和 Module 上(已有笔记覆盖),剩下 20% 的深度知识决定了你能不能写出优雅、健壮、可维护的 IaC 代码。
一、HCL 高级语法
1.1 表达式与运算符
# 三元条件表达式
locals {
instance_type = var.environment == "prod" ? "n1-standard-8" : "n1-standard-2"
enable_backup = var.environment != "dev" ? true : false
}
# for 表达式 —— 列表转换
locals {
# [for item in var.list : upper(item)] # 列表 → 列表
# [for k, v in var.map : "${k}=${v}"] # map → 列表
# {for k, v in var.map : k => upper(v)} # map → map
# [for s in var.list : upper(s) if length(s) > 3] # 带 filter
availability_zones = [for n in range(3) : "us-east1-${chr(97 + n)}"]
# → ["us-east1-a", "us-east1-b", "us-east1-c"]
node_labels = {
for k, v in var.node_pools : k => merge(v, { pool = k })
}
}
# for_each 遍历 map(推荐,比 count 更适合多资源)
resource "google_compute_instance" "vm" {
for_each = var.instances # map(string) → key 是实例名
name = each.key
machine_type = each.value.machine_type
zone = each.value.zone
}
# count —— 基于条件的 0/1 创建
resource "google_compute_instance" "bastion" {
count = var.enable_bastion ? 1 : 0
name = "bastion-${count.index}"
}
# 引用: google_compute_instance.bastion[0]
# 注意:count 改变会导致后续资源 index 偏移,for_each 更安全
1.2 Dynamic Blocks —— 动态生成嵌套块
# 传统写法:每个规则手写一个 block(重复、不可扩展)
resource "aws_security_group" "web" {
name = "web-sg"
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"]
}
}
# Dynamic 写法:从变量动态生成
variable "ingress_rules" {
type = list(object({
port = number
protocol = string
cidr_blocks = list(string)
description = string
}))
default = [
{ port = 80, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"], description = "HTTP" },
{ port = 443, protocol = "tcp", cidr_blocks = ["0.0.0.0/0"], description = "HTTPS" },
{ port = 22, protocol = "tcp", cidr_blocks = ["10.0.0.0/8"], description = "SSH from internal" },
]
}
resource "aws_security_group" "web" {
name = "web-sg"
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
description = ingress.value.description
}
}
dynamic "egress" {
for_each = var.egress_rules
iterator = rule # 自定义迭代器名称(默认是 block 名)
content {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = rule.value.cidr_blocks
}
}
}
1.3 类型系统与自定义校验
# 类型约束
variable "ports" {
type = list(string) # 简单类型
default = ["80", "443"]
}
variable "instance_config" {
type = object({ # 对象类型
name = string
machine_type = string
zone = string
tags = list(string)
enable_gpu = optional(bool, false) # optional + 默认值(>= 1.3)
gpu_count = optional(number, 0)
})
}
variable "labels" {
type = map(string) # map 类型
default = {}
}
# 可空类型(nullable)
variable "ami_id" {
type = string
nullable = false # 必须提供非 null 值
}
# 自定义校验 validation(>= 1.2)
variable "cluster_name" {
type = string
default = "prod-cluster"
validation {
condition = length(var.cluster_name) <= 40 && can(regex("^[a-z0-9-]+$", var.cluster_name))
error_message = "集群名必须 1-40 字符,只含小写字母、数字、连字符。"
}
}
variable "node_count" {
type = number
default = 3
validation {
condition = var.node_count >= 1 && var.node_count <= 100
error_message = "节点数必须在 1-100 之间。"
}
}
variable "cidr_block" {
type = string
default = "10.0.0.0/16"
validation {
condition = can(cidrhost(var.cidr_block, 0))
error_message = "必须是有效的 CIDR 格式,如 10.0.0.0/16。"
}
}
1.4 moved blocks —— 安全重命名资源(>= 1.1)
# 问题:重命名 resource 会导致 destroy + create,生产环境灾难
# 解决:moved block 告诉 Terraform "旧地址 → 新地址",不重建
# 旧代码:
# resource "google_compute_instance" "web" {
# name = "web-server"
# }
# 新代码(重命名):
moved {
from = google_compute_instance.web
to = google_compute_instance.web_server
}
resource "google_compute_instance" "web_server" {
name = "web-server"
machine_type = "n1-standard-4"
}
# terraform plan 输出:
# # google_compute_instance.web has moved to google_compute_instance.web_server
# resource "google_compute_instance" "web_server" {
# ...
# }
# Plan: 0 to add, 0 to change, 0 to destroy.
最佳实践:重命名后,下一个
plan会显示 “has moved to”,确认无误后apply。之后movedblock 可以删除(它已完成使命)。
1.5 import block —— 声明式导入(>= 1.5)
# 传统方式:terraform import 命令行逐个导入(见生产级实践笔记)
# 新方式:在代码中声明 import block,plan 时预览,apply 时执行
import {
to = aws_instance.example
id = "i-1234567890abcdef0"
}
resource "aws_instance" "example" {
# 写入与云上资源匹配的配置
ami = "ami-12345678"
instance_type = "t3.micro"
}
# 批量导入:配合 for_each
import {
for_each = {
sg_web = "sg-aaa"
sg_db = "sg-bbb"
sg_ssh = "sg-ccc"
}
to = aws_security_group.imported[each.key]
id = each.value
}
resource "aws_security_group" "imported" {
for_each = toset(["sg_web", "sg_db", "sg_ssh"])
name = each.key
}
# 生成配置文件(不写 resource,让 Terraform 帮你生成)
# terraform plan -generate-config-out=generated.tf
# → 自动生成 resource 块,你只需调整参数
1.6 Locals —— 局部变量
locals {
# 复杂逻辑集中管理,避免在 resource 中重复
common_tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "terraform"
CreatedAt = formatdate("YYYY-MM-DD", timestamp())
}
# 计算子网 CIDR
subnets = cidrsubnets(var.vpc_cidr, 4, 4, 4, 2)
# 10.0.0.0/16 → ["10.0.0.0/20", "10.0.16.0/20", "10.0.32.0/20", "10.0.48.0/18"]
# 条件拼接
enable_monitoring = var.environment == "prod" || var.environment == "staging"
log_retention_days = var.environment == "prod" ? 90 : 30
}
# 在任意位置引用
resource "google_compute_instance" "app" {
name = "app-${var.environment}"
machine_type = local.machine_type
tags = [local.common_tags.Project, local.common_tags.Environment]
}
1.7 字符串与模板
# Heredoc —— 多行字符串
locals {
user_data = <<-EOT
#!/bin/bash
apt-get update -y
apt-get install -y nvidia-driver-535
nvidia-smi
echo "Setup complete on $(hostname)"
EOT
# 模板插值
cluster_fqdn = "${var.cluster_name}.${var.domain}"
# 条件模板
config = templatefile("${path.module}/templates/config.yaml.tpl", {
environment = var.environment
replicas = var.environment == "prod" ? 3 : 1
features = var.features
})
# %{ if } / %{ for } 模板指令
nginx_config = <<-EOT
server {
listen 80;
%{ for port in var.ports }
location /port${port} {
proxy_pass http://127.0.0.1:${port};
}
%{ endfor }
%{ if var.enable_ssl }
listen 443 ssl;
ssl_certificate /etc/ssl/cert.pem;
%{ endif }
}
EOT
}
二、资源生命周期管理
2.1 Lifecycle Meta-Arguments
resource "aws_rds_instance" "main" {
name = "prod-db"
instance_class = "db.r6g.xlarge"
allocated_storage = 200
lifecycle {
# 1. create_before_destroy —— 先建新再删旧(零停机替换)
# 适用于:不可变资源(如带特定 AMI 的 EC2、需要替换的安全组规则)
create_before_destroy = true
# 2. prevent_destroy —— 防止误删(生产保护)
# terraform destroy 会报错,必须手动移除此行才能销毁
prevent_destroy = true
# 3. ignore_changes —— 忽略某些属性的漂移检测
# 适用于:其他工具管理的属性(如 tags 由外部系统管理)
ignore_changes = [
tags["LastModified"],
tags["ModifiedBy"],
user_data, # 忽略 user_data 变更(由 Packer 管理)
]
# 4. replace_triggered_by —— 当依赖资源变更时触发替换
replace_triggered_by = [
aws_ami.app.id, # AMI 更新 → 替换 EC2
random_id.config_hash.hex,
]
# 5. precondition(>= 1.2)—— 资源创建/更新前的断言
precondition {
condition = data.aws_ec2_instance_type.instance.supported_architectures[0] == "x86_64"
error_message = "必须选择 x86_64 架构的实例类型。"
}
# 6. postcondition(>= 1.2)—— 资源创建/更新后的断言
postcondition {
condition = self.status == "available"
error_message = "RDS 实例创建后状态不是 available。"
}
}
}
2.2 Timeout 超时控制
resource "aws_ec2_fleet" "gpu" {
# 默认超时由 Provider 决定,可自定义
timeouts {
create = "30m" # 创建超时 30 分钟(GPU 实例可能需要较长时间)
update = "20m"
delete = "15m"
}
}
resource "aws_db_instance" "main" {
timeouts {
create = "60m" # RDS 创建可能需要很长时间
delete = "30m"
}
}
2.3 depends_on —— 显式依赖
# 通常 Terraform 自动推断依赖(通过引用 resource.id 等)
# 但有时依赖是隐式的,需要显式声明
resource "aws_instance" "app" {
ami = data.aws_ami.app.id
instance_type = "t3.large"
# 显式依赖:EC2 必须在 IAM Profile 创建之后才能启动
depends_on = [
aws_iam_instance_profile.app,
aws_cloudwatch_log_group.app, # 启动脚本中会写日志到这个 group
]
}
# Module 之间的依赖
module "eks" {
source = "./modules/eks"
# ...
depends_on = [module.vpc] # EKS 集群必须在 VPC 就绪后创建
}
# depends_on 的陷阱:
# 1. 只影响创建/销毁顺序,不传递数据
# 2. 过度使用会导致依赖图变复杂,plan 变慢
# 3. 优先用 data 引用替代(如 data.aws_vpc.selected.id)
2.4 Provisioners —— 最后手段
# ⚠️ HashiCorp 官方建议:Provisioners 是最后手段
# 优先用:cloud-init/user_data、Packer 预装、Ansible 配置
resource "aws_instance" "app" {
ami = data.aws_ami.app.id
instance_type = "t3.large"
# local-exec —— 在运行 Terraform 的机器上执行
provisioner "local-exec" {
command = "echo ${self.private_ip} >> inventory.ini"
interpreter = ["/bin/bash", "-c"]
environment = {
KUBECONFIG = "~/.kube/config"
}
}
# remote-exec —— 在目标资源上执行(需要连接信息)
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
"sudo systemctl enable nginx",
]
connection {
type = "ssh"
host = self.public_ip
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
}
# on_failure 控制行为
on_failure = continue # 或 fail(默认)
}
# 销毁时执行(清理操作)
provisioner "local-exec" {
when = destroy
command = "aws s3 rm s3://${self.bucket_name} --recursive"
}
}
2.5 null_resource 与 triggers
# null_resource:不创建实际资源,只用于触发 provisioner
resource "null_resource" "cluster_setup" {
# triggers 变化 → 重新执行 provisioner
triggers = {
cluster_version = google_container_cluster.main.id
config_hash = sha256(local.kubeconfig)
build_id = var.build_id
}
provisioner "local-exec" {
command = <<-EOT
kubectl apply -f manifests/
helm upgrade --install argocd argo/argo-cd
EOT
}
}
# terraform_data(>= 1.4)—— null_resource 的替代品
resource "terraform_data" "deployment" {
input = {
version = var.app_version
replicas = var.replicas
}
provisioner "local-exec" {
command = "kubectl set image deployment/app app=${var.app_image}"
}
}
# terraform_data 的优势:input 属性参与 plan 输出,更透明
三、Provider 高级用法
3.1 Provider 别名 —— 多区域/多账号
# 默认 Provider
provider "aws" {
region = "us-east-1"
}
# 别名 Provider:另一个区域
provider "aws" {
alias = "west"
region = "us-west-2"
}
# 别名 Provider:另一个 AWS 账号(跨账号管理)
provider "aws" {
alias = "shared_services"
region = "us-east-1"
assume_role {
role_arn = "arn:aws:iam::999999999999:role/TerraformCrossAccount"
}
}
# 使用别名 Provider
resource "aws_s3_bucket" "east_bucket" {
bucket = "my-east-bucket"
# 使用默认 provider (us-east-1)
}
resource "aws_s3_bucket" "west_bucket" {
bucket = "my-west-bucket"
provider = aws.west # ← 指定别名
}
resource "aws_s3_bucket" "shared_bucket" {
bucket = "shared-services-bucket"
provider = aws.shared_services
}
# GCP 多项目示例
provider "google" {
project = "main-project"
region = "asia-southeast1"
}
provider "google" {
alias = "data_project"
project = "data-analytics-project"
region = "us-central1"
}
3.2 Data Sources —— 读取已有资源
# 查询 AMI(每次 plan 动态查询最新)
data "aws_ami" "latest" {
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"
filter {
name = "opt-in-status"
values = ["opt-in-not-required"]
}
}
# 查询 VPC(由另一个 Terraform 项目管理)
data "aws_vpc" "selected" {
filter {
name = "tag:Name"
values = ["prod-vpc"]
}
}
# 查询子网(配合 for_each 动态创建资源)
data "aws_subnets" "private" {
filter {
name = "vpc-id"
values = [data.aws_vpc.selected.id]
}
filter {
name = "tag:Tier"
values = ["private"]
}
}
# 在每个私有子网中创建一个副本
resource "aws_db_subnet_group" "main" {
name = "main"
subnet_ids = data.aws_subnets.private.ids
}
# 查询 IAM 策略文档
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
resource "aws_iam_role" "app" {
name = "app-role"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
# 查询外部数据(如调用 API 获取最新配置)
data "external" "instance_info" {
program = ["python3", "${path.module}/scripts/get_instance_info.py"]
query = {
cluster_name = var.cluster_name
}
}
# 返回 JSON,通过 data.external.instance_info.result.xxx 引用
3.3 Provider 函数(>= 1.8)
# 部分 Provider 提供自定义函数(provider-defined functions)
# 这些函数在 plan 和 apply 阶段都可用
# AWS Provider 函数
data "aws_region" "current" {} # 传统 data source
# >= 1.8 可以直接用 provider 函数(无需 data source)
# provider::aws::arn_parse(arn) → 解析 ARN
# provider::aws::arn_build(...) → 构建 ARN
# 注意:provider 函数支持尚在早期,具体可用函数请查看 Provider 文档
3.4 Provider 版本约束
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
# 版本约束语法:
# >= 5.0 至少 5.0
# ~> 5.0 >= 5.0, < 6.0(推荐:锁定大版本)
# ~> 5.30 >= 5.30, < 6.0
# >= 5.0, < 6.0 显式范围
# = 5.31.0 精确版本(不推荐,缺乏补丁更新)
version = "~> 5.30"
}
}
}
# .terraform.lock.hcl —— 依赖锁定文件
# 类似 package-lock.json,记录实际使用的精确版本
# 应该提交到 Git,确保团队使用相同版本
四、函数全集速查
4.1 集合函数
| 函数 | 说明 | 示例 |
|---|---|---|
concat | 合并列表 | concat(["a","b"], ["c"]) → ["a","b","c"] |
distinct | 去重 | distinct(["a","a","b"]) → ["a","b"] |
flatten | 展平嵌套列表 | flatten([["a"],["b",["c"]]]) → ["a","b","c"] |
merge | 合并 map | merge({a=1}, {b=2}) → {a=1, b=2} |
contains | 判断元素存在 | contains(["a","b"], "a") → true |
element | 按索引取元素(循环) | element(["a","b","c"], 5) → "c" |
lookup | map 取值带默认 | lookup({a=1}, "b", 0) → 0 |
keys / values | map 的键/值列表 | keys({a=1, b=2}) → ["a","b"] |
zipmap | 两个列表组合成 map | zipmap(["a","b"], [1,2]) → {a=1, b=2} |
setunion / setintersection | 集合运算 | setunion(["a"],["b"]) → ["a","b"] |
chunklist | 分块 | chunklist(["a","b","c","d"], 2) → [["a","b"],["c","d"]] |
coalesce | 返回第一个非空值 | coalesce("", "", "default") → "default" |
coalescelist | 列表版 coalesce | coalescelist([], ["a"]) → ["a"] |
4.2 字符串函数
# 格式化
format("v%s-%s", "1.0", "prod") # → "v1.0-prod"
formatlist("instance-%s", ["web","db"]) # → ["instance-web", "instance-db"]
# 大小写
upper("hello") # → "HELLO"
lower("HELLO") # → "hello"
title("hello world") # → "Hello World"
# 分割与拼接
split(",", "a,b,c") # → ["a", "b", "c"]
join("-", ["a", "b", "c"]) # → "a-b-c"
# 替换
replace("hello world", "world", "terraform") # → "hello terraform"
regex("[a-z]+", "abc123") # → "abc"
regexall("[a-z]+", "abc123XYZdef") # → ["abc", "def"]
# 前缀/后缀
trimprefix("v1.0.0", "v") # → "1.0.0"
trimsuffix("file.txt", ".txt") # → "file"
trim(" hello ", " ") # → "hello"
# 缩进处理
indent(4, "line1\nline2") # 每行缩进 4 空格
# Base64
base64encode("hello") # → "aGVsbG8="
base64decode("aGVsbG8=") # → "hello"
base64gzip("large text") # → gzip 压缩后 base64
# 文件
file("${path.module}/script.sh") # 读取文件内容
fileexists("${path.module}/config.yaml") # 文件是否存在
fileset("${path.module}", "*.tf") # 匹配文件列表
filebase64("${path.module}/cert.pem") # 文件内容 base64
filebase64sha256("${path.module}/app.zip") # 文件 SHA256 的 base64
# 模板
templatefile("${path.module}/templates/user_data.sh.tpl", {
region = var.region
cluster = var.cluster_name
packages = var.packages
})
4.3 网络/编码函数
# CIDR 计算
cidrhost("10.0.0.0/16", 4) # → "10.0.0.4"(取第 N 个 IP)
cidrnetmask("10.0.0.0/16") # → "255.255.0.0"
cidrsubnet("10.0.0.0/16", 4, 0) # → "10.0.0.0/20"(子网计算)
cidrsubnets("10.0.0.0/16", 4, 4, 8) # → ["10.0.0.0/20", "10.0.16.0/20", "10.0.32.0/24"]
# URL 解析
urlescape("hello world") # → "hello%20world"
urldecode("hello%20world") # → "hello world"
# JSON / YAML
jsonencode({name = "test", port = 80}) # → '{"name":"test","port":80}'
jsondecode('{"name":"test"}') # → {"name" = "test"}
yamlencode({a = 1, b = [1, 2]}) # → "a: 1\nb:\n- 1\n- 2\n"
yamldecode(file("config.yaml")) # 解析 YAML 文件
# 编码/解码
urlencode("https://example.com?a=b c") # URL 编码
textencodebase64("hello", "UTF-8")
textdecodebase64("aGVsbG8=", "UTF-8")
csvdecode(file("users.csv")) # CSV → list of objects
# 哈希
md5("hello") # → "5d41402abc4b2a76b9719d911017c592"
sha1("hello")
sha256("hello")
sha512("hello")
uuid() # 生成随机 UUID
uuidv5("dns", "example.com") # 基于命名空间的确定性 UUID
4.4 时间/日期函数
timestamp() # → "2026-08-03T10:30:00Z"(当前 UTC 时间 RFC3339)
formatdate("YYYY-MM-DD", timestamp()) # → "2026-08-03"
formatdate("YYYY-MM-DD hh:mm:ss", timestamp()) # → "2026-08-03 10:30:00"
formatdate("DD MMM YYYY hh:mm ZZZ", timestamp()) # → "03 Aug 2026 10:30 UTC"
timeadd(timestamp(), "24h") # 当前时间 + 24 小时
timeadd(timestamp(), "-2h30m") # 当前时间 - 2 小时 30 分
# 注意:timestamp() 每次 plan 都会变化,不要直接用作资源属性
# 正确做法:用变量固定时间
variable "deployment_date" {
type = string
default = "2026-08-03"
}
4.5 类型转换与判断
# 类型转换
toint("42") # → 42
tonumber("3.14") # → 3.14
tostring(42) # → "42"
tobool("true") # → true
tolist({a=1}) # → 错误(map 不能转 list)
toset(["a","a"]) # → toset(["a"])(去重)
# 类型判断
can(tostring(var.x)) # 是否可以转换
can(aws_instance.app.id) # 资源是否已存在(用于条件逻辑)
try(aws_instance.app.id, "unknown") # 尝试取值,失败返回默认值
# try 在数据处理中非常有用
locals {
# 安全地从可能缺失的字段中取值
zone = try(data.external.info.result.zone, var.default_zone)
tags = try(var.extra_tags, {})
}
4.6 加密函数
# RSA 加解密(>= 1.6)
rsadecrypt(encrypted_data, private_key_pem)
# 密码生成
# 使用 random provider(需要单独声明 required_providers)
resource "random_password" "db" {
length = 32
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
min_upper = 2
min_lower = 2
min_numeric = 2
min_special = 2
}
resource "random_id" "build" {
byte_length = 8
# 生成如 "aB3xK9mN" 的随机 ID
}
resource "random_shuffle" "azs" {
input = data.aws_availability_zones.available.names
result_count = 3
# 随机打乱可用区列表
}
五、模块开发最佳实践
5.1 模块结构
terraform-modules/
├── modules/
│ └── vpc/
│ ├── main.tf # resource 定义
│ ├── variables.tf # 输入变量(含 validation)
│ ├── outputs.tf # 输出值
│ ├── versions.tf # provider/version 约束
│ ├── README.md # 必须有!
│ ├── examples/ # 使用示例(可被 Terratest 测试)
│ │ └── basic/
│ │ └── main.tf
│ └── tests/ # 单元测试(可选)
│ └── vpc_test.go
├── modules/
│ └── eks-gpu/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── modules/ # 子模块(内部使用,不发布)
└── README.md
5.2 模块版本约束
# versions.tf —— 在模块内部声明依赖
terraform {
required_version = ">= 1.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
# 调用模块时锁定版本
module "vpc" {
source = "git::ssh://git@example.com/terraform-modules.git//vpc?ref=v2.1.0"
# ref=v2.1.0 锁定到 Git tag
# 或从 Terraform Registry
# source = "terraform-aws-modules/vpc/aws"
# version = "5.1.0"
}
5.3 变量设计原则
# variables.tf —— 模块的"API"
# 1. 每个变量都要有 description
variable "name" {
type = string
description = "VPC 名称前缀,将用于所有资源命名"
}
# 2. 合理的默认值(可选变量才有默认值)
variable "cidr" {
type = string
description = "VPC CIDR 块"
default = "10.0.0.0/16"
validation {
condition = can(cidrhost(var.cidr, 0))
error_message = "必须是有效的 IPv4 CIDR 格式。"
}
}
# 3. 复杂对象用 object 类型 + optional 字段
variable "subnets" {
type = list(object({
name = string
cidr = string
availability_zone = string
type = optional(string, "private") # private | public | database
tags = optional(map(string), {})
}))
description = "子网配置列表"
default = []
}
# 4. sensitive 变量不会显示在 plan 输出中
variable "db_password" {
type = string
description = "数据库主密码"
sensitive = true
}
# 5. nullable = false 防止传 null
variable "environment" {
type = string
nullable = false
}
5.4 输出设计原则
# outputs.tf
# 1. 输出关键资源 ID 和名称
output "vpc_id" {
value = aws_vpc.main.id
description = "创建的 VPC ID"
}
# 2. 输出子网 ID 列表(给下游模块用)
output "private_subnet_ids" {
value = aws_subnet.private[*].id
description = "私有子网 ID 列表"
}
output "public_subnet_ids" {
value = aws_subnet.public[*].id
description = "公有子网 ID 列表"
}
# 3. sensitive 输出不会显示在 plan 中,但会写入 State
output "db_endpoint" {
value = aws_db_instance.main.endpoint
description = "RDS 端点地址"
}
output "db_password" {
value = random_password.db.result
sensitive = true
description = "数据库密码(存储在 State 中,使用方需注意安全)"
}
# 4. depends_on —— 当输出被其他模块引用时确保依赖正确
output "vpc" {
value = {
id = aws_vpc.main.id
cidr_block = aws_vpc.main.cidr_block
private_subnets = aws_subnet.private[*].id
public_subnets = aws_subnet.public[*].id
}
description = "VPC 完整信息"
depends_on = [aws_route_table_association.private, aws_route_table_association.public]
}
# 5. precondition —— 输出前的校验
output "subnet_count" {
value = length(aws_subnet.private)
description = "私有子网数量"
precondition {
condition = length(aws_subnet.private) >= 2
error_message = "至少需要 2 个私有子网以支持高可用。"
}
}
5.5 模块测试
# tests/basic.tftest.hcl —— Terraform 原生测试(>= 1.6)
# 文件放在模块目录下的 tests/ 中
# 定义测试变量
variables {
name = "test-vpc"
cidr = "10.1.0.0/16"
subnets = [
{ name = "private-a", cidr = "10.1.0.0/24", availability_zone = "us-east-1a" },
{ name = "private-b", cidr = "10.1.1.0/24", availability_zone = "us-east-1b" },
]
}
# 测试步骤 1:plan
run "validate_plan" {
command = plan
# 断言 plan 输出
assert {
condition = length(plan_values.aws_subnet.private) == 2
error_message = "应该创建 2 个私有子网"
}
}
# 测试步骤 2:apply(需要真实的云凭证)
run "create_vpc" {
command = apply
assert {
condition = output.vpc_id != ""
error_message = "VPC ID 不应为空"
}
assert {
condition = length(output.private_subnet_ids) == 2
error_message = "应该输出 2 个私有子网 ID"
}
}
# 运行测试
# terraform test
# terraform test -filter="tests/basic.tftest.hcl"
// tests/vpc_test.go —— Terratest 集成测试
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestVpcModule(t *testing.T) {
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../examples/basic",
Vars: map[string]interface{}{
"name": "terratest-vpc",
"cidr": "10.2.0.0/16",
},
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcID := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcID)
subnets := terraform.OutputList(t, terraformOptions, "private_subnet_ids")
assert.Equal(t, 2, len(subnets))
}
六、Terraform Cloud / Enterprise
6.1 核心特性对比
| 特性 | OSS | Cloud (Free) | Cloud (Plus) | Enterprise |
|---|---|---|---|---|
| 远程 State + 锁 | 手动配置 | ✅ | ✅ | ✅ |
| VCS 集成 | ❌ | ✅ | ✅ | ✅ |
| 远程 Plan/Apply | ❌ | ✅ | ✅ | ✅ |
| Private Module Registry | ❌ | ❌ | ✅ | ✅ |
| Sentinel Policy | ❌ | ❌ | ✅ | ✅ |
| Team 管理 | ❌ | 限 | ✅ | ✅ |
| SSO / SAML | ❌ | ❌ | ❌ | ✅ |
| 自托管 | N/A | ❌ | ❌ | ✅ |
6.2 Terraform Cloud 工作流
Developer → Push to GitHub
→ TFC webhook 触发
→ TFC 自动 plan(在 TFC 的 Runner 上)
→ plan 输出显示在 PR 评论中
→ Reviewer 审批
→ 点击 "Confirm & Apply" 或自动 apply
→ State 自动保存在 TFC
6.3 Sentinel Policy as Code
# Sentinel 策略:禁止创建未加密的 S3 bucket
import "tfplan/v2" as tfplan
# 规则 1:所有 S3 bucket 必须启用加密
no_unencrypted_s3 = rule {
all tfplan.resources.aws_s3_bucket as _, buckets {
all buckets as _, bucket {
bucket.applied.server_side_encryption_configuration is not null
}
}
}
# 规则 2:所有 EC2 实例必须有 Name 标签
ec2_has_name_tag = rule {
all tfplan.resources.aws_instance as _, instances {
all instances as _, inst {
"Name" in keys(inst.applied.tags)
}
}
}
# 规则 3:生产环境禁止创建 0.0.0.0/0 的入站规则
no_world_open_ingress = rule when tfplan.terraform_version is not "" {
all tfplan.resources.aws_security_group_rule as _, rules {
all rules as _, r {
r.applied.cidr_blocks not contains "0.0.0.0/0" or
r.applied.from_port != 22
}
}
}
main = rule {
no_unencrypted_s3 and
ec2_has_name_tag and
no_world_open_ingress
}
6.4 替代方案:OPA / Conftest
# OPA Rego 策略(开源,不依赖 Terraform Cloud)
package terraform
# 禁止在生产环境创建小型 EC2 实例
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_instance"
resource.change.after.instance_type == "t3.micro"
msg := sprintf("禁止创建 t3.micro 实例: %s", [resource.address])
}
# 禁止未加密的 EBS 卷
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_ebs_volume"
not resource.change.after.encrypted
msg := sprintf("EBS 卷必须加密: %s", [resource.address])
}
# 必须有成本标签
deny[msg] {
resource := input.resource_changes[_]
resource.change.after.tags != null
not "CostCenter" in keys(resource.change.after.tags)
msg := sprintf("资源必须标记 CostCenter: %s", [resource.address])
}
# 使用:conftest test terraform_plan.json
# 或在 CI 中:terraform plan -out=tfplan && terraform show -json tfplan | conftest test -
七、性能调优
7.1 plan 慢的常见原因与解决
# 1. State 太大 → 拆分 State(见生产级实践笔记)
# 2. refresh 慢(每个资源都调云 API 检查实际状态)
# 解决:-refresh=false 跳过 refresh(需要确认 State 是最新的)
terraform plan -refresh=false
# 3. 大量 data source 查询
# 解决:减少 data source,改用变量传入
# 4. Provider 并行度不足
# 默认并行度 10,大型基础设施可提高
terraform plan -parallelism=30
terraform apply -parallelism=30
# 5. 网络延迟(跨区域 Provider 调用)
# 解决:在离云区域近的机器上运行 Terraform
7.2 Targeted Plan —— 只操作部分资源
# 只 plan 指定资源(开发调试时非常有用)
terraform plan -target=aws_instance.app
terraform plan -target=module.vpc
terraform plan -target=aws_instance.app -target=aws_security_group.web
# 注意:-target 不应作为日常操作方式
# 它会导致 State 不完整,下次完整 plan 可能出现意外变更
# 仅用于:调试、紧急修复、逐步迁移
7.3 State 性能优化
# 1. 定期清理 State 中的孤儿资源
terraform state list | grep -v "module\." | xargs -I{} terraform state show {}
# 2. 使用 state push 替代 apply(仅更新 State,不操作云资源)
# 适用场景:手动在云上修改了资源,同步到 State
terraform state push updated.tfstate
# 3. State 文件压缩(大型 State 文件)
# 远程后端会自动处理,本地 State 需手动管理
# 4. 避免在 State 中存储大对象
# 错误:把整个 JSON 配置存到 State
# 正确:存引用(如 S3 路径),运行时读取
7.4 配置优化技巧
# 1. 减少 data source 使用(每次 plan 都会查询云 API)
# 错误:每次 plan 查询最新 AMI
data "aws_ami" "latest" {
most_recent = true
# ...
}
# 正确:用变量固定 AMI ID(由 CI/CD 或 Packer 流水线更新)
variable "ami_id" {
type = string
default = "ami-0123456789abcdef0"
}
# 2. 避免在 for_each 中使用不确定的值
# 错误:for_each 遍历 data source 输出(每次可能变化)
# 正确:for_each 遍历变量(确定的输入)
# 3. 使用 locals 减少重复计算
locals {
# 计算一次,多处引用
azs = slice(data.aws_availability_zones.available.names, 0, 3)
common_tags = merge(var.default_tags, { ManagedBy = "terraform" })
}
八、调试技巧
8.1 日志级别
# TF_LOG 环境变量控制日志级别
export TF_LOG=TRACE # 最详细(含 HTTP 请求/响应)
export TF_LOG=DEBUG # 调试信息
export TF_LOG=INFO # 一般信息
export TF_LOG=WARN # 警告
export TF_LOG=ERROR # 仅错误
export TF_LOG=OFF # 关闭(默认)
# 日志写入文件(不污染终端)
export TF_LOG_PATH=/tmp/terraform.log
# Provider 级别日志
export TF_LOG_PROVIDER=DEBUG # 只看 Provider 日志
# 常见调试场景:
# 1. "Provider 产生了不一致的 plan"
# → TF_LOG=DEBUG 查看每个 API 调用的请求和响应
# 2. "State 锁无法释放"
# → TF_LOG=DEBUG 查看 DynamoDB 锁操作
# 3. "Module 下载失败"
# → TF_LOG=TRACE 查看 Git clone 过程
8.2 Console —— 交互式表达式求值
# 启动交互式控制台
terraform console
# 在控制台中求值
> var.environment
"prod"
> aws_vpc.main.cidr_block
"10.0.0.0/16"
> [for s in data.aws_subnets.private.ids : cidrhost(data.aws_subnet.selected[s].cidr_block, 0)]
> merge({a=1}, {b=2})
{
"a" = 1
"b" = 2
}
> exit
# 非交互式求值
echo 'length(var.subnets)' | terraform console
echo 'jsonencode({environment = var.environment})' | terraform console
8.3 Plan 输出分析
# JSON 格式 plan(便于程序分析)
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
# 用 jq 分析 plan
# 统计将创建的资源数
cat plan.json | jq '.resource_changes | length'
# 查看所有将创建的资源
cat plan.json | jq '.resource_changes[] | select(.change.actions[] | contains("create")) | .address'
# 查看所有将删除的资源
cat plan.json | jq '.resource_changes[] | select(.change.actions[] | contains("delete")) | .address'
# 查看特定资源的变更详情
cat plan.json | jq '.resource_changes[] | select(.address == "aws_instance.app") | .change'
8.4 Crash 日志
# Terraform 崩溃时会生成 crash.log
ls -la crash.log
# crash.log 包含:
# - 崩溃时的 goroutine 堆栈
# - 当时的配置文件内容
# - State 文件内容(可能含敏感信息!)
# 提交 HashiCorp 支持工单时附上 crash.log(注意脱敏)
8.5 常见错误诊断
# 1. "Error: Failed to query available provider packages"
# → 检查网络代理(HTTP_PROXY/HTTPS_PROXY)
# → 检查 required_providers 中的 source 路径
# 2. "Error: Provider produced inconsistent result after apply"
# → Provider bug 或 API 返回异常
# → TF_LOG=DEBUG 查看 API 响应
# → 临时解决:terraform state rm + 重新 import
# 3. "Error: no suitable image found"
# → 检查可用区是否支持该实例类型
# → 检查 AMI 是否在当前区域可用
# 4. "Error: acquiring state lock"
# → 其他人正在 apply,等待或检查锁是否过期
# → 确认无人操作后:terraform force-unlock <lock-id>
# 5. "Error: Cycle detected"
# → 资源之间有循环依赖
# → 检查 depends_on 和 data source 引用链
# → 使用 -target 隔离问题资源
# 6. "Error: Value for unconfigurable attribute"
# → 尝试设置 Provider 不支持修改的属性
# → 添加 lifecycle { ignore_changes = [attr] }
九、密钥管理
9.1 Secret 管理方案对比
| 方案 | 安全性 | 复杂度 | 适用场景 |
|---|---|---|---|
| tfvars + git-crypt | 中 | 低 | 小团队 |
| AWS Secrets Manager | 高 | 中 | AWS 环境 |
| HashiCorp Vault | 高 | 高 | 大型企业 |
| Terraform Cloud Variables | 高 | 低 | TFC 用户 |
| External Data Source | 中 | 中 | 自定义方案 |
9.2 AWS Secrets Manager 集成
# 在 Secrets Manager 中存储密钥(通过 AWS CLI 或控制台)
# aws secretsmanager create-secret --name prod/db --secret-string '{"user":"admin","password":"xxx"}'
# Terraform 中读取
data "aws_secretsmanager_secret" "db" {
name = "prod/db"
}
data "aws_secretsmanager_secret_version" "db" {
secret_id = data.aws_secretsmanager_secret.db.id
}
locals {
db_credentials = jsondecode(data.aws_secretsmanager_secret_version.db.secret_string)
}
resource "aws_db_instance" "main" {
username = local.db_credentials.user
password = local.db_credentials.password
# password 不会出现在 plan 输出中(但会写入 State!)
}
# ⚠️ 注意:即使 data source 读取的 secret,也会被写入 State 文件
# 确保 State backend 启用了加密(S3 SSE 或 GCS CSEK)
9.3 HashiCorp Vault 集成
# Vault Provider
provider "vault" {
address = "https://vault.example.com:8200"
token = var.vault_token # 从环境变量注入
}
# 从 Vault 读取密钥
data "vault_generic_secret" "db" {
path = "secret/data/prod/database"
}
resource "aws_db_instance" "main" {
username = data.vault_generic_secret.db.data["username"]
password = data.vault_generic_secret.db.data["password"]
}
# Vault 动态密钥(按需生成,自动过期)
data "vault_generic_secret" "dynamic_db" {
path = "database/creds/readonly"
# 每次读取生成新的临时凭证
}
9.4 敏感数据在 State 中的保护
# 1. 标记 sensitive
variable "api_key" {
type = string
sensitive = true # plan 输出中不显示
}
output "connection_string" {
value = "postgres://${var.db_user}:${var.db_password}@${aws_db_instance.main.endpoint}"
sensitive = true
}
# ⚠️ 但 sensitive 只影响 plan/apply 输出,密钥仍然明文存储在 State 文件中!
# 2. State 加密
# S3: 开启 SSE-KMS
terraform {
backend "s3" {
bucket = "my-tfstate"
key = "prod/terraform.tfstate"
encrypt = true # 必须开启
kms_key_id = "arn:aws:kms:..." # 使用 KMS 加密
dynamodb_table = "terraform-locks"
}
}
# 3. 完全避免密钥进入 State 的策略
# 方案 A:在 user_data 中从 Secrets Manager 拉取(Terraform 只存引用)
resource "aws_instance" "app" {
user_data = templatefile("${path.module}/user_data.sh.tpl", {
secret_arn = aws_secretsmanager_secret.db.arn # 只传 ARN,不传值
})
}
# user_data.sh.tpl:
# #!/bin/bash
# aws secretsmanager get-secret-value --secret-id ${secret_arn} | jq -r '.SecretString' > /app/config.json
# 方案 B:使用 External Secrets Operator(K8s 集群内拉取)
# Terraform 只创建 SecretStore 和 ExternalSecret CRD,不存实际密钥
十、Terraform 原生测试(>= 1.6)
10.1 测试文件结构
modules/vpc/
├── main.tf
├── variables.tf
├── outputs.tf
└── tests/
├── basic.tftest.hcl # 基础测试
├── multi_az.tftest.hcl # 多可用区测试
└── validation.tftest.hcl # 变量校验测试
10.2 完整测试示例
# tests/basic.tftest.hcl
# 定义测试用变量
variables {
name = "test-vpc"
cidr = "10.10.0.0/16"
subnets = [
{
name = "private-a"
cidr = "10.10.0.0/24"
availability_zone = "us-east-1a"
type = "private"
},
{
name = "private-b"
cidr = "10.10.1.0/24"
availability_zone = "us-east-1b"
type = "private"
},
]
}
# 测试 1:验证 plan 正确
run "plan_validation" {
command = plan
assert {
condition = length(plan_values.aws_subnet.private) == 2
error_message = "应该创建 2 个私有子网"
}
assert {
condition = plan_values.aws_vpc.main[0].cidr_block == "10.10.0.0/16"
error_message = "VPC CIDR 应为 10.10.0.0/16"
}
}
# 测试 2:验证 apply 成功
run "apply_test" {
command = apply
assert {
condition = output.vpc_id != ""
error_message = "VPC ID 不应为空"
}
assert {
condition = length(output.private_subnet_ids) == 2
error_message = "应该输出 2 个子网 ID"
}
}
# 测试 3:验证变量校验
run "invalid_cidr_rejected" {
command = plan
variables {
cidr = "invalid-cidr"
}
# 预期失败
expect_failures = [
var.cidr,
]
}
# 测试 4:使用不同的 provider 配置
run "multi_region" {
command = plan
variables {
region = "us-west-2"
}
provider "aws" {
region = "us-west-2"
}
assert {
condition = plan_values.aws_subnet.private[0].availability_zone == "us-west-2a"
error_message = "子网应该在 us-west-2 区域"
}
}
# 运行所有测试
terraform test
# 运行特定测试文件
terraform test -filter=tests/basic.tftest.hcl
# 详细输出
terraform test -verbose
# 在 CI 中使用(不需要真实云资源时用 mock)
terraform test -mock=tests/mock.tftest.hcl
10.3 Mock 测试(>= 1.7)
# tests/mock.tftest.hcl —— 不调用真实云 API 的测试
mock_provider "aws" {
mock_resource "aws_vpc" {
defaults = {
id = "vpc-mock12345"
cidr_block = var.cidr
instance_tenancy = "default"
}
}
mock_resource "aws_subnet" {
defaults = {
id = "subnet-mock-${count.index}"
vpc_id = "vpc-mock12345"
}
}
mock_data "aws_availability_zones" {
defaults = {
names = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
}
}
run "mock_test" {
command = plan
assert {
condition = length(plan_values.aws_subnet.private) == 2
error_message = "Mock 测试:应该创建 2 个子网"
}
}
十一、多环境管理策略对比
11.1 三种方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| terraform workspace | 内置、简单 | State 隔离不彻底、容易混淆环境 | 仅 dev/staging(非生产) |
| 目录分离 + tfvars | 彻底隔离、清晰 | 文件重复 | 中小团队 |
| Terragrunt | DRY、自动依赖 | 额外工具学习成本 | 大型多环境 |
11.2 Workspace 模式(不推荐用于生产)
# Workspace 使用同一套 .tf 代码,不同 State
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
terraform workspace select prod
terraform apply -var-file="prod.tfvars"
# 在代码中区分
# ⚠️ 这种 if/else 嵌套是反模式
locals {
env = terraform.workspace
instance_count = local.env == "prod" ? 5 : 1
}
11.3 目录分离模式(推荐)
infrastructure/
├── modules/ # 可复用模块
│ ├── vpc/
│ ├── eks/
│ └── rds/
├── environments/ # 环境配置
│ ├── dev/
│ │ ├── backend.tf
│ │ ├── main.tf # 引用 modules/
│ │ └── terraform.tfvars
│ ├── staging/
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ └── terraform.tfvars
│ └── prod/
│ ├── backend.tf
│ ├── main.tf
│ └── terraform.tfvars
# environments/prod/main.tf
module "vpc" {
source = "../../modules/vpc"
# ...
}
module "eks" {
source = "../../modules/eks"
# ...
}
# environments/prod/backend.tf
terraform {
backend "s3" {
bucket = "my-tfstate-prod"
key = "prod/terraform.tfstate"
# ...
}
}
11.4 Terragrunt 模式(大型环境推荐)
详见 Terraform 生产级实践 中的 Terragrunt 章节
infrastructure-live/
├── terragrunt.hcl # 全局配置(backend + provider)
├── prod/
│ ├── env.hcl # 环境变量
│ ├── vpc/
│ │ └── terragrunt.hcl # 仅 source + inputs(极简)
│ └── eks/
│ └── terragrunt.hcl
└── staging/
└── ...
十二、Terraform 与 K8s 协作边界
12.1 分界原则
┌─────────────────────────────────────────┐
│ Terraform 管理范围 │
│ VPC / 子网 / 安全组 / 路由表 │
│ K8s 集群本身(EKS/GKE/AKS) │
│ 节点池 / 负载均衡器 / IAM 角色 │
│ 数据库 / 缓存 / 消息队列(RDS/ElastiCache)│
│ DNS 记录 / CDN / WAF │
│ K8s 中的 "基础设施级" 资源(Namespace、 │
│ StorageClass、CRD 安装如 ArgoCD) │
├─────────────────────────────────────────┤
│ ArgoCD/Flux 管理范围 │
│ Deployment / Service / Ingress │
│ ConfigMap / Secret(业务配置) │
│ HPA / PDB / NetworkPolicy │
│ Helm Release / Kustomize │
│ 业务应用的一切 │
└─────────────────────────────────────────┘
12.2 冲突避免
# ❌ 错误:Terraform 和 ArgoCD 同时管理同一个 Deployment
# ArgoCD selfHeal 会把 Terraform 的变更覆盖回来
# ✅ 正确:Terraform 只管 "安装 ArgoCD",不管 ArgoCD 管理的应用
resource "helm_release" "argocd" {
name = "argocd"
repository = "https://argoproj.github.io/argo-helm"
chart = "argo-cd"
version = "7.3.0"
namespace = "argocd"
}
# ✅ Terraform 管理的 K8s 资源应该打标签
resource "kubernetes_namespace" "system" {
for_each = toset(["argocd", "monitoring", "ingress-nginx"])
metadata {
name = each.key
labels = {
"managed-by" = "terraform"
"app.kubernetes.io/managed-by" = "terraform"
}
}
}
# ✅ 使用 ArgoCD 的 IgnoreDifferences 避免与 Terraform 冲突
# argocd 应用配置中:
# ignoreDifferences:
# - group: ""
# kind: Namespace
# jsonPointers:
# - /metadata/labels/managed-by
十三、故障排查实战
13.1 资源 Drift 检测
# 手动检测 drift(实际状态 vs State 记录)
terraform plan -detailed-exitcode
# exit code 0: 无变更
# exit code 1: 错误
# exit code 2: 有变更(存在 drift)
# 自动化 drift 检测脚本
#!/bin/bash
set -e
WORKSPACE=$1
terraform workspace select $WORKSPACE
terraform plan -detailed-exitcode -out=/dev/null 2>&1 | tee plan-output.txt
EXIT_CODE=$?
if [ $EXIT_CODE -eq 2 ]; then
echo "⚠️ Drift detected in $WORKSPACE"
# 发送告警
curl -X POST "$ALERT_WEBHOOK" -d "{\"text\": \"Terraform drift in $WORKSPACE: $(cat plan-output.txt | head -50)\"}"
elif [ $EXIT_CODE -eq 0 ]; then
echo "✅ No drift in $WORKSPACE"
else
echo "❌ Plan failed in $WORKSPACE"
exit 1
fi
13.2 State 修复实战
# 场景:手动修改了安全组规则,导致 plan 显示要删除该规则
# 方案 1:接受手动修改,更新 State
terraform plan -refresh=false # 先看 State 中的记录
terraform apply -refresh # refresh 更新 State
# 方案 2:用代码覆盖手动修改
terraform apply # 按 HCL 配置重新应用
# 方案 3:忽略该属性的漂移
# 在 HCL 中添加 lifecycle { ignore_changes = [ingress] }
# 场景:State 中引用了已删除的资源
# 症状:plan 报错 "resource not found"
terraform state rm aws_instance.old # 从 State 中移除
# 然后添加新的 resource 块或 import
# 场景:资源地址错误(Module 重构后)
terraform state mv module.old_module.aws_instance.app module.new_module.aws_instance.app
# 移动 State 中的地址,不操作实际资源
13.3 Provider 版本升级
# 场景:aws provider 从 4.x 升级到 5.x(破坏性变更)
# 1. 查看 upgrade guide
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/version-5-upgrade
# 2. 更新版本约束
# version = "~> 5.0"
# 3. 升级 Provider 插件
terraform init -upgrade
# 4. 运行 plan 查看变更
terraform plan
# 5. 处理 deprecated 资源
# 某些资源可能被重命名或废弃
# 查看迁移指南逐个处理
# 6. 使用 moved blocks 处理重命名的资源
moved {
from = aws_alb.main
to = aws_lb.main # aws_alb → aws_lb
}
十四、常用命令速查
14.1 日常命令
# === 生命周期 ===
terraform init # 初始化(下载 Provider、初始化 backend)
terraform init -upgrade # 初始化并升级 Provider 版本
terraform init -reconfigure # 重新配置 backend(切换 backend 时用)
terraform plan # 预览变更
terraform plan -out=tfplan # 预览并保存计划
terraform apply # 应用变更(会提示确认)
terraform apply tfplan # 应用已保存的计划
terraform apply -auto-approve # 自动确认(CI/CD 中使用)
terraform destroy # 销毁所有资源
terraform destroy -target=... # 销毁指定资源
# === 格式化与校验 ===
terraform fmt # 格式化当前目录
terraform fmt -recursive # 递归格式化所有子目录
terraform fmt -check # 只检查不修改(CI 用)
terraform validate # 验证配置语法
terraform validate -json # JSON 格式输出
# === State 操作 ===
terraform state list # 列出所有资源
terraform state show <address> # 显示资源详情
terraform state mv <old> <new> # 移动资源地址
terraform state rm <address> # 从 State 移除(不删实际资源)
terraform state pull # 输出 State 到 stdout
terraform state push <file> # 从文件更新 State
terraform import <addr> <id> # 导入已有资源
terraform force-unlock <lock-id> # 强制解锁 State
# === 工作区 ===
terraform workspace list # 列出工作区
terraform workspace new <name> # 创建工作区
terraform workspace select <name> # 切换工作区
terraform workspace delete <name> # 删除工作区
terraform workspace show # 当前工作区名
# === 调试 ===
terraform console # 交互式表达式求值
terraform output # 列出所有输出
terraform output <name> # 查看特定输出
terraform output -json # JSON 格式输出
terraform graph # 生成依赖图(DOT 格式)
terraform show tfplan # 显示已保存的计划
terraform show -json tfplan # JSON 格式计划
# === 模块 ===
terraform get # 下载/更新模块
terraform get -update # 强制更新模块
# === 测试 ===
terraform test # 运行 .tftest.hcl 测试
terraform test -filter=<file> # 运行特定测试
terraform test -verbose # 详细输出
# === 其他 ===
terraform version # 版本信息
terraform providers # 列出使用的 Provider
terraform login # 登录 Terraform Cloud
terraform logout # 退出 Terraform Cloud
terraform metadata # 元数据信息
14.2 命令选项速查
# -var / -var-file —— 传入变量
terraform plan -var="region=us-west-2"
terraform plan -var-file="prod.tfvars"
# -target —— 只操作特定资源
terraform plan -target=module.vpc
# -refresh=false —— 跳过 refresh(快速 plan)
terraform plan -refresh=false
# -parallelism=N —— 并行度
terraform apply -parallelism=20
# -lock=false —— 不获取锁(危险!仅在调试时用)
terraform plan -lock=false
# -input=false —— 禁止交互式输入(CI/CD 中使用)
terraform apply -input=false -auto-approve
# -no-color —— 禁用彩色输出(日志文件用)
terraform plan -no-color
# -compact-warnings —— 紧凑警告
terraform plan -compact-warnings
# -json —— JSON 格式输出
terraform plan -json # 流式 JSON 输出(可被程序消费)
# -generate-config-out —— 生成导入的资源配置
terraform plan -generate-config-out=generated.tf
# -migrate-state —— 迁移 State(切换 backend 时)
terraform init -migrate-state
# -reconfigure —— 重新配置 backend(不迁移 State)
terraform init -reconfigure
关联知识
- Terraform 基础设施即代码 — 基础语法、State、Module、GKE 创建、Terraform+ArgoCD
- Terraform 生产级实践 — State 恢复、Atlantis、Terragrunt、Import SOP
- IaC 方法论与实践总览 — IaC 工具全景图、成熟度模型、Policy as Code、测试策略
- Ansible 配置管理实战 — Terraform 创建资源后用 Ansible 配置
- Packer 不可变镜像构建 — Packer 构建镜像,Terraform 引用 AMI ID
- ../k8s/特性详解/ArgoCD GitOps 实战 — Terraform 建集群,ArgoCD 管应用
- ../sre/变更管理全流程 — IaC 变更审批与渐进式发布
- ../sre/高可用架构设计总览 — Terraform 管理高可用基础设施
- ../gpu-cluster-ops/裸金属/GPU 裸金属服务器运维 — 裸金属服务器的 Terraform 管理
参考资源
- Terraform Language 文档:https://developer.hashicorp.com/terraform/language
- Terraform CLI 文档:https://developer.hashicorp.com/terraform/cli
- Terraform Functions:https://developer.hashicorp.com/terraform/language/functions
- Terraform Testing:https://developer.hashicorp.com/terraform/language/tests
- Terraform Module Registry:https://registry.terraform.io/
- Terraform Best Practices:https://www.terraform-best-practices.com/
- Awesome Terraform:https://github.com/shuaibiyy/awesome-terraform
学习时间
| 阶段 | 时间 | 备注 |
|---|---|---|
| HCL 高级语法 | 2026-08-03 | dynamic blocks, for/for_each, validation, moved, import blocks |
| 资源生命周期 | 2026-08-03 | lifecycle meta-args, timeouts, provisioners, null_resource |
| Provider 高级用法 | 2026-08-03 | alias 多区域/多账号, data sources, provider 函数 |
| 函数全集 | 2026-08-03 | 集合/字符串/网络/编码/时间/类型/加密 |
| 模块开发 | 2026-08-03 | 结构设计, 版本约束, 变量/输出原则, 模块测试 |
| 原生测试 | 2026-08-03 | tftest.hcl, mock provider, Terratest |
| 性能调优 | 2026-08-03 | plan 慢排查, parallelism, targeted plan, State 优化 |
| 调试技巧 | 2026-08-03 | TF_LOG, console, JSON plan 分析, crash log |
| 密钥管理 | 2026-08-03 | Secrets Manager, Vault, sensitive, State 加密 |
| 多环境管理 | 2026-08-03 | workspace vs 目录分离 vs Terragrunt |
状态: ✅ 完成 下次复习日期: 2026-08-10