これまでAWSのSAM (Serverless Application Model) などを使ってサーバレスアプリを作成することが多かったのですが、サーバレスの構成情報とそこに必要となるパラーメタなどが密に結合してしまいがちで、どこでも使えるようなアプリが作りづらいという問題がありました。定義ファイルに後から値を埋め込めるParameterなどの機能はあったものの、YAMLやJSON上で無理くりやっている感があってわりと消耗が多かったです。
これに対してAWS CDK (Cloud Development Kit) では定義ファイルに相当する Constrcut をコードで記述することができ、かなり柔軟性高く利用できます。配布可能な形でいくつかのサーバレスアプリを作成してみたので、その際の知見を記事にまとめてみます。
CDKそのものの解説はこちら https://docs.aws.amazon.com/cdk/latest/guide/home.html
CDKのプロジェクトを作成する
今回は onigiri
という名前のモジュールを作成するとします。まず普通のCDKプロジェクトを作成します。
% mkdir onigiri
% cd onigiri
% cdk init --language typescript
CDK Constructを作る
Construct は最終的にCloudFormationでデプロイされるStackのテンプレートです。 onigiri
という名前でinitすると、 lib/onigiri-stack.ts
というファイルが生成されるのでそこに記述します。以下の例は、一定時間ごとに起動されるLambdaが何かをクエリして、その結果をDynamoDBに書き込む、という想定のConstructです。Lambda Functionを1つ、DynamoDBを1つデプロイします。
import * as cdk from "@aws-cdk/core";
import * as dynamodb from "@aws-cdk/aws-dynamodb";
import * as lambda from "@aws-cdk/aws-lambda";
import * as events from "@aws-cdk/aws-events";
import * as eventsTargets from "@aws-cdk/aws-events-targets";
import { NodejsFunction } from "@aws-cdk/aws-lambda-nodejs";
import * as path from "path";
export interface properties extends cdk.StackProps {
someParameter: string;
}
export class OnigiriStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props: properties) {
super(scope, id, props);
const cacheTable = new dynamodb.Table(this, "cacheTable", {
partitionKey: { name: "pk", type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
});
const lambdaQuery = new NodejsFunction(this, "query", {
entry: path.join(__dirname, "lambda/query.js"),
handler: "main",
environment: {
TABLE_NAME: cacheTable.tableName,
SOME_PARAM: props.someParameter,
},
});
cacheTable.grantReadWriteData(lambdaQuery);
new events.Rule(this, "periodicQuery", {
schedule: events.Schedule.rate(cdk.Duration.minutes(10)),
targets: [new eventsTargets.LambdaFunction(lambdaQuery)],
});
}
}
Constructを書く上でのコツは以下の通りです。
- 今回はLambda functionもTypeScriptで作成しするため、
./node_modules
以下のモジュールも同時にデプロイするために@aws-cdk/aws-lambda-nodejs
パッケージにあるNodejsFunction
を利用します。他にも別レイヤーに./node_modules
を格納する方法などがあるようですが、今回は割愛。- 本来
NodejsFunction
のpath
パラメータはこのファイルからの相対パスが使えますが、モジュールとして配布した際に相対パスの基準が変わってしまうため、__dirname
をjoinして絶対パス指定にしています。 - pathを指定する際には
.js
ファイルにします。これはモジュール配布後にNodejsFunction
経由でトランスコンパイルしようとするとうまくいかない場合があるからです
- 本来
-
@aws-cdk/aws-lambda
などCDKのモジュールを追加する場合は、必ずinit時に追加されたaws-cdk/core
とバージョンを揃えてインストールします。わずかでもずれるとtsc
でエラーになります。 - 単体のCDKプロジェクトとして使う場合、
@aws-cdk/aws-lambda
などCDKのモジュールを追加はdevDependencies
になるようnpm i --save-dev @aws-cdk/aws-lambda
などとしますが、今回はモジュール配布先で利用する必要があるため、npm i @aws-cdk/aws-lambda
でインストールします。
また、配布用のconstructではパラメータを可変にしておくのが良いので、もともと OnigiriStack
の引数である cdk.StackProps
を拡張して properties
というinterfaceを作っています。これによってなんらかの環境変数を渡したり、外部リソースを参照したりしたい場合に、情報を引数として渡すことが出来ます。
同時にLambda Functionを作成します。今回は ./onigiri/lib/lambda/query.ts
にLambda Functionのファイルがある想定です。
package.json を編集する
package.json
に以下の変更を加えます。
-
files: ["lib"]
を追加:lib
以下にトランスコンパイル済みのコードを配置するため -
"main": "./lib/onigiri-stack.js"
を追加:配布先のプロジェクトからconstructのコードが読み込めるように -
"type": "./lib/onigiri-stack.d.ts"
を追加:同上 -
scripts
内に"prepare": "tsc"
を追加:パッケージ公開時やgithubからのインストール時にトランスコンパイルを実行させるため
トランスコンパイルの出力先を dist
にするというやりかたもありますが、そのあたりはお好みで調整して下さい。出来上がりのサンプルは https://github.com/m-mizutani/dnstrack/blob/master/package.json などをご参照下さい。
デプロイ
この記事の目的は作成したCDK constructをモジュールとして配布することですが、作成途中でテストとしてデプロイしたい場合があるかと思います。そのための1つの方法として、 bin/onigiri.ts
は環境変数でデプロイできるようになっていると、小回りがきいて便利です。例として、 bin/onigiri.ts
を以下のようにします。
# !/usr/bin/env node
import "source-map-support/register";
import * as cdk from "@aws-cdk/core";
import { OnigiriStack } from "../lib/onigiri-stack";
const stackName = process.env.ONIGIRI_STACK_NAME || "onigiri";
const app = new cdk.App();
new OnigiriStack(app, stackName, {
someParameter: process.env.ONIGIRI_PARAM!,
});
こうしておくことで、環境変数で指定して開発中のレポジトリからそのままデプロイ出来つつ、開発環境のパラメータを埋め込むのを防ぐことができます。
% env ONIGIRI_STACK_NAME=onigiri-1 ONIGIRI_PARAM=nanika cdk deploy
パッケージ公開&配布
ここまでの設定ができていればあとは npm publish
でパッケージ公開、あるいはプッシュされたgithubからインストールすることで、作成したCDK constructを外部プロジェクトで再利用することができるようになります。npm i dareka/onigiri
などでインストールし、配布先の ./bin/nanika.ts
などで以下のように記述して利用できます。
# !/usr/bin/env node
import "source-map-support/register";
import * as cdk from "@aws-cdk/core";
import { OnigiriStack } from "onigiri";
const app = new cdk.App();
new OnigiriStack(app, "wagaya-no-onigiri", {
someParameter: "nanika-sugoi-guzai",
});
サンプル
上記の手法にそって作成したconstructが以下になりますので、ご参考までに。
- DNS track: https://github.com/m-mizutani/dnstrack