Terraform 資源依賴與生命週期管理

Terraform Resource Dependencies and Lifecycle Management

在使用 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 的步驟:

  1. prevent_destroy 設為 false
  2. 執行 terraform apply
  3. 再執行 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 配置更加健壯、安全且易於維護。

參考資料

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