はじめに
今回は、Terraform を使って AWS 上に Application Load Balancer(ALB)と EC2 インスタンスを組み合わせた Web アプリケーション環境を構築してみます。
これまで Terraform にあまり触れてきませんでしたが、今回はEC2 + ALB環境を構築することを通じて、理解を深めたいと思います。
Terraform とは
Terraform は、HashiCorp 社によって開発されたオープンソースの Infrastructure as Code (IaC) ツールです。JSON に似た独自言語である HCL(HashiCorp Configuration Language)という言語を用いて、クラウドインフラストラクチャ(サーバー、ストレージ、ネットワーク、データベースなど)の構成をコードとして記述し、プロビジョニングと管理を自動化します。
Terraform を利用することで、以下のようなメリットがあります。
- 再現性の向上: 同じ構成のインフラを何度でも正確に再現できる
- バージョニング: インフラ構成を Git などのバージョン管理システムで管理できる
- 自動化: インフラの構築、変更、削除を自動化し、手作業によるミスを削減できる
- マルチクラウド対応: AWS、Azure、Google Cloud Platform など、多くのクラウドプロバイダーに対応できる
Terraform の基本的なワークフローは以下の通りです。
- Write: HCL を用いてインフラ構成を記述
-
Plan:
terraform plan
コマンドを実行し、現在の構成と記述された構成の差分を確認 -
Apply:
terraform apply
コマンドを実行し、記述された構成を実際のインフラに適用
ファイル構成
ファイル構成は以下の通りです。
root/
├── provider.tf # AWSプロバイダー設定
├── variables.tf # 変数定義
├── vpc.tf # VPC、サブネット、IGW、ルートテーブル
├── security_groups.tf # セキュリティグループ
├── ec2.tf # EC2インスタンス、AMIデータソース
├── alb.tf # ALB、ターゲットグループ、リスナー
├── locals.tf # ローカル変数定義
└── outputs.tf # 出力値定義
各.tf
ファイルに役割を分割することで、コードの見通しを良くし、管理しやすくしています。
構築した環境の概要
今回構築したのは、以下のような構成です。
実装手順と設定内容
以下のドキュメントに沿って構築していきます。
プロバイダー設定(provider.tf)
まず、Terraform の基本設定からです。
provider "aws" {
region = "ap-northeast-1"
profile = "test"
}
東京リージョンを選択し、事前に設定済みの AWS プロファイルを指定しました。
変数定義(variables.tf)
設定値は変数として管理することで、再利用性を高めています。
variable "prefix" {
description = "Resource name prefix"
default = "test-terraform"
}
variable "instance_type" {
description = "EC2 instance type"
default = "t3.micro"
}
variable "availability_zones" {
description = "Availability zones"
type = list(string)
default = ["ap-northeast-1a", "ap-northeast-1c"]
}
2 つのアベイラビリティゾーンを指定しました。
ネットワーク構築(vpc.tf)
VPC の作成
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(local.tags, {
Name = "${var.prefix}-vpc"
})
}
プライベート IP アドレス範囲(RFC1918 準拠)を使用しています。
パブリックサブネットの作成
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 1}.0/24"
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(local.tags, {
Name = "${var.prefix}-public-subnet-${count.index + 1}"
})
}
# インターネットゲートウェイ
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = merge(local.tags, {
Name = "${var.prefix}-igw"
})
}
# ルートテーブル
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = merge(local.tags, {
Name = "${var.prefix}-public-rt"
})
}
# ルートテーブルの関連付け
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
map_public_ip_on_launch = true
で EC2 インスタンスに自動的にパブリック IP を割り当てるようにしました。
セキュリティグループ設定(security_groups.tf)
ALB 用セキュリティグループ
resource "aws_security_group" "alb" {
name = "${var.prefix}-alb-sg"
description = "Security group for ALB"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(local.tags, {
Name = "${var.prefix}-alb-sg"
})
}
今回は検証のため、一時的に全開放(0.0.0.0/0
)としていますが、実際の運用では環境に合わせて適切な IP アドレス範囲に限定してください。
EC2 用セキュリティグループ
resource "aws_security_group" "ec2" {
name = "${var.prefix}-ec2-sg"
description = "Security group for EC2"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(local.tags, {
Name = "${var.prefix}-ec2-sg"
})
}
EC2 への直接アクセスを防ぎ、ALB を経由したアクセスのみを許可します。
EC2 インスタンス設定(ec2.tf)
AMI 選択
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
}
Amazon Linux 2023 x86_64 系のAMIの最新版を利用する設定です。
EC2 インスタンスの作成
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
resource "aws_instance" "web" {
count = length(aws_subnet.public)
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
subnet_id = aws_subnet.public[count.index].id
vpc_security_group_ids = [aws_security_group.ec2.id]
user_data = <<-EOF
#!/bin/bash
dnf update -y
dnf install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from EC2 Instance ${count.index + 1}</h1>" > /var/www/html/index.html
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/instance-id)
echo "<p>Instance ID: $INSTANCE_ID</p>" >> /var/www/html/index.html
EOF
tags = merge(local.tags, {
Name = "${var.prefix}-web-${count.index + 1}"
})
}
サブネット数分(2 台)の EC2 インスタンスを作成し、user_data スクリプトで起動時に自動的に Apache をインストール・設定します。各インスタンスで異なる内容を表示することで、ロードバランサーが正しく動作することを確認できるようにしています。
Application Load Balancer 設定(alb.tf)
ALB 本体の作成
resource "aws_lb" "main" {
name = "${var.prefix}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = aws_subnet.public[*].id
enable_deletion_protection = false
tags = merge(local.tags, {
Name = "${var.prefix}-alb"
})
}
ターゲットグループの設定
resource "aws_lb_target_group" "web" {
name = "${var.prefix}-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = "/"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 2
}
tags = merge(local.tags, {
Name = "${var.prefix}-tg"
})
}
# ターゲットグループアタッチメント
resource "aws_lb_target_group_attachment" "web" {
count = length(aws_instance.web)
target_group_arn = aws_lb_target_group.web.arn
target_id = aws_instance.web[count.index].id
port = 80
}
# リスナー設定
resource "aws_lb_listener" "web" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web.arn
}
}
30 秒間隔でヘルスチェックを実行する設定です。
ローカル変数定義(locals.tf)
Terraform では locals
を使って、繰り返し利用する値やタグなどを一元管理できます。
locals {
tags = {
Project = "terraform-ec2-alb"
Environment = "dev"
Owner = "test"
}
}
これにより、各リソースの tags
に merge(local.tags, {...})
で共通タグを簡単に付与できます。
出力設定(outputs.tf)
Terraform 実行後に必要な情報を確認できるよう、出力設定を追加しました。
output "alb_dns_name" {
description = "DNS name of the load balancer"
value = aws_lb.main.dns_name
}
output "alb_zone_id" {
description = "The canonical hosted zone ID of the load balancer"
value = aws_lb.main.zone_id
}
output "ec2_instance_ids" {
description = "IDs of the EC2 instances"
value = aws_instance.web[*].id
}
output "ec2_public_ips" {
description = "Public IP addresses of the EC2 instances"
value = aws_instance.web[*].public_ip
}
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
構築完了後に ALB の DNS 名や EC2 インスタンスの IP アドレスなど、動作確認に必要な各種情報を出力させることができます。
構築コマンド
- 初期化
terraform init
- 確認
terraform plan
- 適用
terraform apply
Terraform の実行が完了すると、ALB の DNS などが出力されます。
動作確認
構築完了後、出力された ALB DNS 名(例:test-terraform-alb-xxxxxxxxx.ap-northeast-1.elb.amazonaws.com
)にブラウザでアクセスすると、以下のように表示されます。
Hello from EC2 Instance 1 Instance ID: i-xxxxxxxxxxxxxxxxx
ページをリロードするたびに、「Instance 1」と「Instance 2」が交互に表示され、正しく動作していることがわかります。
リソースの削除
テスト完了後は、以下のコマンドでリソースを削除できます。
terraform destroy
まとめ
今回、Terraform を使って EC2 + ALB 構成の Web アプリケーション環境を構築してみました。
インフラをコードで管理することで、構成の再現性や変更履歴の追跡が簡単になると感じました。
また、手作業による設定ミスも防げるので、運用の効率化や品質向上にも大きく貢献できると感じました。
次回は、Auto Scaling や RDS を追加して、より本格的な Web アプリケーション環境を構築してみたいと思います。
参考資料