概要
前回のLambda群はlayerを使用しておらず、無駄なbundleが発生している。
また、同一stackでlambda layer を作成すると、毎回作り直しになるのも無駄である。
今回は、別StackでLambdaLayerを作成し、SSMにarnを保存して使用する方法をとる。
環境
- Windows10
- gitbash
- aws-cli/2.7.21 Python/3.9.11 Windows/10 exe/AMD64 prompt/off
- cdk v2.35
- node v16.16.0
ディレクトリ構成
- package.json # lambdaで使用するライブラリを定義
- cdk
- .env # 環境変数定義
- package.json # スクリプト定義
- bin
- lambda-layer-version-deploy.ts # lambda layerのデプロイ
- lambdaWithCognito.ts # lambdaのデプロイ
- lib
- lambda-layers-stack.ts
- LambdaWithCognitoStack.ts
- process
- pre.ts
- setup.ts
ソース
スクリプト
cdk/package.json
{
"scripts": {
"createLayer": "ts-node lib/process/pre.ts",
+ "create-layer-for-lambda-with-cognito-deploy": "npm run createLayer && npm run copyData && cdk -a \"npx ts-node --prefer-ts-exts bin/lambda-layer-version-deploy.ts\" deploy -c @aws-cdk/core:newStyleStackSynthesis=true --profile hoge",
"lambda-with-cognito-deploy": "cdk -a \"npx ts-node --prefer-ts-exts bin/lambdaWithCognito.ts\" deploy -c @aws-cdk/core:newStyleStackSynthesis=true --profile hoge"
}
}
lambda_layerのデプロイファイル作成
- lambda layerにデプロイする node_modulesを作成。
-
npm install --production
でdependency
のみをインストール
-
cdk/lib/process/pre.ts
import { bundleNpm } from "./setup";
bundleNpm();
import * as childProcess from 'child_process';
import * as fs from 'fs-extra';
import * as path from 'path'
const nodeModulesPath = './bundle-node_modules';
export const NODE_LAMBDA_LAYER_DIR = path.resolve(process.cwd(), nodeModulesPath);
export const bundleNpm = () => {
createNodeModules();
};
const NODE_LAMBDA_LAYER_RUNTIME_DIR_NAME = `nodejs`;
const runtimeDirName = path.resolve(process.cwd(), `${nodeModulesPath}/${NODE_LAMBDA_LAYER_RUNTIME_DIR_NAME}`);
const distFilePath = (file: string) => path.resolve(process.cwd(), `${nodeModulesPath}/${NODE_LAMBDA_LAYER_RUNTIME_DIR_NAME}/${file}`)
const srcFilePath = (file: string) => path.resolve(`${process.cwd()}/../${file}`)
const createNodeModules = () => {
copyPackageJson();
childProcess.execSync(`npm install --production`, {
cwd: getModulesInstallDirName(),
env: { ...process.env },
});
}
const copyPackageJson = () => {
fs.mkdirsSync(getModulesInstallDirName());
['package.json'].map(file => fs.copyFileSync(srcFilePath(file), distFilePath(file)));
};
const getModulesInstallDirName = (): string => {
return runtimeDirName;
};
package.json
{
"dependencies": {
"aws-jwt-verify": "^3.1.0",
"aws-sdk": "^2.1189.0",
"date-fns": "^2.29.1",
"pg": "^8.7.3"
},
"devDependencies": {
"@types/aws-lambda": "^8.10.101",
"@types/pg": "^8.6.5",
"esbuild": "^0.14.53",
"typescript": "^4.7.4"
}
}
- lambda_layerに入っているモジュールをlabdaのバンドル対象から外す設定を記載しておく
cdk/constants/lambda-layer.ts
export const externalModules = [
'aws-sdk', // Use the 'aws-sdk' available in the Lambda runtime
'date-fns', // Layrerに入れておきたいモジュール
'pg',
'aws-jwt-verify'
]
lambda layerのデプロイ
- SSMのkeyは任意の名前。今回は
/layer_versions_arn
とした。
.env
# 省略
# lambda layerのarn
SSM_PARAM_KEY_LAYER_VERSIONS_ARN=/layer_versions_arn
cdk/bin/lambda-layer-version-deploy.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as dotenv from 'dotenv'
import * as cdk from 'aws-cdk-lib';
import { LambdaLayersStack } from '../lib/lambda-layers-stack';
dotenv.config()
const envList = [
'SSM_PARAM_KEY_LAYER_VERSIONS_ARN'
] as const
for (const key of envList) {
if (!process.env[key]) throw new Error(`please add ${key} to .env`)
}
const processEnv = process.env as Record<typeof envList[number], string>
const app = new cdk.App();
const env = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
}
new LambdaLayersStack(app, 'LambdaLayersStack', {
ssmKey: processEnv.SSM_PARAM_KEY_LAYER_VERSIONS_ARN,
env,
});
cdk/lib/lambda-layers-stack.ts
import { Aspects, Stack, StackProps, Tag, Tags } from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';
import { NODE_LAMBDA_LAYER_DIR } from './process/setup';
import { StringParameter } from 'aws-cdk-lib/aws-ssm';
interface LambdaLayersStackProps extends StackProps {
ssmKey: string
}
export class LambdaLayersStack extends Stack {
constructor(scope: Construct, id: string, props: LambdaLayersStackProps) {
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]
}
);
// Lambda Layer参照用にarnを保存
const layerArnParameter = new StringParameter(this, "ssm-layer-version", {
parameterName: props.ssmKey,
stringValue: nodeModulesLayer.layerVersionArn,
description: 'layer version arn for lambda'
});
Tags.of(layerArnParameter).add('Name', 'ssm-layer-version');
Aspects.of(this).add(new Tag('Stack', id));
}
}
lambdaのデプロイ
cdk/bin/lambdaWithCognito.ts
#!/usr/bin/env node
import 'source-map-support/register'
import * as cdk from 'aws-cdk-lib'
import { LambdaWithCognitoStack } from '../lib/LambdaWithCognitoStack'
import * as dotenv from 'dotenv'
// 依存関係: lambda-layer-vesion-deploy
// create-layer-for-lambda-with-cognito-deploy を事前に行い、Lambda LayerのArnをSSMに保存していること
dotenv.config()
const envList = [
'PROJECT_ID',
'DOMAIN_PREFIX',
'CALLBACK_URLS',
'LOGOUT_URLS',
'FRONTEND_URLS',
+ 'SSM_PARAM_KEY_LAYER_VERSIONS_ARN'
] as const
for (const key of envList) {
if (!process.env[key]) throw new Error(`please add ${key} to .env`)
}
const processEnv = process.env as Record<typeof envList[number], string>
const app = new cdk.App()
const env = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
}
const projectId = processEnv.PROJECT_ID
new LambdaWithCognitoStack(app, `${projectId}-lambda-with-cognito-stack`, {
projectId,
domainPrefix: processEnv.DOMAIN_PREFIX,
callbackUrls: processEnv.CALLBACK_URLS.split(','),
logoutUrls: processEnv.LOGOUT_URLS.split(','),
frontendUrls: processEnv.FRONTEND_URLS.split(','),
+ ssmKeyForLambdaLayerArn: processEnv.SSM_PARAM_KEY_LAYER_VERSIONS_ARN,
env
})
cdk/lib/LambdaWithCognitoStack.ts
import * as lambda from 'aws-cdk-lib/aws-lambda'
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'
+ import * as ssm from 'aws-cdk-lib/aws-ssm';
+ import { externalModules } from '../constants/lambda-layer'
// 省略
interface Props extends StackProps {
projectId: string
domainPrefix: string
frontendUrls: string[]
callbackUrls: string[]
logoutUrls: string[]
+ ssmKeyForLambdaLayerArn: string
}
export class LambdaWithCognitoStack extends Stack {
constructor(scope: Construct, id: string, props: Props) {
// 省略
+ const lambdaLayerArn = ssm.StringParameter.valueForStringParameter(this, props.ssmKeyForLambdaLayerArn);
const baseFunctionProps = {
runtime: lambda.Runtime.NODEJS_14_X,
+ layers: [
+ lambda.LayerVersion.fromLayerVersionArn(this, 'node_modules-layer', lambdaLayerArn)
+ ],
+ bundling: {
+ externalModules
+ }
}
const handler = new NodejsFunction(
this,
`${props.projectId}-hello-lambda`,
{
...baseFunctionProps,
entry: '../src/handler/api/hello.ts',
functionName: 'hello',
description: 'ハロー',
},
)
実行
gitbashで以下のコマンドを実行。
npm run create-layer-for-lambda-with-cognito-deploy
npm run lambda-with-cognito-deploy
確認
layerが設定された。
複数Layerの適用
node_modulesが2つになるが、マージされて普通に使用可能。
今回は、検証のみに必要なモジュールがあるため、それだけ別レイヤに分けて試してみた。