はじめに
社内ハンズオン環境の整備等、複数台のEC2を手作業でコンソールからポチポチ作るのはかなり大変です。台数が増えるほど設定ミスや命名のゆれも発生しがちだと思います。
そこで活躍するのが Terraform の for_each です。変数にEC2の名前を列挙するだけで、同じ構成のインスタンスを一括構築できます。この記事では、実際にハンズオン環境で使ったコードをもとに for_each の使い方を解説します。
対象読者
- Terraformを使い始めたばかりの方
- 複数のEC2を効率よく作りたい方
-
countとfor_eachの違いが気になる方
countとfor_eachの違い
Terraformで複数リソースを作る方法は主に2つあります。
count(インデックス管理)
resource "aws_instance" "main" {
count = 3
# ...
}
シンプルに書けますが、リソースは aws_instance.main[0] aws_instance.main[1] のようにインデックスで管理されます。リスト途中の要素を削除すると後続リソースが一斉に再作成されてしまう問題があります。
for_each(キー管理)
resource "aws_instance" "main" {
for_each = toset(["web", "app", "db"])
# ...
}
各リソースが web app db というキーで管理されるため、特定リソースだけを安全に削除・追加できます。途中で台数が変わる環境には for_each が最適です。
今回はfor_eachを使っていきます。
構築するAWS構成
今回構築する構成は以下の通りです。
- VPC(10.10.0.0/16)
- プライベートサブネット(10.10.1.0/24)
- セキュリティグループ(VPC内からの全通信許可)
- EC2インスタンス × 3台(変数で台数・名前を管理)
ディレクトリ構成
ec2_terraform_foreach/
├── main.tf # プロバイダー定義・モジュール呼び出し
├── terraform.tf # Terraformバージョン・プロバイダーバージョン
├── terraform.tfvars # 変数の値を定義
├── variables.tf # 変数定義
└── modules/
├── variables.tf # モジュール内の変数定義
├── vpc.tf # VPC・サブネット・ルートテーブル
├── security_group.tf # セキュリティグループ
└── ec2.tf # EC2インスタンス(for_each使用)
ルートモジュールからモジュールを呼び出す構成にしています。VPC・SG・EC2の各リソースをファイルで分割することで、管理しやすくなります。
コード解説
terraform.tf(バージョン定義)
terraform {
required_version = "~> 1.13.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.27.0"
}
}
}
variables.tf / terraform.tfvars(変数定義)
variable "project" {
type = string
}
project = "ec2_terraform_foreach"
プロジェクト名を変数化することで、環境ごとに名前を変えやすくなります。
main.tf(モジュール呼び出し)
provider "aws" {
region = "ap-northeast-1"
}
module "multi_ec2_test" {
source = "./modules"
project = var.project
}
modules/variables.tf(モジュール変数定義)
ここが for_each のポイントです。 instance_config に各EC2の名前サフィックスを定義します。
variable "project" {
type = string
}
variable "instance_config" {
type = list(any)
default = ["ec2_1", "ec2_2", "ec2_3"]
}
for_each に渡せるのは map または set です。list 型の変数をそのまま渡すとエラーになるため、toset() で変換する必要があります。
# list型変数はtoset()でset型に変換してからfor_eachに渡す
for_each = toset(var.instance_config)
modules/vpc.tf(VPC・サブネット)
resource "aws_vpc" "main" {
cidr_block = "10.10.0.0/16"
instance_tenancy = "default"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
resource = "vpc"
Name = "${var.project}-vpc"
}
}
resource "aws_subnet" "prisub_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.10.1.0/24"
availability_zone = "ap-northeast-1a"
tags = {
resource = "subnet"
Name = "${var.project}-prisuba"
}
}
resource "aws_route_table" "prirt_a" {
vpc_id = aws_vpc.main.id
tags = {
resource = "route_table"
Name = "${var.project}_prirt"
}
}
resource "aws_route_table_association" "prirt_associate_a" {
subnet_id = aws_subnet.prisub_a.id
route_table_id = aws_route_table.prirt_a.id
}
modules/security_group.tf
resource "aws_security_group" "sg_ec2" {
name = "${var.project}_sg_ec2"
vpc_id = aws_vpc.main.id
# インバウンド:VPC内からの全通信許可
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["10.10.0.0/16"]
}
# アウトバウンド:全許可
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
resource = "security_group"
Name = "${var.project}_sg_ec2"
}
}
modules/ec2.tf(for_each でEC2を一括作成)
# 既存キーペアをデータソース化
data "aws_key_pair" "ec2_key_pair" {
key_name = "your_ec2_key_pair_name"
}
resource "aws_instance" "main" {
for_each = toset(var.instance_config)
ami = "ami-xxxxxxxxxxxxxxxxxx"
instance_type = "t3.medium"
key_name = data.aws_key_pair.ec2_key_pair.key_name
subnet_id = aws_subnet.prisub_a.id
vpc_security_group_ids = [aws_security_group.sg_ec2.id]
tags = {
Name = "${var.project}_${each.value}"
resource = "EC2"
}
}
for_each を使うことで、instance_config の要素ぶんだけEC2が作成されます。each.value がその要素の値(ec2_1, ec2_2, ec2_3)に対応します。
作成されるEC2のNameタグ:
| リソースキー | Nameタグ |
|---|---|
| ec2_1 | ec2_terraform_foreach_ec2_1 |
| ec2_2 | ec2_terraform_foreach_ec2_2 |
| ec2_3 | ec2_terraform_foreach_ec2_3 |
台数を増やしたいときは instance_config にエントリを追加するだけです。terraform apply を再実行すると差分のみが適用されるため、既存のインスタンスには影響しません。
variable "instance_config" {
type = list(any)
default = ["ec2_1", "ec2_2", "ec2_3", "ec2_4", "ec2_5"] # 追加するだけ
}
実行方法
# 初期化
terraform init
# 実行計画確認(何が作られるか事前チェック)
terraform plan
# 構築
terraform apply
# 削除(利用後の後片付け)
terraform destroy
terraform plan で事前に差分を確認できるのもTerraformの大きなメリットです。
まとめ
-
for_eachを使うことで、変数のリストから複数リソースを一括作成できる -
countと違い、キーで管理されるため特定リソースの追加・削除が他に影響しない -
for_eachにはsetまたはmap型を渡す(listの場合はtoset()で変換) - 台数変更は変数を編集して再
applyするだけ
ハンズオン環境のような「同じ構成を複数台」というユースケースでは for_each が非常に便利です。ぜひ活用してみてください。