いわさです。
みなさんよく Application Load Balancer を使われていると思います。
負荷分散や TLS/SSL終端させる目的で導入することが多いと思いますが、リクエストの内容に応じてターゲットへのリクエスト転送内容や転送先をカスタマイズすることもあると思います。
Apache や Nginx でリバースプロキシを構成していた場合でも Application Load Balancer のみで代用できることがあるのですが、それでも仕様上 Application Load Balancer のみで対応できないケースがあって、場合によっては結局 Apache や Nginx でリバースプロキシを構成することも稀にありました。
先日のアップデートでリスナールールでカスタマイズできる項目が増えまして、Application Load Balancer のみで対応できる項目が増えました。
リスナールール上でトランスフォームというものを設定することで、ターゲットへ転送する前に URL パスとホストヘッダーを正規表現ベースで置換できるようになりました。
これによってバックエンドでバーチャルホストを構成している場合とか、パスまで書き換えの必要がある場合とか、今までよりも ALB 単体で対応できるケースが増えそうです。
今回は設定方法の確認と、実際に設定した前後での動作確認を行ってみましたので紹介します。
設定方法
ロードバランサーのコンソールにアクセスしてみると「Application Load Balancer の URL リライトの紹介」というバナーが表示されていますね。これのことみたいだ。

で、設定方法なのですが、ALB のリスナールール上で今回のアップデート機能が利用できます。注意点としてデフォルトルールではトランスフォームを追加することができないので、カスタムルールの追加が必要になります。

ルール編集時に以下からトランスフォームが設定できるようになっています。

本日時点でトランスフォームで設定できるのは「ホストヘッダー」と「URLパス」の2つです。

ホストヘッダーははリクエスト内のホストヘッダーを書き換えてターゲットに転送するおちうものです。正規表現を使ってホストヘッダーのパターンを照合して、そこから置換を行うというもの。
URLパスは同じように正規表現で URL パスの置換を行います。パス構成を帰ることもできますし、クエリ文字列の編集に介入することもできます。
よくクエリ文字列をパスに変換することとかあると思うのですが、あれが ALB だけでできちゃいますね。
URLパスではプロトコルやポートまでは変更できないので注意してください。

