1. ホーム
  2. 記事一覧
  3. 負荷分散実践!TerraformでAWSのALB(Application Load Balancer)構築ハンズオン

2024.05.03

負荷分散実践!TerraformでAWSのALB(Application Load Balancer)構築ハンズオン

はじめに

ELB(Elastic Load Balancing )は、AWSが提供するフルマネージドなロードバランシングサービスです。

ロードバランシングサービスとは、ウェブサイトやアプリケーションなどが複数のサーバーで構成されている場合に、リクエストを自動的にかつ効率良く分散してくれるサービスのことです。

具体的には、ECサイトや動画配信サイト、ゲームサーバーなど大量のリクエストが発生する可能性があるサービスに使用します。

1つのサーバーに対して過剰な負荷がかからないよう、サーバーの前に立ちリクエストの振り分け役をしてくれるのがELBです。

そんなELBには次の4つの種類があります。

  • Application Load Balancer
  • Network Load Balancer
  • Gateway Load Balancer
  • Classic Load Balancer

この記事では、Terraformを利用して4つあるELBサービスのうち、ALB(Application Load Balancer)の構築方法を解説します。

TerraformはIaC(Infrastructure aCode)と呼ばれる手法の1つで、インフラストラクチャをコードとして定義し、コードを通じてインフラストラクチャの作成、変更、管理ができるようになります。以下の記事でIaCを詳しく解説しています。

https://envader.plus/article/136

記事を参考に、是非手を動かしながらELBの構築方法を理解しましょう。

ALBとは

ALB(Application Load Balancer)とは、AWSが提供するロードバランサーの1つで、ネットワークの基本、OSI参照モデル第7層、アプリケーション層で動作します。

ALBはHTTPまたはHTTPS通信に基づいて負荷分散を行うシステムになり、リクエストに含まれるパスやホスト名、ヘッダーなどに応じて通信を複数の転送先に振り分けることができます。

また、負荷の増減に応じて自動的にスケールアップ、スケールアウトしてくれるAWSマネージドなサービスです。

OSI参照モデルについては、以下の記事にて解説しています。

https://envader.plus/article/38

Terraformとは

Terraformとは、インフラ環境をコードで定義して構築するIaC(Infrastructure as Code)ツールの一つで、業界ではデファクトスタンダードとされています。

Terraformでは主に、準備、計画、実行、削除の4つを管理するだけというシンプルさを持っており、最低限この4つのコマンドさえ覚えていればTerraformを扱えるという特徴があります。

Terraformを利用するには、installや設定が必要になります。以下の記事では、install方法や設定方法など環境構築に関して解説しています。

https://envader.plus/article/162

ALBとNLB(Network Load Balancer)の違い

NLB(Network Load Balancer)は、ALBとは異なるレイヤーで動作します。

ALBはアプリケーション層で動作するのに対し、NLBはTCP、UDPといったOSI参照モデル第4層、トランスポート層で動作するロードバランサーです。

ALBとNLBではリクエストの処理に違いがある

NLBは瞬間的な数百万リクエスト/秒の通信も処理できるよう設計されているため、短時間に急激にアクセスが急増しても対応することができます。

対してALBは、リクエストに含まれるURLやホスト名に基づき負荷分散を行いますが、短時間にアクセスが急増した場合、スケーリングが間に合わずレスポンスの遅延やタイムアウト(正常なレスポンスが返せない)が発生することがあります。

ALBのデメリットは事前に対策ができる

ALBのレスポンス遅延、タイムアウトに対しては事前に対策をすることができます。

具体的には、「Pre-Warming」と呼ばれる暖機運転をAWSに申請するか、事前に段階的に負荷をかけスケールさせておくことで対策を実施することが可能です。

ただし、Pre-Warmingを申請するにはBusinessまたはEnterpriseサポートプランが必要になるため注意が必要です。

セキュリティ面での違い

