AWS CDKでリソース作成 第9回: CloudFront編

こんにちは。 エキサイト株式会社で内定者アルバイトをしています。

今回も、実際のリソースの作成を行いたいと思います。第9回は、CloudFrontです。

前の記事

tech.excite.co.jp

CloudFrontとは?

CloudFrontはコンテンツ配信のためのリソースです。コンテンツにはHTMLファイル、CSSファイル、JSファイル、画像ファイルなどが含まれます。
レイテンシーかつ高速な転送速度での配信が実現できるそうです。

また、トラフィックの暗号化やアクセス制御によりセキュリティを向上させ、AWS Shield Standardを利用することで追加料金なしでDDoS攻撃を防御することもできるようです。

このように、低レイテンシーかつ高セキュリティとのことで、ウェブサイトの提供、動的コンテンツ配信、ライブおよびオンデマンドのビデオストリーミング、パッチやアップデートの配信など様々なユースケースが提示されています。

Amazon CloudFront(グローバルなコンテンツ配信ネットワーク)| AWS

コンテンツ配信の流れ

アプリケーションの開発者は配信するコンテンツを格納するサーバであるオリジンサーバを指定します。オリジンサーバにはEC2インスタンスやS3バケットロードバランサーなどを指定することができます。

CloudFrontはオリジンサーバからコンテンツを取得して、これを配信します。

CloudFrontではbehaviorというものを設定します。
behaviorでは、パスパターン、オリジンサーバなどを指定し、どのパスパラメータに対するリクエストが来たらどのオリジンサーバにコンテンツ配信のリクエストを送信するかを設定できます。
また、CloudFrontにはオリジンサーバへのリクエスト回数を減らすために、キャッシュがありbehaviorごとにキャッシュポリシーを設定することができます。

behaviorには順番が存在し、指定した順番にパスパターンを評価します。
ですので、正しくコンテンツ配信を行うためにはbehaviorの順番が重要になる場合もあります。

実際に作成してみる

今回は以下のような設定を持つディストリビューション(CloudFrontにおけるインスタンスのようなもの)を作成します。パスやキャッシュポリシーなどは適当に設定したものです。

behavior

