Terraformで複数azに跨るサブネットを一気に作る

## はじめに
- Terraformの勉強記録です
- 本稿ではTerraformで複数azに跨るサブネットを一気に作る手順を記します
    - 複数リソースのループはfor_eachを使用しています
- 誤りの訂正・より良い方法のご助言、どしどしコメント頂ければ幸いです


### Terraform 実行環境
- AWS Cloud9
    - Amazon Linux2
    - t2.micro
- Cloud9を使う理由
    - 標準でTerraformが備わっているため

### 作成環境の構成
![vpc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779593/390bbb3b-e9a3-5222-faaf-aeed1647d73b.png)




---

### アウトライン
1. プロバイダー・locals変数
1. VPC
1. サブネット
1. インターネットゲートウェイ
1. ルートテーブル
1. NATゲートウェイ

---

### 1. プロバイダー・locals変数
- 東京リージョンを指定

```go:variables.tf
locals {
  name = "terraform"
  region   = "ap-northeast-1"
}
```

- Nameタグに使う文字列など、複数のresourceで共通して使う値をlocals変数に格納

```go:variables.tf
provider "aws" {
  region = local.region
}

```



### 2. VPC
- `vpc_cidr`の値にしたがってVPCを構築

```go:variables.tf
variable "vpc_cidr" {
  type    = string
  default = "10.1.0.0/16"
}
```

```go:vpc.tf
resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr

  instance_tenancy     = "default"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "${local.name}-vpc"
  }
}
```

### 3. サブネット
- 3つのAvailability Zoneそれぞれにパブリック・プライベートサブネットを作成
- プライベート・パブリックサブネットそれぞれの設定情報を持つ変数を宣言
    - for_eachで呼び出すため、map(any)型で情報を格納

```go:variables.tf
variable "subnets" {
  type = map(any)
  default = {
    private_subnets = {
      private-1a = {
        name = "private-1a",
        cidr = "10.1.10.0/24",
        az   = "ap-northeast-1a"
      },
      private-1c = {
        name = "private-1c",
        cidr = "10.1.11.0/24",
        az = "ap-northeast-1c"  
      },
      private-1d = {
        name = "private-1d",
        cidr = "10.1.12.0/24",
        az = "ap-northeast-1d"  
      },
    },
    public_subnets = {
      public-1a = {
        name = "public-1a"
        cidr = "10.1.100.0/24"
        az   = "ap-northeast-1a"
      },
      public-1c = {
        name = "public-1c"
        cidr = "10.1.101.0/24"
        az = "ap-northeast-1c"
      },
      public-1d = {
        name = "public-1d"
        cidr = "10.1.102.0/24"
        az = "ap-northeast-1d"
      }
    }
  }
}
```


- サブネットの作成
    - for_eachで回す
    - `each.value.hoge` で欲しい情報にアクセス

```go:subnet.tf
resource "aws_subnet" "private" {
  for_each = var.subnets.private_subnets

  vpc_id = aws_vpc.main.id

  cidr_block        = each.value.cidr
  availability_zone = each.value.az

  tags = {
    Name = "${local.name}-${each.value.name}"
  }
}

resource "aws_subnet" "public" {
  for_each = var.subnets.public_subnets

  vpc_id = aws_vpc.main.id

  cidr_block        = each.value.cidr
  availability_zone = each.value.az

  tags = {
    Name = "${local.name}-${each.value.name}"
  }
}
```


### 4. インターネットゲートウェイ
- VPCのidと関連付け

```go:gateway.tf
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${local.name}-igw"
  }
}
```


### 5. ルートテーブル
- 各サブネット分作成

```route_table.tf
# create
resource "aws_route_table" "private" {
  for_each = var.subnets.private_subnets
  vpc_id   = aws_vpc.main.id

  tags = {
    Name = "${local.name}-${each.value.name}-rt"
  }
}

resource "aws_route_table" "public" {
  for_each = var.subnets.public_subnets
  vpc_id   = aws_vpc.main.id

  tags = {
    Name = "${local.name}-${each.value.name}-rt"
  }
}
```

- サブネットと関連付ける
    - サブネットもルートテーブルも同一のキーで生成されているため、同じように書ける
  
```route_table.tf
# association
resource "aws_route_table_association" "private" {
  for_each = var.subnets.private_subnets

  subnet_id      = aws_subnet.private[each.key].id
  route_table_id = aws_route_table.private[each.key].id
}

resource "aws_route_table_association" "public" {
  for_each = var.subnets.public_subnets

  subnet_id      = aws_subnet.public[each.key].id
  route_table_id = aws_route_table.public[each.key].id
}
```

- パブリックサブネットはインターネットゲートウェイとも関連付ける

```go:route_table.tf
# associate with gateway
resource "aws_route" "igw" {
  for_each               = var.subnets.public_subnets
  route_table_id         = aws_route_table.public[each.key].id
  gateway_id             = aws_internet_gateway.main.id
  destination_cidr_block = "0.0.0.0/0"
}
```

### 6. NATゲートウェイ
- 一工夫必要

---

- Elastic IPの作成
    - NATゲートウェイに割り当てるもの
    - パブリックサブネット分、作成
    
    ```sh
    resource "aws_eip" "nat_gateway" {
      for_each = var.subnets.public_subnets
    
      vpc = true
      tags = {
        Name = "${local.name}-${each.value.name}-ngw-eip"
      }
    }
    ```

- NATゲートウェイの作成
    - 関連付けるサブネット・EIPを指定
    - （publicサブネットに配置するが、関連付ける対象的に`private`と命名）
    
    ```go
  resource "aws_nat_gateway" "private" {
      for_each = var.subnets.public_subnets
    
      allocation_id = aws_eip.nat_gateway[each.key].id
      subnet_id     = aws_subnet.public[each.key].id
    
      tags = {
        Name = "${local.name}-${each.value.name}-ngw"
      }
    
      depends_on = [aws_internet_gateway.main] # internet_gateway作成後に作成
    }
    ```

- Route Tableの関連付け
    - private subnetのルートテーブルにNATゲートウェイへのルートを追加したい
    - route_tableは`var.private_subnets`のkey、nat_gatewayは`var.public_subnets`のkeyで生成されている（ややこしい）
    - `zipmap`をうまく使って一気に作る

    ```go
    resource "aws_route" "ngw" {
      for_each               = zipmap(keys(var.subnets.public_subnets), keys(var.subnets.private_subnets))
      route_table_id         = aws_route_table.private[each.value].id
      nat_gateway_id         = aws_nat_gateway.private[each.key].id
      destination_cidr_block = "0.0.0.0/0"
    }
    ```
- 完成

## おわりに
- 無事、複数azに跨るサブネットを一気に作ることができました
- ネタがあれば引き続きTerraformの検証記事を書いていく予定です