远程后端配置

学习如何配置和使用 Terraform 远程后端,实现团队协作。

一、为什么需要远程后端

1.1 本地状态的问题

# 本地状态文件
./terraform.tfstate
./terraform.tfstate.backup

问题:

  • ❌ 无法团队协作
  • ❌ 没有版本控制
  • ❌ 容易丢失
  • ❌ 无法并发保护
  • ❌ 敏感信息明文存储

1.2 远程后端的优势

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

二、S3 后端(AWS)

2.1 基础配置

terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-west-2"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

2.2 创建 S3 桶和 DynamoDB 表

# backend-setup.tf
resource "aws_s3_bucket" "terraform_state" {
  bucket = "my-terraform-state"
  
  lifecycle {
    prevent_destroy = true
  }
  
  tags = {
    Name        = "Terraform State"
    Environment = "Infrastructure"
  }
}

# 启用版本控制
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 Locks"
    Environment = "Infrastructure"
  }
}

2.3 迁移到远程后端

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

# 2. 初始化(会提示迁移)
terraform init

# 输出示例:
# Do you want to copy existing state to the new backend?
#   Pre-existing state was found while migrating the previous "local" backend to the
#   newly configured "s3" backend. No existing state was found in the newly
#   configured "s3" backend. Do you want to copy this state to the new "s3"
#   backend? Enter "yes" to copy and "no" to start with an empty state.
#
#   Enter a value: yes

# 3. 验证迁移
terraform state list

2.4 多环境配置

# environments/dev/backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "dev/terraform.tfstate"
    region         = "us-west-2"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

# environments/prod/backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-west-2"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

三、Azure Storage 后端

3.1 基础配置

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

3.2 创建 Azure 存储

# 创建资源组
az group create --name terraform-state-rg --location westus2

# 创建存储账户
az storage account create \
  --name tfstatestorage \
  --resource-group terraform-state-rg \
  --location westus2 \
  --sku Standard_LRS \
  --encryption-services blob

# 创建容器
az storage container create \
  --name tfstate \
  --account-name tfstatestorage

3.3 使用服务主体认证

terraform {
  backend "azurerm" {
    resource_group_name  = "terraform-state-rg"
    storage_account_name = "tfstatestorage"
    container_name       = "tfstate"
    key                  = "prod.terraform.tfstate"
    
    # 使用服务主体
    client_id       = var.client_id
    client_secret   = var.client_secret
    tenant_id       = var.tenant_id
    subscription_id = var.subscription_id
  }
}

四、Google Cloud Storage 后端

4.1 基础配置

terraform {
  backend "gcs" {
    bucket  = "my-terraform-state"
    prefix  = "prod"
    
    # 可选:指定凭证
    credentials = file("account.json")
  }
}

4.2 创建 GCS 桶

# 创建桶
gsutil mb -p my-project -c STANDARD -l us-west1 gs://my-terraform-state

# 启用版本控制
gsutil versioning set on gs://my-terraform-state

# 设置生命周期(保留最近 10 个版本)
cat > lifecycle.json <<EOF
{
  "rule": [
    {
      "action": {"type": "Delete"},
      "condition": {
        "numNewerVersions": 10,
        "isLive": false
      }
    }
  ]
}
EOF

gsutil lifecycle set lifecycle.json gs://my-terraform-state

五、Terraform Cloud

5.1 基础配置

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

5.2 使用标签选择工作区

terraform {
  cloud {
    organization = "my-org"
    
    workspaces {
      tags = ["app:myapp", "env:prod"]
    }
  }
}

5.3 认证配置

# 登录 Terraform Cloud
terraform login

# 手动配置 token
cat > ~/.terraform.d/credentials.tfrc.json <<EOF
{
  "credentials": {
    "app.terraform.io": {
      "token": "YOUR_TOKEN_HERE"
    }
  }
}
EOF

5.4 Terraform Cloud 优势

  • ✅ UI 界面管理
  • ✅ 自动运行 plan/apply
  • ✅ 变量管理
  • ✅ 成本估算
  • ✅ 策略检查(Sentinel)
  • ✅ 私有模块注册表
  • ✅ 团队协作和权限管理

六、后端配置最佳实践

6.1 部分配置(Partial Configuration)

