在 AWS CDK + Typescript 环境中有效地管理 Lambda Layer

使用AWS CDK开发lambda函数时,我想记录下一种管理lambda层的好方法,以备忘。因为我是初学者,可能还有更好的方法。

动机

import { hoge } from ‘layer’ みたいに lambda layer にしたコードを使いたい
vendor ライブラリも実装者が特に意識せず使いたい(vendor ライブラリも layer にしておく必要があります)

package.json は1つだけで良い

出于解决这个问题,同时也希望简单地为这个环境做准备的背景。

确认操作环境

    • typescript: 3.8.3

 

    • aws-cdk: 1.26.0

 

    aws-sam-cli: 0.40.0

首先准备一个简单的 lambda 函数。

以下是基于使用 cdk init –language typescript 创建的 cdk 环境的项目。
项目名称:cdk-project

$ tree -I node_modules -L 3
.
├── README.md
├── bin
│   └── cdk-project.ts
├── cdk.json
├── jest.config.js
├── lib
│   └── cdk-project-stack.ts
├── package-lock.json
├── package.json
├── test
│   └── cdk-project.test.ts
└── tsconfig.json

我会创建一个简单的匿名函数来返回“Hello World”。

$ mkdir -p dir lambda/helloWorld
$ vi lambda/helloWorld/index.ts
// lambda/helloWorld/index.ts
import { APIGatewayEvent, Callback, Cotnext } from 'aws-lambda'

exports.handler = async (event: APIGatewayEvent, context: Context, callback: Callback) => {
  callback(null, 'Hello World')
}

为了创建 lambda,需要按照以下方式修改 lib/cdk-project-stack.ts。

// lib/cdk-project-stack.ts
import * as cdk from '@aws-cdk/core'
import * as lambda from '@aws-cdk/aws-lambda'

export class CdkProjectStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const helloWorldLambda = new lambda.Function(this, 'helloWorldLambda', {
      code: lambda.Code.fromAsset('lambda/helloWorld'),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_12_X
    })
  }
}

// bin/cdk-project.ts にも書かれているがコード記載省略のため
const app = new cdk.App()
new CdkProjectStack(app, 'HelloWorldCdkApp')
app.synth()

我暂时在这里用 sam 确认一下。

$ yarn build
$ cdk synth
$ sam local invoke -t cdk.out/HelloWorldCdkApp.template.json
Invoking index.handler (nodejs12.x)

Fetching lambci/lambda:nodejs12.x Docker container image......
(省略)

"Hello World"

确认已经收到“Hello World”。

准备lambda层

在部署 lambda 层时,必须遵循以下规则。

    • zip で S3 にアップロードされている

 

    zip 展開後の構造が nodejs/node_modules/ となっている

如果使用 aws-cdk,它可以将指定的目录压缩成 zip 并上传到 S3,但是需要我们自己处理 nodejs/node_modules/ 这样的结构。这是个棘手的问题。

我想使用转化为 lambda layer 的代码,就像import { hoge } from ‘layer’一样。

打算做某事

.
├── lambda
│   └── helloWorld
└── layer

如果简单地进行,那么就会变成:“供应商库呢?合并成Lambda层好麻烦。。。”

.
├── lambda
│   └── helloWorld
└── layer
    └── nodejs
        └── nodejs
            └── node_modules

如果这样的话,“哎呀!如果包括供应商的库,我需要在nodejs中准备一个package.json来管理?而且,import语句看起来不太舒服。。怎么办呢?”

供应商库可以无需特别意识到使用(对于aws-cdk 特别需要将供应商库保留为层)
只需要一个 package.json 文件即可

在这一带做得不好,变得很烦恼了。

通过采用以下结构,问题得到了解决。(我是用上述第一种方法)

    1. 在layer文件夹以下管理独自共通化处理

 

    1. 在root的dependencies中添加layer文件夹的路径

 

    1. 在root的dependencies中添加要用作lambda层的vendor库

 

    在root的dependencies中以layer.out/nodejs/node_modules/的结构添加指定的库,并进行yarn安装。

如果你认为 layer.out/nodejs/node_modules/ 这个文件夹有膨胀的情况,建议你考虑将其中应该移至 devDependencies 的内容移动过去。

在layer文件夹中管理单独的共同处理。

.
├── lambda
│   └── helloWorld
└── layer
    └── utils
    │   └── index.ts
    ├── ...
    └── index.ts

在图层中添加一个简单的函数,用来对数值进行加法运算。

