Terraform講座：繰り返し処理編

## 今回の記事について
仕事内容が今年度から変わったため、お久しぶりの投稿になってしまいました。。。
今回は、Terraform講座の第5回目として、繰り返し処理について解説しようと思います！

## 繰り返し処理とは？
東京リージョン内のVPCに、AZごとにプライベートサブネットを作りたいとき、皆さんならどうTerraformコードを書きますか？
パッと思いつくのは、1つ1つリソースを書く方法だと思います。
```terraform
resource "aws_subnet" "private-1a" {
    vpc_id            = aws_vpc.main.id
    cidr_block        = "10.0.0.0/24"
    availability_zone = "ap-northeast-1a"
}

resource "aws_subnet" "private-1c" {
    vpc_id            = aws_vpc.main.id
    cidr_block        = "10.0.1.0/24"
    availability_zone = "ap-northeast-1c"
}

resource "aws_subnet" "private-1d" {
    vpc_id            = aws_vpc.main.id
    cidr_block        = "10.0.2.0/24"
    availability_zone = "ap-northeast-1d"
}
```
直感的で手を動かせばいいだけですが、かなり似たようなコードになっています。
さらに「パブリックサブネットとプライベートサブネット1つずつ作りましょう！」となると、合計で6個も似たようなコードを書くことになり、オプションが増えたりすると管理がどんどん煩雑になります。以上のコーディングの労力と管理を楽にするために、**「1つのテンプレのようなコードを書いて欲しい数だけリソースを作成させる」** というのが繰り返し処理です。

Terraformでは、`count`や`for_each`で繰り返し処理を実現することが出来ます！

## countによる繰り返し処理
### countを使用した例文
`count`による繰り返し処理は、作成したい数を指定して繰り返し処理を行います。
作成したい数については基本的にあらかじめ配列を定義したうえで、その長さを指定することが多いです。
例えば、先ほどのサブネット作成を例にするとAZ名の配列とCIDRの配列を用意しておき、それらの長さをもとに繰り返し処理を定義します。
```terraform
# AZ名の配列
variable "az_names" {
    default = [
        "ap-northeast-1a",
        "ap-northeast-1c",
        "ap-northeast-1d"
    ]
}

# CIDRの配列
variable "cidr_block" {
    default = [
        "10.0.0.0/24",
        "10.0.1.0/24",
        "10.0.2.0/24"
    ]
}

resource "aws_subnet" "private" {
    # まず、countを記述
    count             = length(var.az_names)
    vpc_id            = aws_vpc.main.id
    cidr_block        = cidr_block[count.index]
    availability_zone = az_names[count.index]
}
```
これにより、1つの `aws_subnet` リソースのコードを記述するだけで配列の長さ=3つ分のサブネットを作成することが出来ます！とてもスマートになりました！！

### countの難点
さて、この`count`ですが、コーディング負担は楽になる一方、Terraform内のリソース管理で少し難しくなってしまうケースがあります。
というのも、実際にPlan結果を見てみると、作成されるリソースは
```
# aws_subnet.private["0"] will be created
# aws_subnet.private["1"] will be created
# aws_subnet.private["2"] will be created
```
となります。これでは、先ほどのコードと比較して、どのサブネットがどのAZに構築したリソースなのか直感的に分かりづらくなっています。
では、もっといい方法が無いかというので、今度は`map`を使用して繰り返し処理を行う`for_each`について紹介します。

## for_eachによる繰り返し処理
### mapとは？
いわゆる辞書型のデータで、以下のようにKey-Value形式で値が対応しています。
```terraform
variable "cidr_block" {
    type = map(string)
    default = {
        "ap-notrheast-1a" = "10.0.0.0/24"
        "ap-northeast-1c" = "10.0.1.0/24"
        "ap-northeast-1d" = "10.0.2.0/24"
    }
}
```
例えば、`cidr_block["ap-northeast-1a"] = "10.0.0.0/24"`となります。
そして、`for_each`文では、Keyによるリソース名を作成することが出来ます。
### for_eachを使用した例文
`for_each`によるサブネット構築のコードは以下のようになります。
```terraform
variable "cidr_block" {
    type = map(string)
    default = {
        "1a" = "10.0.0.0/24"
        "1c" = "10.0.1.0/24"
        "1d" = "10.0.2.0/24"
    }
}

resource "aws_subnet" "private" {
    # まず、for_each文を記述
    for_each          = var.cidr_block
    vpc_id            = aws_vpc.main.id
    cidr_block        = each.value
    availability_zone = "ap-northeast-${each.key}"
}
```
これにより、`count`と同様に、3つのサブネットを作成することが出来ます。そして、Planを行うと作成されるリソースは、
```
# aws_subnet.private["1a"] will be created
# aws_subnet.private["1c"] will be created
# aws_subnet.private["1d"] will be created
```
となり、どのサブネットがどのAZに構築されているのか一目で分かる管理となっています！

### forを使用したfor_each
場合によっては、変数`cidr_block`は、`object`型の配列として用意されている可能性もあります。
`object`型は、`map`型に様々な型の値を入れることが出来るデータ型になります。今回は文字列型だけになりますが笑。そして、この例だとあまり恩恵がない感じになっていますがそこもご容赦ください・・・。
```terraform
variable "subnet_variables" {
    type = list(object({
        az_name    = string
        cidr_block = string
    }))
    default = [
        {
            az_name    = "1a"
            cidr_block = "10.0.0.0/24"
        },
        {
            az_name    = "1c"
            cidr_block = "10.0.1.0/24"
        },
        {
            az_name    = "1d"
            cidr_block = "10.0.2.0/24"
        }
    ]
}
```
このような変数の場合、`for`文を使用して、`map`に変更することが出来ます。
記述方法は以下の通りです。
```terraform
subnet_info_map = { for l in subnet_variables : l.az_name => l }
```
これの見方についてです。
まず、`:`で区切ってください。`:`の左側が`for`文で配列の各要素を取り出しており、右側で`subnet_info_map`のKeyとValueを定義しています。つまり、以下の対応になっています！
```terraform
subnet_info_map["1a"] = {
    az_name    = "1a"
    cidr_block = "10.0.0.0/24"
}
```

これを使用した`for_each`は次の通りです。
```terraform
variable "subnet_variables" {
    type = list(object({
        az_name    = string
        cidr_block = string
    }))
    default = [
        {
            az_name    = "1a"
            cidr_block = "10.0.0.0/24"
        },
        {
            az_name    = "1c"
            cidr_block = "10.0.1.0/24"
        },
        {
            az_name    = "1d"
            cidr_block = "10.0.2.0/24"
        }
    ]
}

resource "aws_subnet" "private" {
    for_each          = { for l in subnet_variables : l.az_name => l }
    vpc_id            = aws_vpc.main.id
    cidr_block        = each.value.cidr_block
    availability_zone = "ap-northeast-${each.value.az_name}"
}
```

## まとめ
`count`、`for_each`どちらを使うべきかは、筆者もまだ勉強中です。。。まずは壁にぶち当たってみて（）、どんどんスマートに使い分けて、コード文の記述量・そして管理の負担を少なくしていきましょう！