📑

AWS CDK で CloudFront + S3 を使った Web サイト配信環境をサクッと構築する方法

2024/09/19に公開

これは何

  • AWS CDK で CloudFront + S3 構成で Web サイトを配信する環境を構築するメモ
  • 構成
    • S3 Bucket 自体の Web Site Hosting は利用しない
    • CloudFront から S3 へのアクセスは OAC を利用して制御する

CDK App のプロジェクトを用意

mkdir my-project && cd my-project
npx aws-cdk init app --language typescript ./

ホストする html ファイルとディレクトリを作成

mkdir website-dist
touch website-dist/index.html

index.html の中身

<!DOCTYPE html>
<html>
<head>
    <title>Test</title>
</head>
<body>
    <h1>Hello World!</h1>
</body>
</html>

CDK App を実装

lib/my-project-stack.ts を以下のように実装

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';

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

    const destinationBucket = new s3.Bucket(this, 'WebsiteBucket', {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });

    const distribution = new cloudfront.Distribution(this, 'WebsiteDistribution', {
      defaultRootObject: 'index.html',
      defaultBehavior: {
        origin: origins.S3BucketOrigin.withOriginAccessControl(destinationBucket)
      }
    });

    new s3deploy.BucketDeployment(this, 'DeployWebsite', {
      sources: [s3deploy.Source.asset('./website-dist')],
      destinationBucket,
      distribution,
      distributionPaths: ['/*'],
    });

    new cdk.CfnOutput(this, 'Hosting URL', {
      value: 'https://' + distribution.distributionDomainName
    });
  }
}

各メソッドやリソースのプロパティについて軽く解説

S3 Bucket

    const destinationBucket = new s3.Bucket(this, 'WebsiteBucket', {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3-readme.html

  • removalPolicy: cdk.RemovalPolicy.DESTROY
    • CloudFormation Stack を削除した時に S3 バケットをどう扱うかの設定
    • cdk.RemovalPolicy.DESTROY を指定すると Stack 削除時に S3 バケットも削除する
  • autoDeleteObjects: true
    • S3 バケットが Stack から削除される時、または Stack が削除される時に全てのオブジェクトを自動的に削除するか

CloudFront distribution, origin

    const distribution = new cloudfront.Distribution(this, 'WebsiteDistribution', {
      defaultRootObject: 'index.html',
      defaultBehavior: {
        origin: origins.S3BucketOrigin.withOriginAccessControl(destinationBucket)
      }
    });

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront-readme.html

  • defaultRootObject: 'index.html'
    • distribution のルート URL に対してリクエストした際に、 CloudFront がオリジンにリクエストするオブジェクト
  • defaultBehavior
    • distribution のデフォルト挙動
  • origin
    • behavior に一致した場合に CloudFront がリクエストをルーティングするオリジン

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudfront_origins-readme.html

  • aws_cloudfront_origins
    • CloudFront distribution のオリジンを定義するためのライブラリ
  • origins.S3BucketOrigin.withOriginAccessControl(destinationBucket)
    • S3BucketOrigin
      • S3 Bucket を CloudFront distribution のオリジンとして設定
    • withOriginAccessControl
      • OAC (Origin Access Control) を有効化して S3 Bucket オリジンを設定

S3 Deployment

    new s3deploy.BucketDeployment(this, 'DeployWebsite', {
      sources: [s3deploy.Source.asset('./website-dist')],
      destinationBucket,
      distribution,
      distributionPaths: ['/*'],
    });

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3_deployment-readme.html

  • aws_s3_deployment
    • ローカル等の環境から、指定したファイルやディレクトリを S3 バケットに格納するためのライブラリ
  • sources
    • S3 バケットに格納するデータの指定
  • s3deploy.Source.asset('./website-dist')
    • source として ./website-dist ディレクトリを指定
  • destinationBucket
    • デプロイ先となる S3 バケット
  • distribution
    • デプロイ先となる S3 バケットをオリジンにする CloudFront distribution
    • S3 バケットへのデプロイ後、エッジキャッシュの invalidation が行われる
  • distributionPaths
    • エッジキャッシュの invalidation を行う対象となる file path 指定

Cfn Output

    new cdk.CfnOutput(this, 'Hosting URL', {
      value: 'https://' + distribution.distributionDomainName
    });

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.CfnOutput.html

  • CloudFormation Stack の Outputs を扱う
  • 'https://' + distribution.distributionDomainName で CloudFront distribution にアクセスする URL を生成

CDK App をデプロイ

npm run build
npx aws-cdk deploy

動作を確認

npx aws-cdk deploy が成功したら Outputs に CloudFront distribution の URL が出力されるので、この URL にアクセスして HTML が表示されれば OK

Outputs:
MyProjectStack.HostingURL = https://abcdefg.cloudfront.net

curl で確認する例

% curl https://abcdefg.cloudfront.net
<!DOCTYPE html>
<html>
<head>
    <title>Test</title>
</head>
<body>
    <h1>Hello World!</h1>
</body>
</html>

環境削除

npx aws-cdk destroy

感想など

  • Web で調べた情報が一部古かったりして試行錯誤してしまったが、結果的にはシンプルに実現出来てよかった
  • シンプルにファイルや Static Web Site をホストしたい場合にサクッと使えて便利
    • React などで実装した SPA も、ビルドアーティファクトを Source Asset に指定すれば問題ない
  • ただし CloudFront のデプロイが必要となるため、これはやや時間がかかってしまう
    • 自分の環境だと、長い場合はデプロイに 5分 くらいかかる場面もあった
    • 開発中は S3 の Web Site Hosting で試しつつ、本格的に稼働する際に CloudFront に乗せる・・・みたいな運用でも良いかもしれない

試した環境

開発環境

% sw_vers
ProductName:            macOS
ProductVersion:         14.6.1
BuildVersion:           23G93

package.json

{
  "name": "my-project",
  "version": "0.1.0",
  "bin": {
    "my-project": "bin/my-project.js"
  },
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "test": "jest",
    "cdk": "cdk"
  },
  "devDependencies": {
    "@types/jest": "^29.5.12",
    "@types/node": "22.5.4",
    "jest": "^29.7.0",
    "ts-jest": "^29.2.5",
    "aws-cdk": "2.159.0",
    "ts-node": "^10.9.2",
    "typescript": "~5.6.2"
  },
  "dependencies": {
    "aws-cdk-lib": "2.159.0",
    "constructs": "^10.0.0",
    "source-map-support": "^0.5.21"
  }
}

Discussion