TerraformでVPC、サブネット、インターネットゲートウェイを作成する

# はじめに

現在CloudformationとTerraformを学習しています。
先日以下記事を書きました。
[CloudFormationでVPC、サブネット、インターネットゲートウェイを作成する](https://qiita.com/gogo-muscle/items/3ef45a6fca4bf334bbaa)

これと同じようなこと（＝全く同じではない）をTerraformで作成しました。
まだどちらもそこまで試してないですが、Terraformの方が良い感触です。



~~~shell:環境
% terraform --version
Terraform v0.12.26
~~~

# ディレクトリ構造

Terraformではディレクトリを役割、環境毎に分けた方が良いとのことです。
今回は役割ごと分割にします。結果以下のようなディレクトリ構造としました。

~~~shell
% tree                                                                                             
.
├── README.md
├── main.tf
├── modules
│   ├── alb
│   │   ├── Create-alb.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   ├── ec2
│   │   ├── Create-ec2.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   ├── rds
│   │   ├── Create-rds.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   ├── security_group
│   │   ├── Create-securitygroup.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   └── vpc 　　　＃本記事で作成
│       ├── Create-vpc.tf
│       ├── outputs.tf
│       └── variables.tf
├── outputs.tf
├── terraform.tfstate
├── terraform.tfstate.backup
└── variables.tf
~~~

# コード

## main.tfの作成

Terraformルートディレクトリに設置した`main.tf`には使用するモジュールをメインで書いておきます。
この後作成するvpcモジュールと、プロバイダー情報（今回はAWS）を記載します。（ec2,rds,albなどは別記事で書きます）
`vpc_config`内は環境に合わせて書いてください。必要に応じて変数の追加も出来ます。

~~~terraform:./main.tf
provider "aws" {
  region = "ap-northeast-1"
}

module vpc {
  source = "./modules/vpc"

  vpc_config = {
    NameTag              = "Terraform"
    cidr_block           = "10.2.0.0/16"
    enable_dns_support   = true
    enable_dns_hostnames = true
    subnet_count         = 2
  }
}
~~~

## 変数の作成

vpcに関する変数なので、`modules/vpc`ディレクトリ内に`variables.tf`を作成し、`module vpc`で定義した変数をinputします。
`string`や`bool`などの型を定義してますが、分からなければ`any`でも大丈夫です。

~~~terraform:./modules/vpc/variables.tf
variable "vpc_config" {
  type = object({
    NameTag              = string
    cidr_block           = string
    enable_dns_support   = bool
    enable_dns_hostnames = bool
    subnet_count         = number
  })
}
~~~

## vpcを作成するコード本体の作成

サブネットなど同一構成の場合は作成したい数分`count = int`を定義する。
ただ`count`だと一部だけ削除が出来ないので`for_each`にした方がいいかもしれない。

~~~terraform:./modules/vpc/Create-vpc.tf
# アベイラリティゾーンの取得
data "aws_availability_zones" "available" {
  state = "available"
}

# Cretae VPC
resource "aws_vpc" "MyFirstVPC" {
  cidr_block           = var.vpc_config.cidr_block
  enable_dns_hostnames = var.vpc_config.enable_dns_hostnames
  enable_dns_support   = var.vpc_config.enable_dns_support
  tags = {
    Name = "${var.vpc_config.NameTag}-VPC"
  }
}

# Cretae IGW
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.MyFirstVPC.id
  tags = {
    Name = "${var.vpc_config.NameTag}-IGW"
  }
}

# Cretae Subnets
resource "aws_subnet" "public" {
  count                   = var.vpc_config.subnet_count
  vpc_id                  = aws_vpc.MyFirstVPC.id
  cidr_block              = cidrsubnet(aws_vpc.MyFirstVPC.cidr_block, 8, count.index) #10.2.0.0/24,10.2.1.0/24
  map_public_ip_on_launch = true
  availability_zone       = data.aws_availability_zones.available.names[count.index]
  tags = {
    Name = "${var.vpc_config.NameTag}-PublicSubnet${count.index}"
  }
}
resource "aws_subnet" "private" {
  count                   = var.vpc_config.subnet_count
  vpc_id                  = aws_vpc.MyFirstVPC.id
  cidr_block              = cidrsubnet(aws_vpc.MyFirstVPC.cidr_block, 8, count.index + var.vpc_config.subnet_count) #10.2.2.0/24,10.2.3.0/24
  map_public_ip_on_launch = false
  availability_zone       = data.aws_availability_zones.available.names[count.index]
  tags = {
    Name = "${var.vpc_config.NameTag}-PrivateSubnet${count.index}"
  }
}

# Cretae RouteTables
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.MyFirstVPC.id
  tags = {
    Name = "${var.vpc_config.NameTag}-PublicRT"
  }
}
resource "aws_route_table" "private" {
  vpc_id = aws_vpc.MyFirstVPC.id
  tags = {
    Name = "${var.vpc_config.NameTag}-PrivateRT"
  }
}

# Cretae Route
resource "aws_route" "public" {
  route_table_id         = aws_route_table.public.id
  gateway_id             = aws_internet_gateway.igw.id
  destination_cidr_block = "0.0.0.0/0"
}

# Association RouteTables
resource "aws_route_table_association" "public" {
  count          = var.vpc_config.subnet_count
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "private" {
  count          = var.vpc_config.subnet_count
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private.id
}
~~~

## outputの作成

他のモジュールでIDや値を使うために必要な情報をoutputします。

~~~terraform:./modules/vpc/outputs.tf
output "vpc_id" {
  value = aws_vpc.MyFirstVPC.id
}
output "vpc_cidr" {
  value = aws_vpc.MyFirstVPC.cidr_block
}

output "public_subnet_id" {
  value = aws_subnet.public[*].id
}

output "private_subnet_id" {
  value = aws_subnet.private[*].id
}
~~~


# コードの実行

~~~shell:
% terraform plan
% terraform apply
~~~

PS：変数やループで固めておくと単純構成なら楽なんですが、一部だけ変えたい時にやりにくいかもですね。。

# 参考文献
* [実践Terraform AWSにおけるシステム設計とベストプラクティス-Kindle版](https://amzn.to/3chkz4g)
* [【Terraform】moduleのアンチパターンとそれに対するベストプラクティス5選](https://qiita.com/bigwheel/items/2b420183639416b5c6bb)
* [terraformでVPC, サブネットを作成する
](https://qiita.com/sasshi_i/items/f4f65e18923d856be256)
* [実録！Terraform セルフリファクタリング
](https://dev.classmethod.jp/articles/terraform-self-refactoring/)