91
53

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

TerraformのModuleの仕組みを図解してみた

Last updated at Posted at 2021-08-29

Terraformを学び始めた頃、最初は同じディレクトリにすべてのtfファイルを置いて、Resourceブロックの書き方を中心に学び、作っては簡単に削除できる"全能感"がとても楽しかったのを覚えています。

でも、しばらくするとDRYに書きたくなって、Moduleという機能を知りました。
プログラミングでいうところの関数(Function)のようなものですね。

今でこそModuleをある程度読み書きできるようになりましたが、昔はプログラミングの関数のようなものだ、という例えも知らずイメージが全く掴めませんでしたし、DRYに書きたいのに変数や出力の記述が冗長になってしまったりして、使いこなすのが難しいイメージをずっと持っていました。

Moduleを使いこなすには、ディレクトリ構造とか、モジュール化の粒度など、設計の要素も大切なのですが、そういった話はいったん置いておいて、Moduleに変数を渡す、出力を受け取る、といったInput/Outputまわりの仕組みについて整理してみます。

インフラ構成

サンプルのインフラ構成(AWS)はvpcとsubnet1つだけの超シンプル構成です。Moduleの仕組み理解が目的なので、余計なコードは省いてModuleとの受け渡しにフォーカスして書いていきます。
infra.png

図解してみた

変数を渡すInputの流れが水色の矢印。
出力を受け取るOutputの流れが紫の矢印。
module.png

ディレクトリ構造

envディレクトリで環境ごとのフォルダを分けるよくあるパターン。
Moduleのディレクトリは機能別にrootに作成。vpcとsubnetを分けることは普通はありませんが、今回はあくまでサンプルということで。
ソースコードはGithubに置いています。
https://github.com/m-oka-system/terraform-module-sample

.
├── env
│   ├── dev
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   ├── provider.tf
│   │   ├── terraform.tfvars
│   │   └── variables.tf
│   └── prd
│        :(省略)
├── subnet
│   ├── main.tf
│   ├── outputs.tf
│   └── variables.tf
└── vpc
    ├── main.tf
    ├── outputs.tf
    └── variables.tf

Root Moduleの変数

/env/dev/ をRootModuleとして開発環境のVPCを構築するケースで考えます。
まず、変数部分。環境ごとに異なる変数はterraform.tfvarsに記述します。

./env/dev/variable.tf
variable "region" {
  type    = string
  default = "ap-northeast-1"
}

variable "env" {
  type = string
}

variable "cidr" {
  type = string
}
./env/dev/terraform.tfvars
env  = "dev"
cidr = "172.16.0.0/16"

モジュールの呼び出し部分

main.tfでvpcとsubnetのModuleを呼んでいます。
sourceはModuleの置いてあるディレクトリを相対パスで記述ですね。
後述しますが、Moduleの中では環境ごとに異なる値を変数で抽象化しているので、Moduleを呼ぶ時に変数の値を渡してあげる必要があります。module.vpc.vpc_idは、VPCモジュールのOutputから受け取った値です。他のModuleで作ったリソースの値を利用したい場合、Outputして受け取る必要があります。プログラミングでいうところの戻り値(Return)みたいなもんですかね。

./env/dev/main.tf
module "vpc" {
  source = "../../vpc"

  env  = var.env
  cidr = var.cidr
}

module "subnet" {
  source = "../../subnet"

  vpc_id = module.vpc.vpc_id
  cidr   = var.cidr
}

VPCモジュール

Module側にはリソースを作成するためのResourceブロックを記述します。
繰り返しになりますが、cidr_blocktag:Nameなど、環境ごとに異なる値を変数化しているので、RootModuleから受け取る変数の値によって動的にリソースを作成できるわけですね。

./vpc/main.tf
resource "aws_vpc" "vpc" {
  cidr_block = var.cidr

  tags = {
    Name = "${var.env}-${var.cidr}"
  }
}

変数はModule側でも宣言しておく必要があります。

./vpc/variables.tf
variable "env" {}
variable "cidr" {}

VPCIDはサブネットの作成に必要なので、OutputでRootModuleのmain.tfに返してあげます。

./vpc/outputs.tf
output "vpc_id" {
  value = aws_vpc.vpc.id
}

Subnetモジュール

VPCモジュールでOutputしたVPCのIDをここで使っています。
本筋からは少し逸れますが、cidrsubnet 関数を使って、VPCのCIDRブロックを元にサブネットのCIDRを求めています。

./subnet/main.tf
resource "aws_subnet" "public" {
  vpc_id     = var.vpc_id
  cidr_block = cidrsubnet(var.cidr, 8, 11) # 172.16.0.0/16 -> 172.16.11.0/24
}

Moduleで使う変数の宣言をお忘れなく。

./subnet/variables.tf
variable "vpc_id" {}
variable "cidr" {}

ここでは、SubnetIDを出力して、RootModuleのmain.tfに返しています。

./subnet/outputs.tf
output "subnet_id" {
  value = aws_subnet.public.id
}

Root Moduleの出力

ターミナルの画面に出力したいものはRootModule側でOutputします。サンプルで出力しているのがSubnetのIDですが、これ自体に意味はありません。

./env/dev/outputs.tf
output "public_subnet_id" {
  value = module.subnet.subnet_id
}

ここで意識すべきなのは、次の2通りの使い方があるという点だと思います。

  • Moduleからの戻り値として呼び出し元(main.tf)に返すOutput
  • ターミナルの画面に出力させるためのOutput

まとめ

Moduleを駆使してリソースを使いまわしできるような設計を学習中ですが、Module化しようとした時にだいたい引っかかるのが

  • 変数の宣言し忘れ
  • 出力のし忘れ

なんですよね。なので、冒頭のイメージ図を頭に入れながらInput/Outputの流れを意識しつつ、コーディングするように心がけたいと思います!

91
53
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
91
53

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?