【 terraform module の使い方】EC2の構築をしながら解説します
今回は、 terraform の module を使って複数の環境を作っていきます。
早速!
今回やること
moduleを使って3つの環境を作ります。
ケースとしては、このような感じです。
今回想定するケース
- 開発(dev)/ステージング(stg)/本番(prd)の3環境を作成する。
- VPC、サブネットのIPアドレスは環境ごとに変える。
- 各環境で作成するリソースは異なる。(「開発はELBがない」など)
resourceブロックをまとめて記載したmoduleをmoduleブロックから呼び出すことで実現できます。
完成したものはgithubにありますので、ご参考までにどうぞ。
今回想定する構成は下記の3つです。(インターネットゲートウェイなどは省略しています。)
それぞれ環境ごとに異なる構成ですが同じモジュールに渡す値を変えることによって作成します。
terraform の module について
moduleと呼んでいますが、実態は普通のtfファイルと変わりません。
別のtfファイルからmoduleブロックを利用して呼び出すという違いだけです。
画像のように、部品を選んで組み合わせるように利用するイメージです。
moduleを利用する際に、渡す値を変えることでIPアドレスやサブネットの数など環境ごとの違いをを簡単に実現できます。
渡す値によって、目的の環境を作成できるように抽象化することで再利用製が上がります。
今回完成時のディレクトリはこのような構成になります。
$ tree .
.
├── README.md
├── environments
│ ├── dev
│ │ ├── main.tf
│ │ ├── output.tf
│ │ ├── terraform.tfvars
│ │ └── variables.tf
│ ├── prd
│ │ ├── main.tf
│ │ ├── output.tf
│ │ ├── terraform.tfvars
│ │ └── variables.tf
│ └── stg
│ ├── main.tf
│ ├── output.tf
│ ├── terraform.tfvars
│ └── variables.tf
└── module_aws
├── ec2
│ ├── main.tf
│ ├── output.tf
│ └── variables.tf
├── elb
│ ├── main.tf
│ ├── output.tf
│ └── variables.tf
└── vpc
├── main.tf
├── output.tf
└── variables.tf
8 directories, 28 files
前提
当たり前ですが、Terraformのインストールが必要です。
さらに、AWSに接続するためのアクセス情報も必要になりますのでまだお済みでない方は下記URLから実施してください。
Terraformのインストール | 【初学者向け入門編】terraformとは? 概要+実践で解説します |
AWSプロファイルの設定 | 【AWS初心者向け】IAMユーザの作成とAWSCLIのセットアップ手順を解説 |
moduleの作成
terraformはディレクトリ1つをモジュールとして扱い、呼び出す時もディレクトリのパスを指定します。
今回の場合、vpcモジュール・ec2モジュール・elbモジュールがあります。
それぞれのディレクトリの中には以下のファイルが含まれています。
ファイル名 | 内容 |
main.tf | VPCやサブネットなどのリソースを定義するファイル。 |
variables.tf | 外部から値を受け取るためのファイル。 別ファイルからこのファイルに記載された変数名に値を渡すと、main.tfで別ファイルのリソースを扱える |
outputs.tf | 外部に値を渡すためのファイル。 ここに書かれた変数名を参照することで、main.tfで作成したリソースを別のファイルで扱える。 |
module間で値を受け渡す方法
vpcモジュールで作成したリソースをec2モジュールで参照したい場合があります。
今回の場合、vpcモジュールで作成するサブネットのidをec2モジュールで参照する必要があります。
モジュール間での値の受け渡しは、output.tfとvariables.tfを使って行います。
大まかなイメージは以下の画像の通りです。
モジュール間の値受け渡しの流れ
- terraform.tfvarsに記載された値がvariables.tfに渡される
- main.tfがvariables.tfの値を参照してmodule_awsのvariables.tfに値を渡す
- module_awsのmain.tfはvariables.tfの値を参照してリソースを作成する
- 作成したリソースはoutput.tfに記載された変数に渡される
- main.tfはoutput.tfの値を参照して別のmodue_awsのvariables.tfに値を渡す
- 以下繰り返し
output.tfに渡したい値を記述し、variables.tfで受け取りたい値を記述しておき、呼び出し元のtfファイルを経由して値を受け渡します。
呼び出し元のtfファイル作成
必要なモジュールを呼び出すmain.tfを各環境用のディレクトリの中に作成します。
渡す値や呼び出すモジュールの違いで環境ごとの差異を実現します。
呼び出し元のterraform.tfvars作成
terraform.tfvarsはenvironment/{dev|stg|prd}配下のmain.tfに渡す値を記載します。
環境ごとに異なるIPアドレスやタグなどはここで個別に設定することができます。
system = "" # タグに使用するこの基盤のシステム名称(任意)
env = "" # タグに使用する環境の名称(dev|stg|prd)
myip = "" # セキュリティグループで許可する自分のIP
instance_cnt = 3 # 作成するインスタンスの数
ami = "ami-0701e21c502689c31" # 作成するインスタンスのami
type = "t2.micro" # 作成するインスタンスのタイプ
key_name = "common_key" # 作成するインスタンスのキーペア名
vpc_cidr = "10.100.0.0/16" # VPCのサイダーブロック
cidr_public = ["10.100.10.0/24", "10.100.11.0/24", "10.100.12.0/24"] # サブネットのCIDRブロック
呼び出し元のvariables.tf作成
先ほど、terraform.tfvarsで指定した値を受け取るためのファイルです。
variable [変数名] {}
の形式で記述します。
変数名はterraform.tfvatsで定義したものを同じでなければいけません。
また、{}の中にはdefault="値"としてデフォルトの値を設定することができます。
defaultは、terraform.tfvarsから値を渡されなかった場合に利用します。
variable "system" {}
variable "env" {}
variable "myip" {}
variable "instance_cnt" {}
variable "ami" {}
variable "type" {}
variable "key_name" {}
variable "vpc_cidr" {}
variable "cidr_public" {}
呼び出し元のmain.tf作成(検証環境)
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "3.24.1"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
# vpcモジュールを呼び出す
module "vpc" {
source = "../../module_aws/vpc"
system = var.system
env = var.env
cidr_vpc = var.vpc_cidr
cidr_public = var.cidr_public
}
# ec2モジュールを呼び出す
module "ec2" {
source = "../../module_aws/ec2"
system = var.system
env = var.env
vpc_id = module.network.vpc_id
subnets = module.network.subnets
myip = var.myip
instance_cnt = var.instance_cnt
ami = var.ami
type = var.type
key_name = var.key_name
}
検証環境に必要なモジュールを必要な値とともに呼び出しています。
モジュールの呼び出しは module [任意の名前] { }
で行います。
{}の中にはモジュールに渡す値を記載することで、さきほどterraform.tfvasで指定した環境ごとの値を渡すことができます。
ここでは、/environment/dev/main.tfのみ記載しましたがstgやprd配下のmain.tfに異なる値を渡すことで各環境を簡単に作れます。
vpcモジュールの作成
VPCモジュールではVPC/インターネットゲートウェイ/サブネットのネットワークに関するリソースを作成します。
vpcモジュールのvariables.tf
vpcモジュールで参照する変数を記述します。
実際の値は呼び出し元のtfファイルから渡されます。
variable "system" {}
variable "env" {}
variable "cidr_vpc" {}
variable "cidr_public" {}
vpcモジュールのmain.tf作成
module_aws/vpc/main.tfにはリソースブロックを記述します。
“var."となっている変数は先ほどのvariables.tfで受け取った値を参照しています。
# get availability_zones list.
# For reference: availability_zone = data.aws_availability_zones.available.names[0]
data "aws_availability_zones" "available" {
state = "available"
}
# vpcの作成
resource "aws_vpc" "vpc" {
# variable.tfで定義した"cidr_vpc"の値を参照
cidr_block = var.cidr_vpc
instance_tenancy = "default"
enable_dns_hostnames = true
tags = {
Name = "${var.env}-${var.system}-vpc"
Cost = "${var.system}"
}
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.env}-${var.system}-igw"
Cost = "${var.system}"
}
}
resource "aws_subnet" "public" {
# lengthはterraformで用意された関数で配列の長さを返します
count = length(var.cidr_public)
vpc_id = aws_vpc.vpc.id
cidr_block = element(var.cidr_public, count.index)
# azはdataブロックで取得した配列を参照します。
availability_zone = data.aws_availability_zones.available.names[count.index % length(var.cidr_public)]
tags = {
Name = "${var.env}-${var.system}-pub-${count.index + 1}"
Cost = "${var.system}"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "${var.env}-${var.system}-pub-rt"
Cost = "${var.system}"
}
}
resource "aws_route_table_association" "public" {
count = length(var.cidr_public)
subnet_id = element(aws_subnet.public.*.id, count.index)
route_table_id = aws_route_table.public.id
}
先頭のdataブロックでは、awsから利用可能なAZを取得しています。
dataブロックは、他にも既存リソースのidを取得したりするのに利用します。
vpcモジュールのoutput.tf
output.tfではvpcモジュールのmain.tfで作成したリソースを別のところから参照するために利用します。
output "vpc_id" {
value = aws_vpc.vpc.id
}
output "subnets" {
value = aws_subnet.public
}
outputの次の文字列が変数名、括弧の中のvalueが実際の値になります。
値は、[リソースタイプ].[リソース名].[属性]
の形で指定します。
属性はリソースタイプによって異なりますので公式ドキュメントを参照してください。
ec2モジュールの作成
ec2モジュールでは、ec2インスタンス/セキュリティグループを定義します。
ec2モジュールのvariables.tf作成
variable "system" {}
variable "env" {}
variable "vpc_id" {}
variable "myip" {}
variable "instance_cnt" {}
variable "subnets" {}
variable "ami" {}
variable "type" {}
variable "key_name" {}
ec2モジュールのmain.tf作成
resource "aws_security_group" "sg-ec2" {
name = "tf-sample-module-sg-http"
description = "Allow HTTP inbound traffic"
vpc_id = var.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.sg-elb.id]
cidr_blocks = [var.myip]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.myip]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.env}-${var.system}-sg-http"
Cost = "${var.system}"
}
}
resource "aws_security_group" "sg-elb" {
name = "tf-sample-module-sg-elb"
description = "Allow HTTP inbound traffic"
vpc_id = var.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = [var.myip]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.env}-${var.system}-sg-http"
Cost = "${var.system}"
}
}
resource "aws_instance" "ec2" {
count = var.instance_cnt
ami = var.ami
instance_type = var.type
key_name = var.key_name
vpc_security_group_ids = [
"${aws_security_group.sg-ec2.id}"
]
subnet_id = element(var.subnets.*.id, count.index % length(var.subnets))
associate_public_ip_address = "true"
tags = {
Name = "${var.env}-${var.system}-web"
Cost = "${var.system}"
}
}
ec2モジュールのoutput.tf作成
output "sg-elb" {
value = aws_security_group.sg-elb.id
}
output "pub_instances" {
value = aws_instance.ec2
}
elbモジュールの作成
elbモジュールのvariables.tf作成
ロードバランサーに関するターゲットグループ、リスナー、ALBの定義をします。
ELBはEC2に含まれるサービスですが、「検証環境では使用しない」を実現するためため分けました。
variable "system" {}
variable "env" {}
variable "subnets" {}
variable "sg-elb" {}
variable "vpc_id" {}
variable "pub_instances" {}
albモジュールのmain.tf作成
resource "aws_lb" "alb" {
name = "${var.env}-${var.system}-alb"
internal = false
load_balancer_type = "application"
security_groups = ["${var.sg-elb}"]
subnets = var.subnets.*.id
enable_deletion_protection = false
tags = {
Name = "${var.env}-${var.system}-alb"
Cost = "${var.system}"
}
}
resource "aws_lb_target_group" "tg1" {
name = "${var.env}-${var.system}-tg"
port = 80
protocol = "HTTP"
vpc_id = var.vpc_id
}
resource "aws_lb_listener" "listener80" {
load_balancer_arn = aws_lb.alb.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.tg1.arn
}
}
resource "aws_lb_target_group_attachment" "tg_attach" {
count = length(var.pub_instances)
target_group_arn = aws_lb_target_group.tg1.arn
target_id = var.pub_instances[count.index].id
port = 80
}
terraform の実行
実行は各環境用ディレクトリの中で行います。
そうすることで、main.tfが読み込まれ記載されているモジュールを呼び出します。
terraform init
モジュールを使う場合は必ず実施する必要があります。
$ terraform init
このコマンドを実行することで、main.tfに記載されているモジュールを読み込みます。
読み込まれているモジュールは.terraform/modules/modules.json
に記載されています。
terraform varidate/plan/apply
お馴染みの実行コマンドです。
$ terraform validate
$ terraform plan
$ terraform apply