Route53のレイテンシーに基づくルーティング(ALB向け)

Route53 レイテンシーに基づくルーティング - ALB編

Route53のレイテンシーに基づくルーティング(ALB向け)

Route 53が提供するルーティングポリシーの1つに、レイテンシーに基づくルーティングがあります。

複数の AWS リージョンでアプリケーションがホストされている場合、ネットワークレイテンシーが最も低い AWS リージョンに基づいてリクエストを処理することで、ユーザーのパフォーマンスを向上させることができます。

レイテンシーに基づくルーティング

本ページでは、複数のALB向けに、レイテンシーに基づくルーティングを使用した構成をご紹介します。

構築する環境

Diagram of Route53 latency-based routing for 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公式ページで設定方法が記されています。

https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/resource-record-sets-values-latency-alias.html

特にポイントとなる設定を取り上げます。

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に関する基本的な事項は、以下のページをご確認ください。

あわせて読みたい
CloudFormation StackSets入門 – 自アカウントの複数リージョンにデプロイ 【CloudFormation StackSets入門 - 自アカウントの複数リージョンにデプロイ】 本ページでは、AWS StackSetsを取り上げます。 AWS CloudFormation 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グループはスケーリングポリシーを設定していません。

こちらの構成に関する詳細は、以下のページをご確認ください。

あわせて読みたい
EC2 Auto Scaling入門 – スケーリングポリシーなし 【EC2 Auto Scaling入門 - スケーリングポリシーなし】 EC2 Auto Scalingを利用することによって、任意の数のEC2インスタンスを起動し、アプリケーションの可用性を高め...
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ファイルを配置します。

インスタンスの初期化処理に関する詳細は、以下のページもご確認ください。

あわせて読みたい
Linuxインスタンスの初期化方法4選 【Linuxインスタンスを初期化する4つの方法】 EC2インスタンスの起動時に初期化処理を実行する方法を考えます。 EC2インスタンスを構築時に初期化する以下の4つの手法を...

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関数を自動的に実行します。
カスタムリソースに関する詳細は、以下のページをご確認ください。

あわせて読みたい
CloudFormationカスタムリソース入門 【CloudFormationカスタムリソースの挙動を確認する構成】 CloudFormationの機能の1つにカスタムリソースがあります。 カスタムリソースを使用すると、テンプレートにカ...

実行する関数の内容ですが、ALBのDNS名をSSM Parameter Storeに保存するものです。
先ほど確認したParameter Storeを、これで更新します。

パラメータ名はリージョンごとに異なりますが、SSM用クライアントオブジェクトで指定するリージョンは統一(ap-northeast-1)です。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【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を確認します。

Detail of ELB 1.
Detail of ELB 2.

2つのリージョンにALBが1つずつ作成されています。
CloudFormation StackSetsによって、2つのリージョンに統一的に同一の構成が作成されたということです。

SSM Parameter Storeを確認します。

Detail of SSM 1.
Detail of SSM 2.

2つのパラメータストアに、ALBのDNS名が保存されています。
更新したユーザを見ると、Lambda関数です。
つまりCloudFormationカスタムリソースに関連づいているLambda関数によって、これらのパラメータが更新されたということです。

Route 53を確認します。

Detail of Route53 1.

「awstut.net」というドメインにいくつかレコードが設定されています。
Routing Policyの列を見ると、「Latency」のものが2行あります。
トラフィックのルーティング先を見ると、ALBのDNS名であることがわかります。

ヘルスチェックのLatency Graphを確認します。

Detail of Route53 2.
Detail of Route53 3.
Detail of Route53 4.
Detail of Route53 5.

それぞれのヘルスチェックの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からドメイン名でアクセスします。

Detail of CloudShell 1.

ap-northeast-1側のALBにアクセスしていることがわかります。
レイテンシーを見て、us-east-1ではなく、ap-northeast-1側にルーティングされたということです。

最後にus-east-1リージョンのCloudShellからドメイン名でアクセスします。

Detail of CloudShell 2.

us-east-1側のALBにアクセスしていることがわかります。
レイテンシーを見て、ap-northeast-1ではなく、us-east-1側にルーティングされたということです。

このようにレイテンシーに基づくルーティングを使用することで、レイテンシーが最も低いリージョンのサービスにアクセスできます。

まとめ

複数のALB向けに、レイテンシーに基づくルーティングを使用した構成をご紹介しました。