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 "awsvpc" "mainvpc" {
      cidrblock           = "10.0.0.0/16"
      enablednssupport   = true
      enablednshostnames = true
      tags = {
        Name = "terraform-dev-vpc"
      }
    }
    // インターネットゲートウェイ作成
    resource "awsinternetgateway" "igw" {
      vpcid = awsvpc.mainvpc.id
      tags = {
        Name = "terraform-dev-igw"
      }
    }
    // リージョナルNATゲートウェイ作成
    resource "awsnatgateway" "regionalngw" {
      connectivitytype = "public"
      availabilitymode = "regional"
      vpcid            = awsvpc.mainvpc.id
      tags = {
        Name = "terraform-dev-ngw"
      }
    }
    // ローカルブロックでサブネットCIDRを定義
    locals {
      publicsubnets = {
        az1 = {
          az   = "ap-northeast-1a"
          cidr = "10.0.1.0/24"
        }
        az2 = {
          az   = "ap-northeast-1c"
          cidr = "10.0.2.0/24"
        }
      }
      privatesubnets = {
        az1 = {
          az   = "ap-northeast-1a"
          cidr = "10.0.10.0/24"
        }
        az2 = {
          az   = "ap-northeast-1c"
          cidr = "10.0.20.0/24"
        }
      }
    }
    // パブリックサブネット作成
    resource "awssubnet" "public" {
      foreach          = local.publicsubnets
      vpcid            = awsvpc.mainvpc.id
      cidrblock        = each.value.cidr
      availabilityzone = each.value.az
      tags = {
        Name = "terraform-dev-public-subnet-${each.value.az}"
      }
    }
    // プライベートサブネット作成
    resource "awssubnet" "private" {
      foreach          = local.privatesubnets
      vpcid            = awsvpc.mainvpc.id
      cidrblock        = each.value.cidr
      availabilityzone = each.value.az
      tags = {
        Name = "terraform-dev-private-subnet-${each.value.az}"
      }
    }
    // ルートテーブル作成
    resource "awsroutetable" "public" {
      vpcid = awsvpc.mainvpc.id
      route {
        cidrblock = "0.0.0.0/0"
        gatewayid = awsinternetgateway.igw.id
      }
      tags = {
        Name = "terraform-dev-public-rt"
      }
    }
    resource "awsroutetable" "private" {
      vpcid = awsvpc.mainvpc.id
      route {
        cidrblock     = "0.0.0.0/0"
        natgatewayid = awsnatgateway.regionalngw.id
      }
      tags = {
        Name = "terraform-dev-private-rt"
      }
    }
    // サブネットとルートテーブルの関連付け
    resource "awsroutetableassociation" "publicassoc" {
      foreach       = awssubnet.public
      subnetid      = each.value.id
      routetableid = awsroutetable.public.id
    }
    resource "awsroutetableassociation" "privateassoc" {
      foreach       = awssubnet.private
      subnetid      = each.value.id
      routetableid = awsroutetable.private.id
    }
    

  5. ec2.tfファイル内に以下を記述して保存します。
    ここを押すと展開します
     // プライベートサブネットリスト化
    locals {
      privatesubnetids = values(awssubnet.private)[*].id
    }
    // EC2用セキュリティグループ
    resource "awssecuritygroup" "websg" {
      name   = "terraform-dev-web-sg"
      vpcid = awsvpc.mainvpc.id
      ingress {
        fromport       = 80
        toport         = 80
        protocol        = "tcp"
        securitygroups = [awssecuritygroup.albsg.id]
      }
      egress {
        fromport   = 0
        toport     = 0
        protocol    = "-1"
        cidrblocks = ["0.0.0.0/0"]
      }
      tags = {
        Name = "terraform-dev-web-sg"
      }
    }
    // Amazon Linux2023の最新AMI取得
    data "awsssmparameter" "al2023" {
      name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x8664"
    }
    // webインスタンス作成(2台)
    resource "awsinstance" "web" {
      count         = 2
      ami           = data.awsssmparameter.al2023.value
      instancetype = "t2.micro"
      subnetid = local.privatesubnetids[
        count.index % length(local.privatesubnetids)
      ]
      vpcsecuritygroupids = [awssecuritygroup.websg.id]
      iaminstanceprofile   = awsiaminstanceprofile.ssmprofile.name
      // ユーザーデータ指定
      userdata = <<-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
    iamrole" "ssmrole" { name = "terraform-dev-ssmrole" assumerolepolicy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Principal = { Service = "ec2.amazonaws.com" } Action = "sts:AssumeRole" } ] }) } resource "awsiamrolepolicyattachment" "ssmattach" { role = awsiamrole.ssmrole.name policyarn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" } resource "awsiaminstanceprofile" "ssmprofile" { name = "terraform-dev-ssmprofile" role = awsiamrole.ssmrole.name }

  6. alb.tfファイル内に以下を記述して保存します。
    ここを押すと展開します
     // ALB用セキュリティグループ
    resource "awssecuritygroup" "albsg" {
      name   = "terraform-dev-alb-sg"
      vpcid = awsvpc.mainvpc.id
      ingress {
        fromport   = 80
        toport     = 80
        protocol    = "tcp"
        cidrblocks = ["0.0.0.0/0"]
      }
      egress {
        fromport   = 0
        toport     = 0
        protocol    = "-1"
        cidrblocks = ["0.0.0.0/0"]
      }
      tags = {
        Name = "terraform-dev-alb-sg"
      }
    }
    // ALB作成
    resource "awslb" "alb" {
      name               = "terraform-dev-alb"
      internal           = false
      loadbalancertype = "application"
      securitygroups = [
        awssecuritygroup.albsg.id
      ]
      subnets = values(awssubnet.public)[*].id
      tags = {
        Name = "terraform-dev-alb"
      }
    }
    // ターゲットグループ作成
    resource "awslbtargetgroup" "tg" {
      name        = "terraform-dev-tg"
      port        = 80
      protocol    = "HTTP"
      targettype = "instance"
      vpcid      = awsvpc.mainvpc.id
      healthcheck {
        path                = "/"
        interval            = 30
        timeout             = 5
        healthythreshold   = 3
        unhealthythreshold = 3
      }
      tags = {
        Name = "terraform-dev-tg"
      }
    }
    // HTTPリスナー作成
    resource "awslblistener" "http" {
      loadbalancerarn = awslb.alb.arn
      port              = 80
      protocol          = "HTTP"
      defaultaction {
        type             = "forward"
        targetgrouparn = awslbtargetgroup.tg.arn
      }
    }
    // EC2インスタンスをターゲットグループに登録
    resource "awslbtargetgroupattachment" "web" {
      count            = 2
      targetgrouparn = awslbtargetgroup.tg.arn
      targetid        = awsinstance.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

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