概要
前回にLambdaの実行を確認できた。
生成物を確認し、必要とするモジュールもバンドルされていることも確認できた。
Lambda間で共通のモジュールを使う場合、すべてのLambdaにバンドルするのは無駄である。(容量も重くなるしバンドル時間もかかる)
AWSにはLambda Layerという仕組みがあるのでそれを使って解決する。
[AWS CDK を使って node_modules を AWS Lambda Layers にデプロイするサンプル][*1]を見れば実現できる。
ただし、Windowsで動かすときに少しハマったので備忘録を残す。
やること
SAMを使ったローカル実行
やらないこと
デプロイ
ソースコード修正
ビルド設定
- Lambda Layerにいれる成果物をバンドルのときに作成できるように pre-prrocess追加
bin/sample-index.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { SampleStack } from '../lib/sample-stack';
import { bundleNpm } from '../lib/process/setup';
+ // pre-process
+ bundleNpm();
// create app
const app = new cdk.App();
new SampleStack(app, 'SampleStack2021');
Stackの記述修正
- stackにLamba Layerの作成を記述する
lib/sample-stack.ts
+ import { NODE_LAMBDA_LAYER_DIR } from './process/setup';
export class SampleStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
+ const nodeModulesLayer = new lambda.LayerVersion(this, 'NodeModulesLayer',
+ {
+ code: lambda.AssetCode.fromAsset(NODE_LAMBDA_LAYER_DIR),
+ compatibleRuntimes: [lambda.Runtime.NODEJS_14_X]
+ }
+ );
new NodejsFunction(this, 'test', {
runtime: lambda.Runtime.NODEJS_14_X,
entry: 'src/lambda/handlers/test.ts',
functionName: 'kotatest',
bundling: {
externalModules: [
'aws-sdk', // Use the 'aws-sdk' available in the Lambda runtime
'date-fns', // Layrerに入れておきたいモジュール
],
define: { // Replace strings during build time
'process.env.API_KEY': JSON.stringify(JSON.stringify('"xxx-xxx"')),
},
},
+ layers: [nodeModulesLayer],
});
}
}
全文
lib/sample-stack.ts
import * as cdk from '@aws-cdk/core';
import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs';
import * as lambda from '@aws-cdk/aws-lambda';
import { NODE_LAMBDA_LAYER_DIR } from './process/setup';
export class SampleStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
new NodejsFunction(this, 'hello', {
runtime: lambda.Runtime.NODEJS_14_X,
entry: 'src/lambda/handlers/hello.ts',
functionName: 'kotahello',
handler: 'lambdaHandler'
});
const nodeModulesLayer = new lambda.LayerVersion(this, 'NodeModulesLayer',
{
code: lambda.AssetCode.fromAsset(NODE_LAMBDA_LAYER_DIR),
compatibleRuntimes: [lambda.Runtime.NODEJS_14_X]
}
);
new NodejsFunction(this, 'test', {
runtime: lambda.Runtime.NODEJS_14_X,
entry: 'src/lambda/handlers/test.ts',
functionName: 'kotatest',
bundling: {
externalModules: [
'aws-sdk', // Use the 'aws-sdk' available in the Lambda runtime
'date-fns', // Layrerに入れておきたいモジュール
],
define: { // Replace strings during build time
'process.env.API_KEY': JSON.stringify(JSON.stringify('"xxx-xxx"')), // バグってそう.二重でstringifyしないとInvalid define valueのエラー
},
},
layers: [nodeModulesLayer],
});
}
}
Layerの成果物作成
- pre-prrocessで呼び出される
- Lambda Layerにアップロードするディレクトリの作成
- package.json, package-lock.jsonのコピー
-
npm install
でnode_modulesディレクトリを作成
- [参考][*1]の
npm --prefix ${getModulesInstallDirName()} install --production
だとnpm installされずにシンボリックリンクが作成されてしまった- 以下のようにcwdを使ってディレクトリを指定するよう修正して対応
lib/process/setup.ts
// install package.json (production)
childProcess.execSync(`npm install --production`, {
cwd: getModulesInstallDirName(),
// bundle時にパイプで出力するtemplate.yamlに、余分な文字列が含まれてしまわないように出力はオフ
stdio: ['ignore', 'ignore', 'ignore'],
env: { ...process.env },
shell: 'bash'
});
全文
lib/process/setup.ts
#!/usr/bin/env node
import * as childProcess from 'child_process';
import * as fs from 'fs-extra';
import * as path from 'path'
const bundlePath = './bundle';
export const NODE_LAMBDA_LAYER_DIR = path.resolve(process.cwd(), bundlePath);
const NODE_LAMBDA_LAYER_RUNTIME_DIR_NAME = `nodejs`;
const runtimeDirName = path.resolve(process.cwd(), `${bundlePath}/${NODE_LAMBDA_LAYER_RUNTIME_DIR_NAME}`);
const distFilePath = (file: string) => path.resolve(process.cwd(), `${bundlePath}/${NODE_LAMBDA_LAYER_RUNTIME_DIR_NAME}/${file}`)
const srcFilePath = (file: string) => path.resolve(`${process.cwd()}/${file}`)
export const bundleNpm = () => {
// create bundle directory
copyPackageJson();
// install package.json (production)
childProcess.execSync(`npm install --production`, {
cwd: getModulesInstallDirName(),
// bundle時にパイプで出力するtemplate.yamlに、余分な文字列が含まれてしまわないように出力はオフ
stdio: ['ignore', 'ignore', 'ignore'],
env: { ...process.env },
shell: 'bash'
});
};
const copyPackageJson = () => {
// copy package.json and package.lock.json
fs.mkdirsSync(getModulesInstallDirName());
['package.json', 'package-lock.json']
.map(file => fs.copyFileSync(srcFilePath(file), distFilePath(file)));
};
const getModulesInstallDirName = (): string => {
return runtimeDirName;
};
実践
- 動いた
- Build imageが大分長くなった気がする。。。
- 2回目からはそこまででもないかも
参考
[AWS CDK を使って node_modules を AWS Lambda Layers にデプロイするサンプル][*1]
aws lambda layer
[*1]:https://dev.classmethod.jp/articles/aws-cdk-node-modules-lambda-layer/)