Terraform count:リソースを削除する時リソースのインデックス番号がずれてしまう問題と解決策

# はじめに
Terraformのcount機能は便利ですが、その使用にはいくつかの注意点があります。今回Terraformでcountを使用する際に注意すべきポイントを一つ解説します。

# インデックスのずれに注意
Terraformコードにおけるcount機能は、リソースの個数を指定する便利な機能です。しかし、countを利用している場合、環境変数リストから要素を削除すると、インデックスがずれてしまうことがあります。

## 例えば:

以下のコードでは、`var.subnet_cidrs`のリストに基づいてサブネットを作成します。

```terraform
# Create multiple subnets
variable "subnet_cidrs" {
  type = list(string)
  default = [
    "10.0.1.0/24",
    "10.0.2.0/24",
    "10.0.3.0/24"  # Replace with your actual CIDR blocks
  ]
}

variable "subnet_names" {
  type = list(string)
  default = [
    "sub1",
    "sub2",
    "sub3"  # Replace with your desired subnet names
  ]
}

resource "aws_subnet" "subnets" {
  count = length(var.subnet_cidrs)  # Create multiple subnets based on list length

  vpc_id      = "vpc-12345678"      # Replace with your actual VPC ID
  cidr_block  = var.subnet_cidrs[count.index]  # Access CIDR block using index
  availability_zone = "us-west-2a"      # Choose your desired availability zone
  tags = {
    Name = var.subnet_names[count.index]  # Access subnet name using index
  }
}

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfa5e490"  # Replace with your desired AMI
  instance_type = "t2.micro"             # Choose your instance type

  # Refer to one of the subnets using its index
  subnet_id = element(aws_subnet.subnets.*.id, 2)  # Use 0 to refer to the first subnet

  # Other optional configurations
  vpc_security_group_ids = ["sg-12345678"]  # Replace with your security group IDs
  key_name             = "my-key-pair"  # Replace with your key pair name

  tags = {
    Name = "example-instance"
  }
}
```

上記のコードでは、インデックス番号は以下のようになります。

```
subnet_cidrs[0] = 10.0.1.0/24
subnet_cidrs[1] = 10.0.2.0/24
subnet_cidrs[2] = 10.0.3.0/24
subnet_names[0] = sub1
subnet_names[1] = sub2
subnet_names[2] = sub3
```

## 問題:

`subnet_cidrs`リストから`10.0.2.0/24`サブネットを削除した場合、サブネットのインデックス番号がずれてしまいます。

`var.subnet_cidrs`から`10.0.2.0/24`を削除すると、インデックスは以下のようになります。

```
subnet_cidrs[0] = 10.0.1.0/24
subnet_cidrs[1] = 10.0.3.0/24
```

しかし、`aws_instance`リソースでサブネットIDを参照する場合は、インデックスを1つ減らす必要があります。

```terraform
resource "aws_instance" "example" {
  ...
  subnet_id = element(aws_subnet.subnets.*.id, 1)
  ...
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586946/5cb04cea-dafc-034b-ef94-4e0328788629.png)

このように、`count`を使用すると、コードの変更に伴って関連するリソースのインデックスも変更する必要があります。これは、複雑なコードになると管理が難しくなります。

# 解決策

今回紹介する解決策は、`for_each`構文を使用する方法です。

```terraform

variable "subnet_cidrs" {
 type = list(string)
 default = [
  "10.0.1.0/24",
  "10.0.2.0/24",
  "10.0.3.0/24" # Replace with your actual CIDR blocks
 ]
}

variable "subnet_names" {
 type = list(string)
 default = [
  "sub1",
  "sub2",
  "sub3" # Replace with your desired subnet names
 ]
}

resource "aws_subnet" "subnets" {
 for_each = { for i, cidr in var.subnet_cidrs : i => cidr }

 vpc_id     = "vpc-12345678" # Replace with your actual VPC ID
 cidr_block   = each.value    # Access CIDR block using for_each loop
 availability_zone = "us-west-2a"   # Choose your desired availability zone
 tags = {
  Name = var.subnet_names[each.key] # Access subnet name using for_each loop key
 }
}


resource "aws_instance" "example" {
 ami      = "ami-0c55b159cbfa5e490" # Replace with your desired AMI
 instance_type = "t2.micro"       # Choose your instance type

 # Refer to the subnet by its name
 subnet_id = aws_subnet.subnets["sub3"].id # Use subnet name to refer to subnet

 # Other optional configurations
 vpc_security_group_ids = ["sg-12345678"] # Replace with your security group IDs
 key_name        = "my-key-pair" # Replace with your key pair name

 tags = {
  Name = "example-instance"
 }
}
```
このTerraformコードでは、`for_each`を使用してリソースを作成しています。`for_each`を使うことで、リストから要素を削除してもインデックスがずれる問題を回避できます。

`aws_subnet`リソースでの`for_each`を見てみましょう。

```terraform
resource "aws_subnet" "subnets" {
  for_each = { for i, cidr in var.subnet_cidrs : i => cidr }

  vpc_id            = "vpc-12345678"       # 実際のVPC IDに置き換える
  cidr_block        = each.value           # for_eachループを使用してCIDRブロックにアクセス
  availability_zone = "us-west-2a"         # 望ましいアベイラビリティゾーンを選択
  tags = {
    Name = var.subnet_names[each.key]     # for_eachループのキーを使用してサブネット名にアクセス
  }
}
```

ここで、`for_each`式が使用されています。この式は、`var.subnet_cidrs`リスト内の各CIDRブロックをキーとしてマッピングし、そのキーと値を使用してサブネットを作成します。

例えば、`var.subnet_cidrs`が以下のようなリストだとします。

```plaintext
["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
```

このリストからサブネットを作成する際、それぞれのCIDRブロックがループの各イテレーションで`each.value`として使用されます。この方法では、リストから要素を削除しても、`for_each`ループのキーとして使われるCIDRブロックに依存するため、インデックスのずれに影響されません。

同様に、EC2インスタンスの`subnet_id`もサブネット名を使用して参照しています。このため、リストからサブネットを削除しても、サブネット名に基づいて正しいサブネットが選択されるため、インデックスのずれに影響されません。


# まとめ

Terraformのcountは便利な機能ですが、リソースを削除する時にリソースのインデックスがずれるという問題があります。これにより、コードが複雑になり管理が難しくなります。そのため、for_eachを使用することで、リストから要素を削除してもインデックスのずれを気にする必要がなくなります。また、モジュールの活用も検討すべきです。他にも適切な解決策があれば、ぜひコメントで共有してください。