aws-solutions-constructsを使って楽してAWS CDK構築する(静的サイト構築編)
はじめに
こんにちは、株式会社ゼンビットのゆうとです。
私個人のAWS開発環境について、コンソールからAWS構築している状態です。
本記事ではaws-solutions-constructsを使って、
静的サイト構築をベストプラクティスに沿いつつ少ないコードで構築していきます😎
aws-solutions-constructsとは
ありがちな構成のAWS CDK構成を2つ以上組み合わせてくれるライブラリ集合体という理解です。
対象読者
- 静的サイトのAWS CDK導入を考えている人
記事で説明する内容
- AWS CDKでの以下環境構築
- ACM証明書を含めたクロスリージョン構築
- aws-solutions-constructs
説明しない内容
- 静的サイトの構築など
- AWS CDKの基本的な内容
背景
- 個人開発の環境について、コンソールで開発していました。
- 最近IaCに関心がある且つ、「どこで何設定したのか覚えていない。。。」となってきたのでAWS CDK構築して綺麗にしたいと思っていました。
- べスプラが知りたくなり、Construct HubからいいAWS CDKのテンプレートないかと調べていたところ、aws-solutions-constructsの存在を知りました。
構成
AWS CDKで環境構築する環境は大まかに以下の通りです。
静的Webコンテンツ+APIGateWayでLambdaを実行して、他サービス使うような構成です。

利用するライブラリ調査
Construct Hubから所望のaws-solutions-constructsを検索します。
残念ながら、aws-solutions-constructsで私の作りたい環境の全てをまるまる構築するようなものは見つけられませんでした。
バックエンドとフロントエンド切り離したイメージで調べてみると良さそうなものがヒットしました。
フロントエンド(CloudFront、S3)
バックエンド(APIGateway、Lambda、CloudFront)
ACM証明書周りの作成をしてくれるものは見つからなかったです。見つからないものは素直にL2コンストラクタで作成していきます。
コーディング
コーディングを進めていきます。
長くなりそうなので本記事ではフロント側を構築していきます。
フロント系の環境は大まかに以下の通りです。
- Node.js 18 以上
- pnpm v8 系
- Astro(static output mode) フロントエンド
初期構築
空のフォルダから以下コマンドでAWS CDKのプロジェクトを立ち上げます
cdk init app --language typescript
cdk init後に追加するライブラリとしては「@aws-solutions-constructs/aws-cloudfront-s3」の一点です。
peer dependencyの警告が出たため、aws-cdk-libのバージョンも上げます。
"dependencies": {
+ "@aws-solutions-constructs/aws-cloudfront-s3": "2.93.0",
- "aws-cdk-lib": "2.205.0",
+ "aws-cdk-lib": "^2.219.0",
"constructs": "^10.0.0"
}
バイナリファイル作成(クロスリージョン設定)
ACM証明書だけバージニア北部リージョンで作成して、他は東京リージョンで作成したいです。
スタックを2つに分けて構築します。
crossRegionReferencesオプション+それぞれのスタックにリージョンを明記します。
さらに、CertificateStack完了後にInfrastructureStackを実行したいので依存関係を作成しています。
#!/usr/bin/env node
import * as cdk from "aws-cdk-lib";
import { CertificateStack } from "../lib/certificate-stack";
import { InfrastructureStack } from "../lib/infrastructure-stack";
const APP_NAME = "XXXXXX";
const DOMAIN_NAME = "www.XXXXXX.XXXXX";
const CDK_ACCOUNT = "123456789012";
const app = new cdk.App();
const certificateStack = new CertificateStack(
app,
`CertificateStack-${APP_NAME}`,
{
domainName: DOMAIN_NAME,
appName: APP_NAME,
crossRegionReferences: true, //クロスリージョン設定
env: {
region: "us-east-1",
account: CDK_ACCOUNT
},
}
);
const infrastructureStack = new InfrastructureStack(
app,
`InfrastructureStack-${APP_NAME}`,
{
domainName: DOMAIN_NAME,
appName: APP_NAME,
crossRegionReferences: true, //クロスリージョン設定
certificate: certificateStack.certificate,
env: {
region: "ap-northeast-1",
account: CDK_ACCOUNT
},
}
);
infrastructureStack.addDependency(certificateStack); //依存関係設定
app.synth();
ACM証明書のスタック作成
次にスタック作成です。
まずはACM証明書専用のCertificateStackを作成します。
InfrastructureStackに渡すため、ACM証明書の戻り値はパブリックで取得します。
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as acm from "aws-cdk-lib/aws-certificatemanager";
interface CertificateStackProps extends cdk.StackProps {
domainName: string;
appName: string;
}
export class CertificateStack extends cdk.Stack {
public readonly certificate: acm.ICertificate; //Public指定
constructor(scope: Construct, id: string, props: CertificateStackProps) {
super(scope, id, props);
this.certificate = new acm.Certificate(
this,
`SiteCertificate-${props.appName}`,
{
domainName: props.domainName,
validation: acm.CertificateValidation.fromDns(),
}
);
}
}
静的サイト(aws-solutions-constructs利用)のスタック作成
次にACM証明書以外の部分、InfrastructureStackを構築します。
「CloudFrontToS3」が今回主役の「aws-solutions-constructs」です。
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { CloudFrontToS3 } from "@aws-solutions-constructs/aws-cloudfront-s3";
import * as s3deploy from "aws-cdk-lib/aws-s3-deployment";
import * as acm from "aws-cdk-lib/aws-certificatemanager";
interface InfrastructureStackProps extends cdk.StackProps {
certificate: acm.ICertificate;
domainName: string;
appName: string;
}
export class InfrastructureStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: InfrastructureStackProps) {
super(scope, id, props);
const cloudFrontToS3 = new CloudFrontToS3(
this,
`CloudFrontToS3-${props.appName}`,
{
cloudFrontDistributionProps: {
domainNames: [props.domainName],
certificate: props.certificate,
},
}
);
// ここでデプロイを行う(事前にビルドしておくこと)
new s3deploy.BucketDeployment(
this,
`DeployStaticWebsite-${props.appName}`,
{
sources: [s3deploy.Source.asset("../apps/web/dist")],
destinationBucket: cloudFrontToS3.s3Bucket!,
}
);
}
}
実行
bootstrapしてデプロイ開始します。
deploy中に固まりました。これはACM証明書の設定を待ってくれてるみたいです。想定通りです。