セキュリティ面での主な違いは、WAF、セキュリティグループの扱いです。

ALBはWAF(Web Application Firewall)に関連づけることができますが、NLBではこの機能はサポートされていません。

セキュリティグループに関しては、2023年7月まではALBのみにアタッチすることが可能でしたが、2023年8月よりNLBでもサポートが開始されました。

ただ、2024年4月現在、NLB作成時にアタッチしなければいけないという制限があります。NLB作成後に途中でセキュリティグループをアタッチすることはできない点に注意が必要です。

https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/network/load-balancer-security-groups.html#security-group-considerations

このことから、WAFを使用したより細かな通信制御が必要な場合はALBを利用することが考えられますが、数百万といった大量のリクエストを一度に処理する必要がある場合はNLBが適していると言えます。

ALB、NLBではそれぞれ性能に違いがあるため、扱うサービスによってどちらを利用するべきかは検討が必要です。

ALBの特徴

「NLBとの違い」のセクションでも述べましたが、ここからはもう少し深くALBの特徴を解説します。

コンテンツベースのルーティング

コンテンツベースとは、リクエスト(コンテンツ)の内容に応じてどのサーバーへ転送するかを振り分けることが可能です。

コンテンツとは何かというと、リクエストに含まれるパス、ホスト名、ヘッダー、クエリ文字列などを指します。

パスの例では、/pathAであればサーバーaに、/pathBであればサーバーbに通信を振り分けることが可能です。

ホスト名では、リクエストの内容がtokyo.example.comであればサーバaに、osaka.example.comであればサーバーbに振り分ける、というようになります。

このように、ALBでは1つのロードバランサーで複数のターゲット(サーバー)に柔軟に通信を振り分けることができます。

WAFとの連携

ALBはWAFとの連携ができることも特徴の一つです。

WAFとはWeb Application Firewallの略で、WebアプリケーションをSQLインジェクションやXSS(クロスサイトスクリプティング)、DDos攻撃などのサイバー攻撃から保護することができます。

具体的には、リクエストレート(一定のリクエスト数を超えたらブロックする)の設定や、特定のIPアドレスや地域からのアクセス制限が可能になり、様々な種類のサイバー攻撃からALBを保護することができます。

Lambda関数をターゲットに指定できる

2018年11月以降、ALBではLambda関数をターゲットとして指定できるようになりました。

https://aws.amazon.com/jp/blogs/news/lambda-functions-as-targets-for-application-load-balancers/

これによって、ALB への通信をトリガーにして直接Lambda関数を実行できるようになり、前述したコンテンツベースのルーティングと組み合わせることで複数のLambda関数を実行することが可能になります。

例えば、リクエスト/pathAではLambda関数Aを、/pathBではLambda関数Bを実行するといったように、リクエストの内容によってLambda関数の使い分けができるのもALBの特徴となります。

そのほか、ALBの特徴については次の記事でも解説しています。

https://envader.plus/article/306

ELBの構成要素

ここからは、ELBがどのように構成されているのかを解説します。

ELBは主に次の4つで構成されています。

  • リスナー
  • リスナールール
  • ターゲット
  • ターゲットグループ

リスナー

リスナーとは、ELBが通信を受け付けるプロトコルとポート番号の設定です。ELBにはこの設定が必ず必要で、設定しないとELBで通信を受け付けることができません。

ALBではHTTPとHTTPSに対応しているため、プロトコルとポート番号はこの2つどちらかを指定します。

リスナールール

リスナールールでは、通信を転送する条件と、転送アクションを設定します。

ALB固有の機能であるコンテンツベースのルーティングを実現させるのが、このリスナールールになります。

ここで具体的に、リクエストパスが/pathAだったらサーバーAに転送して、/pathBだったらLambda関数を実行させる。といったルールを定義することができます。

ターゲット

ターゲットとは、受信した通信の最終的な転送先になります。ロードバランサーで受信したリクエストは最終的に、このターゲットに転送されます。

