【Terraform/AWS】【入門編4】モジュールについて

# はじめに
前回は、Terraformを構成するフォルダおよびファイルについてを解説しました。
今回は、モジュールについて、更に深堀していきます。

- 記事構成
<cite><font size="1">[①【Terraform/AWS】【入門編1】環境準備](https://qiita.com/rittan_girla/items/eefc45c1f9dd0a1761fa/)</font></cite>
<cite><font size="1">[②【Terraform/AWS】【入門編2】tfファイル・Terraformコマンドについて](https://qiita.com/rittan_girla/items/7dbd9a9da842ccbf8508/)</font></cite>
<cite><font size="1">[③【Terraform/AWS】【入門編3】フォルダ構成と各ファイルの役割について](https://qiita.com/rittan_girla/items/37458a11c14dacce9ca0/)</font></cite>
<cite><font size="1">[<B>④【Terraform/AWS】【入門編4】モジュールについて](https://qiita.com/rittan_girla/items/81077f1493609eb7a6ca/)</font></cite><B>　★本記事</B>

# 1. モジュールについて
前回はフォルダの推奨構成について触れた通りで、モジュールを使った階層構造が一般的です。
モジュール化せずに単一のファイルで管理した場合、下記の引用の通り管理が煩雑になります。

> - Understanding and navigating the configuration files will become increasingly difficult.
> - Updating the configuration will become more risky, as an update to one section may cause unintended consequences to other parts of your configuration.
> - There will be an increasing amount of duplication of similar blocks of configuration, for instance when configuring separate dev/staging/production environments, which will cause an increasing burden when updating those parts of your configuration.
> - You may wish to share parts of your configuration between projects and teams, and will quickly find that cutting and pasting blocks of configuration between projects is error prone and hard to maintain.
> - Engineers will need more Terraform expertise to understand and modify your configuration. This makes self-service workflows for other teams more difficult, slowing down their development.

引用：https://developer.hashicorp.com/terraform/tutorials/modules/module

※筆者翻訳
- 構成ファイルの理解と操作が難しくなる
- 一つの設定を更新することで、他の設定に影響を及ぼすため、構成の更新がリスクになる
- 例えば、開発・ステージング・本番環境を構成する場合、いくつかの重複したブロックを構成することとなり、更新による負荷が増加する
- プロジェクトやチーム間で構成の一部を共有する場合、プロジェクト間でのコピーアンドペーストによるエラーが発生し、メンテナンスが難しくなる
- 構成を理解して変更するには、Terraformの専門的な知識が必要となるため、他のチームのセルフサービスワークフローがより困難となり、開発が遅くなる

# 2. モジュールのメリット

上記の問題について、モジュールを使用することで解消することができます。
以下の5つがモジュールを使うことによるメリットです。

>- Organize configuration
>- Encapsulate configuration
>- Re-use configuration
>- Provide consistency and ensure best practices
>- Self service

引用：https://developer.hashicorp.com/terraform/tutorials/modules/module

概要を要約した内容を下記表に示します。
| メリット | 概要 | 
| --- | --- |
|構成の整理|構成の役割毎に整理することで、モジュールの管理・理解がしやすくなる|
|構成のカプセル化|他の設定変更による影響を受けないようにする（前回のoutputs.tfの解説の通り、モジュール間でのやり取りができない）|
|構成を再利用する|同様の構成があった場合に、一から作らずに再利用することで、開発時間とエラーの削減ができる|
|一貫性を提供し、ベスト プラクティスを保証|構成の一貫性を確保する|
|セルフサービス|他のチームが構成をコードを利用しやすくなる|

# 3. モジュールで使用するブロック
ブロック{}を使ってモジュール内を定義していきます。
本章では、モジュール内で使われるブロックを紹介していきます。

## 3-1. module{}

モジュールを定義するブロックです。
例えば、下記構成のモジュールの場合、ルートモジュールはルート「.」直下にあるmain.tfで、子モジュールはmodules配下にあるnetworkとsecuritygroupになります。
ルートモジュールから、子モジュール(network, securitygroup)を呼び出す場合に使います。

下記の構成の場合、networkモジュール内で定義したvpc idをsecuritygroupモジュール内では使用できません。
子モジュール間で値を経由するには、一旦ルートモジュールのモジュールブロックで定義する必要があります。

```
.
├── main.tf
├── modules/
│   ├── network/
│   │   ├── main.tf
│   │   ├── outputs.tf
│   ├── securitygroup/
│   │   ├── variables.tf
│   │   ├── main.tf
```
networkモジュールでVPC IDを出力させます
```modules/network/output.tf
output "vpc_id" {
  value = aws_vpc.example.id
}
```
出力した値をモジュールで呼び出します（network_vpc_idの部分）
```main.tf
module "network" {
  source = "./modules/network"
}

module "securitygroup" {
  source         = "./modules/securitygroup"
  network_vpc_id = module.network.vpc_id
}
```

## 3-2. source{}
module{}を定義する場合に必須の項目です。
子モジュールがどのパスに存在するかを定義します。
下記例で示すと、networkモジュールはローカルの./modules/networkにあることを示しています。

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

module "securitygroup" {
  source         = "./modules/securitygroup"
  network_vpc_id = module.network.vpc_id
}
```

なお、ローカルではなくリモートパスを使用する場合は、指定の仕方が異なります。
例えば、Github上のモジュールを使う場合は、「source = "git@github.com:hashicorp/example.git"」となります。

※詳細は下記を参照してください。

https://developer.hashicorp.com/terraform/language/modules/sources

### 3-3. version
上記のようにGithub等のレジストリを使用する場合は、バージョンを指定します。

## 4. moduleで使う引数について

### 4-1. count

一つのモジュールから複数のインスタンスを作成する時に使用します。

> - count - Creates multiple instances of a module from a single module block. See the count page for details.

引用：https://developer.hashicorp.com/terraform/language/modules/syntax#meta-arguments

例えば、全く似た構成のインスタンスを二個作成する場合、下記のようなコードになります。
この例では二つなので、まだ管理できますが、これが複数台になった場合、管理がしにくくなります。
name以外は同じコード使っているので、重複している部分を使いまわせたら見栄えも良くなりそうです。

```main.tf
resource "aws_instance"  "server_a"  { 
  ami            =  "ami-a1b2c3d4" 
  instance_type  =  "t2.micro" 

  tags  =  { 
    Name  =  "example-instance-1" 
  } 
}

resource "aws_instance"  "server_b"  { 
  ami            =  "ami-a1b2c3d4" 
  instance_type  =  "t2.micro" 

  tags  =  { 
    Name  =  "example-instance-2" 
  } 
}
```
countを使った場合は下記のようになります。
二回実行するように定義しています。
また、${count.index}でcountの値を取得することができます。
```main.tf
resource "aws_instance"  "server"  { 
  count  =  2

  ami            =  "ami-a1b2c3d4" 
  instance_type  =  "t2.micro" 

  tags  =  { 
    Name  =  "example-instance-${count.index}" 
  } 
}
```

参考：https://developer.hashicorp.com/terraform/language/meta-arguments/count#basic-syntax

### 4-2. for_each

一つのモジュールから複数のインスタンスを作成する時に使用します。
> - for_each - Creates multiple instances of a module from a single module block. 

引用：https://developer.hashicorp.com/terraform/language/modules/syntax#meta-arguments

この文章だけだと、countと同じように見えます。
使い方としては、複数のリソースを作成する時に使うという意味では同じです。
違いを簡単にまとめると以下のようになります。
- count :　指定の回数繰り返す
- for_each：リスト化した値を参照して繰り返す

例えば、インスタンスに異なる名称を付ける場合で示します。
下記コードの場合、下記二つのインスタンス名で登録されます。
- example-instance1-dev
- example-instance2-test

```main.tf
resource "aws_instance" "server" {

  for_each = var.project

  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"

  tags = {
    Name = "example-${each.key}-${each.value.environment}"
  }
}
```
```variables.tf
variable "project" {
  description = "Map of project names to configuration."
  type        = map(any)

  default = {
    instance1 = {
      environment             = "dev"
    },
    instance2 = {
      environment             = "test"
    }
  }
}
```

## 4-3. providers

providersの役割としては、下記パターンがあります。

> - Using different default provider configurations for a child module.
> - Configuring a module that requires multiple configurations of the same provider.

引用：https://developer.hashicorp.com/terraform/language/meta-arguments/module-providers

基本的にルートモジュールで定義したprovidersが参照されます
ルートモジュールとは異なるモジュールを使いたい場合、どうしたらよいでしょうか
こういったケースでは、子モジュールで再度providersを定義することで異なるprovidersが使用できます
例えば、Cloudfrontを利用する場合、SSL/TLS証明書(ACM)が必要となります
Cloudfrontはグローバルなリソースとなるため、証明書をus-east-1に指定する必要があります
ルートモジュールでは、ap-northeast-1を指定していた場合、Cloudfrontのモジュールではus-east-1を使用するといった使い方ができます
このように、モジュール毎に異なるprovidersの設定をする時に使用します

## 4-4. depends on

依存関係を設定する時に使用します
Terraformでは基本的には依存関係を解消してモジュールの順番を自動で制御できる仕組みとなっていますが、一部解消できないリソースも存在します
依存関係というと、例えば、セキュリティグループに対して、ルールを登録するといったケースを考えます
ルールを登録するためには、セキュリティグループができていることが前提となりますよね
そういった関係性のことを依存関係といいます

ただし、下記引用文の通り、depends onは最終手段にすることが望ましく、式参照をメインにすべきであるため、Terraformを実行して、どうしても上手く動かない場合に留めるのが良いです
> You should use depends_on as a last resort because it can cause Terraform to create more conservative plans that replace more resources than necessary. 
> Instead of depends_on, we recommend using expression references to imply dependencies when possible.

引用：https://developer.hashicorp.com/terraform/language/meta-arguments/depends_on

depends onの記載方法は下記引用の通り、[]内に依存関係にあるモジュールを指定します
解説すると、EC2インスタンスを構築するためには、とあるIAMロールが先に作成されていることが前提となるといった内容となっています
IAMロール⇒EC2の順で構築するように明示しています

> ```
> resource "aws_instance" "example" {
>   ami           = "ami-a1b2c3d4"
>   instance_type = "t2.micro"
>
>   # Terraform can infer from this that the instance profile must
>   # be created before the EC2 instance.
>   iam_instance_profile = aws_iam_instance_profile.example
>
>   # However, if software running in this EC2 instance needs access
>   # to the S3 API in order to boot properly, there is also a "hidden"
>   # dependency on the aws_iam_role_policy that Terraform cannot
>   # automatically infer, so it must be declared explicitly:
>   depends_on = [
>     aws_iam_role_policy.example
>   ]
> }
> ```

# おわりに
本章では、Terraformのモジュールについて解説しました
モジュール構成を使うことによって、類似環境を構築する際にコードの再利用ができるため、開発時間の削減ができます
ただ、モジュールを多用すると、逆に管理が煩雑になることもあるため、単純にリソース毎にモジュール化すれば良いというわけではありません（最近登場したスタイルガイドでもある程度グループ化してモジュール化するといった記載があります）
詳細は是非スタイルガイドを参照してみてください

※スタイルガイド
https://developer.hashicorp.com/terraform/language/style