AWS CDKでCloudFront Functionを使ったリダイレクト環境を構築する
ときどき、CloudFront Functionsを使ってHTTPリダイレクト環境を構築することがある。
今まではAWS Console上でAWSリソースを作成していたが、設定内容を構成管理できるようにAWS Cloud Development Kit(AWS CDK)を使ってみる。
この記事では、AWS CDKを使って次のAWSリソースを作成する。
- S3(CloudFrontのオリジンに設定する)
- CloudFrontディストリビューション
- CloudFront Functions
動作を確認した環境
- Node.js vv18.12.0
- AWS CLI v2.9.11
- AWS CDK v2.58.1
初回:AWS CDKのセットアップ
AWS CDKのインストール
AWS CDKをインストールする。プロジェクト単位でインストールしても良い。
$ npm install -g aws-cdk
# バージョンの確認
$ cdk --version
2.58.1 (build 3d8df57)
デプロイ⽤のS3バケットの作成
はじめてCDKでデプロイするときは、デプロイ⽤のS3バケットを作成する必要がある。
このS3バケットはリージョン単位で作成するため、リソースを作成するリージョンを変更する場合はcdk bootstrap
を実行する。
$ cdk bootstrap aws://AWS_ACCOUNT_ID/AWS_REGION --profile PROFILE_NAME
⏳ Bootstrapping environment aws://123456789012/ap-northeast-1...
Trusted accounts for deployment: (none)
Trusted accounts for lookup: (none)
Using default execution policy of 'arn:aws:iam::aws:policy/AdministratorAccess'. Pass '--cloudformation-execution-policies' to customize.
CDKToolkit: creating CloudFormation changeset...
✅ Environment aws://123456789012/ap-northeast-1 bootstrapped.
STEP1:CDKプロジェクトの作成
cdk init app
で、CDKプロジェクトを作成できる。
今回は、TypeScriptで記述したいので、language
オプションで「typescript」を設定する。
$ mkdir example && cd $_
$ cdk init app --language typescript
Applying project template app for typescript
# Welcome to your CDK TypeScript project
This is a blank project for CDK development with TypeScript.
The `cdk.json` file tells the CDK Toolkit how to execute your app.
## Useful commands
* `npm run build` compile typescript to js
* `npm run watch` watch for changes and compile
* `npm run test` perform the jest unit tests
* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk synth` emits the synthesized CloudFormation template
Executing npm install...
✅ All done!
次のファイル群が生成される。
.
├── README.md
├── bin
│ └── example.ts # CDKアプリのエントリーポイント。lib/example-stack.tsのインスタンスを生成する。
├── cdk.json # ビルド設定や環境変数を定義する
├── jest.config.js
├── lib
│ └── example-stack.ts # CloudFormationスタックを定義する。
├── node_modules
│ └── ...
├── package-lock.json
├── package.json
├── test
│ └── example.test.ts
└── tsconfig.json
STEP2:スタックテンプレートの修正
lib/example-stack.ts
を次のように修正する。
import * as cdk from "aws-cdk-lib";
import {
aws_s3 as s3,
aws_s3_deployment as s3deploy,
aws_cloudfront as cf,
aws_cloudfront_origins as cfo,
aws_iam as iam,
} from "aws-cdk-lib";
import { Construct } from "constructs";
// アカウント名やリージョンは、AWS プロファイルから設定される
const { CDK_DEFAULT_ACCOUNT, CDK_DEFAULT_REGION } = process.env;
const prefix = `example-${CDK_DEFAULT_REGION}-${CDK_DEFAULT_ACCOUNT}`;
export class ExampleStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// S3 バケットを作成する
// パブリックアクセスをすべてブロックする
const bucket = new s3.Bucket(this, "S3Bucket", {
bucketName: `${prefix}-bucket`,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
versioned: false,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
// オリジンアクセスアイデンティティを作成する
const identity = new cf.OriginAccessIdentity(this, "OriginAccessIdentity", {
comment: `${bucket.bucketName} access identity`,
});
// オリジンアクセスアイデンティティに S3 のアクセスを許可する IAM を設定する
const bucketPolicyStatement = new iam.PolicyStatement({
actions: ["s3:GetObject"],
effect: iam.Effect.ALLOW,
principals: [identity.grantPrincipal],
resources: [`${bucket.bucketArn}/*`],
});
bucket.addToResourcePolicy(bucketPolicyStatement);
// CloudFront Function で動かす、リダイレクトスクリプトを設定する
const cfFunction = new cf.Function(this, "CloudFrontFunction", {
code: cf.FunctionCode.fromFile({
filePath: "assets/redirect.js",
}),
});
// CloudFront のディストリビューションを作成する
const distribution = new cf.Distribution(this, "CloudFrontDistribution", {
comment: "cloudfront distribution",
defaultRootObject: "index.html",
defaultBehavior: {
allowedMethods: cf.AllowedMethods.ALLOW_GET_HEAD,
cachedMethods: cf.CachedMethods.CACHE_GET_HEAD,
cachePolicy: cf.CachePolicy.CACHING_OPTIMIZED,
viewerProtocolPolicy: cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
origin: new cfo.S3Origin(bucket, {
originAccessIdentity: identity,
}),
functionAssociations: [
{
function: cfFunction,
eventType: cf.FunctionEventType.VIEWER_REQUEST,
},
],
},
priceClass: cf.PriceClass.PRICE_CLASS_100,
});
// S3 バケットに index.html を置く
new s3deploy.BucketDeployment(this, "BucketDeployment", {
sources: [
s3deploy.Source.data(
"/index.html",
"<html><body><h1>Hello World</h1></body></html>"
),
],
destinationBucket: bucket,
distribution: distribution,
distributionPaths: ["/*"],
});
}
}
STEP3:リダイレクトスクリプトの作成
プロジェクトのルートにassets
ディレクトリを作成し、redirect.js
を作成する。
$ mkdir assets
$ touch assets/redirect.js
redirect.js
は、次の内容にする。
CloudFrontのURLにアクセスすると、https://new.example.com
にリダイレクトするスクリプトである。
function handler(event) {
var newUrl = `https://new.example.com`;
var response = {
statusCode: 302,
statusDescription: "Found",
headers: { location: { value: newUrl } },
};
return response;
}
STEP4:AWSリソースのデプロイ
スタップテンプレートに記述したAWSリソースをデプロイするには、cdk deploy
を実行する。
$ cdk deploy --profile PROFILE_NAME
ExampleStack: building assets...
...
[100%] success: Published randomvalue:current_account-current_region
...
Deployment time: ...
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:123456789012:stack/ExampleStack/12345678-1234-1234-1234-123456789012
Total time: ...
STEP5:動作確認
AWS CLIでCloudFrontのURLを確認する。
aws cloudfront list-distributions --profile PROFILE_NAME | jq '.DistributionList.Items[].DomainName'
"foo.cloudfront.net"
CloudFrontのURLにGETリクエストを送信する。
HTTPステータス302で、new.example.comにリダイレクトしていることを確認できた。
$ curl -I https://foo.cloudfront.net
HTTP/2 302
server: CloudFront
...
content-length: 0
location: https://new.example.com
x-cache: FunctionGeneratedResponse from cloudfront
via: 1.1 example-example.cloudfront.net (CloudFront)
...
おまけ:デプロイしたAWSリソースの削除
作成したAWSリソースを削除するには、cdk destroy スタック名
を実行する。
$ cdk destroy example --profile PROFILE_NAME