状态管理详解

什么是 Terraform 状态

Terraform 状态(State)是 Terraform 跟踪管理资源的数据文件,记录了配置文件与真实基础设施的映射关系。

状态文件的作用

┌──────────────────┐
│  配置文件 (.tf)   │
│                  │
│  resource "..." {│
│    ...          │
│  }              │
└────────┬─────────┘
         │
         │ terraform apply
         ▼
┌──────────────────┐       ┌──────────────────┐
│   状态文件        │<─────>│   真实基础设施    │
│ terraform.tfstate│       │                  │
│                  │       │  云平台资源       │
│ {               │       │  (AWS/Azure/...)  │
│   "resources": [│       │                  │
│     {          │       └──────────────────┘
│       "id": "..." │
│     }          │
│   ]           │
│ }             │
└────────────────┘

核心功能

  1. 资源映射:配置文件中的资源对应到真实 ID
  2. 元数据存储:依赖关系、资源属性
  3. 性能优化:避免每次都查询云 API
  4. 协作基础:团队共享状态,协同工作

本地状态文件

默认行为

# 执行 terraform apply 后
ls -la
# .
# ├── main.tf
# ├── terraform.tfstate           # 当前状态
# └── terraform.tfstate.backup    # 上一次状态

terraform.tfstate 结构

{
  "version": 4,
  "terraform_version": "1.6.6",
  "serial": 3,
  "lineage": "abc123-def456-...",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_instance",
      "name": "web",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "id": "i-1234567890abcdef0",
            "ami": "ami-0c55b159cbfafe1f0",
            "instance_type": "t2.micro",
            "public_ip": "54.123.45.67"
          }
        }
      ]
    }
  ]
}

状态文件命令

# 查看状态
terraform show

# 列出资源
terraform state list

# 查看特定资源
terraform state show aws_instance.web

# 刷新状态(同步真实状态)
terraform refresh

远程状态

为什么使用远程状态

本地状态的问题

  • ❌ 单人工作,无法协作
  • ❌ 状态文件容易丢失
  • ❌ 并发修改会冲突
  • ❌ 敏感信息存储不安全

远程状态的优势

  • ✅ 团队协作
  • ✅ 状态锁定,防止并发冲突
  • ✅ 自动备份
  • ✅ 加密存储
  • ✅ 版本历史

S3 后端(推荐)

配置 S3 后端

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"  # 状态锁定
  }
}

创建 S3 Bucket 和 DynamoDB 表

# 一次性设置(在其他目录运行)
resource "aws_s3_bucket" "terraform_state" {
  bucket = "my-terraform-state"
  
  lifecycle {
    prevent_destroy = true
  }
  
  tags = {
    Name = "Terraform State"
  }
}

resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

resource "aws_s3_bucket_public_access_block" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# DynamoDB 表用于状态锁定
resource "aws_dynamodb_table" "terraform_locks" {
  name         = "terraform-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"
  
  attribute {
    name = "LockID"
    type = "S"
  }
  
  tags = {
    Name = "Terraform State Lock"
  }
}

迁移到远程状态

# 1. 添加 backend 配置到 main.tf 或 backend.tf

# 2. 重新初始化
terraform init

# Terraform will ask:
# Do you want to copy existing state to the new backend?
# Enter "yes"

# 3. 验证
terraform state list

# 4. 删除本地状态文件(可选)
rm terraform.tfstate
rm terraform.tfstate.backup

其他后端选项

Azure Storage

terraform {
  backend "azurerm" {
    resource_group_name  = "terraform-state-rg"
    storage_account_name = "tfstate"
    container_name       = "tfstate"
    key                  = "prod.terraform.tfstate"
  }
}

Google Cloud Storage

terraform {
  backend "gcs" {
    bucket = "my-terraform-state"
    prefix = "prod"
  }
}

Terraform Cloud

terraform {
  cloud {
    organization = "my-org"
    
    workspaces {
      name = "my-workspace"
    }
  }
}

Consul

terraform {
  backend "consul" {
    address = "consul.example.com:8500"
    path    = "terraform/state"
  }
}

