2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

aws-cdk v2でlambda layers を扱ったメモ

Last updated at Posted at 2021-12-05

概要

aws-cdkでもlambda layersが扱えることを確認する。
コードの共通化を試す。
ソースコード

node_modulesのLayer化

必要なことは以下。

  • node_modulesフォルダをLayerのフォルダに指定する
    • 実行する上ではdevDependencyはいらないので、dependencyのみを含んだnode_modulesにする
  • ビルド時にnode_modulesのファイルはバンドルしないようにする

dev(ローカルで確認)やdeployの前に、Layerのフォルダを作成するスクリプトpre.tsを実行するようにする。npmのルールで、preスクリプト名で定義したスクリプトは、目的のスクリプトの実行前に行うようになるのでそれを利用する。

cdk/package.json
{
  "scripts": {
    "createLayer": "ts-node lib/process/pre.ts",
    "predev": "npm run createLayer",
    "dev": "sam-beta-cdk local start-api",
    "predeploy": "npm run createLayer",
    "deploy": "cdk deploy -c @aws-cdk/core:newStyleStackSynthesis=true --profile produser"
  }
}
cdk/lib/process/pre.ts
import { bundleNpm } from "./setup";

// 実行
bundleNpm();
cdk/lib/process/setup.ts
import * as childProcess from 'child_process';
import * as fs from 'fs-extra';
import * as path from 'path'

const nodeModulesPath = './bundle-node_modules';
const commonModulesPath = './bundle-common';
export const NODE_LAMBDA_LAYER_DIR = path.resolve(process.cwd(), nodeModulesPath);
export const COMMON_LAMBDA_LAYER_DIR = path.resolve(process.cwd(), commonModulesPath);

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}`)

export const bundleNpm = () => {
  createNodeModules();
};


const createNodeModules = () => {
  // create bundle directory
  copyPackageJson();

  // install package.json (production)
  childProcess.execSync(`npm install --production`, {
    cwd: getModulesInstallDirName(),
    env: { ...process.env },
  });
}


const copyPackageJson = () => {
  fs.mkdirsSync(runtimeDirName);
  ['package.json']
    .map(file => fs.copyFileSync(srcFilePath(file), distFilePath(file)));
};

externalModulesでnode_modulesで管理するスクリプトをバンドルに含めないようにする。今回の場合だと、date-fns

cdk/lib/cdk-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';
import { LambdaIntegration, RestApi } from 'aws-cdk-lib/aws-apigateway';
import { COMMON_LAMBDA_LAYER_DIR, NODE_LAMBDA_LAYER_DIR } from './process/setup';

export class CdkStack extends Stack {
  constructor(scope: Construct, id: string, props?: 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]
      }
    );

    const helloLambda = new NodejsFunction(this, 'helloLambda', {
      runtime: lambda.Runtime.NODEJS_14_X,
      entry: `../src/handler/api/hello.ts`,
      layers: [nodeModulesLayer],
      bundling: {
        externalModules: [
          'aws-sdk', // Use the 'aws-sdk' available in the Lambda runtime
          'date-fns', // Layrerに入れておきたいモジュール
        ],

      }
    });
    const api = new RestApi(this, 'ServerlessRestApi', { cloudWatchRole: false });
    api.root.addResource('hello').addMethod('GET', new LambdaIntegration(helloLambda));
  }
}

ユーティリティのLayer化

/opt/nodejs/配下にレイヤーのパスが通ることを利用する。
トランスパイルが通るよう、tsconfigとesbuildの設定を行う。

tsconfig.json
{
  "compilerOptions": {
    // 省略
    "baseUrl": ".",
    "paths": {
+      "/opt/nodejs/*": ["src/common/*"],
      "@/*" : ["./src/*"]
    }
  }
}
cdk/lib/cdk-stack.ts
// 省略
    const helloLambda = new NodejsFunction(this, 'helloLambda', {
      runtime: lambda.Runtime.NODEJS_14_X,
      entry: `../src/handler/api/hello.ts`,
      layers: [nodeModulesLayer, commonModulesLayers],
      bundling: {
        externalModules: [
          'aws-sdk', // Use the 'aws-sdk' available in the Lambda runtime
          'date-fns', // Layrerに入れておきたいモジュール
+          '/opt/nodejs/*'
        ],

      }
    });
// 省略

このときのフォルダ構造は以下となっている

- tsconfig
- src
  - common
    - utils
      - index.ts
    - index.ts
  - handler
    - api
      - hello.ts

利用の仕方は以下。

src/handler/api/hello.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
import { format } from 'date-fns';
- import { formatDate } from '@/common/index';
+ import { formatDate } from '/opt/nodejs/index';

export const handler: APIGatewayProxyHandler = async (event) => {
  const datetime = formatDate(new Date(event.requestContext.requestTimeEpoch))
  return {
    'statusCode': 200,
    'body': JSON.stringify({
      message: `hello world. ${datetime} = ${event.requestContext.requestTimeEpoch}. now: ${format(new Date(), 'yyyy-MM-dd HH:mm:ss.SSS')}`,
    })
  };
};

参考

AWS CDK + Typescript 環境で lambda layer を上手く管理する
How to Split TypeScript-Based Lambda Functions into Lambda Layers

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?