🌱

AWS&TerraformでWebサーバとALBを立ててみる

2022/04/19に公開

想定読者

  • Terraformの構文は勉強したけど、リソース作成はこれからの方
    • 構成例を元に手を動かしてみたい方
  • AWSを軽く触ったことがある方

はじめに

Terraform初学者の人がAWS上で実際に手を動かして作れる構成を考えてみました。
構成図は以下の通りです。
EC2-ALB

全てのコードは以下のリポジトリに格納されています。
https://github.com/guranytou/tf-ec2-alb

各リソースのパラメータについては公式ドキュメントをご確認ください。
https://registry.terraform.io/providers/hashicorp/aws/latest

事前準備

terraformを利用するために、main.tfを用意します。
また利用したいAWSのregionはここで設定します(今回はap-northeasw-1(東京)を指定)

main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.9"
    }
  }
}

variable "aws_access_key" {}
variable "aws_secret_access_key" {}

provider "aws" {
  region     = "ap-northeast-1"
  access_key = var.aws_access_key
  secret_key = var.aws_secret_access_key
}

AWSのCredentialを設定しておく必要があります。
今回はサクッと使いたいので、terraform.tfvarsを利用することとします。

terraform.tfvars
aws_access_key        = "YOUR_AWS_ACCESS_KEY"
aws_secret_access_key = "YOUR_AWS_SECRET_ACCESS_KEY"

上記を用意後、CLI上でterraform initを叩き、Terraform has been successfully initialized!と表示されることを確認しましょう。

解説

  1. VPCを作成する
    まずはVPCを作成していきます。CIDRなどはお好きなもので大丈夫です。
    Nameタグは自分が今回作ったリソースだとわかるようなものに変更してください。
    (その他のリソースも同様ですが、ここでは例のためexampleとしています)
network.tf
resource "aws_vpc" "example" {
  cidr_block = "10.100.0.0/16"

  tags = {
    Name = "example"
  }
}

試しにここで terraform planを叩き、以下の表示が出ることを確認しましょう。

  # aws_vpc.example will be created
  + resource "aws_vpc" "example" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.100.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_classiclink                   = (known after apply)
      + enable_classiclink_dns_support       = (known after apply)
      + enable_dns_hostnames                 = (known after apply)
      + enable_dns_support                   = true
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Name" = "example"
        }
      + tags_all                             = {
          + "Name" = "example"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

では実際にterraform applyを実行し、リソースを作成してみましょう。
Enter a valueと聞かれるので、yesを入力します。
以下の表示が出れば作成できているので、GUI上でも確認してみましょう。

aws_vpc.example: Creating...
aws_vpc.example: Creation complete after 1s [id=vpc-02c546f38de3782ac]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

これ以降のステップでも都度terraform plan及びterraform applyを行って、実際にリソースができているか確認していきましょう。

  1. サブネットを作成する
    次にサブネットを作成していきます。CIDRなどはお好きなもので大丈夫です。
    今回public subnetを2つ作成していますが、これはALBにサブネットを登録する際に2つ以上のAZにまたがっている必要があるためです。
    実際に今回利用するのは1aのみです(そのためprivateは1aのみ作成しています)
network.tf
# public
resource "aws_subnet" "example_public_1a" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.100.0.0/24"
  availability_zone       = "ap-northeast-1a"

  tags = {
    Name = "example_public_1a"
  }
}

resource "aws_subnet" "example_public_1c" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.100.1.0/24"
  availability_zone       = "ap-northeast-1c"

  tags = {
    Name = "example_public_1c"
  }
}

# private
resource "aws_subnet" "example_private_1a" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.100.100.0/24"
  availability_zone       = "ap-northeast-1a"

  tags = {
    Name = "example_private_1a"
  }
}

  1. IGW/NAT GWを作成する
    インターネットからとの接続に必要なInternet GatewayとNAT Gatewayを作成します。
network.tf
# Internet GW
resource "aws_internet_gateway" "example" {
  vpc_id = aws_vpc.example.id

  tags = {
    Name = "example"
  }
}

# NAT GW
resource "aws_nat_gateway" "example" {
  allocation_id = aws_eip.nat_gw.id
  subnet_id     = aws_subnet.example_public_1a.id
  depends_on    = [aws_internet_gateway.example]

  tags = {
    Name = "example"
  }
}

# EIP(NATGW)
resource "aws_eip" "nat_gw" {
  vpc = true
}
  1. IGW/NATGW用のルートテーブルを作成する
    ルートテーブルにて、各サブネットとInternet Gateway及びNAT Gatewayとの経路を用意します。