状态锁定

为什么需要锁定

防止多人同时修改基础设施:

User A: terraform apply
   │
   ├── 获取状态锁 ✅
   ├── 读取状态
   ├── 计划变更
   │
User B: terraform apply
   │
   └── 尝试获取锁 ❌ (被 User A 持有)
       等待...
   
User A:
   ├── 应用变更
   ├── 更新状态
   └── 释放锁 ✅
   
User B:
   └── 获取锁 ✅
       继续执行...

S3 + DynamoDB 锁定

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"  # 锁定表
    encrypt        = true
  }
}

锁定流程

  1. Terraform 尝试在 DynamoDB 表中创建锁记录
  2. 如果锁已存在,等待或失败
  3. 操作完成后,删除锁记录

强制解锁

# 如果锁异常(如进程崩溃)
terraform force-unlock <LOCK_ID>

# 获取 LOCK_ID
# Error: Error acquiring the state lock
# Lock Info:
#   ID:        abc123-def456-...
#   Path:      ...
#   Operation: OperationTypeApply
#   Who:       user@host
#   Version:   1.6.6
#   Created:   2024-01-09 10:00:00

⚠️ 谨慎使用:确保没有其他 Terraform 进程在运行。

状态操作

查看状态

# 完整状态
terraform show

# 列出所有资源
terraform state list

# 查看特定资源
terraform state show aws_instance.web

# 输出 JSON 格式
terraform show -json > state.json

移动资源

# 重命名资源
terraform state mv aws_instance.example aws_instance.web

# 移动到模块
terraform state mv aws_instance.web module.web.aws_instance.server

# 移动模块
terraform state mv module.old_module module.new_module

删除资源

# 从状态中删除(不删除真实资源)
terraform state rm aws_instance.old

# 删除整个模块
terraform state rm module.old_module

导入资源

# 导入现有资源
terraform import aws_instance.web i-1234567890abcdef0

# 导入到模块
terraform import module.web.aws_instance.server i-1234567890abcdef0

替换资源

# 强制重建资源
terraform apply -replace=aws_instance.web

# 标记污染(下次 apply 时重建)
terraform taint aws_instance.web
terraform apply

# 取消污染
terraform untaint aws_instance.web

工作空间(Workspace)

工作空间允许在同一配置下管理多个状态文件。

基本操作

# 列出工作空间
terraform workspace list

# 创建新工作空间
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod

# 切换工作空间
terraform workspace select dev

# 查看当前工作空间
terraform workspace show

# 删除工作空间
terraform workspace delete dev

使用场景

# 根据工作空间设置不同配置
locals {
  env = terraform.workspace
  
  instance_type = {
    default = "t2.micro"
    dev     = "t2.micro"
    staging = "t2.small"
    prod    = "t2.large"
  }
}

resource "aws_instance" "web" {
  ami           = "ami-xxx"
  instance_type = local.instance_type[local.env]
  
  tags = {
    Name        = "web-${local.env}"
    Environment = local.env
  }
}

状态文件位置

# 本地状态
terraform.tfstate.d/
├── dev/
│   └── terraform.tfstate
├── staging/
│   └── terraform.tfstate
└── prod/
    └── terraform.tfstate

# S3 后端
# key = "env:/${terraform.workspace}/terraform.tfstate"

工作流程

# 开发环境
terraform workspace select dev
terraform apply

# 预发布环境
terraform workspace select staging
terraform apply

# 生产环境
terraform workspace select prod
terraform apply

状态安全

1. 加密存储

S3 加密

terraform {
  backend "s3" {
    bucket  = "my-terraform-state"
    key     = "terraform.tfstate"
    region  = "us-east-1"
    encrypt = true  # 启用加密
    kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/xxx"
  }
}

2. 访问控制

