業務でGlobal Acceleratorを検討する機会があったのですが、恥ずかしながら使用したことがないため勉強がてらCDKで実装してみました。
題材としては以下のWorkshopのGlobal Acceleratorパートを使用しています。
実装は以下にあります。
目次
Global Acceleratorとは
以下の動画が非常にわかりやすかったです(12:25~18:00あたり)。 www.youtube.com
以下上記動画から一部引用します。
まずGlobal Acceleratorとしてできることは主に以下3つです。
- 複数のエンドポイント(オリジン)に対して単一のエンドポイント(2つの静的IPアドレス)を提供。エンドポイントはリージョン跨ぎも可能。
- Global Accelerator -> エンドポイント はAWSのネットワークを通るため通信が高速になる。
- エンドポイント障害を検知してフェイルオーバーも可能
単一のエンドポイントによるグローバル展開や、エンドポイント障害時のフェイルオーバーが可能。
よく比較されるCloudFrontとの違いについて。以下がGlobal Acceleratorの大きなメリットかと思います。
- HTTP/HTTPS 以外でも利用可能
- オリジンの完全な隠匿が可能。
CloudFrontとの違いという意味では以下の記事も参考になります。
アーキテクチャ
以下のようなシンプルな構成です。
- バックエンドはALB+Lambdaで構成。Lambdaは動作しているリージョンと自身の関数名を返すのみ。
- その前段にGlobal Acceleratorを配置。
なお今回はあえてALBにも直接アクセス可能な構成(ALBをパブリックサブネットに配置)としている点にご注意ください(Global Accelerator経由のアクセスと、ALB経由のアクセスでパフォーマンスの計測・比較を行うため)。
オリジン(ALB)を隠匿したい場合はプライベートサブネットに配置することが可能です。なおその際もVPCにインターネットゲートウェイをアタッチしておく必要はあるので、その点も注意が必要です(以下のドキュメントに記載あり)。
CDKプロジェクトの構成
├── README.md ├── architecture.drawio.svg ├── bin │ └── workshop_globalaccelerator.ts ├── cdk.json ├── cdk.out │ ├── cdk.out │ ├── manifest.json │ └── tree.json ├── curl-format.txt # パフォーマンステストで利用 ├── jest.config.js ├── lib │ ├── construct │ │ ├── backend.ts # ALB+Lambdaを実装 │ │ └── edge.ts # Global Acceleratorを実装 │ ├── lambda │ │ └── index.py │ └── workshop_globalaccelerator-stack.ts ├── package-lock.json ├── package.json ├── test │ └── workshop_globalaccelerator.test.ts └── tsconfig.json
CDKの実装
backendの実装
VPC, ALB, Lambdaを実装していきます。シンプルなのでサクッと実装してきます。
まずVPCはパブリックサブネットを2つ作成します。
// ------ VPC ------- const vpc = new ec2.Vpc(this, 'Vpc', { natGateways: 0, // デフォルトだとNAT Gatewayが作成されてしまうため0を指定 maxAzs: 2, ipAddresses: ec2.IpAddresses.cidr('10.135.0.0/16'), // CIDRはWorkshopで使っていたものを指定(深い意味はない) subnetConfiguration: [ { cidrMask: 24, name: 'PublicSubnet', subnetType: ec2.SubnetType.PUBLIC, } ], enableDnsHostnames: true, enableDnsSupport: true } )
次にLambdaとALBを実装してきます。ここもあまり難しいところはありません。
ターゲットグループのデフォルトアクションとしてLambdaに転送します。
// ------ Lambda ------- const lambdaFunction = new lambda.Function(this, 'LambdaFunction', { runtime: lambda.Runtime.PYTHON_3_12, description: 'This Lambda function simply returns the AWS region and its name.', handler: 'index.lambda_handler', code: lambda.Code.fromAsset('./lib/lambda/') }) // ------ ALB ------- const alb = new elbv2.ApplicationLoadBalancer(this, 'ApplicationLoadBalancer', { vpc: vpc, vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }), internetFacing: true, }) this.alb = alb // connectionsを使用して、HTTP/Sを許可 alb.connections.allowFromAnyIpv4(ec2.Port.tcp(443), 'Enable HTTPS access on the Application Load Balancer') alb.connections.allowFromAnyIpv4(ec2.Port.tcp(80), 'Enable HTTP access on the Application Load Balancer') // ターゲットグループを作成してLambdaを登録 const targetGroup = new elbv2.ApplicationTargetGroup(this, 'LambdaTargetGroup', { healthCheck: { interval: cdk.Duration.seconds(35), timeout: cdk.Duration.seconds(30), healthyThresholdCount: 3, unhealthyThresholdCount: 5 }, targetType: elbv2.TargetType.LAMBDA, targets: [new targets.LambdaTarget(lambdaFunction)] }) // リスナーを作成 new elbv2.ApplicationListener(this, 'Listener', { loadBalancer: alb, port: 80, protocol: elbv2.ApplicationProtocol.HTTP, defaultAction: elbv2.ListenerAction.forward([targetGroup]) })
edgeの実装
Global Acceleratorを実装していきます。
L2 Constructが存在するのでサクサク実装できます(aws_globalacceleratorとaws_globalaccelerator_endpoints)。
まずアクセラレータを作成します。
// アクセラレータを作成 const accelerator = new globalaccelerator.Accelerator(this, 'Accelerator', { ipAddressType: globalaccelerator.IpAddressType.IPV4 })
次にリスナーを作成します。
// リスナーを作成 const listener = new globalaccelerator.Listener(this, 'Listener', { accelerator: accelerator, // 作成したアクセラレータを設定 portRanges: [{ fromPort: 80 }], // ポートを指定 clientAffinity: globalaccelerator.ClientAffinity.NONE, // クライアントアフィニティの設定(今回は無し) protocol: globalaccelerator.ConnectionProtocol.TCP // プロトコルとして TCP or UDPを指定 })
最後にリスナーにエンドポイント(今回はALB)を登録します。
// リスナーにALBのエンドポイントグループを追加 listener.addEndpointGroup('AlbEndpointGroup', { endpoints: [ new ga_endpoints.ApplicationLoadBalancerEndpoint( props.alb, // ALBを指定(今回はbackend.tsのalbをpropsで受け渡して設定) { weight: 128, // 重みを設定 preserveClientIp: true // クライアントのIPを保持するかを設定 } ) ], })
パフォーマンス計測
WorkshopのGlobal Accelerator パフォーマンスを参考に、Global Accelerator経由でのアクセスと、直接ALBにアクセスした際のパフォーマンスの違いをテストします。
今回は curl で 100kbのデータを5回取得した際のパフォーマンスの違いを確認します。
ap-northeast-1の場合
最初のリクエストはGlobal Acceleratorの方がかなり早いものの、それ以降は多少早いぐらいの違いです。
# Global Accelerator $ for i in {1..5}; do curl -w @curl-format.txt -o /dev/null -s http://YOUR_ENDPOINT.com/100KB; sleep 3; done | grep -v time_total:0 Total Time: 0.156340s | Time to Connect: 0.011343s | Time To Transfer: 0.011371s | Time To First Byte: 0.116007s Total Time: 0.141812s | Time to Connect: 0.014920s | Time To Transfer: 0.015003s | Time To First Byte: 0.097861s Total Time: 0.135022s | Time to Connect: 0.013116s | Time To Transfer: 0.013166s | Time To First Byte: 0.096225s Total Time: 0.132770s | Time to Connect: 0.009638s | Time To Transfer: 0.009662s | Time To First Byte: 0.093471s Total Time: 0.151768s | Time to Connect: 0.015104s | Time To Transfer: 0.015224s | Time To First Byte: 0.112916s # ALB $ for i in {1..5}; do curl -w @curl-format.txt -o /dev/null -s http://YOUR_ENDPOINT.us-east-1.elb.amazonaws.com/100KB; sleep 3; done | grep -v time_total:0 Total Time: 0.335299s | Time to Connect: 0.132908s | Time To Transfer: 0.133032s | Time To First Byte: 0.257954s Total Time: 0.168047s | Time to Connect: 0.014835s | Time To Transfer: 0.014932s | Time To First Byte: 0.109962s Total Time: 0.167855s | Time to Connect: 0.012157s | Time To Transfer: 0.012183s | Time To First Byte: 0.108340s Total Time: 0.203879s | Time to Connect: 0.015783s | Time To Transfer: 0.015824s | Time To First Byte: 0.141916s Total Time: 0.180761s | Time to Connect: 0.014315s | Time To Transfer: 0.014356s | Time To First Byte: 0.120533s
us-east-1の場合
オリジンが地理的に遠いケースとして、us-east-1でもやってみました。
この場合は安定してGlobal Acceleratorの方が早い
# Global Accelerator $ for i in {1..5}; do curl -w @curl-format.txt -o /dev/null -s http://YOUR_ENDPOINT.com/100KB; sleep 3; done | grep -v time_total:0 Total Time: 0.928702s | Time to Connect: 0.013130s | Time To Transfer: 0.013305s | Time To First Byte: 0.575303s Total Time: 0.796797s | Time to Connect: 0.015324s | Time To Transfer: 0.015440s | Time To First Byte: 0.449703s Total Time: 0.777429s | Time to Connect: 0.009782s | Time To Transfer: 0.009876s | Time To First Byte: 0.429472s Total Time: 0.791958s | Time to Connect: 0.010153s | Time To Transfer: 0.010175s | Time To First Byte: 0.443317s Total Time: 0.783636s | Time to Connect: 0.015055s | Time To Transfer: 0.015138s | Time To First Byte: 0.436786s # ALB $ for i in {1..5}; do curl -w @curl-format.txt -o /dev/null -s http://YOUR_ENDPOINT.ap-northeast-1.elb.amazonaws.com/100KB; sleep 3; done | grep -v time_total:0 Total Time: 1.210771s | Time to Connect: 0.287146s | Time To Transfer: 0.287263s | Time To First Byte: 0.549709s Total Time: 1.075512s | Time to Connect: 0.168787s | Time To Transfer: 0.168881s | Time To First Byte: 0.414120s Total Time: 1.191307s | Time to Connect: 0.185972s | Time To Transfer: 0.186055s | Time To First Byte: 0.463744s Total Time: 1.097473s | Time to Connect: 0.169945s | Time To Transfer: 0.170174s | Time To First Byte: 0.436452s Total Time: 1.122181s | Time to Connect: 0.174007s | Time To Transfer: 0.174257s | Time To First Byte: 0.430129s
AWS Global Accelerator Comparison
以下AWSがGlobal Acceleratorのパフォーマンステストをするサイトを公開しています。
speedtest.globalaccelerator.aws
やってみたところ、オリジンが地理的に遠い海外リージョンはGlobal Acceleratorの方が早いものの、東京リージョンはむしろ少し遅くなってしまいました(何回かやったら東京リージョンでも若干早くなったりしました。まあ誤差の範囲ですね)
高速化だけを目的にGlobal Acceleratorを導入する場合、オリジンの地理的な位置は考慮する必要がありそうです。
おまけ:ALBをプライベートサブネットに配置してオリジンを隠匿
最後にオリジンを隠匿したい場合のCDKの実装を紹介して終わりにします。
ALBをプライベートサブネットに配置します。
まずVPCの実装において以下2点の修正を行います。
- サブネットをプライベートサブネットに変更
- インターネットゲートウェイを作成してアタッチ
// ------ VPC ------- const vpc = new ec2.Vpc(this, 'Vpc', { natGateways: 0, maxAzs: 2, ipAddresses: ec2.IpAddresses.cidr('10.135.0.0/16'), subnetConfiguration: [ { cidrMask: 24, name: 'PrivateSubnet', subnetType: ec2.SubnetType.PRIVATE_ISOLATED, // ★プライベートサブネットに変更 } ], enableDnsHostnames: true, enableDnsSupport: true } ) // ★インターネットゲートウェイを作成 const cfnInternetGateway = new ec2.CfnInternetGateway(scope, 'InternetGateway', { }) // ★IGWをVPCにアタッチ new ec2.CfnVPCGatewayAttachment(scope, 'IGW2VPC', { vpcId: vpc.vpcId, internetGatewayId: cfnInternetGateway.ref })
先述した通り、Global Acceleratorからプライベートサブネット内のオリジンにアクセスするために、VPCにインターネットゲートウェイをアタッチしておく必要があります(L2 ConstructだとサブネットにPublicを含まない場合は、明示的にインターネットゲートウェイを作成してアタッチする必要あり)。
なおVPCにインターネットゲートウェイをアタッチしていない状態だと、Global Acceleratorのデプロイ時にエラーになります。
17:10:34 | CREATE_FAILED | AWS::GlobalAccelerator::EndpointGroup | EdgeConstructListe...pointGroupCC007889 Resource handler returned message: "arn:aws:elasticloadbalancing:us-east-1:000000000000:loadbalancer/app/Worksh-Backe-1Va3Z1XjTWyk/58e70c28cb484862 does not have an internet gateway in its VPC vpc-0000000000000 (Service: AWSGlobalAccelerator; Status Code: 400; Error Code: InvalidArgumentException; Request ID: beca2206-bd21-49d3-97a2-7 b17deac6212; Proxy: null)" (RequestToken: 838c4d3f-5055-c750-5ed0-d090e5ea90fd, HandlerErrorCode: GeneralServiceException)
次にALBをプライベートサブネットに配置して、タイプをinternalにします。
// ------ ALB ------- const alb = new elbv2.ApplicationLoadBalancer(this, 'ApplicationLoadBalancer', { vpc: vpc, vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE_ISOLATED }), // ★プライベートサブネットに変更 internetFacing: false, // ★Internet-facingをオフ(internalに変更) })
これでオリジンを隠匿しつつ、Global Accelerator経由でアクセスは可能になります。