後述するターゲットグループを作成するにはこのターゲットを指定する必要があり、ターゲットには次の種類を指定することが可能です。

  • インスタンスID
  • IPアドレス
  • Lambda関数

ターゲットグループ

1つ以上のターゲットをまとめたグループがターゲットグループです。リスナールールでは、このターゲットグループを通信の転送先に指定します。

ターゲットグループに転送された通信は、最終的にグループ内の対象のターゲットにルーティングされます。

このように、通信は対象のターゲットに直接転送されるのではなく、一度ターゲットグループへ転送されて、ターゲットグループから対象のターゲットへと転送される仕組みになっています。

https://aws.amazon.com/jp/builders-flash/202211/awsgeek-alb/?awsf.filter-name=*all

ALBを実装してみよう

ここからは、Terraformを使って実際にALBを作成してみましょう。

今回はEC2 Auto Scaling Groupを作成し、Auto Scaling Groupに対してALBを設定します。

EC2 Auto Scaling Groupは以下の記事で作成したものを利用するため、構築方法が分からない方はリンクの記事を参照してください。

https://envader.plus/article/356

IGW(Internet Gateway)を追加

はじめに、インターネットとの通信を実現させるため、IGW(Internet Gateway)を追加します。

上記リンクの記事、main.tf内にこれ以降のソースコードを追加します。

# Internet Gatewayリソースを定義し、VPCに紐付け
resource "aws_internet_gateway" "myapp_igw" {
   vpc_id = aws_vpc.myapp_vpc.id
 }

Route Tableの作成と関連付け

次に、Route Tableを作成し、IGWへのルーティングとそれぞれのサブネットへの関連付けを行います。

# ルートテーブルを作成し、デフォルトルートとしてIGWを設定
resource "aws_route_table" "myapp_rt" {
   vpc_id = aws_vpc.myapp_vpc.id

   route {
     cidr_block = "0.0.0.0/0"
     gateway_id = aws_internet_gateway.myapp_igw.id
   }
 }

 # ルートテーブルをサブネットに関連付け
 resource "aws_route_table_association" "myapp_rta" {
   subnet_id      = aws_subnet.myapp_subnet_a.id
   route_table_id = aws_route_table.myapp_rt.id
 }

 resource "aws_route_table_association" "myapp_rta2" {
   subnet_id      = aws_subnet.myapp_subnet_c.id
   route_table_id = aws_route_table.myapp_rt.id
 }

セキュリティグループの設定

前回作成したセキュリティグループをもとに、定義の変更とALB用のセキュリティグループを追加します。

# EC2インスタンス用のセキュリティグループを定義
 resource "aws_security_group" "myapp_ec2_sg" {
   vpc_id = aws_vpc.myapp_vpc.id
   ingress {
     from_port       = 0
     to_port         = 0
     protocol        = "-1"
     security_groups = [aws_security_group.myapp_alb_sg.id]
   }
   egress {
     from_port   = 0
     to_port     = 0
     protocol    = "-1"
     cidr_blocks = ["0.0.0.0/0"]
   }
   tags = {
     "Name" = "myapp_ec2_sg"
   }
 }

 # ALB用のセキュリティグループを定義
 resource "aws_security_group" "myapp_alb_sg" {
   vpc_id = aws_vpc.myapp_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" = "myapp_alb_sg"
   }
 }

1つ目のセキュリティグループでは、ALBにアタッチしているセキュリティグループからのみの通信を許可しています。

2つ目のセキュリティグループで、外部からの通信をHTTPの80番ポートで許可します。

ALBの作成

ALBの定義は以下のようにしました。

internalfalseに設定することで、インターネットからアクセス可能なロードバランサーにすることができます。trueにすると、内部向けの通信を行うロードバランサーにすることも可能です。

enable_deletion_protectionでは、削除保護の設定を行います。今回は削除保護は必要ないためfalseに設定します。