// layer/utils/index.ts
export const sum = (num1: number, num2: number): number => num1 + num2

在 layer 层下管理通用处理并且在 layer/index.ts 文件中进行集中导出。

// layer/index.ts
export * from './utils'
...

在root的依赖项中使用路径指定方式添加layer目录。

修改 package.json 如下所示。

{
  ...
  "dependencies": {
    "layer": "file:./layer"
  }
}

在root的dependencies中添加想要作为Lambda层使用的供应商库。

为了做一个供应商代码的例子,我们还可以在依赖中添加dayjs。

{
  ...
  "dependencies": {
    "dayjs": "^1.8.22",
    "layer": "file:./layer"
  }
}

4. 将根目录的 package.json 文件中指定的依赖库以 layer.out/nodejs/node_modules/ 结构的方式进行 yarn install 安装。

创建一个预处理脚本,用于按照指定的结构来安装在dependencies中指定的库。

// lib/layerSetup.ts
import * as childProcess from 'child_process'

const LAMBDA_LAYER_DIR_NAME = './layer.out/nodejs/node_modules/'
export const bundleLayer = () => childProcess.execSync(`yarn install --production --modules-folder ${LAMBDA_LAYER_DIR_NAME}`)
// bin/cdk-project.ts
import ...
import { bundleLayer } from '../lib/layerSetup'

// pre-process
bundleLayer()

const app = new cdk.App()
new CdkProjectStack(app, 'HelloWorldCdkApp')

只需要在使用tsc进行转译之前升级layer,这样当更新layer时也可以轻松地进行反映,因此可以npm脚本。

{
  ...
  "scripts": {
    "build": "yarn upgrade layer && tsc",
    ...
  }
}

作为一个选项,看起来不错。

最后将 Lambda 层添加到堆栈中。

import * as cdk from '@aws-cdk/core'
import * as lambda from '@aws-cdk/aws-lambda'

export class CdkProjectStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    const layer = new lambda.LayerVersion(this, 'layer', {
      compatibleRuntimes: [lambda.Runtime.NODEJS_12_X],
      code: lambda.Code.fromAsset('layer.out'),
    })

    const helloWorldLambda = new lambda.Function(this, 'helloWorldLambda', {
      code: lambda.Code.fromAsset('lambda/helloWorld'),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_12_X,
      layers: [layer]
    })
  }
}
$ yarn build
$ cdk synth

如果这样做,就会在预处理中创建 layer.out/nodejs/node_modules/…,然后将 layer.out 设置为 layer。

用Sam进行Lambda Layer的操作验证

将 helloWorldLambda 更改为使用 lambda layer 的函数。

import { APIGatewayEvent, Callback, Context } from 'aws-lambda'
import * as dayjs from 'dayjs'
import { sum } from 'layer'
dayjs.locale('ja')

exports.handler = async (event: APIGatewayEvent, context: Context, callback: Callback) => {
  console.log('Hello World')
  console.log(sum(1, 2)) // 3
  callback(null, dayjs('2020-01-01'))
}
$ yarn build
$ cdk synth
$ sam local invoke -t ./cdk.out/HelloWorldCdkApp.template.json
Invoking index.handler (nodejs12.x)
layer27F209B1 is a local Layer in the template
Building image...
Requested to skip pulling images ...

(省略)
... INFO    Hello World
... INFO    3
(省略)

"2020-01-01T00:00:00.000Z"

出来了。

顺便提一句,在不将供应商代码设为 Lambda 层的情况下

{"errorType":"Runtime.ImportModuleError","errorMessage":"Error: Cannot find module 'dayjs'\nRequire stack:\n- /var/task/index.js\n- /var/runtime/UserFunction.js\n- /var/runtime/index.js"}

变得像那样。

总结

    • dependencies で lambda layer を管理できる体制にする。(dependencies と devDependencies を区別する意識をつけてこう)

 

    • preprocess で dependencies を nodejs/node_modules の形式にする。

 

    npm scripts で synth までのコマンドをあまり意識しなくて良いようにする。

在进行 synth 之前,如果不删除 cdk.out 和 layer.out 等文件,有时候更新可能不会成功,所以最好在 cdk:synth 这样的 npm 脚本中添加清理操作,以确保在 cdk synth 之前执行清理处理。(我还没有研究 aws-cdk 的这方面行为)

如果有更好的建议,请告诉我!

广告
将在 10 秒后关闭
bannerAds