Terraform と ecspresso を使って CloudFront + ALB + ECS + gRPC な環境を構築してみた
こんにちは!クラウド事業本部の枡川です。
少し前の話題となりますが、CloudFront が gRPC 通信を受け入れるようになっています。
What's New
AWS ブログ
CloudFront を挟むといっても、キャッシュを利用してトラフィックを捌くようなことはできません。
Because gRPC is used only for non-cacheable API traffic, your cache configurations won't affect gRPC requests.
Using gRPC with CloudFront distributions
単一リージョンで世界中にサービス展開している際に、早めに高帯域な AWS ネットワークに引き込めることなどがメリットになります。
CloudFront は、最も近いエッジへのインテリジェントなルーティング機能を備えた 600 以上のエッジロケーションで構成されたグローバルネットワークを提供します。エッジロケーションは TLS ターミネーションに加えて、静的コンテンツのキャッシュ (オプション) を提供します。CloudFront は、フルマネージド型、低レイテンシー、高帯域幅のプライベート AWS ネットワークを介してクライアントアプリケーションのリクエストを gRPC オリジンに転送します。
Amazon CloudFront がアプリケーションの gRPC 呼び出しを受け入れるようになりました
また、キャッシュが効かなくても DDoS 耐性を向上させる効果は得られるため、DDoS が気になる場面で挟むのも良いでしょう。
今回は Terraform と ecspresso を使って、ECS + ALB + CloudFront + gRPC な環境を構築してみました。
Terraform を利用した AWS 環境構築
構築するアーキテクチャは下記のようになります。
各種バージョンは下記としており、mise を利用してインストールしました。
ツール名 | バージョン |
---|---|
Terraform | 1.11.3 |
ecspresso | 2.4.6 |
コード類は以下のリポジトリに公開していますので、ご興味のある方はご参照下さい。
まず、AWS インフラから作成していきます。
cd terraform
terraform init
terraform apply
※ 上記リポジトリにあるコード類を利用する場合は、locals.tf
で利用する Route53 のドメイン名を指定することと、backend.tf
で S3 バックエンドの設定を行う必要があります。
CloudFront を作成する際はビヘイビア設定で、grpc_config.enabled = true
とします。
resource "aws_cloudfront_distribution" "grpc_app" {
aliases = ["${local.cloudfront_sub_domain_name}.${local.hosted_zone_name}"]
enabled = true
http_version = "http2"
is_ipv6_enabled = true
price_class = "PriceClass_All"
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cache_policy_id = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" // Managed-CachingDisabled
cached_methods = ["GET", "HEAD"]
compress = true
default_ttl = 0
max_ttl = 0
min_ttl = 0
origin_request_policy_id = "b689b0a8-53d0-40ab-baf2-68738e2966ac" // Managed-AllViewerExceptHostHeader
target_origin_id = "${local.alb_sub_domain_name}.${local.hosted_zone_name}"
viewer_protocol_policy = "https-only"
grpc_config {
enabled = true
}
}
origin {
connection_attempts = 3
connection_timeout = 10
domain_name = "${local.alb_sub_domain_name}.${local.hosted_zone_name}"
origin_id = "${local.alb_sub_domain_name}.${local.hosted_zone_name}"
custom_origin_config {
http_port = 80
https_port = 443
origin_keepalive_timeout = 5
origin_protocol_policy = "https-only"
origin_read_timeout = 30
origin_ssl_protocols = ["TLSv1.2"]
}
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["JP"]
}
}
viewer_certificate {
acm_certificate_arn = module.cloudfront_acm.acm_certificate_arn
cloudfront_default_certificate = false
minimum_protocol_version = "TLSv1.2_2021"
ssl_support_method = "sni-only"
}
}
CloudFront のオリジンとしては ALB に設定したドメインをカスタムドメインとして登録しました。
VPC Origin は対応していないので、注意が必要です。
(最初設定しようとして CloudFront ログに OriginConnectError
と記録されました...)
CloudFront の Lambda@Edge を使用した WebSocket、gRPC トラフィック、およびオリジンの書き換えは、VPC オリジンではサポートされていません。
VPC オリジンを使用したアクセス制限
ターゲットグループ設定でも、protocol_version = "GRPC"
を指定します。
resource "aws_lb" "alb" {
name = "${local.resource_prefix}-alb"
load_balancer_type = "application"
internal = "false"
idle_timeout = 60
enable_deletion_protection = false
subnets = module.vpc.public_subnets
security_groups = [
aws_security_group.alb.id
]
}
resource "aws_security_group" "alb" {
vpc_id = module.vpc.vpc_id
name = "${local.resource_prefix}-sg-alb"
ingress {
description = "HTTP from VPC"
from_port = 443
to_port = 443
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"]
}
}
resource "aws_lb_listener" "grpc" {
load_balancer_arn = aws_lb.alb.arn
port = "443"
protocol = "HTTPS"
certificate_arn = module.alb_acm.acm_certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.grpc_app.arn
}
}
resource "aws_lb_target_group" "grpc_app" {
name = "${local.resource_prefix}-tg-grpc-app"
target_type = "ip"
port = 50051
protocol = "HTTP"
protocol_version = "GRPC"
vpc_id = module.vpc.vpc_id
}
イメージビルドと ecspresso を利用した ECS 関連リソースの作成
作成した ECR に対してコンテナイメージを push していきます。
アプリケーションコードは grpc 公式リポジトリのサンプルを活用した簡単なものを利用しました。
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
type server struct{
pb.UnimplementedGreeterServer
}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.Name)
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
reflection.Register(s)
log.Printf("serving on :50051\n")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
app ディレクトリに移動してコンテナイメージをビルドします。
export IMAGE_NAME="grpc-app"
export AWS_ACCOUNT_ID="xxxxxxxxxxxx"
export COMMIT_HASH=$(git rev-parse --short HEAD)
docker build \
--platform=linux/x86_64 \
-t $IMAGE_NAME:$COMMIT_HASH \
-f Dockerfile --no-cache .
コンテナイメージを ECR に push します。
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com
docker tag $IMAGE_NAME:$COMMIT_HASH $AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/$IMAGE_NAME:$COMMIT_HASH
docker push $AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/$IMAGE_NAME:$COMMIT_HASH
続いて ECS 関連のリソースを作成します。
ecspresso deploy --config=./.ecspresso/config.yaml
※ ecspresso deploy を行う前に、ecs-task-def.json
でイメージタグを push したものに置き換える必要があります。また、config.yaml
で tfstate ファイルの場所を指定する必要があります。
動作確認
一通り構築が完了したので grpcurl でリクエストを送ってみます。
% grpcurl -vv -d '{"name": "kentaro"}' www.example.com:443 helloworld.Greeter.SayHello
Resolved method descriptor:
rpc SayHello ( .helloworld.HelloRequest ) returns ( .helloworld.HelloReply );
Request metadata to send:
(empty)
Response headers received:
content-type: application/grpc
date: Sun, 20 Apr 2025 04:04:53 GMT
server: CloudFront
via: 1.1 5f18cdf7ce4383d3046c528d1ee9da8a.cloudfront.net (CloudFront)
x-amz-cf-id: w4gEZazRSvwxG4QnQ2w38wg_GNHI8de5HmlODB41ON-WnFOL2NfxhA==
x-amz-cf-pop: NRT20-P4
x-cache: Miss from cloudfront
Estimated response size: 15 bytes
Response contents:
{
"message": "Hello kentaro"
}
Response trailers received:
(empty)
Sent 1 request and received 1 response
Timing Data: 383.45875ms
Dial: 252.740667ms
TLS Setup: 13.334µs
BlockingDial: 252.697459ms
InvokeRPC: 113.586458ms
CloudFront に対して gRPC リクエストを送信できました!
まとめ
ECS + ALB + CloudFront で gRPC アプリケーションを公開する構成を Terraform と ecspresso で構築してみました。
VPC オリジンや標準アクセスログ強化と合わせて、CloudFront が直近かなり強化されたなぁと感じております。
気になる方は是非試してみて下さい!