AWS CDKを使ってS3とCloudFrontでWebサイトを配信する
AWS CDK を使って S3 と CloudFront で Web サイトを配信します。
S3 にアップロードした HTML を CloudFront で配信し、CloudFront を経由してのみ HTML にアクセスできるように設定します。
また、今回 CDK で使用する言語は TypeScript です。
環境
- macOS Big Sur 11.2.3
- node 15.14.0
- npm 7.7.6
- yarn 1.22.4
- cdk 1.98.0
プロジェクトの作成
作業ディレクトリとプロジェクトを作成します。
作業ディレクトリは cdk-s3-cloud-front-deploy
という名前にしています。
mkdir cdk-s3-cloud-front-deploy && cd cdk-s3-cloud-front-deploy
cdk init
を実行して雛形を作成します。
cdk init --language typescript
実行が完了すると以下雛形が作成されました。
├── README.md
├── bin
│ └── cdk-s3-cloud-front-deploy.ts
├── cdk.json
├── jest.config.js
├── lib
│ └── cdk-s3-cloud-front-deploy-stack.ts
├── node_modules
│ ├── @aws-cdk
│ ├── @babel
│ :
│ :
├── package-lock.json
├── package.json
├── test
│ └── cdk-s3-cloud-front-deploy.test.ts
└── tsconfig.json
AWS module の install
S3 と CloudFront のモジュールを追加します。
S3 と CloudFront に加え IAM も利用するのでインストールしておきます。
$ yarn add @aws-cdk/aws-iam @aws-cdk/aws-s3 @aws-cdk/aws-cloudfront
S3 の Bucket を作成する
最初に S3 の Bucket を新規作成してみたいと思います。
今回作成するバケット名は cdk.json に記述したいと思います。
今回は cdk-sample-bucket
というバケット名にします。
プロジェクトルート直下にある cdk.json を開いて、context の中に以下の記述をします。
{
"app": "npx ts-node --prefer-ts-exts bin/cdk-s3-cloud-front-deploy.ts",
"context": {
"s3": {
"bucketName": "cdk-sample-bucket"
}
}
}
次に実際に S3 のバケットを作成します。
記述は先程作成した雛形の以下ファイルを編集していきます。
bin/cdk-s3-cloud-front-deploy.ts
import * as cdk from '@aws-cdk/core';
import * as s3 from '@aws-cdk/aws-s3';
export class CdkS3CloudFrontDeployStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const bucketName: string = this.node.tryGetContext('s3').bucketName;
const originBucket = new s3.Bucket(this, 'S3Bucket', {
bucketName: bucketName,
// Bucketへの直接アクセスを禁止
accessControl: s3.BucketAccessControl.PRIVATE,
// CDK Stack削除時にBucketも削除する
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
}
}
this.node.tryGetContext('s3').bucketName;
で先程 cdk.json の context に記述した要素が取得できます。
それでは以下のコマンドを実行して deploy してみましょう。
cdk deploy
実行して以下のログが流れれば成功です。
$ cdk deploy
CdkS3CloudFrontDeployStack: deploying...
CdkS3CloudFrontDeployStack: creating CloudFormation changeset...
[█████████████████████████████·····························] (1/2)
✅ CdkS3CloudFrontDeployStack
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:999216002524:stack/CdkS3CloudFrontDeployStack/cedbfd30-a24c-11eb-bd62-0ed47b91b1b7
実際にバケットが作成されたかコンソールを開いて確認します。
確認したら以下のコマンドでバケットを削除しておきます。
cdk destroy
以下ログが流れたらコンソールを確認して S3 のバケットが削除されているか確認しましょう。
$ cdk destroy
Are you sure you want to delete: CdkS3CloudFrontDeployStack (y/n)? y
CdkS3CloudFrontDeployStack: destroying...
12:05:59 | DELETE_IN_PROGRESS | AWS::CloudFormation::Stack | CdkS3CloudFrontDeployStack
12:06:02 | DELETE_IN_PROGRESS | AWS::CDK::Metadata | CDKMetadata/Default
12:06:03 | DELETE_IN_PROGRESS | AWS::S3::Bucket | S3Bucket
✅ CdkS3CloudFrontDeployStack: destroyed
CloudFormation の Distribution を作成する
先程のファイルに CloudFormation の distribution を作成する実装を追記します。
ソースコード全文はこちらになります。
bin/cdk-s3-cloud-front-deploy.ts
import * as cdk from '@aws-cdk/core';
import * as s3 from '@aws-cdk/aws-s3';
import * as cloudfront from '@aws-cdk/aws-cloudfront';
import * as iam from '@aws-cdk/aws-iam';
export class CdkS3CloudFrontDeployStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// cdk.jsonからbucketNameを取得
const bucketName: string = this.node.tryGetContext('s3').bucketName;
// bucketを新規作成
const bucket = new s3.Bucket(this, 'S3Bucket', {
bucketName: bucketName,
// Bucketへの直接アクセスを禁止
accessControl: s3.BucketAccessControl.PRIVATE,
// CDK Stack削除時にBucketも削除する
removalPolicy: cdk.RemovalPolicy.DESTROY,
websiteIndexDocument: 'index.html',
websiteErrorDocument: 'index.html',
});
// S3 を公開状態にすることなく、S3 へのアクセスを CloudFront からのリクエストに絞る為の仕組み
const identity = new cloudfront.OriginAccessIdentity(this, 'OriginAccessIdentity', {
comment: `${bucket.bucketName} access identity`,
});
// principalsに設定したアクセス元からのみに S3 バケットのGetObject権限を渡す
// ポリシーを設定することで、S3 バケットのオブジェクトは CloudFront を介してのみアクセスできる
const bucketPolicyStatement = new iam.PolicyStatement({
actions: ['s3:GetObject'],
effect: iam.Effect.ALLOW,
principals: [identity.grantPrincipal],
resources: [`${bucket.bucketArn}/*`],
});
// bucketにポリシーをアタッチ
bucket.addToResourcePolicy(bucketPolicyStatement);
// CloudFrontのdistribution作成
new cloudfront.CloudFrontWebDistribution(this, 'WebDistribution', {
enableIpV6: true,
httpVersion: cloudfront.HttpVersion.HTTP2,
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
originConfigs: [
{
s3OriginSource: {
s3BucketSource: bucket,
originAccessIdentity: identity,
},
behaviors: [
{
isDefaultBehavior: true,
allowedMethods: cloudfront.CloudFrontAllowedMethods.GET_HEAD,
cachedMethods: cloudfront.CloudFrontAllowedCachedMethods.GET_HEAD,
forwardedValues: {
queryString: false,
},
},
],
},
],
// 403/404エラーはindex.htmlを表示
errorConfigurations: [
{
errorCode: 403,
responseCode: 200,
errorCachingMinTtl: 0,
responsePagePath: '/index.html',
},
{
errorCode: 404,
responseCode: 200,
errorCachingMinTtl: 0,
responsePagePath: '/index.html',
},
],
priceClass: cloudfront.PriceClass.PRICE_CLASS_200,
});
}
}
Deploy する
それでは以下のコマンドを実行して deploy してみましょう。
cdk deploy
途中で Do you wish to deploy these changes (y/n)?
と効かれるので y を入力します。
以下のログが流れれば deploy 成功です。
$ cdk deploy
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:
IAM Statement Changes
┌───┬─────────────────────────────────────┬────────┬──────────────┬──────────────────────────────────────┬───────────┐
│ │ Resource │ Effect │ Action │ Principal │ Condition │
├───┼─────────────────────────────────────┼────────┼──────────────┼──────────────────────────────────────┼───────────┤
│ + │ ${S3Bucket.Arn}/* │ Allow │ s3:GetObject │ CanonicalUser:${OriginAccessIdentity │ │
│ │ │ │ │ .S3CanonicalUserId} │ │
└───┴─────────────────────────────────────┴────────┴──────────────┴──────────────────────────────────────┴───────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)
Do you wish to deploy these changes (y/n)? y
CdkS3CloudFrontDeployStack: deploying...
CdkS3CloudFrontDeployStack: creating CloudFormation changeset...
[██████████████████████████████████████████████████████████] (6/6)
✅ CdkS3CloudFrontDeployStack
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:999216002524:stack/CdkS3CloudFrontDeployStack/cd1f9570-a273-11eb-80c3-06420aaf754f
動作確認
動作確認の為、ローカルの HTML ファイルを S3 にアップロードします。
mkdir ./html
echo '<html><head><title>CDK deploy test</title></head><body><h1>Hello! CDK!</h1></body></html>' > ./html/index.html
上記のように index.html
を作成して S3 にファイルをアップロードしておきます。
次に CloudFront コンソールにアクセスして CloudFront の distribution が作成されていることを確認します。
Domain Name が作成された CloudFront のドメインです。
CloudFront のドメインにアクセスして先程作成した HTML が表示されれば成功です。
次に S3 のドメインにアクセスしてみてください。
S3 のドメインは CloudFront の Origin に記載されている ドメイン です。
アクセスすると以下のように Access Denied になるはずです。
また、CloudFront のドメインの後に適当なパスを入力してみてください。
404 エラーは index.html を表示するように実装しているので、先程の Hello! CDK!
が表示されるはずです。
Deploy した環境を削除する
作成した環境を削除したい場合は cdk destroy
コマンドを実行してください。
cdk destroy
途中で Are you sure you want to delete: {StackName} (y/n)?
と聞かれますので y を入力します。
ここで注意が必要なのが S3 にファイルが残っていると以下のエラーが発生します。
以下ログが流れたら S3 にあるファイルを全て削除して再度 cdk destroy
を実行します。
❌ CdkS3CloudFrontDeployStack: destroy failed Error: The stack named CdkS3CloudFrontDeployStack is in a failed state. You may need to delete it from the AWS console : DELETE_FAILED (The following resource(s) failed to delete: [S3Bucket07682993]. )
at Object.waitForStackDelete (/usr/local/lib/node_modules/aws-cdk/lib/api/util/cloudformation.ts:277:11)
at processTicksAndRejections (node:internal/process/task_queues:94:5)
at Object.destroyStack (/usr/local/lib/node_modules/aws-cdk/lib/api/deploy-stack.ts:395:28)
at CdkToolkit.destroy (/usr/local/lib/node_modules/aws-cdk/lib/cdk-toolkit.ts:253:9)
at initCommandLine (/usr/local/lib/node_modules/aws-cdk/bin/cdk.ts:208:9)
The stack named CdkS3CloudFrontDeployStack is in a failed state. You may need to delete it from the AWS console : DELETE_FAILED (The following resource(s) failed to delete: [S3Bucket07682993]. )
以下のログが流れれば削除成功です。
$ cdk destroy
Are you sure you want to delete: CdkS3CloudFrontDeployStack (y/n)? y
CdkS3CloudFrontDeployStack: destroying...
17:21:45 | DELETE_IN_PROGRESS | AWS::CloudFormation::Stack | CdkS3CloudFrontDeployStack
✅ CdkS3CloudFrontDeployStack: destroyed
S3 と CloudFront のコンソールにアクセスして、Bucket や Distribution が削除されていることを確認してください。
おわりに
簡単ですが S3 と CloudFront で Web サイトを配信する方法でした。
筆者は CDK 初心者の為、内容が誤っていたり、もっとこういうやり方があるよ!という方はぜひ Twitter で DM していただくか Contact で教えて頂けると助かります。
更に調べてみるとローカルのソースを CDK で S3 にデプロイしたり、CloudFront のキャッシュ削除の自動化、独自ドメインを CloudFront に割り当てるなど色々出来そうです。
これらも CDK で実装できたら記事にしていきます。
最後に、ここまでのソースは Github にもありますので参照ください。