使用AWS CDK设置Lambda Layer并从Lambda中读取并进行本地执行的备忘录

总结

我之前已经确认了Lambda的执行情况。
我已经确认了生成物并且确认了必要的模块也已经被打包。
如果要在Lambda之间使用共同的模块,把它们都打包到每个Lambda中是浪费的(占用空间且打包时间长)。
AWS有一个叫Lambda Layer的机制可以用来解决这个问题。

如果你想使用AWS CDK来将node_modules部署到AWS Lambda Layers,你可以参考一个示例来实现。然而,我在Windows系统上遇到了一些问题,所以在此留下备忘录。

源代码

要做的事情。

使用SAM进行本地执行

不做的事情

部署

源代码修正

建置設定

    Lambda Layerにいれる成果物をバンドルのときに作成できるように pre-prrocess追加
#!/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にLamba Layerの作成を記述する


+ 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’, // 使用 Lambda 运行时中可用的 ‘aws-sdk’
‘date-fns’, // 希望放在 Layer 中的模块
],
define: { // 在构建时替换字符串
‘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ディレクトリを作成

参考のnpm –prefix ${getModulesInstallDirName()} install –productionだとnpm installされずにシンボリックリンクが作成されてしまった

以下のようにcwdを使ってディレクトリを指定するよう修正して対応

  // 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 = () => {
// 创建 bundle 目录
copyPackageJson();

// 安装 package.json (production)
childProcess.execSync(`npm install –production`, {
cwd: getModulesInstallDirName(),
// 在打包时不输出多余的字符串到 template.yaml 文件中
stdio: [‘ignore’, ‘ignore’, ‘ignore’],
env: { …process.env },
shell: ‘bash’
});
};

const copyPackageJson = () => {

// 复制 package.json 和 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回目からはそこまででもないかも

image.png

请参考
考虑这个问题时,请参考这个方案。

使用AWS CDK将node_modules部署到AWS Lambda Layers的示例。

bannerAds