terraform —— 基础设施即代码
terraform —— 基础设施即代码
分类:07 自动化与版本 适用:Terraform 1.x(0.12 之前语法差异大) 一句话:用声明式配置描述云资源,
plan看差异、apply落地、state记现状。
1. 它解决什么问题
- 云上资源靠控制台点,谁改了什么没人知道。
- 同一套环境要在多个区域/账号复刻。
- 变更前想先知道"会改什么、会不会删库"。
- 资源要能版本化、评审、回滚。
2. 安装与初始化
# 官方仓库
apt install -y terraform
# 或下载二进制
curl -LO https://releases.hashicorp.com/terraform/1.9.5/terraform_1.9.5_linux_amd64.zip
unzip terraform_1.9.5_linux_amd64.zip -d /usr/local/bin/
terraform version
terraform init # 下载 provider、初始化 backend
terraform init -upgrade # 升级 provider
目录结构:
infra/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
├── terraform.tfvars
└── modules/
3. 核心工作流
terraform init # 初始化(首次/换目录/改 backend 后)
terraform fmt # 格式化
terraform validate # 语法校验
terraform plan # 看将要发生什么
terraform apply # 执行
terraform destroy # 销毁(⚠️)
标准动作永远是 plan → 人工确认 → apply,CI 里可以把 plan 结果作为评审附件。
terraform plan -out=tfplan
terraform apply tfplan # 执行上次的 plan,避免漂移
用 -out 能保证"评审的计划"和"执行的计划"一致,这是生产环境的规范做法。
4. 变量与输出
# variables.tf
variable "region" {
type = string
default = "cn-hangzhou"
description = "地域"
}
variable "instance_count" {
type = number
default = 2
}
variable "tags" {
type = map(string)
default = {}
}
传值优先级(低 → 高):
- 环境变量
TF_VAR_region terraform.tfvars*.auto.tfvars-var-file=prod.tfvars-var 'region=x'
terraform plan -var-file=prod.tfvars -var 'instance_count=5'
输出:
output "public_ips" {
value = module.ecs.public_ips
}
terraform output
terraform output -json public_ips
terraform output -raw public_ips
5. State(最重要)
State 记录"代码"与"真实资源"的映射,丢了就等于失去管理能力。
本地 vs 远程
本地 terraform.tfstate 只适合个人试验。团队必须用远程后端:
terraform {
backend "s3" {
bucket = "my-tfstate"
key = "prod/network/terraform.tfstate"
region = "cn-hangzhou"
encrypt = true
dynamodb_table = "terraform-locks" # 状态锁
}
}
阿里云 OSS:
terraform {
backend "oss" {
bucket = "my-tfstate"
prefix = "prod/network"
region = "cn-hangzhou"
}
}
State 含明文敏感信息(密码、密钥),bucket 必须私有 + 加密 + 开版本控制。
常用 state 命令
terraform state list # 所有资源
terraform state show aws_instance.web # 单资源详情
terraform state mv A B # 重命名/移动(重构时保命)
terraform state rm A # 从 state 移除(不删真实资源)
terraform state pull > backup.tfstate # 备份
terraform state push backup.tfstate # 恢复(谨慎)
state rm 是"放手不管",destroy 才是"真删"。
导入已有资源
terraform import aws_instance.web i-1234567890abcdef0
# 1.5+ 可用 import block
import {
to = aws_instance.web
id = "i-1234567890abcdef0"
}
导入后要 plan 对照,把代码改成与现状一致(否则下次 apply 会"修正"成代码描述的样子)。
6. 常用命令
terraform plan -target=module.network # 只针对某模块(临时排障用)
terraform apply -auto-approve # 跳过确认(仅 CI)
terraform refresh # 用真实资源刷新 state
terraform apply -replace=aws_instance.web # 强制重建某资源(替代已弃用的 taint)
terraform workspace list
terraform workspace new prod
terraform workspace select prod
terraform providers
terraform graph | dot -Tpng > graph.png # 依赖图
-target 会破坏依赖一致性,只用于救火,之后必须跑一次完整 plan 校正。
7. 代码要点
count 与 for_each
resource "aws_instance" "web" {
count = var.instance_count
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = { Name = "web-${count.index}" }
}
# 推荐 for_each(增删不牵连其他资源)
resource "aws_instance" "web" {
for_each = toset(["web01", "web02"])
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = { Name = each.key }
}
count 删除中间一个会导致后面全部重建;for_each 不会。生产优先 for_each。
依赖
resource "aws_eip" "web" {
instance = aws_instance.web.id # 隐式依赖
}
resource "aws_instance" "web" {
depends_on = [aws_security_group.web] # 显式依赖
}
生命周期
lifecycle {
create_before_destroy = true
prevent_destroy = true # 防误删(数据库必备)
ignore_changes = [tags["LastModified"]]
}
prevent_destroy 是数据库、对象存储桶的保命符。
模块
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.8.1"
name = "prod-vpc"
cidr = "10.0.0.0/16"
}
模块必须锁版本,否则某天上游更新导致 plan 全变。
8. 实战场景
8.1 安全的日常变更
git pull
terraform init -upgrade
terraform fmt -check
terraform validate
terraform plan -out=tfplan
# 人工评审 tfplan(重点看 destroy / replace)
terraform apply tfplan
plan 输出里出现 -/+ 表示"销毁重建",看到 - 要格外警惕。
8.2 资源被手工改过(漂移)
terraform plan
# 出现意外 diff → 判断:
# a) 接受手工改动:把代码改成一致
# b) 恢复代码描述:直接 apply
# c) 只是想同步 state:terraform refresh
8.3 重构目录结构不重建资源
# 把资源从 root 移到 module
terraform state mv aws_instance.web module.app.aws_instance.web
terraform plan # 应该显示 No changes
8.4 多环境隔离
terraform workspace new prod
terraform workspace select prod
terraform apply -var-file=env/prod.tfvars
或按目录拆分(更清晰):envs/prod/、envs/dev/ 各自独立 state。
8.5 紧急阻止删除数据库
resource "aws_db_instance" "main" {
lifecycle {
prevent_destroy = true
}
}
此时 destroy 会报错中止,必须先从代码移除该块。
9. 常见坑
- State 冲突:两人同时 apply,没有锁会互相覆盖。远程后端必须配锁(S3 + DynamoDB,OSS 自带)。
- State 进 Git:含明文密码,绝对不能提交。加
.gitignore。 terraform destroy无差别删除:先plan -destroy看清楚,重要资源加prevent_destroy。count下标陷阱:删中间元素导致后续重建,用for_each。-target遗留问题:只 apply 了部分资源,state 与代码不一致,之后要完整跑一次。- Provider 版本漂移:不锁版本,
init -upgrade后 plan 大变。用required_providers锁住。 ignore_changes掩盖问题:过度使用会让真实漂移无法发现。- 模块未锁版本:
version必须写,最好用精确版本。 - 敏感变量出现在 plan 输出:用
sensitive = true,但仍会存进 state。 terraform.tfvars提交泄露密钥:用环境变量或 secrets manager,.tfvars加 gitignore。local-exec不可控:破坏幂等,尽量用 provider 原生资源。- 中国区 provider 差异:阿里云/腾讯云用各自 provider,AWS 中国区需单独 region 与 endpoint。
apply中途失败留下半成品:state 会记录已创建部分,修代码后重新plan/apply,不要手工删资源后不管 state。
10. 速查表
terraform init [-upgrade]
terraform fmt -recursive
terraform validate
terraform plan -out=tfplan
terraform apply tfplan
terraform plan -destroy
terraform destroy
terraform output [-json|-raw NAME]
terraform state list
terraform state show <addr>
terraform state mv <src> <dst>
terraform state rm <addr>
terraform state pull > backup.tfstate
terraform import <addr> <id>
terraform apply -replace=<addr>
terraform workspace list/new/select
terraform providers
三条铁律:state 远程+加锁;plan 必评审;关键资源 prevent_destroy。