[Terraform]同一リソース内で複数作成する場合、countよりfor_eachを使う

Terraform 0.12以前は<i>count-function</i>と<i>cidrsubnet-function</i>を使用してネットワークを作成していましたが、ネットワークに変更が入るときにリソースが再作成されてしまう問題がありました。また、可読性も落ちます。HCL2では<i>for_each</i>を使うことで可読性を上げ、既存リソースに影響が出ないようにリファクタリングできます。


### count

https://www.terraform.io/intro/examples/count.html


リソースのcountパラメーターを使用すると、構成を単純化し、単純に数値を増加させることによってリソースを拡張することができます。

```aws_instance.tf
resource "aws_instance" "web" {
  instance_type = "t3.small"
  ami           = lookup(var.aws_amis, var.aws_region)

  # This will create 4 instances
  count = 4
}
```

### cidrsubnet

https://www.terraform.io/docs/configuration/functions/cidrsubnet.html
 

3つの引数を使用してネットワークを計算してくれます。

`cidrsubnet(prefix, newbits, netnum)`

- prefixはCIDR表記で指定
- newbitsはプレフィックスを拡張する追加ビットの数
- netnumは、2進数で表現できる整数

```sh
> cidrsubnet("172.20.0.0/16", 3, 2)
172.20.64.0/19
> cidrsubnet("172.20.0.0/24", 4, 15)
172.20.0.240/28
```

cidrsubnet("172.20.0.0/16", 3, x)を例にすると、netnumをインクリメントしていくと/16の上位3bitにインクリメントしたビットが割り当てられるので、
下記のようなネットワークが作成されます。

```txt
172.20.0.0           10101100.00010100. 00000000.00000000
172.20.32.0          10101100.00010100.001 00000.00000000
172.20.64.0          10101100.00010100.010 00000.00000000
172.20.96.0          10101100.00010100.011 00000.00000000
172.20.128.0         10101100.00010100.100 00000.00000000
172.20.160.0         10101100.00010100.101 00000.00000000
172.20.192.0         10101100.00010100.110 00000.00000000
172.20.224.0         10101100.00010100.111 00000.00000000
```


上記を踏まえ、4つのネットワークを作成する設定は下記になります。

- region = ap-northeast-1
- availability zone = apne1-az4,apne1-az1

```aws_subnet.tf
resource "aws_subnet" "environment_example_subnet" {
  count                   = 4
  vpc_id                  = data.aws_vpc.environment_example.id
  availability_zone       = var.environment_example_subnet_availability_zones[count.index % 2]
  cidr_block              = cidrsubnet(data.aws_vpc.environment_example.cidr_block, 3, count.index)

  tags = {
    Name        = var.environment_example_subnet_names[count.index]
    Service     = "environment_example"
    Description = "Managed by Terraform"
  }
}
```

```variables.tf
variable "environment_example_subnet_availability_zones" {
  type = list

  default = [
    "ap-northeast-1a",
    "ap-northeast-1c",
  ]
}
```

## 問題点

この設定だと、countやvariableの値を変更すると、`aws_subnet.environment_example_subnet[x]`の各配列が再作成されて既存環境に影響が出てしまいます。

## 解決策

### for_each

https://www.terraform.io/docs/configuration/resources.html#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings


for_eachでmapを使用すると、定義されている要素の数だけループするので、配列に配慮せずに変更が可能になり、可読性も上がります。

```aws_subnet.tf
resource "aws_subnet" "environment_example_subnet" {
  vpc_id            = aws_vpc.environment_example.id
  availability_zone = each.value.zone

  for_each = var.environment_example_subnets

  cidr_block              = each.value.cidr
  map_public_ip_on_launch = each.value.launch

  tags = {
    Name        = each.value.name
    Service     = "environment_example"
    Description = "Managed by Terraform"
  }
}
```

```variables.tf
variable "environment_example_subnets" {
  type = map

  default = {
    public-1a = {
      cidr   = "172.20.0.0/19"
      zone   = "ap-northeast-1a"
      launch = "true"
      name   = "environment-example-public-a"
    }
    public-1c = {
      cidr   = "172.20.32.0/19"
      zone   = "ap-northeast-1c"
      launch = "true"
      name   = "environment-example-public-c"
    }
    private-1a = {
      cidr   = "172.20.64.0/19"
      zone   = "ap-northeast-1a"
      launch = "false"
      name   = "environment-example-private-a"
    }
    private-1c = {
      cidr   = "172.20.128.0/19"
      zone   = "ap-northeast-1c"
      launch = "false"
      name   = "environment-example-private-c"
    }
  }
}
```