このあたりのトランスフォームの詳細は以下の公式ドキュメントに情報が記載されているのでこちらもご確認ください。
転送先サーバーのバーチャルホスト設定にあわせてホストヘッダーを変換する
ということで試してみましょう。
色々なシナリオ考えられると思いますが、今回は EC2 にセットアップされたアプリケーションでバーチャルホストが構成されていて、それを ALB が許可するホストヘッダーと EC2 が期待するホストヘッダーで異なる場合に、このトランスフォームを使って変換してみたいと思います。
本題から逸れますが事前に以下の VPC + ALB + EC2 の CloudFormation スタックをデプロイ済みです。Amazon Q Developer ちゃんに作ってもらいました。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'ALB with EC2 backend and Apache virtual hosts'
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: ALB-VPC
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: ALB-IGW
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: Public-Subnet-1
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: Public-Subnet-2
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: Public-Route-Table
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicRouteTable
PublicSubnetRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet2
RouteTableId: !Ref PublicRouteTable
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for ALB
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: ALB-SecurityGroup
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for EC2
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref ALBSecurityGroup
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: EC2-SecurityGroup
EC2Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Tags:
- Key: Name
Value: EC2-SSM-Role
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref EC2Role
EC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-070e0d4707168fc07
InstanceType: t3.micro
SubnetId: !Ref PublicSubnet1
SecurityGroupIds:
- !Ref EC2SecurityGroup
IamInstanceProfile: !Ref EC2InstanceProfile
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
mkdir -p /var/www/hoge
mkdir -p /var/www/fuga
cat > /var/www/hoge/pages/index.html !DOCTYPE html>
>
>
>Hoge Site >
>
>
>
>This is the content for hoge.example.com
>
>
>
EOF
cat > /var/www/fuga/index.html !DOCTYPE html>
>
>
>Fuga Site >
>
>
>
>This is the content for fuga.example.com
>
>
>
EOF
cat > /etc/httpd/conf.d/virtual-hosts.conf *:80>
ServerName hoge.example.com
DocumentRoot /var/www/hoge
ErrorLog logs/hoge_error.log
CustomLog logs/hoge_access.log combined
>
*:80>
ServerName fuga.example.com
DocumentRoot /var/www/fuga
ErrorLog logs/fuga_error.log
CustomLog logs/fuga_access.log combined
>
EOF
systemctl restart httpd
Tags:
- Key: Name
Value: Web-Server
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: WebServer-ALB
Scheme: internet-facing
Type: application
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
SecurityGroups:
- !Ref ALBSecurityGroup
Tags:
- Key: Name
Value: WebServer-ALB
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: WebServer-TG
Port: 80
Protocol: HTTP
VpcId: !Ref VPC
HealthCheckPath: /
HealthCheckProtocol: HTTP
HealthCheckIntervalSeconds: 30
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
UnhealthyThresholdCount: 3
Targets:
- Id: !Ref EC2Instance
Port: 80
Listener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref TargetGroup
LoadBalancerArn: !Ref ApplicationLoadBalancer
Port: 80
Protocol: HTTP
Outputs:
LoadBalancerDNS:
Description: DNS name of the load balancer
Value: !GetAtt ApplicationLoadBalancer.DNSName
Export:
Name: !Sub "${AWS::StackName}-LoadBalancerDNS"
VPCId:
Description: VPC ID
Value: !Ref VPC
Export:
Name: !Sub "${AWS::StackName}-VPC"
上記を東京リージョンにデプロイすると Amazon Linux 2023 上で Web ページがホスティングされるのですが、バーチャルホストを構成しているのでホストヘッダーの内容によってレスポンスが変わります。
% curl http://13.231.188.58/
html>body>h1>It works!/h1>/body>/html>
% curl http://13.231.188.58/ -H "Host:hoge.example.com"
html>body>h1>It works!/h1>/body>/html>
% curl http://13.231.188.58/ -H "Host:fuga.example.com"
!DOCTYPE html>
html>
head>
title>Fuga Site/title>
/head>
body>
h1>Welcome to Fuga Site/h1>
p>This is the content for fuga.example.com/p>
/body>
/html>
良いですね。
つづいて、ALB のターゲットに EC2 を設定しているのでこちらでも試してみます。
ALB ではexample.comホストヘッダー形式でのリクエストを想定しています。
% curl http://WebServer-ALB-2001862097.ap-northeast-1.elb.amazonaws.com/ -H "Host:hoge.com"
html>body>h1>It works!/h1>/body>/html>
% curl http://WebServer-ALB-2001862097.ap-northeast-1.elb.amazonaws.com/ -H "Host:fuga.com"
html>body>h1>It works!/h1>/body>/html>
デプロイされたロードバランサーに新しいリスナールールを追加します。
その中でトランスフォームの設定をするのですが、今回は次のようにトランスフォームを1件追加し、ホストヘッダー正規表現と置換後の値を設定しました。

設定箇所の右側の矢印を選択すると変換のテストを行うことができますので試してみましょう。hoge.comを入力するとhoge.example.comに変換されました。

期待した変換の挙動をしていそうですね。
設定後はリスナールール一覧からは次のように確認ができます。
なお、ひとつのルールのトランスフォームに対して複数のホストヘッダー書き換えルールを設定することは出来ませんのでうまく正規表現一本で行う必要があります。

設定後、また cURL でアクセスしてみましょう。ホストヘッダーを指定します。
% curl http://WebServer-ALB-2001862097.ap-northeast-1.elb.amazonaws.com/ -H "Host:hoge.com"
html>body>h1>It works!/h1>/body>/html>
% curl http://WebServer-ALB-2001862097.ap-northeast-1.elb.amazonaws.com/ -H "Host:fuga.com"
!DOCTYPE html>
html>
head>
title>Fuga Site/title>
/head>
body>
h1>Welcome to Fuga Site/h1>
p>This is the content for fuga.example.com/p>
/body>
/html>
ALB 経由でホストヘッダーがリライトされターゲットに転送されていることが確認出来ましたね。
さいごに
本日は Application Load Balancer のリスナールールでトランスフォームを構成し、ターゲットにルーティングする前にホストヘッダーや URL パスを書き換えれるようになったので試してみました。
パス、クエリ文字列、ホストヘッダーの変更などを ALB にオフロードできるようになりました。
これまで細かい調整が必要でアプリケーションコードの実装やリバースプロキシの追加導入をしていた場合は今回の機能のみで今後は対応できるようになりそうですね。
元の記事を確認する