【Terraform】モジュール利用時のリソース変更の考慮点

AWS

Terraformを利用しAWSを構築することになったため、Terraformモジュールの利用を検討しました。モジュール化することで、コードの再利用性や可視性を高めて運用しやすいコードにすることを狙っています。
モジュールで複数のリソースを作成する際、リスト型の変数を使う場合にリソースのインデックスの番号がずれてしまい、変更しないリソースも変更対象となってしまうことが分かりました。

リソースのインデックスの番号がずれてしまう例

リスト型の変数で以下のようにサブネットを作成する例を以下に記載します。
概要としては、subnet_cidrsにリスト型のcidrブロックを渡します。
そして、モジュール内部でcountを使ってリソースを作成すると、Terraformは自動的にインデックスに番号を割り当てます。

モジュール呼び出し側

module "subnets" {
  source       = "./modules/subnet"
  subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}

モジュール

resource "aws_subnet" "this" {
  count      = length(var.subnet_cidrs)
  cidr_block = var.subnet_cidrs[count.index]
  # 他の設定項目
}

ここでの、count.indexがインデックスであり、0から始まる番号が振られます。つまり、リソースはaws_subnet.this[0]aws_subnet.this[1]aws_subnet.this[2]として識別されます。

問題点

例えば、リストの2番目の要素を削除すると、リソースのインデックスが["10.0.1.0/24", "10.0.3.0/24"]となります。aws_subnet.this[2]だったサブネットがaws_subnet.this[1]として再作成され、変更していないcidr_blockもTerraformは変更対象と判断してしまいます。

対処法(インデックスに番号を指定しない方法)

この問題を回避するために、リストではなくマップ型を使う方法があります。マップはキーとバリューのペアで構成されているので、番号ではない一意のキーを指定することができます。

モジュール呼び出し側(マップ型を使ったサブネットの指定)

module "subnets" {
  source       = "./modules/subnet"
  subnet_cidrs = {
    "subnet-a" = "10.0.1.0/24",
    "subnet-b" = "10.0.2.0/24",
    "subnet-c" = "10.0.3.0/24"
  }
}

モジュール

resource "aws_subnet" "this" {
  for_each = var.subnet_cidrs

  cidr_block = each.value
  tags = {
    Name = each.key
  }
  # 他の設定項目
}

この場合、インデックスは”subnet-a”、”subnet-b”、”subnet-c”となるため、各リソースはaws_subnet.this["subnet-a"]aws_subnet.this["subnet-b"]aws_subnet.this["subnet-c"]として識別されます。
つまり、インデックスが番号ではないので、aws_subnet.this["subnet-b"]を削除したとしても、他のサブネットのリソースはTerraformで変更と判断されません。

メリット

  • 一意のキーでリソースを管理するため、特定のリソースを追加・削除しても他のリソースに影響を与えにくい。
  • インデックスのずれによるリソースの不必要な再作成を防げる。

リスト型でもfor_eachを使うことで、インデックスを一意のものに指定可能

リスト型を使う場合は、for_eachtoset関数を組み合わせて、リストをセットに変換し、一意の値をインデックスとして利用できます。

モジュール呼び出し側(subnet_cidrsはリスト型で指定)

module "subnets" {
  source       = "./modules/subnet"
  subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}

モジュール

resource "aws_subnet" "this" {
  for_each = toset(var.subnet_cidrs)  # インデックスはCIDRブロックの値自体

  cidr_block = each.key
  tags = {
    Name = "subnet-${replace(each.key, "/", "-")}"  # CIDRブロックを使って名前を生成
  }
  # 他の設定項目
}

インデックスがcidrブロックの値となるので、each.keyを参照すると中にcidrブロックの値が入っています。cidrブロックの値が一意であれば、問題なくリソースを管理できます。

まとめ

  • リソースのインデックスには、一意のキーや値を使うことを意識する必要があります。
  • 基本的には、for_eachとマップ型やセット型を組み合わせて、リソースを管理することが重要かと思います。

コメント

タイトルとURLをコピーしました