S3 Bucket 策略

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/TerraformRole"
      },
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::my-terraform-state/*"
    }
  ]
}

3. 版本控制

启用 S3 版本控制:

resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  
  versioning_configuration {
    status = "Enabled"
  }
}

恢复旧版本:

# 列出版本
aws s3api list-object-versions \
  --bucket my-terraform-state \
  --prefix terraform.tfstate

# 恢复版本
aws s3api copy-object \
  --bucket my-terraform-state \
  --copy-source my-terraform-state/terraform.tfstate?versionId=VERSION_ID \
  --key terraform.tfstate

4. 敏感数据处理

# ❌ 避免在状态中存储敏感数据
resource "aws_db_instance" "database" {
  password = "hardcoded-password"  # 会存储在状态文件
}

# ✅ 使用 Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
  secret_id = "db-password"
}

resource "aws_db_instance" "database" {
  password = data.aws_secretsmanager_secret_version.db_password.secret_string
}

# 标记为敏感
output "db_password" {
  value     = aws_db_instance.database.password
  sensitive = true  # 不会在输出中显示
}

状态迁移

本地到远程

# 1. 配置远程后端
cat >> backend.tf <<EOF
terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "prod/terraform.tfstate"
    region = "us-east-1"
  }
}
EOF

# 2. 初始化并迁移
terraform init -migrate-state

# 3. 验证
terraform state list

更换后端

# 1. 更新后端配置
# 2. 重新初始化
terraform init -reconfigure

# 或强制重新配置
terraform init -migrate-state -force-copy

最佳实践

1. 始终使用远程状态

terraform {
  backend "s3" {
    bucket         = "company-terraform-state"
    key            = "${var.project}/${var.environment}/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

2. 启用状态锁定

# S3 后端自动锁定(需要 DynamoDB)
dynamodb_table = "terraform-locks"

3. 版本控制和备份

  • 启用 S3 版本控制
  • 定期备份状态文件
  • 使用 lifecycle 策略管理旧版本

4. 分离环境状态

# 选项 1:使用不同的 key
backend "s3" {
  key = "env/${var.environment}/terraform.tfstate"
}

# 选项 2:使用 Workspace
terraform workspace new dev
terraform workspace new prod

# 选项 3:完全分离的后端
# dev/backend.tf
backend "s3" { bucket = "dev-terraform-state" }

# prod/backend.tf
backend "s3" { bucket = "prod-terraform-state" }

5. 不要手动编辑状态文件

# ✅ 使用 Terraform 命令
terraform state mv
terraform state rm
terraform import

# ❌ 不要直接编辑
vi terraform.tfstate  # 危险!

6. 定期刷新状态

# 同步真实状态
terraform refresh

# 或在 plan/apply 时自动刷新(默认行为)
terraform apply

7. 状态文件不要提交到 Git

# .gitignore
.terraform/
*.tfstate
*.tfstate.backup

故障排查

状态不一致

# 问题:状态与真实资源不一致

# 解决方案 1:刷新状态
terraform refresh

# 解决方案 2:重新导入
terraform import aws_instance.web i-xxx

# 解决方案 3:手动修复
terraform state rm aws_instance.broken
# 重新创建配置并 apply

状态锁定失败

# 检查锁
aws dynamodb get-item \
  --table-name terraform-locks \
  --key '{"LockID":{"S":"my-terraform-state/prod/terraform.tfstate"}}'

# 强制解锁
terraform force-unlock <LOCK_ID>

状态文件损坏

# 从 S3 版本恢复
aws s3api list-object-versions \
  --bucket my-terraform-state \
  --prefix terraform.tfstate

# 下载旧版本
aws s3api get-object \
  --bucket my-terraform-state \
  --key terraform.tfstate \
  --version-id VERSION_ID \
  terraform.tfstate.restored

# 替换当前状态
cp terraform.tfstate.restored terraform.tfstate
terraform state list  # 验证

总结

Terraform 状态管理要点:

  • 状态文件:记录资源映射关系
  • 远程状态:团队协作必备
  • 状态锁定:防止并发冲突
  • 工作空间:管理多环境
  • 安全:加密、访问控制、版本控制
  • 最佳实践:远程后端、锁定、备份、分离环境

正确管理状态是 Terraform 稳定运行的关键!


下一章模块化设计