ENGINEER BLOG ENGINEER BLOG
  • 公開日
  • 最終更新日

【Terraform】Terraformで作るAWSインフラ!ALBとEC2構成で「Hello World!」表示

この記事を共有する

目次

はじめに

こんにちは、サービスGの羽生です。
クラウド環境を管理コンソールから手作業で作るの面倒ですよね! 今回は Terraform を使って、ブラウザから 「Hello World!」 を表示できる環境を作ってみます!
さらに、 Amazon EC2(以下、EC2)にAWS Systems Manager(以下、SSM)で接続し、外部通信が可能な状態まで確認していきます。

前提

以下の準備が整っていることを前提に進めます。
  • 作業用の EC2 にTerraform がインストール済みであること
  • 作業用の EC2 に AWS CLI がインストール済みで、認証設定が完了していること

環境準備ができていない場合は、以下のドキュメントをもとに設定作業を実施してください。

・Terraform インストール
Install Terraform

・AWS CLI インストール
AWS CLI の最新バージョンのインストールまたは更新

概要

構築

以下の環境を構築します。

  • Amazon VPC(以下、VPC):パブリック+プライベートサブネット
  • EC2:プライベートサブネットに2台、Webサーバー(Apache)を起動
  • Application Load Balancer(以下、ALB):パブリックサブネットに配置、EC2 をターゲットにHTTPを転送
  • NAT ゲートウェイ:プライベートサブネットからの外部通信用
  • インターネットゲートウェイ:ALBのインターネットアクセス

動作確認

構築後、以下の動作確認を行います。

  • Webブラウザからのアクセス:ブラウザからALB のDNS名にアクセスし、「Hello World! <Hostname>」 が表示されること
  • SSMによるEC2への接続:管理コンソールから接続可能なこと
  • EC2から外部への通信:EC2へ接続後、外部へ通信が可能であること

構成図

ba0c7fd1340e8fd6a91711804bcc5977_2.png

