0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Terraform】for_eachで複数EC2を同時に構築する

0
Posted at

はじめに

社内ハンズオン環境の整備等、複数台のEC2を手作業でコンソールからポチポチ作るのはかなり大変です。台数が増えるほど設定ミスや命名のゆれも発生しがちだと思います。

そこで活躍するのが Terraform の for_each です。変数にEC2の名前を列挙するだけで、同じ構成のインスタンスを一括構築できます。この記事では、実際にハンズオン環境で使ったコードをもとに for_each の使い方を解説します。

対象読者

  • Terraformを使い始めたばかりの方
  • 複数のEC2を効率よく作りたい方
  • countfor_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 が非常に便利です。ぜひ活用してみてください。

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?