Terraform 資料來源與動態配置

Terraform Data Sources and Dynamic Configuration

Data Source 概述

在 Terraform 中,Data Source(資料來源)是一個強大的功能,允許我們查詢和使用已存在的基礎設施資源資訊。與 Resource 不同的是,Data Source 只會讀取資料,而不會建立、修改或刪除任何資源。

Data Source 的主要用途包括:

  • 查詢現有資源的屬性(如 VPC ID、AMI ID 等)
  • 獲取動態資訊(如可用區域列表)
  • 跨專案或跨帳號共享資源資訊
  • 減少硬編碼,提高配置的靈活性

基本語法

Data Source 的基本語法結構如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
data "<PROVIDER>_<TYPE>" "<NAME>" {
  # 查詢條件
  filter {
    name   = "attribute-name"
    values = ["attribute-value"]
  }
}

# 引用 Data Source 的值
output "result" {
  value = data.<PROVIDER>_<TYPE>.<NAME>.<ATTRIBUTE>
}

與 Resource 的主要差異在於使用 data 關鍵字而非 resource,並且通常需要提供 filter 或其他查詢條件來定位特定資源。

常用 AWS Data Sources

AWS Provider 提供了豐富的 Data Source,以下介紹幾個最常用的類型。

查詢 AMI

查詢最新的 Amazon Linux 2 AMI:

 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
data "aws_ami" "amazon_linux_2" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  filter {
    name   = "root-device-type"
    values = ["ebs"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux_2.id
  instance_type = "t3.micro"

  tags = {
    Name = "Web Server"
  }
}

查詢 VPC 和 Subnet

查詢特定標籤的 VPC 及其子網路:

 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
data "aws_vpc" "selected" {
  filter {
    name   = "tag:Environment"
    values = ["production"]
  }
}

data "aws_subnets" "private" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.selected.id]
  }

  filter {
    name   = "tag:Type"
    values = ["private"]
  }
}

# 取得每個子網路的詳細資訊
data "aws_subnet" "private" {
  for_each = toset(data.aws_subnets.private.ids)
  id       = each.value
}

output "private_subnet_cidrs" {
  value = [for s in data.aws_subnet.private : s.cidr_block]
}

查詢 IAM 資訊

查詢現有的 IAM Role 和 Policy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
data "aws_iam_role" "existing_role" {
  name = "my-existing-role"
}

data "aws_iam_policy" "admin_policy" {
  name = "AdministratorAccess"
}

output "role_arn" {
  value = data.aws_iam_role.existing_role.arn
}

output "policy_arn" {
  value = data.aws_iam_policy.admin_policy.arn
}

使用 External Data Source

當內建的 Data Source 無法滿足需求時,可以使用 external data source 執行外部腳本來獲取資料:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
data "external" "git_info" {
  program = ["bash", "-c", <<-EOF
    echo "{\"branch\": \"$(git rev-parse --abbrev-ref HEAD)\", \"commit\": \"$(git rev-parse --short HEAD)\"}"
  EOF
  ]
}

resource "aws_instance" "app" {
  ami           = data.aws_ami.amazon_linux_2.id
  instance_type = "t3.micro"

  tags = {
    Name      = "App Server"
    GitBranch = data.external.git_info.result["branch"]
    GitCommit = data.external.git_info.result["commit"]
  }
}

注意事項:

  • 外部程式必須輸出有效的 JSON 格式
  • 所有值都必須是字串類型
  • 腳本執行失敗會導致 Terraform 執行失敗

Dynamic 區塊使用

dynamic 區塊允許我們動態生成重複的巢狀區塊,常與 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
variable "ingress_rules" {
  type = list(object({
    port        = number
    protocol    = string
    cidr_blocks = list(string)
    description = string
  }))
  default = [
    {
      port        = 80
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
      description = "HTTP"
    },
    {
      port        = 443
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
      description = "HTTPS"
    },
    {
      port        = 22
      protocol    = "tcp"
      cidr_blocks = ["10.0.0.0/8"]
      description = "SSH from internal"
    }
  ]
}

resource "aws_security_group" "web" {
  name        = "web-sg"
  description = "Security group for web servers"
  vpc_id      = data.aws_vpc.selected.id

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
      description = ingress.value.description
    }
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

for_each 與 Data Source 結合

for_each 與 Data Source 結合,可以實現更靈活的資源部署:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_subnet" "public" {
  for_each = toset(slice(data.aws_availability_zones.available.names, 0, 3))

  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(aws_vpc.main.cidr_block, 8, index(data.aws_availability_zones.available.names, each.value))
  availability_zone = each.value

  tags = {
    Name = "public-subnet-${each.value}"
    Type = "public"
  }
}

更進階的範例 - 根據標籤動態建立資源:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
locals {
  environments = ["dev", "staging", "prod"]
}

data "aws_secretsmanager_secret" "db_credentials" {
  for_each = toset(local.environments)
  name     = "db-credentials-${each.value}"
}

data "aws_secretsmanager_secret_version" "db_credentials" {
  for_each  = data.aws_secretsmanager_secret.db_credentials
  secret_id = each.value.id
}

output "db_secrets" {
  value     = { for env, secret in data.aws_secretsmanager_secret_version.db_credentials : env => jsondecode(secret.secret_string) }
  sensitive = true
}

最佳實踐

使用 Data Source 時,請遵循以下最佳實踐:

  1. 優先使用 Data Source 而非硬編碼:減少環境間的配置差異
  2. 適當處理查詢失敗的情況:使用 try() 函數或 count 條件
  3. 注意查詢效能:避免在大型環境中進行過於寬泛的查詢
  4. 善用 filter:精確的過濾條件可以提高查詢效率
  5. 結合 locals 使用:將複雜的 Data Source 結果處理邏輯放在 locals 中

參考資料

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