【 terraform module の使い方】EC2の構築をしながら解説します

2022年2月16日AWS

アイキャッチ Terraform モジュール

今回は、 terraform の module を使って複数の環境を作っていきます。

早速!

今回やること

moduleを使って3つの環境を作ります。

ケースとしては、このような感じです。

今回想定するケース

  • 開発(dev)/ステージング(stg)/本番(prd)の3環境を作成する。
  • VPC、サブネットのIPアドレスは環境ごとに変える。
  • 各環境で作成するリソースは異なる。(「開発はELBがない」など)

resourceブロックをまとめて記載したmoduleをmoduleブロックから呼び出すことで実現できます。

完成したものはgithubにありますので、ご参考までにどうぞ。

今回想定する構成は下記の3つです。(インターネットゲートウェイなどは省略しています。)

それぞれ環境ごとに異なる構成ですが同じモジュールに渡す値を変えることによって作成します。

moduleで作成する環境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.tfVPCやサブネットなどのリソースを定義するファイル。
variables.tf外部から値を受け取るためのファイル。
別ファイルからこのファイルに記載された変数名に値を渡すと、main.tfで別ファイルのリソースを扱える
outputs.tf外部に値を渡すためのファイル。
ここに書かれた変数名を参照することで、main.tfで作成したリソースを別のファイルで扱える。

module間で値を受け渡す方法

vpcモジュールで作成したリソースをec2モジュールで参照したい場合があります。

今回の場合、vpcモジュールで作成するサブネットのidをec2モジュールで参照する必要があります。

モジュール間での値の受け渡しは、output.tfとvariables.tfを使って行います。

大まかなイメージは以下の画像の通りです。

moduleの値受け渡し方法

モジュール間の値受け渡しの流れ

  1. terraform.tfvarsに記載された値がvariables.tfに渡される
  2. main.tfがvariables.tfの値を参照してmodule_awsのvariables.tfに値を渡す
  3. module_awsのmain.tfはvariables.tfの値を参照してリソースを作成する
  4. 作成したリソースはoutput.tfに記載された変数に渡される
  5. main.tfはoutput.tfの値を参照して別のmodue_awsのvariables.tfに値を渡す
  6. 以下繰り返し

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

参考

AWS

Posted by kotaro