Terraformのモジュールを試してみた

# 背景・目的
過去に、[Terraformのメモ](https://qiita.com/zumax/items/71184fc108b6894e5b27)、[Terraform ステートを試してみた](https://qiita.com/zumax/items/74fad1270d3deab7379f)、[Terraformの環境をセットアップしてみた](https://qiita.com/zumax/items/71184fc108b6894e5b27)等で、Terraformを試してきました。


今回は、詳解 Terraform 第3版の、「第4章 モジュールで再利用可能なインフラを作る」を元に、モジュール化を学びます。


# まとめ
下記に特徴をまとめます。

|特徴 |説明|
|:--|:--|
|概要|モジュールは、本場とステージングのように、共通の環境は同じでスペックが異なる場合がある。Terraformモジュールに入れて、そのモジュールをコード内の複数の場所で使い回す|
|モジュールの基本|あるフォルダにあるTerraform設定ファイルの集まりを指す。|
|モジュールの入力|入力パラメータは、variables.tfに定義する|
|モジュールのローカルな値|他から参照されて上書きされる場合、localsブロックを使用する|


# 概要
本場とステージングのように、共通の環境は同じでスペックが異なる場合がある。
このようなコードは、Terraformモジュールに入れて、そのモジュールをコード内の複数の場所で使い回す

```
|-modules
  |-services
    |-webserver-cluster
       |- main.tf
```

## モジュールの基本
あるフォルダにあるTerraform設定ファイルの集まりは、モジュールになる。

## モジュールの入力
モジュールは、入力パラメータを持てる。入力パラメータを定義するには、variables.tfに定義する。

## モジュールのローカルな値
入力変数として定義した場合、他から参照されて上書きされる可能性がある。その場合、localsブロックでローカルな値を使用する。

localブロックにより、ローカルな値を使う場合、その名前をモジュール内で使用できる。この名前は、そのモジュール内でしか利用できないので他のモジュールに影響を与えない。
下記に例を示す。

```
locals {
  http_port   = 80
}
```

読み出すには、`local.<NAME>`のように読み出す


## モジュールのバージョン管理
ステージングと本番の両環境が同じモジュールのフォルダを指定しているときに、そのフォルダ内に何らかの変更を加えると、次にデプロイを行うときにすぐに両環境に反映されてしまう。これにより、本番に影響を与えずにテストすることが困難になる。

この問題を解決するために、モジュールのバージョン管理を行う。


# 実践
## 呼び出し元 Network
```
$ pwd
{HOME}/aws_infra_tf/environments/prod
$ tree network 
network
├── backend.hcl
├── backend.tf
├── local.tf
├── main.tf
└── provider.tf

1 directory, 5 files
$
```

### main.tf
```
module "network" {
  source = "../../../modules/network"

  vpc_cidr        = local.vpc.cidr
  env             = local.vpc.env
  public_subnets  = local.public_subnets
  private_subnets = local.private_subnets
  db_subnets      = local.db_subnets
}
```
### local.tf
```
locals {
  vpc = {
    cidr = "120.100.0.0/16"
    env  = "prod"
  }
}
locals {
  public_subnets = {
    public01 = {
      name = "public01",
      cidr = "120.100.0.0/24",
      az   = "ap-northeast-1a"
    },
    public02 = {
      name = "public02",
      cidr = "120.100.1.0/24",
      az   = "ap-northeast-1c"
    },
    public03 = {
      name = "public03",
      cidr = "120.100.2.0/24",
      az   = "ap-northeast-1d"
    },
  }
}
locals {
  private_subnets = {
    private01 = {
      name = "private01",
      cidr = "120.100.10.0/24",
      az   = "ap-northeast-1a"
    },
    private02 = {
      name = "private02",
      cidr = "120.100.11.0/24",
      az   = "ap-northeast-1c"
    },
    private03 = {
      name = "private03",
      cidr = "120.100.12.0/24",
      az   = "ap-northeast-1d"
    },
  }
}
locals {
  db_subnets = {
    db01 = {
      name = "db01",
      cidr = "120.100.20.0/24",
      az   = "ap-northeast-1a"
    },
    db02 = {
      name = "db02",
      cidr = "120.100.21.0/24",
      az   = "ap-northeast-1c"
    },
    db03 = {
      name = "db03",
      cidr = "120.100.22.0/24",
      az   = "ap-northeast-1d"
    },
  }
}
```
## Modules Network
```
$ pwd
{HOME}/aws_infra_tf/aws_infra_tf
$ tree modules 
modules
└── network
    ├── main.tf
    └── variables.tf

2 directories, 2 files
$
```

### main.tf
```
# VPC
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc
resource "aws_vpc" "terraform_modules_vpc" {
  cidr_block           = var.vpc_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "terraform-modules-vpc-${var.env}"
    Env  = var.env
  }
}
# Public Subnets
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet
resource "aws_subnet" "public_subnets" {
  for_each = var.public_subnets

  vpc_id = aws_vpc.terraform_modules_vpc.id

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

  tags = {
    Name = "terraform-modules-vpc-subnet-${var.env}-${each.value.name}"
    Env  = var.env
  }
}
# Private Subnets
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet
resource "aws_subnet" "private_subnets" {
  for_each = var.private_subnets

  vpc_id = aws_vpc.terraform_modules_vpc.id

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

  tags = {
    Name = "terraform-modules-vpc-subnet-${var.env}-${each.value.name}"
    Env  = var.env
  }
}
# DB Subnets
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet
resource "aws_subnet" "db_subnets" {
  for_each = var.db_subnets

  vpc_id = aws_vpc.terraform_modules_vpc.id

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

  tags = {
    Name = "terraform-modules-vpc-subnet-${var.env}-${each.value.name}"
    Env  = var.env
  }
}

# Internet Gateway
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway
resource "aws_internet_gateway" "terraform_igw" {
  vpc_id = aws_vpc.terraform_modules_vpc.id

  tags = {
    Name = "terraform-modules-vpc-igw-${var.env}"
    Env  = var.env
  }

}

# Elastic IP
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip
resource "aws_eip" "nat_eip" {
  domain = "vpc"

  tags = {
    Name = "terraform-modules-vpc-eip-${var.env}"
    Env  = var.env
  }

}

# NAT Gateway
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/nat_gateway
resource "aws_nat_gateway" "nat_gateway" {
  allocation_id = aws_eip.nat_eip.id
  subnet_id     = aws_subnet.public_subnets["public01"].id

  tags = {
    Name = "terraform-modules-vpc-nat-${var.env}"
    Env  = var.env
  }
}


# Public Subnet Route Table
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table
resource "aws_route_table" "public_route_table" {
  # for_each = var.public_subnets
  vpc_id = aws_vpc.terraform_modules_vpc.id

  tags = {
    Name = "terraform-modules-vpc-routetable-${var.env}-public"
    Env  = var.env
  }
}

# Public Subnet Route
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route
resource "aws_route" "public_route" {
  for_each               = var.public_subnets
  route_table_id         = aws_route_table.public_route_table.id
  destination_cidr_block = var.any_cidr_block
  gateway_id             = aws_internet_gateway.terraform_igw.id
}

# Associate Route Table
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association
resource "aws_route_table_association" "public_route_table_association" {
  for_each = var.public_subnets

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

# Private Subnet Route Table
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table
resource "aws_route_table" "private_route_table" {
  for_each = var.private_subnets
  vpc_id   = aws_vpc.terraform_modules_vpc.id

  tags = {
    Name = "terraform-modules-vpc-routetable-${var.env}-${each.key}"
    Env  = var.env
  }
}

# Private Subnet Route
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route
resource "aws_route" "private_route" {
  for_each               = var.private_subnets
  route_table_id         = aws_route_table.private_route_table[each.key].id
  nat_gateway_id         = aws_nat_gateway.nat_gateway.id
  destination_cidr_block = var.any_cidr_block
}

# Associate Route Table
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association
resource "aws_route_table_association" "private_route_table_association" {
  for_each = var.private_subnets

  subnet_id      = aws_subnet.private_subnets[each.key].id
  route_table_id = aws_route_table.private_route_table[each.key].id

}
```

### variables.tf
```
variable "vpc_cidr" {
  type        = string
  description = "VPC_CIDR"
}

variable "env" {
  type        = string
  description = "Environment。dev,stg,prdを選択すること"
}

variable "public_subnets" {
  type        = map(any)
  description = "Public Subnets"
}
variable "private_subnets" {
  type        = map(any)
  description = "Private Subnets"
}
variable "db_subnets" {
  type        = map(any)
  description = "DB Subnets"
}

variable "any_cidr_block" {
  type        = string
  description = "CIDR"
  default     = "0.0.0.0/0"
}
```

# 考察
前回、[Terraformのメモ](https://qiita.com/zumax/items/71184fc108b6894e5b27)で作成したNetworkをモジュール分割してみました。まだまだ、分かっていないことも多いので、少しづづ使って慣れていこうと思います。

# 参考
https://qiita.com/zumax/items/71184fc108b6894e5b27