この記事は AWS CDK v1 を使用しています。
2022/09/03 現在は AWS CDK v2 がリリースされており、内容が古くなっている可能性があります。
本記事でやること
- AWS 上に CloudFront + S3 の構成 で静的ウェブサイトをホスティングします
- create-react-app で作成した React のアプリケーションを題材にします
-
AWS CDK を使用して、CloudFront や S3 をプロビジョニングし、アプリケーションのデプロイまで行います
AWS CDK を使用すると、アプリの S3 へのアップロードから CloudFront の Invalidation まで、デプロイ作業がすべて自動化できます!すごい!
いいからコードを!という方はこちらをご参照ください。
手順
AWS のアクセスキーの設定
aws コマンドを使用して、アクセスキーなどの AWS のアクセス情報を設定しておきます。
これにより、AWS CDK が AWS のサービスへアクセスできるようになります。
アクセスキーは AWS マネジメントコンソールの IAM から発行できます。
% aws configure --profile AWSプロファイル名
AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXX # アクセスキーを指定
AWS Secret Access Key [None]: XXXXXXXXXXXXXXXXXXX # シークレットアクセスキーを指定
Default region name [None]: ap-northeast-1 # お好みで。ここでは東京リージョンを指定
Default output format [None]: json
ここで指定した AWS プロファイル名は後程利用します。
React のアプリケーション作成
デプロイ対象の React アプリケーションを作成します。
こちらに変更を加えていきます。
% yarn create react-app --template typescript react-aws-cdk
% cd react-aws-cdk
依存ライブラリのインストール
必要なライブラリをインストールします。
yarn add -D \
@aws-cdk/aws-cloudfront \
@aws-cdk/aws-cloudfront-origins \
@aws-cdk/aws-s3 \
@aws-cdk/aws-s3-deployment \
@aws-cdk/core \
aws-cdk \
ts-node \
cross-env
package.json に以下のような差分が生じます。
@aws-cdk/*
のパッケージはバージョンが同じでないと不整合を起こすことがあります。
この場合は大丈夫ですが、あとから依存ライブラリを追加するときは要注意です。
"devDependencies": {
"@aws-cdk/aws-cloudfront": "^1.118.0",
"@aws-cdk/aws-cloudfront-origins": "^1.118.0",
"@aws-cdk/aws-s3": "^1.118.0",
"@aws-cdk/aws-s3-deployment": "^1.118.0",
"@aws-cdk/core": "^1.118.0",
"aws-cdk": "^1.118.0",
"cross-env": "^7.0.3",
"ts-node": "^10.2.0"
}
AWS CDK のセットアップ
AWS CDK を使用するには、事前セットアップが必要です。
AWS CDK が使用する S3 バケットを作成するなどが行われるようです。
% yarn cdk bootstrap --profile AWSプロファイル名 aws://AWSアカウントID/リージョン名
# 例えば、以下のとき
# AWSプロファイル名: hoge
# AWSアカウントID: 999999999999
# リージョン名: ap-northeast-1
% yarn cdk bootstrap --profile hoge aws://999999999999/ap-northeast-1
AWS CDK のコードを作成
TypeScript の設定ファイルを作成します。
React と AWS CDK では異なる設定が必要なため、ここで作成しておきます。
{
"compilerOptions": {
"target": "es2020",
"moduleResolution": "node"
}
}
app.ts
は AWS CDK のエントリーポイントです。
AWS CloudFormation のスタック名を環境変数で指定できるようにしています。
作成するリソースの定義は後述の spa-stack.ts
に記述していきます。
import * as cdk from "@aws-cdk/core";
import { exit } from "process";
import { SpaStack } from "./spa-stack";
const stackName = process.env["STACK_NAME"];
if (!stackName) {
console.error("missing environment variable STACK_NAME");
exit(1);
}
const app = new cdk.App();
new SpaStack(app, stackName, {});
spa-stack.ts
に作成するリソースを定義します。
設定内容はコードのコメントを参照してください。
import * as cdk from "@aws-cdk/core";
import * as cloudfront from "@aws-cdk/aws-cloudfront";
import * as s3 from "@aws-cdk/aws-s3";
import * as s3deploy from "@aws-cdk/aws-s3-deployment";
import { Duration } from "@aws-cdk/core";
import * as origins from "@aws-cdk/aws-cloudfront-origins";
export type SpaStackProps = {};
export class SpaStack extends cdk.Stack {
constructor(
scope: cdk.Construct,
id: string,
props?: cdk.StackProps & SpaStackProps
) {
super(scope, id, props);
// 配信するコンテンツを配置する S3 バケット
const originBucket = new s3.Bucket(this, "OriginBucket");
// CloudFront のログを配置する S3 バケット
const logBucket = new s3.Bucket(this, "LogBucket");
const distribution = new cloudfront.Distribution(this, "Distribution", {
defaultBehavior: {
// 配信するコンテンツを指定する
origin: new origins.S3Origin(originBucket),
// HTTP でアクセスされたら HTTPS へリダイレクトする
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
// ログの出力先を設定する
logBucket: logBucket,
// ルートへのアクセスに対して返却するコンテンツを設定する
defaultRootObject: "index.html",
errorResponses: [
// S3 に指定されたファイルが存在しないとき、S3 は 403 エラーを返すが、
// CloudFront により エラーを index.html の返却に置き換える
// (SPA では、どの画面についても、index.html により表示を行うため)
// https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-website-cloudfront-error-403/
{
httpStatus: 403,
responseHttpStatus: 200,
responsePagePath: "/index.html",
ttl: Duration.minutes(5),
},
],
// 日本を含む地域のエッジロケーションからコンテンツを配信する
// https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/PriceClass.html
priceClass: cloudfront.PriceClass.PRICE_CLASS_200,
});
// build ディレクトリを S3 にアップロードして、アプリケーションをデプロイする
new s3deploy.BucketDeployment(this, "Deployment", {
sources: [s3deploy.Source.asset("./build")],
destinationBucket: originBucket,
// CloudFront のキャッシュを削除し、コンテンツを最新化する
distribution: distribution,
});
}
}
npm scripts にデプロイコマンドを追加し、実行する
package.json
の scripts にデプロイ用のコマンドを追加します。
デプロイは cdk deploy
コマンドであり、 オプション -a
でエントリーポイントを指定します。
作成する CloudFormation スタック名は環境変数から取得することにしているので、コマンド内に指定しています。
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
- "eject": "react-scripts eject"
+ "eject": "react-scripts eject",
+ "deploy": "cross-env STACK_NAME=react-aws-cdk-dev cdk -a \"ts-node ./deployment/app.ts\" deploy"
},
アプリケーションをビルドし、次にデプロイします。
% yarn build
% yarn deploy --profile AWSプロファイル名
デプロイ結果の確認
AWS マネジメントコンソールで CloudFront にアクセスし、URL を確認します。
おわりに
初めて AWS CDK を使いましたが、おもしろいです!
CloudFormation と比較すると…
- プログラミング言語を使用するから、for も if も使い放題
→ 柔軟性・拡張性が高い - 各リソースのプロパティに AWS 様おすすめのデフォルト値が割り当てられている
→ 記述負荷が低く、意識せずともベストプラクティスに従える - s3-deployment などのリソース作成ではなく、ワークフローを担う機能を利用できる
今回は実施しませんでしたが、以下ができるとよりよそうです。
- Lambda@Edge や CloudFront Functions で レスポンスに HTTP ヘッダーを付与する
- ACM, Route53 と連携させて、ドメイン設定を行う
参考
作成にあたり公式ドキュメントの他、DevelopersIO様を参考にさせていただきました。
本記事では、比較的新しいクラスの cloudfront.Distribution
を利用して、記述をブラッシュアップしました。
明示的に OAI (Origin Access Identity)を記述しなくても、しっかりと OAI が生成されていい感じです。
以上です。
ありがとうございました。