# ALBのリソースを定義
 resource "aws_lb" "myapp_alb" {
   name                       = "myapp-alb"
   internal                   = false
   load_balancer_type         = "application"
   security_groups            = [aws_security_group.myapp_alb_sg.id]
   subnets                    = [aws_subnet.myapp_subnet_a.id, aws_subnet.myapp_subnet_c.id]
   enable_deletion_protection = false

   tags = {
     "Name" = "myappALB"
   }
 }

リスナーとターゲットグループの定義

続いてリスナーとターゲットグループを定義します。

リスナーでは、どのポートとプロトコルを使用して通信を待ち受けるかを定義します。

default_actionでは、条件に一致しないリクエストを受信した時に、どうレスポンスを返すかを定義しています。今回は、ステータスコードを404に、メッセージをNot foundと返すよう設定しています。

またリスナールールでは、パスパターンを設定しています。今回は、/helloというパスにリクエストが来たら、ターゲットグループへ転送する、というルールを設定しています。

# リスナーの設定
 resource "aws_lb_listener" "myapp_listener" {
   load_balancer_arn = aws_lb.myapp_alb.arn
   port              = 80
   protocol          = "HTTP"

   default_action {
     type = "fixed-response"

     fixed_response {
       content_type = "text/plain"
       message_body = "Not found"
       status_code  = "404"
     }
   }
 }

 # リスナールールの設定
 resource "aws_lb_listener_rule" "myapp_listener_rule" {
   listener_arn = aws_lb_listener.myapp_listener.arn
   priority     = 100

   action {
     type             = "forward"
     target_group_arn = aws_lb_target_group.myapp_tg.arn
   }

   condition {
     path_pattern {
       values = ["/"]
     }
   }
 }

起動テンプレートの変更

Auto Scalingの作成時、起動テンプレートの作成を行いました。

今回はALBのDNS名でアクセスした際に文字列を表示したいため、user_dataを使用して文字列を表示するための設定に内容を変更します。

webサーバーのNginxをインストールし、Hello Worldを表示させるよう設定を記述します。

# Launch Template
 resource "aws_launch_template" "myapp_launch_template" {
   name          = "myapp-lt"
   image_id      = local.ami_id
   instance_type = local.instance_type
   key_name      = aws_key_pair.myapp_key_pair.key_name
# 以下を追加
   user_data = base64encode(<<-EOF
               #!/bin/bash
               yum update -y
               amazon-linux-extras install -y nginx1.12
               echo "Hello, world" > /usr/share/nginx/html/index.html
               systemctl start nginx
               systemctl enable nginx
 EOF
   )
   network_interfaces {
     associate_public_ip_address = true
     security_groups             = [aws_security_group.myapp_ec2_sg.id]
   }

   tag_specifications {
     resource_type = "instance"
     tags = {
       "Name" = "myapp-instance"
     }
   }
 }

最後にALB作成後アクセスできるよう、DNS名をアウトプットしておきます。

output "alb_dns_name" {
   value = aws_lb.myapp_alb.dns_name
 }

ここまで完了したらapplyを実行し、アプトプットしたDNS名にアクセスします。

ブラウザにHello Worldと表示されれば完了です。

まとめ

今回の記事では、AWSのALB(Application Load Balancer)の基本と、Terraformを使用してALBの構築方法を解説しました。ALBを利用することで、効率良くサーバーへの負荷を分散させることができ、ウェブサイトやアプリケーションのパフォーマンス向上を実現することができます。

さらにALBの前段にAWSのCDNサービスであるCloudFrontを配置することで、さらに効率的な環境構築をすることも可能です。

この記事を参考に、さらに一歩ずつステップアップしていければ幸いです。

エンベーダー編集部

エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。

RareTECH 無料体験授業開催中! オンラインにて実施中! Top10%のエンジニアになる秘訣を伝授します! RareTECH講師への質疑応答可

関連記事