August 28, 2020
TerraformでALB(Application Load Balancing)を作成する方法を解説します。
目次
Terraformを使用します。ソースコードの構成は以下の通り。
|-- main.tf ←モジュールを読み込むトップのファイル。
`-- modules
|-- vpc.tf ←VPCを設定するファイル。
|-- security_group.tf ←SecurityGroupを設定するファイル。
|-- iam.tf ←バケットポリシーを設定するファイル。
|-- s3.tf ←ALBのログ格納用バケットを設定するファイル。
|-- elb.tf ←ALBを設定するファイル。
また、Terraformのトップファイルは以下の通りです。共通の設定のため解説は割愛します。
【AWS】AssumeRoleしてTerraformをより安全に実行する!
./main.tf
provider "aws" {
region = "ap-northeast-1"
assume_role {
role_arn = "arn:aws:iam::XXXXXXXXXX:role/SystemAdmin"
}
}
terraform {
required_version = "0.12.24"
backend "s3" {
bucket = "tfstate.mini-schna.com"
region = "ap-northeast-1"
key = "blog/terraform-elb-basic.tfstate"
encrypt = true
role_arn = "arn:aws:iam::XXXXXXXXXX:role/SystemAdmin"
}
}
module "aws" {
source = "./modules"
}
以下の図は、今回作成する構成の全体像です。
今回のポイント!
※今回はALBの解説になるため、振り分け先であるECSを作成するコードは記載しておりません。
ALBを配置するVPCを作成します。今回のメインはALBのため、詳細な解説は省略します。
./modules/vpc.tf
variable "vpc_cidr_block" {
default = "150.0.0.0/16"
}
variable "subnet_cidr_block_public_a" {
default = "150.0.1.0/24"
}
variable "subnet_cidr_block_public_c" {
default = "150.0.2.0/24"
}
variable "subnet_cidr_block_private_web_a" {
default = "150.0.10.0/24"
}
variable "subnet_cidr_block_private_web_c" {
default = "150.0.20.0/24"
}
variable "az_a" {
default = "ap-northeast-1a"
}
variable "az_c" {
default = "ap-northeast-1c"
}
##################################################
# vpc
##################################################
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr_block
enable_dns_support = true
enable_dns_hostnames = true
}
##################################################
# Internet gateway
##################################################
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
}
##################################################
# public subnet
##################################################
resource "aws_subnet" "public_a" {
vpc_id = aws_vpc.main.id
cidr_block = var.subnet_cidr_block_public_a
availability_zone = var.az_a
}
resource "aws_subnet" "public_c" {
vpc_id = aws_vpc.main.id
cidr_block = var.subnet_cidr_block_public_c
availability_zone = var.az_c
}
resource "aws_route_table" "public_a" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table" "public_c" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table_association" "public_a" {
subnet_id = aws_subnet.public_a.id
route_table_id = aws_route_table.public_a.id
}
resource "aws_route_table_association" "public_c" {
subnet_id = aws_subnet.public_c.id
route_table_id = aws_route_table.public_c.id
}
# public_aがインターネットと通信するためのルーティング。
resource "aws_route" "to_internet_from_public_a" {
route_table_id = aws_route_table.public_a.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
# public_aがインターネットと通信するためのルーティング。
resource "aws_route" "to_internet_from_public_c" {
route_table_id = aws_route_table.public_c.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
##################################################
# private subnet (web)
##################################################
resource "aws_subnet" "private_web_a" {
vpc_id = aws_vpc.main.id
cidr_block = var.subnet_cidr_block_private_web_a
availability_zone = var.az_a
}
resource "aws_subnet" "private_web_c" {
vpc_id = aws_vpc.main.id
cidr_block = var.subnet_cidr_block_private_web_c
availability_zone = var.az_c
}
resource "aws_route_table" "private_web_a" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table" "private_web_c" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table_association" "private_web_a" {
subnet_id = aws_subnet.private_web_a.id
route_table_id = aws_route_table.private_web_a.id
}
resource "aws_route_table_association" "private_web_c" {
subnet_id = aws_subnet.private_web_c.id
route_table_id = aws_route_table.private_web_c.id
}
上述のコードでは、ざっくりと以下のようなリソースを作成しています。
・インターネットゲートウェイ
・パブリックサブネット
→インターネットと通信できるサブネット。ここにALBを配置していきます。
・プライベートサブネット
→インターネットと通信できないサブネット。ALBの振り分け先であるWebサーバを配置していきます。
ALBとWebサーバ用のセキュリティグループを作成します。
今回、許可が必要な通信を図にしました。
・インターネット→ALB
・ALB→ECS
この2パターンを作成していきます。
./moudles/security_group.tf
##################################################
# security group (ALB)
##################################################
resource "aws_security_group" "alb" {
name = "alb"
vpc_id = aws_vpc.main.id
revoke_rules_on_delete = false
}
# インターネットからALBに対するHTTP通信(ポート80)を許可するSG
resource "aws_security_group_rule" "alb_permit_from_internet_http" {
security_group_id = aws_security_group.alb.id
cidr_blocks = ["0.0.0.0/0"]
description = "permit from internet for http."
type = "ingress"
protocol = "tcp"
from_port = "80"
to_port = "80"
}
# インターネットからALBに対するHTTPS通信(ポート80)を許可するSG
resource "aws_security_group_rule" "alb_permit_from_internet_https" {
security_group_id = aws_security_group.alb.id
cidr_blocks = ["0.0.0.0/0"]
description = "permit from internet for https."
type = "ingress"
protocol = "tcp"
from_port = "443"
to_port = "443"
}
##################################################
# security group (web)
##################################################
resource "aws_security_group" "web" {
name = "web"
vpc_id = aws_vpc.main.id
revoke_rules_on_delete = false
}
# ALBからのHTTP通信(ポート80)を許可するSG
resource "aws_security_group_rule" "web_permit_from_alb" {
security_group_id = aws_security_group.web.id
source_security_group_id = aws_security_group.alb.id
description = "permit from alb."
type = "ingress"
protocol = "tcp"
from_port = "80"
to_port = "80"
}
ALBのアクセスログを格納するためのS3バケットを作成します。
【所要時間10分】Terraformを使って、S3でWebサイトを公開する!
ALBがS3にログを格納できるようにポリシーを作成していきます。
./modules/iam.tf
# ALBアカウントIDを取得するために使用
data "aws_elb_service_account" "main" {}
###############################################
# ログ格納用バケットポリシー
###############################################
data "aws_iam_policy_document" "logging_bucket" {
statement {
sid = ""
effect = "Allow"
principals {
identifiers = [
data.aws_elb_service_account.main.arn
]
type = "AWS"
}
actions = [
"s3:PutObject"
]
resources = [
"arn:aws:s3:::logging.mini-schna.com",
"arn:aws:s3:::logging.mini-schna.com/*"
]
}
}
ポイントは、principals
の identifiers
に設定する値。
ALBのアクセスログをS3に格納するためには、バケットポリシーにてリージョン毎に設定されているELBアカウントを許可する必要があります。(このELBアカウントは、自分のアカウントとは違うので注意!)
Terraformでは、このELBアカウントIDを取得するためのデータソースが用意されています。
data "aws_elb_service_account" "main" {}
こちらを使用して、principals
を設定します。
principals {
identifiers = [
data.aws_elb_service_account.main.arn
]
type = "AWS"
}
CloudFrontのアクセスログを格納するS3バケットの設定と同じため、より詳しい解説を知りたい方は以下の記事をご参照ください。
【AWS】Terraformを使って、CloudFrontを構築する!
./modules/s3.tf
###############################################
# ログ格納用バケット
###############################################
resource "aws_s3_bucket" "logging" {
bucket = "logging.mini-schna.com"
policy = data.aws_iam_policy_document.logging_bucket.json ## iam.tfで設定したポリシーを使用。
force_destroy = true
versioning {
enabled = true
mfa_delete = false
}
## オブジェクトのライフサイクル設定。
lifecycle_rule {
id = "assets"
enabled = true
## オブジェクトの保存期限。
expiration {
days = "365" ## 1年
}
## 現在のオブジェクトの移行設定。
transition {
## オブジェクトが作成されてから移行するまでの日数。
days = "93" ## 3ヶ月
storage_class = "STANDARD_IA"
}
## オブジェクトの以前のバージョンの保存期限。
noncurrent_version_expiration {
days = "1095" ## 3年
}
## 古いのオブジェクトの移行設定。
noncurrent_version_transition {
## オブジェクトが古いバージョンになってから移行するまでの日数。
days = "365" ## 1年
storage_class = "GLACIER"
}
}
request_payer = "BucketOwner"
}
# S3 Public Access Block
## パブリックアクセスはしないため全て有効にする。
resource "aws_s3_bucket_public_access_block" "logging" {
bucket = aws_s3_bucket.logging.bucket
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
ALBの振り分け先であるターゲットグループを作成します。
./modules/elb.tf
##################################################
# Target group
##################################################
resource "aws_lb_target_group" "mini_schna_com" {
name = "mini-schna-com"
port = 80
protocol = "HTTP"
target_type = "ip"
vpc_id = aws_vpc.main.id
# 登録解除を実行するまでの待機時間。
deregistration_delay = 300 # 処理中のリクエストが完了するのを待つためにデフォルト値を採用。
# 登録された後にリクエストを開始する猶予時間
slow_start = 0 # 登録されたらすぐに開始してよいので無効。
load_balancing_algorithm_type = "round_robin" # ラウンドロビンで平均的にリクエストを分散。
stickiness {
type = "lb_cookie"
cookie_duration = 86400 # 要件が決まっていないのでとりあえず1日を設定。
enabled = true
}
health_check {
enabled = true
interval = 30
path = "/"
port = "traffic-port" # トラフィックを受信するポートを使用。デフォルト。
protocol = "HTTP"
timeout = 5
healthy_threshold = 3
unhealthy_threshold = 3
matcher = "200-299"
}
}
コメントで解説しきれていない部分を補足していきます。
target_type = "ip"
ターゲットの種類を設定します。設定可能な値は以下の通り。
instance
:インスタンス
ip
:IPアドレス
lambda
:Lambda関数
今回はターゲットにECSを使用する予定なので、ip
を指定しています。
stickiness {
type = "lb_cookie"
cookie_duration = 86400 # 要件が決まっていないのでとりあえず1日を設定。
enabled = true
}
スティッキーセッションの設定。ALBでCookieに「AWSALB」を自動で設定して、同じターゲットに振り分けを行うことができる機能。
リスナー(接続をチェックする機能)を作成します。
./modules/elb.tf
variable "acm_certificate_arn" {
default = "arn:aws:acm:ap-northeast-1:XXXXXXXXXXXX:certificate/XXXXXXXXXXXXXXXXXXX"
}
...
##################################################
# Listener
##################################################
# HTTPS通信のためのリスナー
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.mini_schna_com.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-2016-08"
certificate_arn = var.acm_certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.mini_schna_com.arn
}
}
# HTTPをHTTPSにリダイレクトするためのリスナー
resource "aws_lb_listener" "http_redirect_to_https" {
load_balancer_arn = aws_lb.mini_schna_com.arn
port = 80
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = 443
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
それでは解説します。
作成するリスナーは2種類。
1つ目は、HTTPS通信のためのリスナー。「https://〜」にアクセスされた時、その通信をターゲットグループに転送するようにルールを設定しています。また、SSL証明書は、ACMで作成したもの指定しています。
2つ目は、HTTP通信のためのリスナー。「http://~」にアクセスされた時、「https://~」にリダイレクトするようルールを設定しています。
default_action {
type = "redirect"
redirect {
port = 443
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
ALBを作成します。
./modules/elb.tf
##################################################
# LB
##################################################
resource "aws_lb" "mini_schna_com" {
name = "mini-schna-com"
internal = false # 内部で使用しないため無効。
load_balancer_type = "application"
security_groups = [
aws_security_group.alb.id
]
access_logs {
bucket = aws_s3_bucket.logging.bucket
prefix = "elb"
enabled = true
}
subnets = [
aws_subnet.public_a.id,
aws_subnet.public_c.id
]
idle_timeout = 60 # デフォルトの60秒を設定。
enable_deletion_protection = false # Terraformで削除したいため無効。
enable_http2 = true
ip_address_type = "ipv4" # ipv6は使用しないためipv4を指定。
}
それでは解説します。
load_balancer_type = "application"
ALBを使用するため、application
を設定します。
idle_timeout = 60
①ALBからターゲットにリクエストを転送する。
②ターゲットで処理を行いALBに通信を返す。
この①と②に掛かる時間の設定になります。①と②が idle_timeout
の時間を超えた場合、ALBはエラーをクライアントに返します。今回はデフォルト値の60sを設定しました。適宜、アプリによって調整が必要です。
それでは作成したコードを元にTerraformを実行して、ALBを作成していきます。
$ cd /infra/terraform/20200825_terraform-elb-basic
$ terraform init
$ terraform apply
※Dockerコンテナ内での実行を前提にしています。
以上で、ALBの作成が完了です。
最後までご覧頂きありがとうございました。