Route53のレイテンシーに基づくルーティング(ALB向け)
Route 53が提供するルーティングポリシーの1つに、レイテンシーに基づくルーティングがあります。
複数の AWS リージョンでアプリケーションがホストされている場合、ネットワークレイテンシーが最も低い AWS リージョンに基づいてリクエストを処理することで、ユーザーのパフォーマンスを向上させることができます。
レイテンシーに基づくルーティング
本ページでは、複数のALB向けに、レイテンシーに基づくルーティングを使用した構成をご紹介します。
構築する環境
2つのリージョンに同一の構成を用意します。
具体的には、ALBにAuto Scalingグループを関連づけて、同グループ内にEC2インスタンスを作成します。
2つのALBのDNS名をRoute 53に登録します。
これらはレイテンシーに基づくルーティングのレコードとします。
なお今回の構成で使用するドメインは、Route 53で取得したもの(awstut.net)を使用します。
そのためドメインのホストゾーン(HostedZone)に関する設定は行いません。
また今回の構成で、ALBを配置するリージョンは以下の2つとします。
- ap-northeast-1
- us-east-1
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-soa/tree/main/05/004
テンプレートファイルのポイント解説
Route 53
Resources:
HealthCheck1:
Type: AWS::Route53::HealthCheck
Properties:
HealthCheckConfig:
FailureThreshold: !Ref FailureThreshold
FullyQualifiedDomainName: !Sub "{{resolve:ssm:${SSMParameterALBDNSName1}}}"
MeasureLatency: true
Port: !Ref HTTPPort
RequestInterval: !Ref RequestInterval
ResourcePath: /
Type: HTTP
HealthCheck2:
Type: AWS::Route53::HealthCheck
Properties:
HealthCheckConfig:
FailureThreshold: !Ref FailureThreshold
FullyQualifiedDomainName: !Sub "{{resolve:ssm:${SSMParameterALBDNSName2}}}"
MeasureLatency: true
Port: !Ref HTTPPort
RequestInterval: !Ref RequestInterval
ResourcePath: /
Type: HTTP
RecordSetGroup:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName: !Sub "${DomainName}."
RecordSets:
- AliasTarget:
DNSName: !Sub "{{resolve:ssm:${SSMParameterALBDNSName1}}}"
EvaluateTargetHealth: true
HostedZoneId: !Ref ALBHostedZoneId1
HealthCheckId: !Ref HealthCheck1
Name: !Ref DomainName
Region: !Ref Region1
SetIdentifier: !Ref Region1
Type: A
- AliasTarget:
DNSName: !Sub "{{resolve:ssm:${SSMParameterALBDNSName2}}}"
EvaluateTargetHealth: true
HostedZoneId: !Ref ALBHostedZoneId2
HealthCheckId: !Ref HealthCheck2
Name: !Ref DomainName
Region: !Ref Region2
SetIdentifier: !Ref Region2
Type: A
Code language: YAML (yaml)
1つのドメインに2つのレコードを追加します。
そのためRecordSetGroupに、2つのレコード用の設定を行います。
ALBのように、エイリアスレコード用のレイテンシーに基づくルーティングを行う場合、AWS公式ページで設定方法が記されています。
特にポイントとなる設定を取り上げます。
1点目はNameプロパティです。
これはレコード名に相当するパラメータですが、以下の通りに説明されています。
トラフィックをルーティングするドメインまたはサブドメインの名前を入力します。
レコード名
上記に従い、両レコードの本プロパティに「awstut.net」を指定します。
2点目はTypeプロパティです。
これはレコードタイプに相当するパラメータです、以下の通りに説明されています。
ELB ロードバランサー
[A — IPv4 アドレス] または [AAAA — IPv6 アドレス] を選択します。
レコードタイプ
上記に従い、両レコードの本プロパティに「A」を指定します。
3点目はAliasTargetプロパティです。
後述のALBのDNS名とホストゾーンIDを指定します。
加えてヘルスチェックも有効化します。
なおDNS名はSSM動的参照を使用しています。
4点目はRegionプロパティです。
こちらは各ALBが配置されているリージョン名を指定します。
本プロパティを指定することで、レイテンシーに基づいたルーティング用のレコードとなります。
ヘルスチェックのポイントはMeasureLatencyを有効化している点です。
これは本来不要な設定ですが、後述のLatency Graphsを描画ために必要となります。
(参考) Stacksets
CloudFormation Stacksetsを使用することで、2つのリージョンにVPCやALB等を統一的に作成します。
Stacksetsに関する基本的な事項は、以下のページをご確認ください。
本ページでは、本構成を構築する上でのポイントを取り上げます。
SSM Parameter Store
Resources:
Parameter1:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub "${Prefix}-alb-dns-name-${Region1}"
Type: String
Value: " "
Parameter2:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub "${Prefix}-alb-dns-name-${Region2}"
Type: String
Value: " "
Code language: YAML (yaml)
Stacksetsを使用する上で、考慮するべき点の1つに、Stacksets内で作成したリソースを、Stacksets外から参照する方法があります。
今回はSSM Paramater Storeを使用します。
つまりStacksets外からアクセスするデータを、Parameter Storeに保存します。
今回の構成においては、各リージョンに作成するALBのDNS名を、Parameter Storeに保存します。
Route 53に両DNS名を登録するためです。
先ほどのRecordSetGroupにおいて、SSM動的参照を使用してDNS名を指定していたのは、こういった理由があります。
SSM Parameter Storeは1つのリージョンだけ使用します。
今回の構成では、ap-northeast-1です。
これはSSM動的参照を使用する場合、CloudFormationスタックを作成するリージョンのParameter Storeを使用した方が都合がいいためです。
両パラメータの値は何でも良いです。
今回はスペースにしました。
これは何かしら文字列を指定しなければ、パラメータを作成できないためです。
この値は、CloudFormationカスタムリソースを使用して、ALB作成後、DNS名が確定した時点で更新します。
Stacksets
Resources:
StackSet:
Type: AWS::CloudFormation::StackSet
Properties:
AdministrationRoleARN: !Ref AdministrationRoleArn
Capabilities:
- CAPABILITY_IAM
Parameters:
- ParameterKey: ImageId
ParameterValue: !Ref ImageId
- ParameterKey: InstanceCapacity
ParameterValue: !Ref InstanceCapacity
- ParameterKey: InstanceType
ParameterValue: !Ref InstanceType
- ParameterKey: HTTPPort
ParameterValue: !Ref HTTPPort
- ParameterKey: LambdaHandler
ParameterValue: !Ref LambdaHandler
- ParameterKey: LambdaRuntime
ParameterValue: !Ref LambdaRuntime
- ParameterKey: Prefix
ParameterValue: !Ref Prefix
- ParameterKey: SSMParameter
ParameterValue: ""
- ParameterKey: SSMParameterRegion
ParameterValue: !Ref Region1
- ParameterKey: TemplateDir
ParameterValue: !Ref TemplateDir
PermissionModel: SELF_MANAGED
StackInstancesGroup:
- DeploymentTargets:
Accounts:
- !Ref AWS::AccountId
ParameterOverrides:
- ParameterKey: SSMParameter
ParameterValue: !Ref Parameter1
Regions:
- !Ref Region1
- DeploymentTargets:
Accounts:
- !Ref AWS::AccountId
ParameterOverrides:
- ParameterKey: SSMParameter
ParameterValue: !Ref Parameter2
Regions:
- !Ref Region2
StackSetName: !Sub "${Prefix}-stacksets"
TemplateURL: !Sub "${TemplateDir}/${Prefix}-stacksets-template.yaml"
Code language: YAML (yaml)
Stacksetsを作成する上で、ポイントとなる点は、各スタックに渡すパラメータです。
各スタックで共通するものと、個別のものがあります。
前者はParametersプロパティで、後者はParameterOverridesプロパティを使用します。
例えばAuto Scalingグループ内に作成するインスタンスタイプは共通ですから、前者で指定します。
一方、Parameter StoreはリージョンごとにDNS名を保存するパラメータが異なりますので、後者で指定します。
(参考)ALBとAuto Scalingグループ
今回の構成では、ALBにAuto Scalingグループを関連づけます。
Auto Scalingグループはスケーリングポリシーを設定していません。
こちらの構成に関する詳細は、以下のページをご確認ください。
ALB
Resources:
ALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub "${Prefix}-ALB"
Scheme: internet-facing
SecurityGroups:
- !Ref ALBSecurityGroup
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
Type: application
ALBTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
VpcId: !Ref VPC
Name: !Sub "${Prefix}-ALBTargetGroup"
Protocol: HTTP
Port: !Ref HTTPPort
HealthCheckProtocol: HTTP
HealthCheckPath: /
HealthCheckPort: traffic-port
HealthyThresholdCount: !Ref HealthyThresholdCount
UnhealthyThresholdCount: !Ref UnhealthyThresholdCount
HealthCheckTimeoutSeconds: !Ref HealthCheckTimeoutSeconds
HealthCheckIntervalSeconds: !Ref HealthCheckIntervalSeconds
Matcher:
HttpCode: !Ref HttpCode
ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- TargetGroupArn: !Ref ALBTargetGroup
Type: forward
LoadBalancerArn: !Ref ALB
Port: !Ref HTTPPort
Protocol: HTTP
Code language: YAML (yaml)
2つのAZにまたがる形でALBを構成します。
Auto Scalingグループ
Resources:
LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateData:
IamInstanceProfile:
Arn: !GetAtt InstanceProfile.Arn
ImageId: !Ref ImageId
InstanceType: !Ref InstanceType
SecurityGroupIds:
- !Ref InstanceSecurityGroup
UserData: !Base64 |
#!/bin/bash -xe
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
ec2-metadata -i > /var/www/html/index.html
LaunchTemplateName: !Sub "${Prefix}-LaunchTemplate"
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
AutoScalingGroupName: !Sub "${Prefix}-AutoScalingGroup"
DesiredCapacity: !Ref DesiredCapacity
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplate
Version: !GetAtt LaunchTemplate.LatestVersionNumber
MaxSize: !Ref MaxSize
MinSize: !Ref MinSize
VPCZoneIdentifier:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
TargetGroupARNs:
- !Ref ALBTargetGroup
Code language: YAML (yaml)
先ほどのALBターゲットグループに、Auto Scalingグループを関連付けます。
起動テンプレートのポイントは、ユーザデータです。
Apacheをインストールして、インスタンスIDを書き込んだHTMLファイルを配置します。
インスタンスの初期化処理に関する詳細は、以下のページもご確認ください。
CloudFormationカスタムリソース
Resources:
CustomResource:
Type: Custom::CustomResource
Properties:
ServiceToken: !GetAtt Function.Arn
Function:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
import boto3
import cfnresponse
import os
parameter_name = os.environ['PARAMETER_NAME']
parameter_value = os.environ['PARAMETER_VALUE']
region = os.environ['REGION']
ssm_client = boto3.client('ssm', region_name=region)
CREATE = 'Create'
response_data = {}
def lambda_handler(event, context):
try:
if event['RequestType'] == CREATE:
response = ssm_client.put_parameter(
Name=parameter_name,
Value=parameter_value,
Type='String',
Overwrite=True
)
print(response)
cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
except Exception as e:
print(e)
cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
Environment:
Variables:
PARAMETER_NAME: !Ref SSMParameter
PARAMETER_VALUE: !Ref ALBDNSName
REGION: !Ref SSMParameterRegion
FunctionName: !Sub "${Prefix}-function-01"
Handler: !Ref Handler
Runtime: !Ref Runtime
Role: !GetAtt FunctionRole.Arn
FunctionRole:
Type: AWS::IAM::Role
DeletionPolicy: Delete
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service:
- lambda.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: CreateSSMParameterStorePolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- ssm:PutParameter
Resource: "*"
Code language: YAML (yaml)
CloudFormationカスタムリソースを使用して、CloudFormationスタック作成時に、Lambda関数を自動的に実行します。
カスタムリソースに関する詳細は、以下のページをご確認ください。
実行する関数の内容ですが、ALBのDNS名をSSM Parameter Storeに保存するものです。
先ほど確認したParameter Storeを、これで更新します。
パラメータ名はリージョンごとに異なりますが、SSM用クライアントオブジェクトで指定するリージョンは統一(ap-northeast-1)です。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
なお今回の構成では、名前を持ったIAMロールを作成します。
ですから以下のようなコマンドでスタックを作成します。
$ aws cloudformation create-stack \
--stack-name [stack-name] \
--template-url https://[template-path]/soa-05-004.yaml \
--capabilities CAPABILITY_NAMED_IAM
Code language: Bash (bash)
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- ALBのDNS名1:soa-05-004-ALB-2078359657.ap-northeast-1.elb.amazonaws.com
- ALBのDNS名2:soa-05-004-ALB-1191006444.us-east-1.elb.amazonaws.com
- SSM Parameter Storeパラメータ1:soa-05-004-alb-dns-name-ap-northeast-1
- SSM Parameter Storeパラメータ2:soa-05-004-alb-dns-name-us-east-1
作成されたリソースをAWS Management Consoleから確認します。
2つのALBを確認します。
2つのリージョンにALBが1つずつ作成されています。
CloudFormation StackSetsによって、2つのリージョンに統一的に同一の構成が作成されたということです。
SSM Parameter Storeを確認します。
2つのパラメータストアに、ALBのDNS名が保存されています。
更新したユーザを見ると、Lambda関数です。
つまりCloudFormationカスタムリソースに関連づいているLambda関数によって、これらのパラメータが更新されたということです。
Route 53を確認します。
「awstut.net」というドメインにいくつかレコードが設定されています。
Routing Policyの列を見ると、「Latency」のものが2行あります。
トラフィックのルーティング先を見ると、ALBのDNS名であることがわかります。
ヘルスチェックのLatency Graphを確認します。
それぞれのヘルスチェックの2つのリージョンに対する遅延状況です。
ご覧の通り、ALBが設置されているリージョンからのヘルスチェックに対しては、ほとんど1ms以内で応答しています。
対してそれ以外のリージョンからのヘルスチェックに対しては、約150ms程度かかります。
動作確認
準備が整いましたので、実際に挙動を確認します。
まずそれぞれのALBのDNS名にアクセスしてみます。
$ curl http://soa-05-004-ALB-2078359657.ap-northeast-1.elb.amazonaws.com
instance-id: i-014e7a7755f6aa5ca
$ curl http://soa-05-004-ALB-1191006444.us-east-1.elb.amazonaws.com
instance-id: i-08c5f073c2534990d
Code language: Bash (bash)
各ALB配下のEC2インスタンスのIDが返ってきました。
両ALBは正常に動作しています。
次にap-northeast-1リージョンのCloudShellからドメイン名でアクセスします。
ap-northeast-1側のALBにアクセスしていることがわかります。
レイテンシーを見て、us-east-1ではなく、ap-northeast-1側にルーティングされたということです。
最後にus-east-1リージョンのCloudShellからドメイン名でアクセスします。
us-east-1側のALBにアクセスしていることがわかります。
レイテンシーを見て、ap-northeast-1ではなく、us-east-1側にルーティングされたということです。
このようにレイテンシーに基づくルーティングを使用することで、レイテンシーが最も低いリージョンのサービスにアクセスできます。
まとめ
複数のALB向けに、レイテンシーに基づくルーティングを使用した構成をご紹介しました。