対象読者
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を定義します。
resource "aws_vpc" "this" {
cidr_block = "192.168.0.0/20"
instance_tenancy = "default"
tags = {
"Name" = "this"
}
}
このmodules/vpc/main.tf
を呼び出してstag環境を作ろうとするならば、
相対パスで指定してmodule化したディレクトリ指定します。
module "vpc" {
source = "../modules/vpc"
}
moduleの値を参照する
moduleで定義した内容を別で定義したresource
で参照したい場合は、output
を定義して使えるようにします。
逆にmoduleに値を渡したい時はvariable
を使います。
言い換えると、moduleから出力されたものを定義するのがoutput
で、moduleに入力するものを定義するのがvariable
です。
これを使ってmoduleを直していきます。
stag環境以外に本番環境も使えるmoduleにしたい場合はこのように修正しましょう。
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
に値を渡してあげましょう。
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を呼び出して作れます。
module "vpc" {
source = "../modules/vpc"
vpc_cidr = "172.16.0.0"
}
またmoduleで定義した内容を他のresourceでも参照したい時には output
を使います。
先ほど設定したmodules/vpc/main.tf
にoutput
で別のresourceに参照される値を定義します。
~省略~
output "this_vpc_id" {
value = aws_vpc.this.id
}
ここでは、stag環境で新たにigw.tf
を作りインターネットゲートウェイの設定を定義します。
module
で定義した内容を読み込む場合はmodule.<module名>.<outputの定義名>
で値を参照します。
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
この記事の内容はこちらにサンプルコードとして載せて置くので、
完成したコードを読みたい方がいればご覧ください。
所感
moduleは大変便利だと感じた一方、値の参照が多いネットワーク系(AWSでいうとvpcやsecurity groupなど)で使う場合は、何をどこで参照させるかを考える必要が出てくると感じました。
ただ、ECRなどの他のリソースとの依存関係がほぼなくサービス単体で成り立つものに関してはmodule化をすると見通しがよいコードになると感じます。