cloudflareのCNAMEレコード作成します。
CLIで作成がスマートだと思いますが今回は手入力します。

証明書認証できたみたいです🤗。
AWS CDK再開してCertificateStackが終わりました。

続けてデプロイできたので動作確認してみると
なんだかjavascriptがなくなってます。。。
ブラウザの開発者ツールみてみるとエラーが😿

どうやらディストリビューションのcloudfrontfunctionに以下関数がされているみたいです。

ガチガチのCSP設定なので緩和しましょう。
修正します。
const cloudFrontToS3 = new CloudFrontToS3(
this,
`CloudFrontToS3-${props.appName}`,
{
+ insertHttpSecurityHeaders: false,
cloudFrontDistributionProps: {
domainNames: [props.domainName],
certificate: props.certificate,
},
+ responseHeadersPolicyProps: {
+ securityHeadersBehavior: {
+ contentSecurityPolicy: {
+ override: true,
+ contentSecurityPolicy:
+ "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';",
+ },
+ },
+ },
}
);
すっかり忘れていましたが、私の環境だとインデックスドキュメント機能の追加対応が必要でした。
「CloudFrontToS3」にCloudFrontFunctionをオーバーライドしています。
+ const functionForDirIndex = new cloudfront.Function(
+ this,
+ `DirIndexFunction--${props.appName}`,
+ {
+ code: cloudfront.FunctionCode.fromInline(`
+ function handler(event) {
+ var request = event.request;
+ var uri = request.uri;
+ if (uri.endsWith("/")) {
+ request.uri += "index.html";
+ }
+ else if (!uri.includes(".")) {
+ request.uri += "/index.html";
+ }
+ return request;
+ }
+ `),
+ }
+ );
const cloudFrontToS3 = new CloudFrontToS3(
this,
`CloudFrontToS3-${props.appName}`,
{
insertHttpSecurityHeaders: false,
cloudFrontDistributionProps: {
domainNames: [props.domainName],
certificate: props.certificate,
+ defaultBehavior: {
+ functionAssociations: [
+ {
+ eventType: cloudfront.FunctionEventType.VIEWER_REQUEST,
+ function: functionForDirIndex,
+ },
+ ],
+ },
+ },
responseHeadersPolicyProps: {
securityHeadersBehavior: {
contentSecurityPolicy: {
override: true,
contentSecurityPolicy:
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';",
},
},
},
}
);
デプロイすると期待通りのcloudfrontFunctionが設定できました🎇

エラーもなくなりました🎇

結論・まとめ
- 期待していた静的ウェブサイトの構築が、シンプルで綺麗にAWS CDKで実現できました。
- CSPガチガチなところが気になりました。こういうものなのでしょうか。
- インデックスドキュメントの対応が必要なことを忘れていました。AWS CDKでコード管理するのは大事だと感じました。
今後の発展
- 次回はlambda部分の構築を進めていきます。
- lambda部分終わればcdk-nagの導入をしたいです。
Discussion