優先順位 パスパターン オリジン キャッシュポリシー
0 /load/* ロードバランサー キャッシュ無効
1 /s3/*.js S3(JS、CSSファイル用) assets-cache-policy
2 /s3/*.css S3(JS、CSSファイル用) assets-cache-policy
3 デフォルト S3(デフォルト用) デフォルト

assets-cache-policy

項目 設定
デフォルトTTL(秒) 600
最小TTL(秒) 1
最大TTL(秒) 31536000
キャッシュキー設定:HTTPヘッダ なし
キャッシュのGzip圧縮 有効
キャッシュのBrotli圧縮 有効

前回までのコード

前回はCloudWatchのダッシュボードとアラームを作成しました。

しかし、今回はその部分は使いませんので、VPCおよびサブネットを作成した部分を再掲します。

package com.myorg;

import software.amazon.awscdk.CfnTag;
import software.amazon.awscdk.services.ec2.CfnInternetGateway;
import software.amazon.awscdk.services.ec2.CfnVPCGatewayAttachment;
import software.amazon.awscdk.services.ec2.PrivateSubnet;
import software.amazon.awscdk.services.ec2.PublicSubnet;
import software.amazon.awscdk.services.ec2.Vpc;
import software.constructs.Construct;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;

import java.util.List;

public class MyProjectStack extends Stack {
    public MyProjectStack(final Construct scope, final String id) {
        this(scope, id, null);
    }

    public MyProjectStack(final Construct scope, final String id, final StackProps props) {
        super(scope, id, props);

        Vpc vpc = Vpc.Builder.create(this, "SampleVPC")
                .vpcName("sample-vpc")
                .cidr("cidr")
                .subnetConfiguration(List.of())
                .build();

        // Availability zone: ap-northeast-1a
        PrivateSubnet privateSubnetA = PrivateSubnet.Builder.create(this, "PrivateSubnetA")
                .availabilityZone("ap-northeast-1a")
                .vpcId(vpc.getVpcId())
                .cidrBlock("cidr")
                .build();

        PublicSubnet publicSubnetA = PublicSubnet.Builder.create(this, "PublicSubnetA")
                .availabilityZone("ap-northeast-1a")
                .vpcId(vpc.getVpcId())
                .cidrBlock("cidr")
                .build();

        // Availability zone: ap-northeast-1c
        PrivateSubnet privateSubnetB = PrivateSubnet.Builder.create(this, "PrivateSubnetB")
                .availabilityZone("ap-northeast-1c")
                .vpcId(vpc.getVpcId())
                .cidrBlock("cidr")
                .build();

        PublicSubnet publicSubnetB = PublicSubnet.Builder.create(this, "PublicSubnetB")
                .availabilityZone("ap-northeast-1c")
                .vpcId(vpc.getVpcId())
                .cidrBlock("cidr")
                .build();

        // NAT gatewayの追加
        publicSubnetA.addNatGateway();
        publicSubnetB.addNatGateway();

        // Internet Gateway
        CfnInternetGateway internetGateway = CfnInternetGateway.Builder.create(this, "InternetGateway")
                .tags(List.of(CfnTag.builder()
                        .key("Name")
                        .value("sample-internet-gateway")
                        .build()))
                .build();

        // Internet gatewayをVPCにattachする
        CfnVPCGatewayAttachment.Builder.create(this, "VpcGateAwayAttachment")
                .vpcId(vpc.getVpcId())
                .internetGatewayId(internetGateway.getAttrInternetGatewayId())
                .build();
    }
}

CloudFrontのディストリビューションを作成するコード

        // CloudFront
        // オリジン(S3バケット)の作成
        Bucket defaultOriginBucket = Bucket.Builder.create(this, "CloudFrontOriginBucket")
                .bucketName("cloud-front-origin-bucket-01")
                .encryption(BucketEncryption.S3_MANAGED)
                .build();

        Bucket assetsOriginBucket = Bucket.Builder.create(this, "CloudFrontOriginBucket2")
                .bucketName("cloud-front-origin-bucket-02")
                .encryption(BucketEncryption.S3_MANAGED)
                .build();

        // オリジン(ロードバランサー)の作成
        ApplicationLoadBalancer cloudFrontOriginLoadBalancer = ApplicationLoadBalancer.Builder.create(this, "CloudFrontOriginLoadBalancer")
                .loadBalancerName("origin-load-balancer")
                .vpc(vpc)
                .vpcSubnets(SubnetSelection.builder()
                        .subnets(List.of(publicSubnetA, publicSubnetB))
                        .build())
                .internetFacing(true)
                .build();

        // ① キャッシュポリシーを定義
        CachePolicy assetsCachePolicy = CachePolicy.Builder.create(this, "SampleCustomCachePolicy")
                .cachePolicyName("assets-cache-policy")
                .comment("Cache policy for js, css file")
                .defaultTtl(Duration.seconds(600))
                .minTtl(Duration.seconds(1))
                .maxTtl(Duration.seconds(31536000))
                .headerBehavior(CacheHeaderBehavior.none())
                .enableAcceptEncodingGzip(true)
                .enableAcceptEncodingBrotli(true)
                .build();

        IOrigin s3Origin = S3Origin.Builder.create(assetsOriginBucket).build();
        IOrigin loadBalancerOrigin = LoadBalancerV2Origin.Builder.create(cloudFrontOriginLoadBalancer).build();

        // ② behaviorを作成
        BehaviorOptions assetsBehaviorOptions = BehaviorOptions.builder()
                .origin(s3Origin)
                .cachePolicy(assetsCachePolicy)
                .cachedMethods(CachedMethods.CACHE_GET_HEAD)
                .viewerProtocolPolicy(ViewerProtocolPolicy.ALLOW_ALL)
                .allowedMethods(AllowedMethods.ALLOW_GET_HEAD)
                .compress(true)
                .build();

        BehaviorOptions loadBalancerBehaviorOptions = BehaviorOptions.builder()
                .origin(loadBalancerOrigin)
                .cachePolicy(CachePolicy.CACHING_DISABLED)
                .build();

        // ③ ディストリビューションを作成
        Distribution sampleDistribution = Distribution.Builder.create(this, "SampleDistribution")
                .defaultBehavior(BehaviorOptions.builder()
                        .origin(S3Origin.Builder.create(defaultOriginBucket).build())
                        .build())
                .build();

        sampleDistribution.addBehavior("/load/*", loadBalancerOrigin, loadBalancerBehaviorOptions);
        sampleDistribution.addBehavior("/s3/*.js", s3Origin, assetsBehaviorOptions);
        sampleDistribution.addBehavior("/s3/*.css", s3Origin, assetsBehaviorOptions);
    }
}

前半ではオリジンサーバとなるS3バケットロードバランサーを作成しています。

①では、キャッシュポリシーを定義しています。上記の表で示した通りの設定ですので、特に説明は不要かと思います。

②では、behaviorを作成しています。CDKではBehaviorOptionsクラスを使います。
originでオリジンを指定し、そのほかの設定項目を指定していきます。viewerProtocolPolicyはビューワーがこのbehaviorによって管理されるファイルにアクセスするために許可されたプロトコルを表します。
ここではすべてのプロトコルを許可します。
この他にはHTTPSのみを許可するオプションと、HTTPでアクセスされたらHTTPSにリダイレクトするオプションがあります。

③では、実際にディストリビューションを作成しています。ここでは、デフォルトのbehaviorと追加のbehaviorを設定しています。
ちなみにデフォルトのbehaviorは、すべてのパスにマッチします(追加で設定したbehaviorのどれにもマッチしなかった場合に使われるので「デフォルト」と呼ばれます)。

さて、behaviorの優先順位をどのように指定するかですが、2通りの方法があるように思います。

1つ目はDistributionaddBehaviorメソッドを使う方法です。
これを使えば、addBehaviorで追加した順にbehaviorの優先順位が決まります。
addBehaviorには第1引数にパスパターンを、第2引数にオリジンを、オプショナルで第3引数にはBehaviorOptionsを渡します。
サンプルコードではこちらを使っています。

2つ目はLinkedHashMapを使う方法です。
これは順番が保障されたMapです。通常のHashMapは順番が保障されていないため、Map.of()Mapを作成するとbehaviorの順番はバラバラになってしまう可能性があります。
LinkedHashMapを使う場合には以下のようにします。

        Map<String, BehaviorOptions> additionalBehaviors = new LinkedHashMap<>();
        additionalBehaviors.put("/load/*", loadBalancerBehaviorOptions);
        additionalBehaviors.put("/s3/*.js", assetsBehaviorOptions);
        additionalBehaviors.put("/s3/*.css", assetsBehaviorOptions);

        Distribution sampleDistribution = Distribution.Builder.create(this, "SampleDistribution")
                .defaultBehavior(BehaviorOptions.builder()
                        .origin(S3Origin.Builder.create(defaultOriginBucket).build())
                        .build())
                .additionalBehaviors(additionalBehaviors)
                .build();

デプロイ

ここまで記述できたら、あとはデプロイするだけです。

cdk deploy

エラーなくデプロイが完了したらOKです。

終わりに

今回は、AWS CDKを用いてCloudFrontのディストリビューションを作成しました。

今回で、実際のAWSリソースの作成は一旦終了です。次回は、AWS CDKでGradleを使う方法についてご紹介したいと思います。

では、また別の記事で。

次の記事

tech.excite.co.jp

参考文献