5
2

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でEC2 + ALB環境を構築してみた

Posted at

はじめに

今回は、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 の基本的なワークフローは以下の通りです。

  1. Write: HCL を用いてインフラ構成を記述
  2. Plan: terraform plan コマンドを実行し、現在の構成と記述された構成の差分を確認
  3. 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.tf
provider "aws" {
  region  = "ap-northeast-1"
  profile = "test"
}

東京リージョンを選択し、事前に設定済みの AWS プロファイルを指定しました。

変数定義(variables.tf)

設定値は変数として管理することで、再利用性を高めています。

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 の作成

vpc.tf
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 準拠)を使用しています。

パブリックサブネットの作成

vpc.tf
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 用セキュリティグループ

security_groups.tf
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 用セキュリティグループ

security_groups.tf
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 選択

ec2.tf
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 インスタンスの作成

ec2.tf
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 本体の作成

alb.tf
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"
  })
}

ターゲットグループの設定

alb.tf
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.tf
locals {
  tags = {
    Project     = "terraform-ec2-alb"
    Environment = "dev"
    Owner       = "test"
  }
}

これにより、各リソースの tagsmerge(local.tags, {...}) で共通タグを簡単に付与できます。

出力設定(outputs.tf)

Terraform 実行後に必要な情報を確認できるよう、出力設定を追加しました。

outputs.tf
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 アドレスなど、動作確認に必要な各種情報を出力させることができます。

構築コマンド

  1. 初期化
    terraform init
    
  2. 確認
    terraform plan
    
  3. 適用
    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 アプリケーション環境を構築してみたいと思います。

参考資料

5
2
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
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?