「AWS CDK Advent Calendar 2022」の8日目の記事になります。(空いていたので後から埋めました。最後の1日でした!)
目次
概要
前回、「Go言語をマネージドランタイムとして選択したAWS App Runnerを、AWS CDK for Goで作ってみる」という記事を書きました。
(※ちなみにマネージドランタイムというのは、DockerfileもECRも必要のない、GitHubへのpushをトリガー(または手動で)にデプロイしてくれる便利なものです。)
そこで今回はせっかくなので、「TypeScriptをマネージドランタイムとして選択したAWS App Runnerを、AWS CDK for TypeScriptで作ってみる」というのもやってみました。
AWS CDK for TypeScriptって書きましたが、もはやfor TypeScript無くてもいいですかね。。。
また現在2022年12月16日時点で、CDKのApp RunnerのL2 Constructはα版ですが、「L1 Construct」と「L2 Constructのα版」どちらの方法でも構築してみました。
前提
今回、CDKのスタックの構成・書き方・リソース定義は、Goで作ったものとほぼ同じです(できるだけ、コンストラクタからパラメータまでかなり寄せました)。
そのため本記事ではそれほど細かい説明はしませんが、細かい説明の方は上記Go版の方の記事をご覧ください。
TypeScriptとGoで、CDKの書き方がどう変わるのか比べたりも面白いので、興味がある方はぜひ見てみてください。
GitHub
全コードはGitHubに公開しています。
大まかな内容
以下のようなことをやっています。
- AutoScalingConfiguration(スケーリング設定)をカスタムリソースLambdaで作成
- CDKモジュールの
AwsCustomResource
でなくLambdaで作っているので、AutoScalingConfigurationの作成だけでなく更新・削除も可能
- CDKモジュールの
- App Runnerインスタンスロール作成
- AppRunner Service作成
- L1, L2(α)どちらも作成
- 自動デプロイ:オン
- VPC Connector作成
- L1, L2(α)どちらも作成
- AWS SDKでGitHub接続ARNの取得
(Go版で作ったものと)違う点
カスタムリソースLambdaの定義
これはTypeScript(Node.js)お馴染み、aws-lambda-nodejsのNodejsFunctionを使っています。
そのため、Lambdaのpackage.jsonはCDKのものと共有して、シングルパッケージにしています。(※App Runnerで公開するアプリケーションのpackage.jsonは別で作成)
const customResourceLambda = new NodejsFunction(this, "custom", { runtime: Runtime.NODEJS_16_X, bundling: { forceDockerBundling: false, }, timeout: Duration.seconds(900), initialPolicy: [ new PolicyStatement({ actions: [ "apprunner:*AutoScalingConfiguration*", "apprunner:UpdateService", "apprunner:ListOperations", ], resources: ["*"], }), new PolicyStatement({ actions: ["cloudformation:DescribeStacks"], resources: [this.stackId], }), ], });
カスタムリソースLambdaのProvider
Goの方ではCustomResource
コンストラクタに、LambdaのArnをそのままServiceToken
として渡してカスタムリソースを作ったのですが、TypeScriptの方では(CustomResourceの)Provider
というものを作り、そのプロパティのserviceToken
を渡しています。
- (CDK定義)
const autoScalingConfigurationProvider = new Provider( this, "AutoScalingConfigurationProvider", { onEventHandler: customResourceLambda, }, ); // (省略) const autoScalingConfiguration = new CustomResource(this, "AutoScalingConfiguration", { resourceType: "Custom::AutoScalingConfiguration", properties: autoScalingConfigurationProperties, serviceToken: autoScalingConfigurationProvider.serviceToken, });
これはTypeScriptではLambdaのハンドラーの型にCdkCustomResourceHandler
というものが提供されていて使ったところ、Provider経由でないとうまく動かなかったためです。
- (Lambdaコード)
export const handler: CdkCustomResourceHandler = async function (event) { // (省略)
[追記]
こちらの記事を書いたところ、Twitterでお世話になっている ゆっきー (@WinterYukky) / Xさん からCdkCustomResourceに関して教えて頂いたので(ありがとうございます!)、補足します。
Providerを経由しない場合にCdkCustomResourceが利用できない理由は、「通常のCloudFormationのカスタムリソースとして利用されるため」、だそうです。
通常のカスタムリソースは、CloudFormationCustomResourceHandler
型が使えるとのことです。
CDKとCloudFormationのハンドラでそれぞれ役割が分かれているんですね。(ご丁寧にこんなに分かりやすい図まで頂き感激です;;)
またProviderを利用する場合、以下のようなメリットがあるそうです。
- Handles responses to AWS CloudFormation and protects against blocked deployments
- Validates handler return values to help with correct handler implementation
- Supports asynchronous handlers to enable operations that require a long waiting period for a resource, which can exceed the AWS Lambda timeout
- Implements default behavior for physical resource IDs.
最初はなんとなく使っていたのですが、色々と恩恵のある機能なんですね。3つ目の、非同期ハンドラによるLambdaのタイムアウトを超えるような長時間の処理が可能になるのは、色々カスタムリソースのユースケースの幅が広がりそうで良さそうです。
ゆっきーさんありがとうございました!
GitHub接続の扱い方
Go版の方では、
- CDKデプロイの際にGitHub接続があるか確認し、なかったら作る・あれば使う
- 作った場合や"PENDING_HANDSHAKE"の場合はprompt待ちにして、コンソールにボタンを押しに行く時間を作り、その後Enterを押したらCDK処理を再開させる
というちょっと凝ったことをしていたのですが、今回そこまでしなくてもいいかと思い、単純に「あれば取得、なければエラー」というような事前に作るのを前提にしました。
GitHub接続は、アプリケーションと同じ単位のこともあればAWSアカウントなどもう少し大きい単位で使いまわしたりすることもあるかなと考え、カスタムリソースとしてアプリケーションのスタックに組み込むなどはやりませんでした。
また、一応GitHub接続を作成するシェルスクリプトは作っておいたので、それを走らせてからCDK叩く感じになります。
#!/bin/bash set -eu cd `dirname $0` PROFILE="" PROFILE_OPTION="" REGION="ap-northeast-1" CONNECTION_NAME="" PROVIDER_TYPE="GITHUB" while getopts p:c: OPT; do case $OPT in p) PROFILE="$OPTARG" ;; c) CONNECTION_NAME="$OPTARG" ;; esac done if [ -z "${CONNECTION_NAME}" ]; then echo "CONNECTION_NAME (-c) is required" exit 1 fi if [ -n "${PROFILE}" ]; then PROFILE_OPTION="--profile ${PROFILE}" fi aws apprunner create-connection \ --connection-name ${CONNECTION_NAME} \ --provider-type ${PROVIDER_TYPE} \ ${PROFILE_OPTION}
ビルド
マネージドランタイムでApp Runnerを使う場合、ビルドコマンド・起動コマンドを指定する必要があります。
ビルドコマンド
今回TypeScriptなので、esbuildを使ってビルド(トランスパイル)しました。
cd app && yarn install --non-interactive --frozen-lockfile && yarn build
yarn build
は、package.jsonのscriptsでこんな感じで定義しています。
esbuild ./index.ts --bundle --outfile=./index.js --platform=node --minify --allow-overwrite
起動コマンド
トランスパイル済みのjsファイルをnodeコマンドで起動します。
node app/index.js
最後に
App RunnerのマネージドランタイムをGo版で作ったので、TypeScriptでも書いてみたら面白いんじゃないかと思い書いてみました。
TypeScriptとGoのCDKの違いなどもわかって良い経験になりました。