在多人協作的 Terraform 專案中,狀態檔的管理是確保基礎設施一致性的關鍵。本文將深入探討如何透過遠端狀態管理、狀態鎖定機制和最佳實踐,實現安全高效的團隊協作。
狀態檔概述(State File Overview)
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 │
│ (過時) │ │ (過時) │
└─────────────┘ └─────────────┘
│ │
└────────┬────────────────┘
▼
┌─────────────┐
│ 狀態衝突! │
│ 資源覆蓋! │
└─────────────┘
|
常見問題
- 狀態檔不同步:團隊成員擁有不同版本的狀態檔
- 並行操作衝突:多人同時執行 apply 導致資源覆蓋
- 敏感資料暴露:狀態檔可能包含密碼、金鑰等敏感資訊
- 檔案遺失風險:本地檔案可能被意外刪除或損壞
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"
}
}
|
後端參數說明
| 參數 | 說明 | 必填 |
|---|
bucket | S3 儲存桶名稱 | 是 |
key | 狀態檔在儲存桶中的路徑 | 是 |
region | S3 儲存桶所在區域 | 是 |
encrypt | 啟用伺服器端加密 | 否 |
dynamodb_table | 狀態鎖定用的 DynamoDB 表 | 否 |
profile | AWS 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 提供了完整的狀態管理解決方案:
| 功能 | 說明 |
|---|
| 自動狀態管理 | 無需設定 S3/DynamoDB |
| 內建狀態鎖定 | 自動處理並行存取 |
| 狀態版本歷史 | 完整的狀態變更記錄 |
| 存取控制 | 細粒度的權限管理 |
| 遠端執行 | 在雲端環境執行 plan/apply |
1
2
3
4
5
6
7
8
9
10
11
| # backend.tf
terraform {
cloud {
organization = "my-company"
workspaces {
name = "web-app-production"
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
| # 登入 Terraform Cloud
terraform login
# 初始化專案
terraform init
# 執行計畫(在雲端執行)
terraform plan
# 套用變更
terraform apply
|
最佳實踐(Best Practices)
狀態管理最佳實踐
- 永遠使用遠端狀態:生產環境必須使用遠端狀態
- 啟用狀態鎖定:使用 DynamoDB 或 Terraform Cloud
- 加密狀態檔:使用 KMS 或內建加密
- 啟用版本控制:S3 bucket 啟用 versioning
- 限制存取權限:使用 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
]
}
}
|
參考資料