network.tf
# RouteTable/Public
resource "aws_route_table" "example_internet_gw" {
  vpc_id = aws_vpc.example.id

  tags = {
    Name = "example_route_internet_gw"
  }
}

resource "aws_route" "example_internet_gw_default" {
  route_table_id         = aws_route_table.example_internet_gw.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.example.id
}

resource "aws_route_table_association" "example_internet_gw_1a" {
  subnet_id      = aws_subnet.example_public_1a.id
  route_table_id = aws_route_table.example_internet_gw.id
}

resource "aws_route_table_association" "example_internet_gw_1c" {
  subnet_id      = aws_subnet.example_public_1c.id
  route_table_id = aws_route_table.example_internet_gw.id
}

# RouteTable/Private
resource "aws_route_table" "example_private" {
  vpc_id = aws_vpc.example.id

  tags = {
    Name = "example_route_private"
  }
}

resource "aws_route" "example_private_default" {
  route_table_id         = aws_route_table.example_private.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.example.id
}

resource "aws_route_table_association" "example_private_1" {
  subnet_id      = aws_subnet.example_private_1a.id
  route_table_id = aws_route_table.example_private.id
}
  1. EC2を作成する
    Private SubnetにEC2を作成します。合わせてEC2に付与するSGも作成していきます。
server.tf
# EC2
resource "aws_instance" "example" {
  ami                    = "ami-0992fc94ca0f1415a"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.example_for_instance.id]
  subnet_id              = aws_subnet.example_private_1a.id

  user_data = file("install_apache.sh")

  tags = {
    Name = "example"
  }
}

# SG(ALB Allow)
resource "aws_security_group" "example_for_instance" {
  name   = "example_instance"
  vpc_id = aws_vpc.example.id

  tags = {
    Name = "example_for_instance"
  }
}

resource "aws_security_group_rule" "sg_ingress_instance" {
  type                     = "ingress"
  from_port                = 80
  to_port                  = 80
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.example_for_alb.id
  security_group_id        = aws_security_group.example_for_instance.id
}

resource "aws_security_group_rule" "sg_egress_instance" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.example_for_instance.id
}

今回はuser_dataも作成と合わせて読み込みたいので、user_data用にinstall_apache.shも作成します。

install_apache.sh
#! /bin/bash

yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
  1. ALBを作成する
    EC2の前面にALBを配置したいので、Public SubnetにALBを作成します。
    合わせてHTTPのみ許可するSGを作成します。
server.tf
# ALB
resource "aws_lb" "example" {
  name                             = "example"
  load_balancer_type               = "application"
  internal                         = false
  security_groups                  = [aws_security_group.example_for_alb.id]
  subnets = [
    aws_subnet.example_public_1a.id,
    aws_subnet.example_public_1c.id
  ]

  tags = {
    Name = "example"
  }
}

# SG
resource "aws_security_group" "example_for_alb" {
  name   = "example_http"
  vpc_id = aws_vpc.example.id

  tags = {
    Name = "example_for_alb"
  }
}

resource "aws_security_group_rule" "sg_ingress" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.example_for_alb.id
}

resource "aws_security_group_rule" "sg_egress" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.example_for_alb.id
}
  1. ALB用のListener/TGを作成する
    先ほど作成したEC2をALBと紐付けしたいので、ListenerとTarget Groupを作成します。
    また、今回Apacheのデフォルト画面を表示させる関係上、health_checkのmatcherを403にしています。
    (Apacheのデフォルト画面のステータスコードが403であるため)
service.tf
# LB - Listener
resource "aws_lb_listener" "example" {
  load_balancer_arn = aws_lb.example.arn
  port              = 80
  protocol          = "HTTP"
  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.example.arn
  }
}

# LB - TargetGroup
resource "aws_lb_target_group" "example" {
  name     = "example"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.example.id

  health_check {
    path                = "/"
    matcher             = 403
    port                = 80
    protocol            = "HTTP"
  }

  depends_on = [aws_lb.example]
}

resource "aws_lb_target_group_attachment" "example" {
  target_group_arn = aws_lb_target_group.example.arn
  target_id        = aws_instance.example.id
  port             = 80
}
  1. 確認する
    terraform applyを行い、各リソースをapplyし終えたら、ALBのDNS名からアクセスしてみます。
    Apacheのデフォルト画面が表示されればOKです。

まとめ

AWS&TerraformでALB-EC2の構成を簡単に作ってみました。
この記事を参考にリソースを作ってみて、少しでもTerraform&AWSの理解の助けになれば幸いです。

Discussion