在使用 Terraform 管理基礎設施時,了解資源之間的依賴關係以及如何控制資源的生命週期是非常重要的。本文將深入探討 Terraform 的資源依賴機制和 lifecycle 區塊的各種配置選項。
資源依賴概述
Terraform 會自動分析資源之間的依賴關係,並根據這些依賴關係決定資源的建立、更新和刪除順序。依賴關係分為兩種類型:隱式依賴和顯式依賴。
依賴關係的重要性
| 類型 | 說明 | 使用時機 |
|---|
| 隱式依賴 | 透過資源引用自動建立 | 大多數情況 |
| 顯式依賴 | 使用 depends_on 明確指定 | 特殊情況或無法推斷時 |
隱式依賴(Implicit Dependencies)
當一個資源引用另一個資源的屬性時,Terraform 會自動建立隱式依賴關係。這是最常見且推薦的依賴建立方式。
基本範例
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
37
38
39
40
41
42
43
44
| # VPC 資源
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "main-vpc"
}
}
# 子網路隱式依賴於 VPC
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id # 隱式依賴
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
map_public_ip_on_launch = true
tags = {
Name = "public-subnet"
}
}
# Internet Gateway 隱式依賴於 VPC
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id # 隱式依賴
tags = {
Name = "main-igw"
}
}
# Route Table 隱式依賴於 VPC 和 Internet Gateway
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id # 隱式依賴
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id # 隱式依賴
}
tags = {
Name = "public-rt"
}
}
|
依賴關係圖
1
2
3
4
5
6
7
8
| aws_vpc.main
│
├─── aws_subnet.public
│
├─── aws_internet_gateway.main
│ │
│ ▼
└─── aws_route_table.public
|
顯式依賴(Explicit Dependencies)
有時候資源之間存在依賴關係,但這種關係無法透過屬性引用表達。這時候需要使用 depends_on 來明確指定依賴關係。
depends_on 語法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| resource "aws_instance" "web" {
ami = "ami-0123456789abcdef0"
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
depends_on = [
aws_internet_gateway.main,
aws_route_table_association.public
]
tags = {
Name = "web-server"
}
}
|
使用 depends_on 的時機
| 情境 | 說明 |
|---|
| API 限制 | 某些 API 需要等待前一個資源完全可用 |
| 間接依賴 | 資源之間存在邏輯依賴但無屬性引用 |
| IAM 權限 | IAM 政策需要先附加才能執行操作 |
| 外部資源 | 依賴於 Terraform 外部管理的資源 |
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
| # IAM 角色
resource "aws_iam_role" "lambda_role" {
name = "lambda-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
# IAM 政策附加
resource "aws_iam_role_policy_attachment" "lambda_basic" {
role = aws_iam_role.lambda_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
# Lambda 函數需要等待 IAM 政策附加完成
resource "aws_lambda_function" "example" {
function_name = "example-function"
role = aws_iam_role.lambda_role.arn
handler = "index.handler"
runtime = "nodejs18.x"
filename = "lambda.zip"
depends_on = [
aws_iam_role_policy_attachment.lambda_basic
]
}
|
lifecycle 區塊說明
lifecycle 區塊允許您自訂資源的建立、更新和刪除行為。這是 Terraform 中非常強大的功能,可以幫助您避免意外的基礎設施變更。
lifecycle 區塊語法
1
2
3
4
5
6
7
8
9
10
11
| resource "aws_instance" "example" {
ami = "ami-0123456789abcdef0"
instance_type = "t3.micro"
lifecycle {
create_before_destroy = true
prevent_destroy = false
ignore_changes = [tags]
replace_triggered_by = [null_resource.trigger.id]
}
}
|
create_before_destroy
當資源需要被替換時(例如更改了不可變的屬性),預設行為是先刪除舊資源,再建立新資源。create_before_destroy 可以改變這個順序,先建立新資源,再刪除舊資源。
使用情境
- 避免服務中斷
- 確保至少有一個資源可用
- 資料庫替換時保持資料可用
範例
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
| resource "aws_launch_template" "web" {
name_prefix = "web-"
image_id = "ami-0123456789abcdef0"
instance_type = "t3.micro"
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group" "web" {
name_prefix = "web-sg-"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
lifecycle {
create_before_destroy = true
}
}
|
prevent_destroy
prevent_destroy 可以防止資源被意外刪除。當設為 true 時,任何嘗試刪除該資源的操作都會失敗。
使用情境
- 保護關鍵資源(資料庫、儲存桶)
- 防止意外的 terraform destroy
- 確保重要資料不會被刪除
範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| resource "aws_db_instance" "production" {
identifier = "production-db"
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.medium"
allocated_storage = 100
db_name = "production"
username = "admin"
password = var.db_password
skip_final_snapshot = false
deletion_protection = true
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket" "critical_data" {
bucket = "critical-data-bucket"
lifecycle {
prevent_destroy = true
}
}
|
注意事項
移除 prevent_destroy = true 的步驟:
- 將
prevent_destroy 設為 false - 執行
terraform apply - 再執行
terraform destroy 或移除資源
ignore_changes
ignore_changes 可以忽略特定屬性的變更,即使實際值與設定不同,Terraform 也不會嘗試更新這些屬性。
使用情境
| 情境 | 說明 |
|---|
| 外部修改 | 屬性由外部系統或手動修改 |
| 自動標籤 | AWS 自動添加的標籤 |
| 動態屬性 | 由 Auto Scaling 等服務動態調整的值 |
範例
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
37
38
39
40
41
42
43
44
45
| resource "aws_instance" "web" {
ami = "ami-0123456789abcdef0"
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
lifecycle {
ignore_changes = [
ami, # 忽略 AMI 變更
tags["Owner"], # 忽略特定標籤
]
}
}
# 忽略 Auto Scaling 調整的容量
resource "aws_autoscaling_group" "web" {
name = "web-asg"
desired_capacity = 2
max_size = 10
min_size = 1
vpc_zone_identifier = [aws_subnet.public.id]
launch_template {
id = aws_launch_template.web.id
version = "$Latest"
}
lifecycle {
ignore_changes = [
desired_capacity, # 忽略由 Auto Scaling 政策調整的容量
]
}
}
# 忽略所有變更(極少使用)
resource "aws_instance" "managed_externally" {
ami = "ami-0123456789abcdef0"
instance_type = "t3.micro"
lifecycle {
ignore_changes = all
}
}
|
replace_triggered_by
replace_triggered_by 是 Terraform 1.2 引入的新功能,可以指定當某些資源或屬性變更時,強制替換當前資源。
使用情境
- 配置檔變更時重新部署
- 相關資源更新時需要連動替換
- 版本更新觸發重建
範例
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
37
38
39
40
41
| resource "null_resource" "config_trigger" {
triggers = {
config_hash = filemd5("${path.module}/config.json")
}
}
resource "aws_instance" "app" {
ami = "ami-0123456789abcdef0"
instance_type = "t3.micro"
user_data = file("${path.module}/user_data.sh")
lifecycle {
replace_triggered_by = [
null_resource.config_trigger.id
]
}
tags = {
Name = "app-server"
}
}
# 當 Launch Template 版本變更時替換 ASG
resource "aws_autoscaling_group" "app" {
name = "app-asg"
desired_capacity = 2
max_size = 5
min_size = 1
launch_template {
id = aws_launch_template.app.id
version = aws_launch_template.app.latest_version
}
lifecycle {
replace_triggered_by = [
aws_launch_template.app.latest_version
]
}
}
|
實際應用範例
以下是一個完整的範例,展示如何在實際專案中結合使用依賴和生命週期管理。
零停機部署架構
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
| # 變數定義
variable "app_version" {
description = "Application version"
type = string
default = "1.0.0"
}
# 觸發器資源
resource "null_resource" "deployment_trigger" {
triggers = {
app_version = var.app_version
timestamp = timestamp()
}
}
# Launch Template
resource "aws_launch_template" "app" {
name_prefix = "app-"
image_id = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
user_data = base64encode(templatefile("${path.module}/user_data.sh", {
app_version = var.app_version
}))
lifecycle {
create_before_destroy = true
}
tags = {
Version = var.app_version
}
}
# Auto Scaling Group
resource "aws_autoscaling_group" "app" {
name = "app-asg-${aws_launch_template.app.latest_version}"
desired_capacity = 2
max_size = 10
min_size = 2
vpc_zone_identifier = aws_subnet.private[*].id
health_check_type = "ELB"
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
instance_refresh {
strategy = "Rolling"
preferences {
min_healthy_percentage = 50
}
}
lifecycle {
create_before_destroy = true
ignore_changes = [desired_capacity]
}
depends_on = [
aws_lb_listener.app
]
tag {
key = "Name"
value = "app-instance"
propagate_at_launch = true
}
}
# 資料庫(防止刪除)
resource "aws_db_instance" "app" {
identifier = "app-database"
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.medium"
allocated_storage = 100
db_name = "appdb"
username = "admin"
password = var.db_password
vpc_security_group_ids = [aws_security_group.db.id]
db_subnet_group_name = aws_db_subnet_group.app.name
skip_final_snapshot = false
deletion_protection = true
lifecycle {
prevent_destroy = true
ignore_changes = [password]
}
}
|
最佳實踐總結
| 功能 | 建議用法 |
|---|
| create_before_destroy | 用於需要零停機的資源替換 |
| prevent_destroy | 用於保護關鍵資料和資源 |
| ignore_changes | 用於外部管理或動態變更的屬性 |
| replace_triggered_by | 用於配置變更觸發的資源重建 |
| depends_on | 僅在無法建立隱式依賴時使用 |
總結
透過本文的介紹,您應該已經了解:
- 隱式依賴:透過資源引用自動建立,是首選的依賴方式
- 顯式依賴:使用
depends_on 處理無法推斷的依賴關係 - lifecycle 區塊:提供細緻的資源生命週期控制
- create_before_destroy:實現零停機部署
- prevent_destroy:保護關鍵資源不被意外刪除
- ignore_changes:忽略外部或動態變更的屬性
- replace_triggered_by:根據其他資源變更觸發替換
正確使用這些功能可以讓您的 Terraform 配置更加健壯、安全且易於維護。
參考資料