Terraformにおけるmoduleの使い所

# 対象読者
Terraformは触ったことがあるが、moduleがあまりわからない。または、moduleをつかったことがない人が対象になります。
またAWSをTerraform化する場合を例に出すので、AWSの知識も少し必要です。

# はじめに
私はTerraformを触って2年近くになりますが、moduleを使った設計は行わずに基本的に以下のフューチャーアーキテクトさんの記事で書かれている `環境毎にディレクトリで分離派` に則ってTerraformを作成しておりました。
参考資料: https://future-architect.github.io/articles/20190903/

ただ、やはり環境ごとで同様の設定を書くことがあり、冗長に感じたので、DRYの法則に則りmoduleを使ってみました。
moduleの使い方と設計の勘所のメモとしてこの記事を書きます。

# moduleとは
アプリケーションで使う場合のmoduleと同じく、terraformでも共通で使いたい部分をテンプレートとして使うためのものです。

# 既存のコードをmodule化する
このようなterraformのコードが存在するとします。
この場合、 `vpc.tf` が共通化できる設定であると仮定してmodule化していきます。

```
├── prod
│   ├── main.tf
│   ├── variables.tf
│   └── vpc.tf
└── stag
    ├── main.tf
    ├── variables.tf
    └── vpc.tf
```

## module作成
terraformではmodule化をしたものを読み込む時に`module`を宣言して扱います。
例えば、terraformのルートディレクトリにmodulesディレクトリを掘って、その中に共通化したい resourceを定義します。
この場合は、 `vpc.tf` を共通化したいので、vpcのresourceを定義します。

```ruby:modules/vpc/main.tf
resource "aws_vpc" "this" {
  cidr_block           = "192.168.0.0/20"
  instance_tenancy     = "default"

  tags = {
    "Name" = "this"
  }
}
```

この`modules/vpc/main.tf`を呼び出してstag環境を作ろうとするならば、
相対パスで指定してmodule化したディレクトリ指定します。

```ruby:stag/vpc.tf
module "vpc" {
  source = "../modules/vpc"
}
```

## moduleの値を参照する
moduleで定義した内容を別で定義した`resource`で参照したい場合は、`output`を定義して使えるようにします。
逆にmoduleに値を渡したい時は`variable`を使います。
言い換えると、moduleから出力されたものを定義するのが`output`で、moduleに入力するものを定義するのが`variable`です。

これを使ってmoduleを直していきます。
stag環境以外に本番環境も使えるmoduleにしたい場合はこのように修正しましょう。

```ruby:modules/vpc/main.tf
variable "vpc_cidr" {}

resource "aws_vpc" "this" {
  cidr_block           = "${var.vpc_cidr}/20"
  instance_tenancy     = "default"

  tags = {
    "Name" = "this"
  }
}
```

`vpc_cidr`が空なのはmodule参照時に定義される値を読み込むためです。
先ほど定義した`stag/vpc.tf`でmoduleの`variable`で定義した`vpc_cidr`に値を渡してあげましょう。

```ruby:stag/vpc.tf
module "vpc" {
  source   = "../modules/vpc"
  vpc_cidr = "192.168.0.0"
}
```

このようにすると`modules/vpc/main.tf`の`vpc_cidr`に`192.168.0.0`が定義されて、
variable定義前と同じ設定内容になります。

prod環境でvpcを作りたい場合もmoduleを呼び出して作れます。

```ruby:prod/vpc.tf
module "vpc" {
  source   = "../modules/vpc"
  vpc_cidr = "172.16.0.0"
}
```

またmoduleで定義した内容を他のresourceでも参照したい時には `output`を使います。
先ほど設定した`modules/vpc/main.tf`に`output`で別のresourceに参照される値を定義します。

```ruby:modules/vpc/main.tf
~省略~
output "this_vpc_id" {
  value = aws_vpc.this.id
}
```

ここでは、stag環境で新たに`igw.tf`を作りインターネットゲートウェイの設定を定義します。
`module`で定義した内容を読み込む場合は`module.<module名>.<outputの定義名>`で値を参照します。

```:stag/igw.tf
resource "aws_internet_gateway" "this" {
  vpc_id = module.vpc.this_vpc_id

  tags = {
    "Name" = "this"
  }
}
```

## module化したあとのコード
全体のディレクトリ構成はこのようになります。
現在はprod/stagでmoduleを読み込むtfファイルとmoduleのディレクトリ名を1対1に作ってますが、
modulesにEC2やVPCを全部いれたmoduleを作り、それをprod/stagで読むこともできます。

```
├── modules
│   └── vpc
│       └── main.tf
├── prod
│   ├── main.tf
│   ├── variables.tf
│   └── vpc.tf
└── stag
    ├── igw.tf
    ├── main.tf
    ├── variables.tf
    └── vpc.tf
```

この記事の内容は<a href="https://github.com/yoshuxaaaa/terraform-module-sample">こちら</a>にサンプルコードとして載せて置くので、
完成したコードを読みたい方がいればご覧ください。

# 所感
moduleは大変便利だと感じた一方、値の参照が多いネットワーク系(AWSでいうとvpcやsecurity groupなど)で使う場合は、何をどこで参照させるかを考える必要が出てくると感じました。

ただ、ECRなどの他のリソースとの依存関係がほぼなくサービス単体で成り立つものに関してはmodule化をすると見通しがよいコードになると感じます。