Terraform 云部署:远程状态与模块化
bash
aws s3api create-bucket
--bucket terraform-remote-state-2024
--region us-east-1
--create-bucket-configuration LocationConstraint=us-east-1
**启用版本控制**(用于状态回滚):
```bash
aws s3api put-bucket-versioning \
--bucket terraform-remote-state-2024 \
--versioning-configuration Status=Enabled
启用服务端加密(保护静态数据):
aws s3api put-bucket-encryption \
--bucket terraform-remote-state-2024 \
--server-side-encryption-configuration '{
"Rules": [
{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
}'
创建 DynamoDB 表(状态锁表):
aws dynamodb create-table \
--table-name terraform-state-locks \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--region us-east-1
表结构说明:
LockID为分区键,Terraform 会在执行apply前尝试向该表写入一条记录,通过条件写入检查实现分布式锁。
配置后端
在 Terraform 配置文件的顶层(或专门的 backend.tf 文件)中定义远程后端。
terraform {
backend "s3" {
bucket = "terraform-remote-state-2024"
key = "production/network/terraform.tfstate" # 路径分隔,支持多项目
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-locks"
}
}
- key:状态文件在桶中的路径,通常按
环境/项目/terraform.tfstate组织,实现多环境隔离。 - encrypt:开启服务端加密。
- dynamodb_table:指定用于状态锁的 DynamoDB 表名。
首次配置后端时,运行 terraform init 会提示迁移现有本地状态到远程,按提示操作即可。
最佳实践:将后端配置中的 bucket、region 等参数用变量或者单独的后端配置文件管理(部分参数不支持变量),避免硬编码。
第二步:使用远程状态进行协作
状态锁定演示
当开发者 A 正在执行 terraform apply 时,开发者 B 尝试同样操作会立即收到锁定错误:
Error acquiring the state lock: ConditionalCheckFailedException
这有效防止了并行修改。操作完成后,Terraform 会自动释放锁(正常或异常退出时强制清理)。
共享状态引用:terraform_remote_state
远程状态不仅用于主工作目录,还能被其他 Terraform 项目读取,实现跨项目数据共享。例如,网络团队输出的 VPC ID 可以被应用团队直接引用。
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "terraform-remote-state-2024"
key = "production/network/terraform.tfstate"
region = "us-east-1"
}
}
# 在其他资源中使用
resource "aws_instance" "web" {
subnet_id = data.terraform_remote_state.network.outputs.public_subnet_id
#...
}
注意:此方法需要上游项目通过
output明确暴露所需值。另一种更解耦的方式是使用数据源直接查询(如aws_vpcdata source),但远程状态在跨团队交付时更直接。
第三步:构建可复用的模块
模块是 Terraform 代码组织和复用的基本单元,可以是本地目录、Git 仓库或 Terraform Registry。
模块基本结构
一个典型的 VPC 模块目录:
modules/vpc/
├── main.tf
├── variables.tf
├── outputs.tf
└── README.md
variables.tf —— 暴露输入参数
variable "name" {
description = "VPC name tag"
type = string
}
variable "cidr" {
description = "VPC CIDR block"
type = string
}
variable "azs" {
description = "Availability zones"
type = list(string)
}
main.tf —— 定义资源
resource "aws_vpc" "main" {
cidr_block = var.cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = var.name
}
}
resource "aws_subnet" "public" {
count = length(var.azs)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.cidr, 8, count.index)
availability_zone = var.azs[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.name}-public-${count.index}"
}
}
outputs.tf —— 输出关键属性
output "vpc_id" {
value = aws_vpc.main.id
}
output "public_subnet_ids" {
value = aws_subnet.public[*].id
}
调用模块
在根配置中引用模块,通过传递不同变量值创建多个环境或项目。
module "prod_vpc" {
source = "./modules/vpc"
name = "prod-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
}
module "staging_vpc" {
source = "./modules/vpc"
name = "stg-vpc"
cidr = "10.1.0.0/16"
azs = ["us-east-1a"]
}
模块来源管理
- 本地:
source = "./modules/vpc" - Git 仓库:
source = "git::https://github.com/your-org/terraform-modules.git//vpc?ref=v1.2.0"(标签固定版本) - Terraform Registry:
source = "terraform-aws-modules/vpc/aws"(官方社区模块)
版本固定非常重要,避免上游变更破坏你的基础设施。
第四步:将远程状态与模块结合用于云部署
目录结构最佳实践
一个多环境项目的推荐布局:
.
├── backend.tf # 后端配置(所有环境可能指向同一桶,不同 key)
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ ├── stg/
│ └── prod/
├── modules/
│ ├── vpc/
│ ├── ecs-service/
│ └── rds/
└── global/ # 全局资源(如 IAM、Route53)
每个环境都有自己的状态文件 key,例如:
dev/network/terraform.tfstateprod/network/terraform.tfstate
创建环境入口(environments/prod/main.tf)
provider "aws" {
region = "us-east-1"
assume_role {
role_arn = "arn:aws:iam::123456789012:role/terraform-deploy-role"
}
}
module "vpc" {
source = "../../modules/vpc"
name = "prod-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
}
module "service" {
source = "../../modules/ecs-service"
vpc_id = module.vpc.vpc_id
subnets = module.vpc.public_subnet_ids
# ...其他参数
}