# backend.tf
terraform {
  backend "s3" {
    # 基础配置
    region  = "us-west-2"
    encrypt = true
  }
}
# 通过命令行或配置文件提供其余配置
terraform init \
  -backend-config="bucket=my-terraform-state" \
  -backend-config="key=prod/terraform.tfstate" \
  -backend-config="dynamodb_table=terraform-locks"

# 或使用配置文件
# backend-config.tfbackend
bucket         = "my-terraform-state"
key            = "prod/terraform.tfstate"
dynamodb_table = "terraform-locks"

terraform init -backend-config=backend-config.tfbackend

6.2 多环境管理

project/
├── backend.tf
├── environments/
│   ├── dev.tfbackend
│   ├── staging.tfbackend
│   └── prod.tfbackend
└── deploy.sh

dev.tfbackend:

bucket = "my-terraform-state"
key    = "dev/terraform.tfstate"

deploy.sh:

#!/bin/bash
ENV=$1

if [ -z "$ENV" ]; then
  echo "Usage: ./deploy.sh <env>"
  exit 1
fi

terraform init -backend-config="environments/${ENV}.tfbackend"
terraform plan -var-file="environments/${ENV}.tfvars"

6.3 状态锁定

# 自动获取锁
terraform apply

# 强制解锁(谨慎使用)
terraform force-unlock LOCK_ID

锁定信息:

{
  "ID": "abc-123-def",
  "Operation": "OperationTypeApply",
  "Info": "",
  "Who": "user@example.com",
  "Version": "1.6.0",
  "Created": "2024-01-10T12:00:00Z",
  "Path": "prod/terraform.tfstate"
}

七、后端迁移

7.1 从本地迁移到 S3

# 1. 配置新后端
cat >> main.tf <<EOF
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "terraform.tfstate"
    region         = "us-west-2"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}
EOF

# 2. 重新初始化
terraform init -migrate-state

# 3. 验证
terraform state list

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

7.2 更换后端

# 1. 修改 backend 配置

# 2. 重新初始化(会提示迁移)
terraform init -migrate-state -force-copy

# 3. 验证新后端
terraform state pull

7.3 从 S3 迁移回本地

# 1. 移除 backend 配置块
# terraform {
#   backend "s3" { ... }
# }

# 2. 初始化
terraform init -migrate-state

八、安全最佳实践

8.1 加密配置

# S3 后端加密
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "terraform.tfstate"
    region         = "us-west-2"
    encrypt        = true  # 启用加密
    kms_key_id     = "arn:aws:kms:us-west-2:ACCOUNT_ID:key/KEY_ID"  # 使用 KMS
    dynamodb_table = "terraform-locks"
  }
}

8.2 访问控制

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

8.3 审计和日志

# 启用 S3 访问日志
resource "aws_s3_bucket_logging" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  
  target_bucket = aws_s3_bucket.logs.id
  target_prefix = "terraform-state/"
}

# 启用 CloudTrail
resource "aws_cloudtrail" "terraform_audit" {
  name                          = "terraform-audit"
  s3_bucket_name                = aws_s3_bucket.audit_logs.id
  include_global_service_events = true
  is_multi_region_trail         = true
  
  event_selector {
    read_write_type           = "All"
    include_management_events = true
    
    data_resource {
      type   = "AWS::S3::Object"
      values = ["${aws_s3_bucket.terraform_state.arn}/*"]
    }
  }
}

九、故障排查

9.1 常见问题

状态锁定超时:

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

# 强制解锁
terraform force-unlock LOCK_ID

后端配置错误:

# 重新配置后端
terraform init -reconfigure

# 迁移状态
terraform init -migrate-state

权限问题:

# 验证 AWS 凭证
aws sts get-caller-identity

# 测试 S3 访问
aws s3 ls s3://my-terraform-state/

9.2 备份和恢复

# 手动备份状态
terraform state pull > backup-$(date +%Y%m%d-%H%M%S).tfstate

# 恢复状态
terraform state push backup.tfstate

# 从 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 \
  restored.tfstate

小结

远程后端配置要点:

  • 选择合适的后端:S3、Azure、GCS、Terraform Cloud
  • 启用加密:保护敏感数据
  • 使用状态锁定:防止并发冲突
  • 版本控制:保留历史记录
  • 访问控制:限制权限
  • 定期备份:防止数据丢失

配置好远程后端是团队协作的基础,下一章我们将学习模块化设计。