AWS CDK V2でCloudFront+S3の静的サイトホスティングを構築してみた

2022.03.19

こんにちは、CX事業本部 IoT事業部の若槻です。

昨年末にAWS CDK V2がデプロイされました。

V2では、CDK Constructの構成で必要となるパッケージが1つに集約されるなど、V1と比べてよりよい開発体験が提供されるようになりました。

また、V1は2022/6/1以降はメンテナンスモードに移行してしまうので、今後新規のCDKプロジェクトを作る際には必然的にV2を使うこととなりそうです。

今回は、そんなAWS CDK V2の肩慣らしとして、CloudFront+S3の静的サイトホスティングを構築してみました。

やってみた

CDKアプリ初回作成

cdk initでCDKアプリを初回作成します。

$ npx cdk init --language typescript

作成されたディレクトリの構成です。V1とほぼ同じに見えます。

package.jsonを見ると、CDKパッケージのバージョンが2.14.0とあり、CDKアプリがV2で作成されていることが分かります。

package.json

{
  "name": "aws-cdk-v2-app",
  "version": "0.1.0",
  "bin": {
    "aws-cdk-v2-app": "bin/aws-cdk-v2-app.js"
  },
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "test": "jest",
    "cdk": "cdk"
  },
  "devDependencies": {
    "@types/jest": "^26.0.10",
    "@types/node": "10.17.27",
    "jest": "^26.4.2",
    "ts-jest": "^26.2.0",
    "aws-cdk": "2.14.0",
    "ts-node": "^9.0.0",
    "typescript": "~3.9.7"
  },
  "dependencies": {
    "aws-cdk-lib": "2.14.0",
    "constructs": "^10.0.0",
    "source-map-support": "^0.5.16"
  }
}

あとは必要に応じてcdk bootstrapを行ってください。

CloudFront+S3静的サイトホスティングの構成

今回作りたかったCloudFront+S3静的サイトホスティングを構成してみます。

やはり注目すべきはaws-cdk-libという1つのパッケージからすべてのConstructのクラスをImportできている点です。これにより作りたいサービスやリソース毎にパッケージを追加したり、すべてのパッケージのバージョンを揃えたりする手間が省けるのが何より嬉しいです。

また、StackのコードはCDK V1で同様の構成を作った際のコードをほぼそのまま流用できました。Importしたクラスの使用はV1とV2でほぼ互換性があるようです。

lib/aws-cdk-v2-app-stack.ts

import { Construct } from 'constructs';
import {
  Stack,
  StackProps,
  aws_s3,
  aws_cloudfront,
  aws_cloudfront_origins,
  aws_s3_deployment,
  aws_iam,
  RemovalPolicy,
  Duration,
} from 'aws-cdk-lib';

export class AwsCdkV2AppStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const websiteBucket = new aws_s3.Bucket(this, 'WebsiteBucket', {
      removalPolicy: RemovalPolicy.DESTROY,
    });

    const originAccessIdentity = new aws_cloudfront.OriginAccessIdentity(
      this,
      'OriginAccessIdentity',
      {
        comment: 'website-distribution-originAccessIdentity',
      }
    );

    const webSiteBucketPolicyStatement = new aws_iam.PolicyStatement({
      actions: ['s3:GetObject'],
      effect: aws_iam.Effect.ALLOW,
      principals: [
        new aws_iam.CanonicalUserPrincipal(
          originAccessIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId
        ),
      ],
      resources: [`${websiteBucket.bucketArn}/*`],
    });

    websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement);

    const distribution = new aws_cloudfront.Distribution(this, 'distribution', {
      comment: 'website-distribution',
      defaultRootObject: 'index.html',
      errorResponses: [
        {
          ttl: Duration.seconds(300),
          httpStatus: 403,
          responseHttpStatus: 403,
          responsePagePath: '/error.html',
        },
        {
          ttl: Duration.seconds(300),
          httpStatus: 404,
          responseHttpStatus: 404,
          responsePagePath: '/error.html',
        },
      ],
      defaultBehavior: {
        allowedMethods: aws_cloudfront.AllowedMethods.ALLOW_GET_HEAD,
        cachedMethods: aws_cloudfront.CachedMethods.CACHE_GET_HEAD,
        cachePolicy: aws_cloudfront.CachePolicy.CACHING_OPTIMIZED,
        viewerProtocolPolicy:
          aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        origin: new aws_cloudfront_origins.S3Origin(websiteBucket, {
          originAccessIdentity,
        }),
      },
      priceClass: aws_cloudfront.PriceClass.PRICE_CLASS_ALL,
    });

    new aws_s3_deployment.BucketDeployment(this, 'WebsiteDeploy', {
      sources: [
        aws_s3_deployment.Source.data(
          '/index.html',
          '<html><body><h1>Hello World</h1></body></html>'
        ),
        aws_s3_deployment.Source.data(
          '/error.html',
          '<html><body><h1>Error!!!!!!!!!!!!!</h1></body></html>'
        ),
        aws_s3_deployment.Source.data('/favicon.ico', ''),
      ],
      destinationBucket: websiteBucket,
      distribution: distribution,
      distributionPaths: ['/*'],
    });
  }
}

cdk deployを実行してStackをデプロイします。

ディストリビューションドメインにアクセスすると、サイトにアクセスできました!

おわりに

AWS CDK V2でCloudFront+S3の静的サイトホスティングを構築してみました。

V1を使っていた時にインストールされるCDKパッケージのバージョンの固定を忘れてパッケージ間で不整合が起きデプロイが失敗するが原因がなかなか分からないというトラウマな経験があったのですが、V2となり今後そのような悲劇が誰かに降りかかることは無いと思うと隔世の感です。

参考

以上