Serverless FrameworkでLambda Layerを導入!node_modulesをLayer化してコールドスタートを軽減しましょう😎

Serverless FrameworkでLambda Layerを導入!node_modulesをLayer化してコールドスタートを軽減しましょう😎

こんにちは!

AWS Lambda の NodeJS 開発時に、Lambda にデプロイするソースコードのパッケージサイズの大きさに悩んだことがあるのではないでしょうか。

Lambda はパッケージサイズに比例してコールドスタートが大きくなり、API のレイテンシーに繋がります。

本記事では、Serverless Framework を使用した Lambda Layer の実装方法について解説します。

想定する読者

  • Serverless Frameworkで開発しているヒト
  • Lambda関数のパッケージサイズに悩んでいるヒト
  • Serverless Framework で Lambda Layer を実現する方法を知りたいヒト

はじめに

わたしたちは通常、TypeScript で AWS Lambda の NodeJS を実装しますが、その際 Webpack を使用し TypeScript を NodeJS にビルドしデプロイします。Lambdaへデプロイされるソースコードには、Webpack の実行により生成されたバンドルJSファイルと 一緒に node_modules も含まれ、結果 Lambda のソースコードサイズが肥大します。

しかし、node_modules のパッケージは全ての Lambda で汎用的なものだし、もちろんビジネスロジックでもありません。 node_modules は全ての Lambda 毎にデプロイする必要が本当にあるのでしょうか ?

叶うなら、以下のように共通のモジュール(node_modules)を共通の場所(Layer)に分離したいと考えるはずです。

How to use Lambda layers with the Amplify CLI | Front-End Web & Mobile
Lambda Layer

本記事では、node_modules を全ての Lambda で共通利用出来る Lambda Layer にデプロイする方法について解説します。

Lambda Layerのデプロイ方法

Serverless Frameworkでは、Lambda Layer を簡単に実現するためのプラグインが提供されています。

serverless-layers

具体的な実装方法は以下です。(細かいソースコードは省略)

パッケージのインストール

serverless-layersをプロジェクトにインストールします。

$ yarn add serverless-layers -D
# or
$ serverless plugin install --name serverless-layers

Layer デプロイ用の S3 バケットを生成

serverless-layers プラグインで使用するバケットを生成します。バケットは CloudFormation で作成し GetAtt 等で ARN や名称を参照したいところですが、技術検証したところ自動生成される CloudFormation が原因で参照できませんでした。そのため、事前に AWS CLI でバケットを作成しておきます。

$ aws s3 mb "s3://my-bucket-name"

serverless.ymlの設定

serverless.yml を以下のように設定します。

provider:
  deploymentBucket:
    name: '${self:custom.deploymentBucketName}'

plugins:
  - serverless-layer

package:
  individually: true
  patterns:
    - '!node_modules/**'

custom:
  deploymentBucketName: 'layer-bucket-name'
  serverless-layers:
    packageManager: yarn

デプロイ

通常のデプロイコマンドを実行し、レイヤーをデプロイします。

$ sls deploy --stage=dev

これで Lambda Layer が自動的にデプロイ&適用されます。

Lambda のパッケージサイズは大幅に削減されて、数十 MB の Lambda のサイズが数百 KB 程度まで削減できたと思います!

APIGateway や AppSync からキックしている Lambda であれば、API 自体のレイテンシーが大幅に無くなりレスポンスが速くなったことでしょう。

注意点(ハマりどころ)

既存 Lambda へ適用する場合は Lambda の 250MB サイズ制限に要注意

serverless-layersプラグイン適用時は、以下のような順序でブラックボックスに Lambda Layer を適用します。

  1. Lambda Layer を node_modules デプロイ
  2. 既存の Lambda へ Layer を適用
  3. Lambda へソースコードをデプロイ

つまり、既存の Lambda のサイズが大きい状態(node_modulesを含む)で Layer 適用しようと試みる為、プロセス 3 の手前で Lambda の合計サイズが、既存 Lambda パッケージサイズ+Layer サイズ となり、この合計サイズが250MBを超えるリミットエラーとなります。

Lambda関数は、Layer サイズ+Lambda サイズの合計は250MB以下になるようにデプロイする必要があります

そのため、既存の Lambda へのデプロイについては、Layer の適用前に以下のいずれかの手順を踏む必要があります。

  • 手動で node_modules を除いたソースコードをデプロイ
  • 管理コンソールでソースコードの編集が可能なら手動で node_modules を削除
  • 新環境にデプロイ

Lambdaのデプロイサイズ250MB制限には効果無し

前述の通り、Lambdaのソースコードサイズは Lambda パッケージサイズ+Layer サイズ となりますので、250MB対策にはなりません。他の方法を検討しましょう。

node_modules以外の不要なパッケージにも気を配りましょう

node_modules 以外のディレクトリがもし不要であれば、デプロイから確実に除外しておきましょう。除外する方法は以下です。

package:
  individually: true
  patterns:
    - "!**/**"
    # 必要なディレクトリーを配列に追加する
    - "dist/**"

webpack を使用したプロジェクトであれば、individually を true にすることで必要なソースコードを一つのJSファイルにバンドル出来るため、例えば service 層や repository 層などのディレクトリも一式不要となりますね。

まとめ

Lambda を使用したプロジェクトであれば、ほぼ必須の調整と言えます。NodeJS でなくても、例えば Python や Java でも同様の調整が可能です。

わたしたちは、ほぼすべての案件で本記事の調整を施します。

サーバーレスの開発相談はお気軽にご相談ください。