CDKを使ったLambdaデプロイの最適解を考える

公開:2020/12/05
更新:2020/12/29
3 min読了の目安(約3100字TECH技術記事

この記事はニフティグループアドベントカレンダー6日目の記事です。

はじめに

先週のre:InventでLambdaでコンテナイメージがサポートがされるようになりました。
今後さらに活躍の場が広がりそうなLambdaですが、今回はCDKを使ったLambdaのデプロイについて書いていきたいと思います。

LambdaとCDK

まずはCDKについておさらいをします。
CDKはインフラをコードで管理、すなわち Infrastrucure as Code(IaC) を実現するためのツールでありCloudFormationやTerraformといったものもこれに属します。

CDKの特徴はJavaScript、TypeScript、Python、Java、C#から好みの言語を使うことができ、CloudFormationやTerraformと比べてより抽象化された記述でインフラを定義することが可能です。

最近は個人的な開発でLambdaを使うときはCDKを使い、LambdaとAPIGatewayなどの関連するリソースを管理しています。
CDKを使う理由としては、一般的なプログラミング言語で記述するためエディタの補完が効いて書きやすいなどがありますが、何よりもLambdaのソースコードもインフラの定義も同一の言語で記述できる点が非常に大きいです。

なお、APIGateway+Lambdaを使ったアプリを構築に関しては公式のworkshopの内容が非常に良いため、触ったことがない方は一度試してみるのをお勧めします。

https://cdkworkshop.com/

JavaScriptを使ったときのLambda+CDKでの悩み

さて、ここからが本題です。
先に述べたようにLambdaをCDKで管理する開発は非常に快適ですが、いくつか悩みが出てきます。

1つ目は外部ライブラリの使用です。
aws-cdkとNode.jsのAPIだけを使ったLambda関数を作るときには問題はないですが、外部のライブラリを使って開発する際はデプロイ時に依存関係を含めたソースコードが必要です。
故に、デプロイパッケージにnode_modulesを入れる必要があります。

2つ目はTypeScriptへのトランスパイルです。
CDKがTypeScriptで書けるため理想としてはLambdaのソースコードもTypeScriptで書きたいところですが、当然ながらLambdaでTypeScriptを実行することはできないのでJavaScriptへのトランスパイルが必要になります。

これらの問題は双方とも、工夫すれば不可能ではないですがやや面倒でしょう。
ですが、これらの問題を一気に解決するCDKのモジュールが存在します。

aws-lambda-nodejsモジュール

aws-lambda-nodejsというCDKのモジュールがあります。
これはCDKでNode.jsを使ったLambdaのコードを構成するための高レベルなconstruct libraryです。
要するにこのモジュールを使えばCDK側でライブラリが含まれたり、TypeScriptからトランスパイルされたコードをデプロイパッケージとしてデプロイできるようになります。

https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-nodejs-readme.html

このモジュールの注意点としてはExperimentalなため互換性のない変更が行われる可能性があります。

基本的な使い方

基本的にCDKのドキュメントにすべて書かれているのでそれを読めばよいのですが、aws-lambda-nodejsのいくつかの機能についてドキュメントから引用しつつ解説していきます。

Lambdaの定義は以下のようになります。

new lambda.NodejsFunction(this, 'my-handler');

この場合、この記述をしたファイル名がstock.tsの場合stock.my-handler.tsというファイルを探し、そのファイルをentryファイルとしてバンドルします。

もしくは以下のようにentryファイルを自分で指定することもできます。

new lambda.NodejsFunction(this, 'MyFunction', {
  entry: '/path/to/my/file.ts', // accepts .js, .jsx, .ts and .tsx files
  handler: 'myExportedFunc'
});

それ以外はpropertyとして使える値もaws-lambdalambda.Functionと同じため普通にLambdaの定義を書くときと同じように記述していきます。

内部構造を紐解く

とても便利なモジュールですが、内部ではどのような処理が行われているのでしょうか。
CDKのソースコードを見ればわかりますが仕組み自体はシンプルです。

https://github.com/aws/aws-cdk/tree/master/packages/%40aws-cdk/aws-lambda-nodejs

aws-lambda-modulelambda.Code.fromAsset メソッドにはDockerコンテナ内でコードをバンドルするための機能 が備わっています。
aws-lambda-nodejsはその機能を使用して内部でモジュールバンドラーを動かしているだけです。

モジュールバンドラーとして使われているのはesbuild。
他のモジュールバンドラーと比べて群を抜いて早いことで少し前に話題になりましたね。まさかこんなところで使われているとは...
esbuildを使うようになったのは割と最近で以前はモジュールバンドラーとしてParcelを使っていましたが、よりパフォーマンスが高くCLIですべて設定できることからesbuild切り替えられたらしいです。

https://github.com/aws/aws-cdk/pull/11289

興味深い点としてローカル環境にesbuildがインストールされている場合、ローカルのesbuildを用いてバンドルが行われる点。
ドキュメントにも書いてあるとおり、macOSではDockerのvolumeのパフォーマンスが悪いため非常に合理的な機能ですね。

まとめ

CDKを使えばTypeScriptの世界ですべてが完結するので非常に気持ちがいいですね。
モジュールバンドラーを使うこと自体はSAMや他のIaCツールでも可能ですが、やはりconstruct libraryを使って少ない記述量で書けるというのはCDK最大の強みだと思うので、Lambdaの管理に関してはこれを推していきたいところです。