Terraform 遠端狀態鎖定與協作

Terraform Remote State Locking and Collaboration

在多人協作的 Terraform 專案中,狀態檔的管理是確保基礎設施一致性的關鍵。本文將深入探討如何透過遠端狀態管理、狀態鎖定機制和最佳實踐,實現安全高效的團隊協作。

狀態檔概述(State File Overview)

Terraform State 的核心角色

Terraform 狀態檔(terraform.tfstate)是一個 JSON 格式的檔案,記錄了 Terraform 管理的所有基礎設施資源的當前狀態。它扮演著以下關鍵角色:

功能說明
資源映射將設定檔中的資源對應到雲端實際資源
元資料儲存記錄資源相依性和屬性
效能優化快取資源資訊,減少 API 呼叫
變更追蹤比對期望狀態與實際狀態的差異

State 檔結構示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "version": 4,
  "terraform_version": "1.6.0",
  "serial": 15,
  "lineage": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "outputs": {
    "vpc_id": {
      "value": "vpc-0abc123def456789",
      "type": "string"
    }
  },
  "resources": [
    {
      "mode": "managed",
      "type": "aws_vpc",
      "name": "main",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [...]
    }
  ]
}

本地狀態問題(Local State Issues)

本地狀態的風險

當使用預設的本地狀態時,團隊協作會面臨多種挑戰:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
團隊協作時的本地狀態問題:

開發者 A                     開發者 B
    │                           │
    ▼                           ▼
┌─────────────┐           ┌─────────────┐
│ terraform   │           │ terraform   │
│   apply     │           │   apply     │
└──────┬──────┘           └──────┬──────┘
       │                         │
       ▼                         ▼
┌─────────────┐           ┌─────────────┐
│ 本地 state  │           │ 本地 state  │
│   (過時)    │           │   (過時)    │
└─────────────┘           └─────────────┘
       │                         │
       └────────┬────────────────┘
        ┌─────────────┐
        │  狀態衝突!  │
        │  資源覆蓋!  │
        └─────────────┘

常見問題

  1. 狀態檔不同步:團隊成員擁有不同版本的狀態檔
  2. 並行操作衝突:多人同時執行 apply 導致資源覆蓋
  3. 敏感資料暴露:狀態檔可能包含密碼、金鑰等敏感資訊
  4. 檔案遺失風險:本地檔案可能被意外刪除或損壞

S3 後端設定(S3 Backend Configuration)

建立 S3 儲存桶

首先,建立用於存放狀態檔的 S3 儲存桶:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# s3-backend.tf

# 建立 S3 儲存桶
resource "aws_s3_bucket" "terraform_state" {
  bucket = "my-company-terraform-state"

  lifecycle {
    prevent_destroy = true
  }

  tags = {
    Name        = "Terraform State Storage"
    Environment = "management"
    ManagedBy   = "terraform"
  }
}

# 啟用版本控制
resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id

  versioning_configuration {
    status = "Enabled"
  }
}

# 封鎖公開存取
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
}

配置 Backend

在專案中配置 S3 後端:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# backend.tf

terraform {
  backend "s3" {
    bucket         = "my-company-terraform-state"
    key            = "projects/web-app/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-state-locks"
  }
}

後端參數說明

參數說明必填
bucketS3 儲存桶名稱
key狀態檔在儲存桶中的路徑
regionS3 儲存桶所在區域
encrypt啟用伺服器端加密
dynamodb_table狀態鎖定用的 DynamoDB 表
profileAWS CLI profile 名稱
role_arn跨帳號存取的 IAM 角色

DynamoDB 狀態鎖定(DynamoDB State Locking)

為什麼需要狀態鎖定?

狀態鎖定可以防止多個使用者同時修改狀態檔,避免衝突和資料損壞。

建立 DynamoDB 表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# dynamodb-lock.tf

resource "aws_dynamodb_table" "terraform_locks" {
  name         = "terraform-state-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }

  point_in_time_recovery {
    enabled = true
  }

  tags = {
    Name        = "Terraform State Locks"
    Environment = "management"
    ManagedBy   = "terraform"
  }
}

鎖定機制運作流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
狀態鎖定流程:

1. 使用者執行 terraform apply
2. Terraform 嘗試在 DynamoDB 建立鎖定記錄
        ┌─────┴─────┐
        ▼           ▼
    成功取得      鎖定已存在
      鎖定         (等待)
        │           │
        ▼           │
3. 執行 apply     重試或
     操作         放棄
4. 操作完成,釋放鎖定
5. 刪除 DynamoDB 鎖定記錄

處理鎖定問題

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 查看鎖定錯誤訊息
terraform apply
# Error: Error acquiring the state lock
# Lock Info:
#   ID:        a1b2c3d4-e5f6-7890
#   Path:      my-company-terraform-state/projects/web-app/terraform.tfstate
#   Operation: OperationTypeApply
#   Who:       developer@workstation
#   Created:   2024-09-09T10:30:00Z

# 強制解除鎖定(確認安全後使用)
terraform force-unlock a1b2c3d4-e5f6-7890

# 設定鎖定等待時間
terraform apply -lock-timeout=10m