手順

  1. 作業用EC2にログイン後、作業用ディレクトリに移動します。
    $ cd terraform-dev/
    

  2. 移動したディレクトリを以下のようなファイル構成にします。
    terraform-dev/
    ├── provider.tf
    ├── vpc.tf
    ├── ec2.tf
    └── alb.tf
    

  3. provider.tfファイル内に以下を記述して保存します。
    ここを押すと展開します
     provider "aws" {
      region  = "ap-northeast-1"
      // AWS CLIの認証プロファイル名を指定してください。
      profile = "psw-terraform"
    }
    

  4. vpc.tfファイル内に以下を記述して保存します。
    ここを押すと展開します
     // VPC作成
    resource "aws_vpc" "main_vpc" {
      cidr_block           = "10.0.0.0/16"
      enable_dns_support   = true
      enable_dns_hostnames = true
      tags = {
        Name = "terraform-dev-vpc"
      }
    }
    // インターネットゲートウェイ作成
    resource "aws_internet\_gateway" "igw" {
      vpc_id = aws_vpc.main_vpc.id
      tags = {
        Name = "terraform-dev-igw"
      }
    }
    // リージョナルNATゲートウェイ作成
    resource "aws_nat_gateway" "regional_ngw" {
      connectivity_type = "public"
      availability_mode = "regional"
      vpc_id            = aws_vpc.main_vpc.id
      tags = {
        Name = "terraform-dev-ngw"
      }
    }
    // ローカルブロックでサブネットCIDRを定義
    locals {
      public_subnets = {
        az1 = {
          az   = "ap-northeast-1a"
          cidr = "10.0.1.0/24"
        }
        az2 = {
          az   = "ap-northeast-1c"
          cidr = "10.0.2.0/24"
        }
      }
      private_subnets = {
        az1 = {
          az   = "ap-northeast-1a"
          cidr = "10.0.10.0/24"
        }
        az2 = {
          az   = "ap-northeast-1c"
          cidr = "10.0.20.0/24"
        }
      }
    }
    // パブリックサブネット作成
    resource "aws_subnet" "public" {
      for_each          = local.public_subnets
      vpc_id            = aws_vpc.main_vpc.id
      cidr_block        = each.value.cidr
      availability_zone = each.value.az
      tags = {
        Name = "terraform-dev-public-subnet-${each.value.az}"
      }
    }
    // プライベートサブネット作成
    resource "aws_subnet" "private" {
      for_each          = local.private_subnets
      vpc_id            = aws_vpc.main_vpc.id
      cidr_block        = each.value.cidr
      availability_zone = each.value.az
      tags = {
        Name = "terraform-dev-private-subnet-${each.value.az}"
      }
    }
    // ルートテーブル作成
    resource "aws_route_table" "public" {
      vpc_id = aws_vpc.main_vpc.id
      route {
        cidr_block = "0.0.0.0/0"
        gateway_id = aws_internet_gateway.igw.id
      }
      tags = {
        Name = "terraform-dev-public-rt"
      }
    }
    resource "aws_route_table" "private" {
      vpc_id = aws_vpc.main_vpc.id
      route {
        cidr_block     = "0.0.0.0/0"
        nat_gateway_id = aws_nat_gateway.regional_ngw.id
      }
      tags = {
        Name = "terraform-dev-private-rt"
      }
    }
    // サブネットとルートテーブルの関連付け
    resource "aws_route_table_association" "public_assoc" {
      for_each       = aws_subnet.public
      subnet_id      = each.value.id
      route_table_id = aws_route_table.public.id
    }
    resource "aws_route_table_association" "private_assoc" {
      for_each       = aws_subnet.private
      subnet_id      = each.value.id
      route_table_id = aws_route_table.private.id
    }
    

  5. ec2.tfファイル内に以下を記述して保存します。
    ここを押すと展開します
     // プライベートサブネットリスト化
    locals {
      private_subnet_ids = values(aws_subnet.private)[*].id
    }
    // EC2用セキュリティグループ
    resource "aws_security_group" "web_sg" {
      name   = "terraform-dev-web-sg"
      vpc_id = aws_vpc.main_vpc.id
      ingress {
        from_port       = 80
        to_port         = 80
        protocol        = "tcp"
        security_groups = [aws_security_group.alb_sg.id]
      }
      egress {
        from_port   = 0
        to_port     = 0
        protocol    = "-1"
        cidr_blocks = ["0.0.0.0/0"]
      }
      tags = {
        Name = "terraform-dev-web-sg"
      }
    }
    // Amazon Linux2023の最新AMI取得
    data "aws_ssm_parameter" "al2023" {
      name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"
    }
    // webインスタンス作成(2台)
    resource "aws_instance" "web" {
      count         = 2
      ami           = data.aws_ssm_parameter.al2023.value
      instance_type = "t2.micro"
      subnet_id = local.private_subnet_ids[
        count.index % length(local.private_subnet_ids)
      ]
      vpc_security_group_ids = [aws_security_group.web_sg.id]
      iam_instance_profile   = aws_iam_instance_profile.ssm_profile.name
      // ユーザーデータ指定
      user_data = <<-EOF
            //!/bin/bash
            yum update -y
            yum install -y httpd
            systemctl enable httpd
            systemctl start httpd
            /*
            Amazon Linux 2023 では SSM Agent はデフォルトでインストール・起動されていますが、念のため明示的に起動しています
            */
            systemctl enable amazon-ssm-agent
            systemctl start amazon-ssm-agent
            HOSTNAME=$(hostname)
            echo "

    Hello World!

    Hostname: $HOSTNAME

    " > /var/www/html/index.html EOF tags = { Name = "terraform-dev-web-ec2-${count.index + 1}" } } // IAMロール作成 resource "aws_iam_role" "ssm_role" { name = "terraform-dev-ssmrole" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Principal = { Service = "ec2.amazonaws.com" } Action = "sts:AssumeRole" } ] }) } resource "aws_iam_role_policy_attachment" "ssm_attach" { role = aws_iam_role.ssm_role.name policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" } resource "aws_iam_instance_profile" "ssm_profile" { name = "terraform-dev-ssmprofile" role = aws_iam_role.ssm_role.name }

  6. alb.tfファイル内に以下を記述して保存します。
    ここを押すと展開します
     // ALB用セキュリティグループ
    resource "aws_security_group" "alb_sg" {
      name   = "terraform-dev-alb-sg"
      vpc_id = aws_vpc.main_vpc.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 = {
        Name = "terraform-dev-alb-sg"
      }
    }
    // ALB作成
    resource "aws_lb" "alb" {
      name               = "terraform-dev-alb"
      internal           = false
      load_balancer_type = "application"
      security_groups = [
        aws_security_group.alb_sg.id
      ]
      subnets = values(aws_subnet.public)[*].id
      tags = {
        Name = "terraform-dev-alb"
      }
    }
    // ターゲットグループ作成
    resource "aws_lb_target_group" "tg" {
      name        = "terraform-dev-tg"
      port        = 80
      protocol    = "HTTP"
      target_type = "instance"
      vpc_id      = aws_vpc.main_vpc.id
      health_check {
        path                = "/"
        interval            = 30
        timeout             = 5
        healthy_threshold   = 3
        unhealthy_threshold = 3
      }
      tags = {
        Name = "terraform-dev-tg"
      }
    }
    // HTTPリスナー作成
    resource "aws_lb_listener" "http" {
      load_balancer_arn = aws_lb.alb.arn
      port              = 80
      protocol          = "HTTP"
      default_action {
        type             = "forward"
        target_group_arn = aws_lb_target_group.tg.arn
      }
    }
    // EC2インスタンスをターゲットグループに登録
    resource "aws_lb_target_group_attachment" "web" {
      count            = 2
      target_group_arn = aws_lb_target_group.tg.arn
      target_id        = aws_instance.web[count.index].id
      port             = 80
    }
    

  7. 以下コマンドを順番に実行します。
    // モジュールインストール
    $ terraform init
    
    // 実行内容を確認 $ terraform plan
    // 実行 $ terraform apply

  8. マネジメントコンソールを開き、構築予定のリソースが作成されていることを確認します。

動作確認

  1. EC2→ロードバランサーの順に操作し、「詳細」タブで、DNS名を確認します。 ALB-DNS.png
  2. Webブラウザ(Chromeなど)を起動し、アドレスバーに以下を入力しアクセスします。
    http://"ALBのDNS名"
    

  3. ページにアクセスでき、「Hello World!」が表示されていたら成功です! HostnameにはEC2のホスト名が表示されるようになっているので、何度かリロードしてホスト名が切り替わることも確認してみましょう!(ALBのラウンドロビン負荷分散の動作) Hello-World!.png
  4. 管理コンソールに戻り、対象インスタンスにチェックをいれて「接続」を押し、EC2に接続できることを確認します。 EC2-SSM.png
  5. コマンドラインの画面が表示されれば接続完了です! CLI.png
  6. EC2への接続が確認できたら、以下コマンドを実行してインターネットへの接続ができることを確認します。
    // Googleドメイン名への疎通確認(外部向け通信)
    $ ping google.com
    

  7. 上記すべての動作確認が正常であれば完了です!構築と動作確認まで問題なくできましたね!

リソース削除

構築したリソースは利用料金が発生する可能性がありますので、以下コマンドを実行して環境を削除しましょう。
// 環境削除
$ terraform destroy

最後に

今回は Terraform を使って、基本的なWeb構成を構築しました。

  • ALB 経由で Hello World が表示されること
  • EC2 に SSM で安全に接続できること
  • プライベートサブネットから外部通信が可能であること

これら動作をTerraformで再現できることを確認できました。
また、Auto ScalingやHTTPS化などを追加すれば、より実践的な環境にも発展させることができます!

コードによる再現性やヒューマンエラーの防止目的で多くのシステムで活用されるTerraformですので、引き続き知見を深めていきたいですね!

おまけ

本題から逸れるため説明は省きましたが、本構成での NAT ゲートウェイ は、昨年11月にリリースされた リージョナルNAT Gateway で構築しています。 従来のNAT Gatewayとの比較やメリットは、AWS公式ドキュメントが参考になるのでぜひご覧ください。

自動マルチ AZ 拡張用のリージョン NAT ゲートウェイ

この記事は私が書きました

S.Hanyu

記事一覧

サッカー観戦(プレミア、ラ・リーガ)とコーヒーが好きです。 Terraformが好きでよく触っています。

S.Hanyu

この記事を共有する

クラウドのご相談

CONTACT

クラウド導入や運用でお悩みの方は、お気軽にご相談ください。
専門家がサポートします。

サービス資料ダウンロード

DOWNLOAD

ビジネスをクラウドで加速させる準備はできていますか?
今すぐサービス資料をダウンロードして、詳細をご確認ください。