狀態檔加密(State File Encryption)

S3 伺服器端加密

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 使用 AWS 管理的金鑰 (SSE-S3)
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"
    }
  }
}

使用 KMS 加密

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 建立 KMS 金鑰
resource "aws_kms_key" "terraform_state" {
  description             = "KMS key for Terraform state encryption"
  deletion_window_in_days = 30
  enable_key_rotation     = true

  tags = {
    Name = "terraform-state-key"
  }
}

resource "aws_kms_alias" "terraform_state" {
  name          = "alias/terraform-state"
  target_key_id = aws_kms_key.terraform_state.key_id
}

# 使用 KMS 加密的後端配置
terraform {
  backend "s3" {
    bucket         = "my-company-terraform-state"
    key            = "projects/web-app/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    kms_key_id     = "alias/terraform-state"
    dynamodb_table = "terraform-state-locks"
  }
}

跨團隊協作(Cross-Team Collaboration)

使用 Remote State Data Source

允許不同專案之間共享狀態資料:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 讀取網路團隊的狀態
data "terraform_remote_state" "network" {
  backend = "s3"

  config = {
    bucket = "my-company-terraform-state"
    key    = "teams/network/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

# 讀取安全團隊的狀態
data "terraform_remote_state" "security" {
  backend = "s3"

  config = {
    bucket = "my-company-terraform-state"
    key    = "teams/security/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

# 使用其他團隊的輸出值
resource "aws_instance" "web" {
  ami           = "ami-0123456789abcdef0"
  instance_type = "t3.micro"

  subnet_id              = data.terraform_remote_state.network.outputs.private_subnet_ids[0]
  vpc_security_group_ids = [data.terraform_remote_state.security.outputs.web_sg_id]
}

定義 Outputs 供其他團隊使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# outputs.tf

output "vpc_id" {
  description = "VPC ID for other teams to reference"
  value       = aws_vpc.main.id
}

output "private_subnet_ids" {
  description = "Private subnet IDs"
  value       = aws_subnet.private[*].id
}

output "public_subnet_ids" {
  description = "Public subnet IDs"
  value       = aws_subnet.public[*].id
}

狀態檔遷移(State File Migration)

從本地遷移至 S3

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. 備份現有狀態
cp terraform.tfstate terraform.tfstate.backup

# 2. 新增 backend 配置
# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-company-terraform-state"
    key            = "projects/web-app/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-state-locks"
  }
}

# 3. 初始化並遷移
terraform init -migrate-state

# 4. 驗證遷移結果
terraform state list
terraform plan

在不同後端之間遷移

1
2
3
4
5
6
7
8
# 從舊後端拉取狀態
terraform state pull > terraform.tfstate.backup

# 修改 backend 配置後重新初始化
terraform init -reconfigure

# 推送狀態到新後端
terraform state push terraform.tfstate.backup

Terraform Cloud(Terraform Cloud)

Terraform Cloud 優勢

Terraform Cloud 提供了完整的狀態管理解決方案:

功能說明
自動狀態管理無需設定 S3/DynamoDB
內建狀態鎖定自動處理並行存取
狀態版本歷史完整的狀態變更記錄
存取控制細粒度的權限管理
遠端執行在雲端環境執行 plan/apply

配置 Terraform Cloud

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# backend.tf

terraform {
  cloud {
    organization = "my-company"

    workspaces {
      name = "web-app-production"
    }
  }
}

使用 Terraform Cloud 的狀態

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 登入 Terraform Cloud
terraform login

# 初始化專案
terraform init

# 執行計畫(在雲端執行)
terraform plan

# 套用變更
terraform apply

最佳實踐(Best Practices)

狀態管理最佳實踐

  1. 永遠使用遠端狀態:生產環境必須使用遠端狀態
  2. 啟用狀態鎖定:使用 DynamoDB 或 Terraform Cloud
  3. 加密狀態檔:使用 KMS 或內建加密
  4. 啟用版本控制:S3 bucket 啟用 versioning
  5. 限制存取權限:使用 IAM 政策控制存取

目錄結構建議

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
terraform-states/
├── production/
│   ├── network/
│   │   └── terraform.tfstate
│   ├── compute/
│   │   └── terraform.tfstate
│   └── database/
│       └── terraform.tfstate
├── staging/
│   └── ...
└── development/
    └── ...

IAM 政策範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 狀態檔存取政策
data "aws_iam_policy_document" "terraform_state" {
  statement {
    effect = "Allow"
    actions = [
      "s3:GetObject",
      "s3:PutObject",
      "s3:DeleteObject"
    ]
    resources = [
      "${aws_s3_bucket.terraform_state.arn}/*"
    ]
  }

  statement {
    effect = "Allow"
    actions = [
      "s3:ListBucket"
    ]
    resources = [
      aws_s3_bucket.terraform_state.arn
    ]
  }

  statement {
    effect = "Allow"
    actions = [
      "dynamodb:GetItem",
      "dynamodb:PutItem",
      "dynamodb:DeleteItem"
    ]
    resources = [
      aws_dynamodb_table.terraform_locks.arn
    ]
  }
}

參考資料

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy