serverless教程五:使用serverless开发API(Lambda)


在开发无服务器应用的时候,通过纯手工方式创建各种资源的话,效率会非常低,因此需要借助一些框架来提高开发效率。下面介绍如何在serverless中开发API(Lambda)。

serverless教程
serverless教程

一个简单的Lambda

首先创建文件:api/common/Responses.js。这个文件把每个API调用都需要的返回信息封装了一下,以便重用。

Javascript
const common_headers = {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Methods': '*',
    'Access-Control-Allow-Origin': '*',
};

const Responses = {
    HTTP_RESPONSE(http_code, response_data = {}) {
        return {
            headers: common_headers,
            statusCode: http_code,
            body: JSON.stringify(response_data)
        }
    }
}

module.exports = Responses

接下来创建Lambda的相应函数,添加api/students/getStudent.js:

Javascript
const Responses = require('../common/Responses');

const student_data = {
    9901: {name: "Anna Smith", age: 18, subject: "Economics"},
    9902: {name: "Jack Chen", age: 20, subject: "Math"},
    9903: {name: "Lucy Male", age: 19, subject: "History"}
};

exports.handler = async event => {
    console.log('event', event);
    
    if (!event.pathParameters || !event.pathParameters.ID) {
        // 如果没有ID参数,报错
        return Responses.HTTP_RESPONSE(400, {message: 'missing the ID from the path.'});
    }
    
    let ID = event.pathParameters.ID;
    
    if(student_data[ID]) {
        // 返回数据
        return Responses.HTTP_RESPONSE(200, student_data[ID]);
    }
    HHT
    // 如果有ID参数,但找不到相应记录,也报错
    return Responses.HTTP_RESPONSE(500, {message: 'cannot find this ID.'})    
}

至此,需要的JS文件都有了,下面在serverless.yml中进行定义:

yaml
service: service-05-api

provider:
  name: aws
  region: eu-west-1
  runtime: nodejs14.x
  lambdaHashingVersion: 20201221
  profile: default

functions:
  getStudent:
    handler: api/students/getStudent.handler
    events:
      - http:
          path: get-student/{ID}
          method: GET
          cors: true

进行发布:

bash
sls deploy

其输出结果类似这样:

bash
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
........
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service service-05-api.zip file to S3 (39.26 kB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
....................................
Serverless: Stack update finished...
Service Information
service: service-05-api
stage: dev
region: eu-west-1
stack: service-05-api-dev
resources: 13
api keys:
  None
endpoints:
  GET - https://xxxxxx8.execute-api.eu-west-1.amazonaws.com/dev/get-student/{ID}
functions:
  getStudent: service-05-api-dev-getUser
layers:
  None

对API的payload进行合法性验证

在serverless中,可以定义一个json格式的schema,然后将其与API相关联,后续接收到请求时,AWS都会根据给定的schema进行数据的合法性验证。

比如定义user.json:

json
{
  "definitions": {},
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "title": "Object metadata Schema",
  "required": ["email", "firstName", "lastName"],
  "properties": {
    "email": { "type": "string" },
    "firstName": { "type": "string" },
    "lastName": { "type": "string" }
  }
}

然后在serverless.yml中定义API时,通过这个json文件对request中的payload进行验证:

yaml
addUser:
  handler: src/functions/user/addUser.handler
  description: 'add user'
  memorySize: 256
  events:
    - http:
        path: api/user/new
        method: post
        cors: true
        request:
          schemas:
            application/json: 
              schema: ${file(serverless/schemas/user/user.json)}
              name: addUserRequestValidation
              description: 'Validate user'         

这样在通过/api/user/new调用这个API时,就需要传递如下格式的参数:

json
{
  "email": "xxx",
  "firstName": "xxx",
  "lastName": "xxx"
}

需要注意的是,在使用serverless-offline插件进行开发的时候,目前版本的插件不支持http.request.schemas(不会进行验证),但仍支持旧版的http.request.schema。

使用serverless webpack对Lambda进行优化

待解决的问题

当使用serverless开发多个Lambda时,由于每个JS都可能有自己的依赖库,于是发布Lambda的时候,serverless会把所有相关的资源都打包并发布,因此会导致文件过大。甚至在有些时候会出现无法进行发布的问题。如果登入到AWS控制台,有些Lambda会有这样的提示:

The deployment package of your Lambda function “xxxx” is too large to enable inline code editing. However, you can still invoke your function.

这个时候,就需要使用webpack对Lambda进行优化了。最终的目标是:使用webpack来确保我们只部署需要的代码到AWS。当我们的代码量增加时,针对某一个lambda,只部署与其相关的代码。

安装依赖库

bash
npm install --save serverless-webpack
npm install --save webpack

修改serverless.yml

在serverless.yml中,添加插件:

yaml
plugins:
  - serverless-webpack

同时添加设置:

yaml
package:
  individually: true

添加webpack.config.js

添加webpack配置文件: /webpack.config.js

Javascript
module.exports = {
    target: 'node',
    mode: 'production',
}

测试

重新部署:

bash
sls deploy

可以看到lambda瘦身成功,同时其代码被压缩了。

这是在使用webpack之前的.serverless目录,可以看到,所有的Lambda会被压缩成一个zip:

bash
ls -alh .serverless\
total 84K
drwxr-xr-x 1 lscoding lscoding    0 Nov 26 11:42 ./
drwxr-xr-x 1 lscoding lscoding    0 Nov 26 11:56 ../
-rw-r--r-- 1 lscoding lscoding 2.1K Nov 26 11:42 cloudformation-template-create-stack.json
-rw-r--r-- 1 lscoding lscoding  12K Nov 26 11:42 cloudformation-template-update-stack.json
-rw-r--r-- 1 lscoding lscoding  19K Nov 26 11:42 serverless-state.json
-rw-r--r-- 1 lscoding lscoding  39K Nov 26 11:42 service-05-api.zip

这是在使用webpack之后的目录,可以看到,getStudent这个Lambda被单独打包成一个zip:

bash
ls -alh .serverless\
total 48K
drwxr-xr-x 1 lscoding lscoding    0 Nov 26 11:58 ./
drwxr-xr-x 1 lscoding lscoding    0 Nov 26 11:58 ../
-rw-r--r-- 1 lscoding lscoding 2.1K Nov 26 11:58 cloudformation-template-create-stack.json
-rw-r--r-- 1 lscoding lscoding  11K Nov 26 11:58 cloudformation-template-update-stack.json
-rw-r--r-- 1 lscoding lscoding  716 Nov 26 11:58 getStudent.zip
-rw-r--r-- 1 lscoding lscoding  19K Nov 26 11:58 serverless-state.json

如果把webpack.config.js中的”production”改为”none”并再次部署,

bash
sls deploy function -f getStudent

可以看到,webpack把getUser这个函数以及所需要的文件Responses.js打包成了一个单一文件,并发布到了Lambda。

其输出结果类似这样:

bash
sls deploy function -f getStudent
Serverless: Bundling with Webpack...
asset api/students/getStudent.js 1.04 KiB [emitted] [minimized] (name: api/students/getStudent)
./api/students/getStudent.js 846 bytes [built] [code generated]
./api/common/Responses.js 757 bytes [built] [code generated]
webpack compiled successfully in 335 ms
Serverless: Copying existing artifacts...
Serverless: Packaging function: getStudent...
Serverless: Uploading function: getStudent (716 B)...
Serverless: Successfully deployed function: getStudent
Serverless: Configuration did not change. Skipping function configuration update.

进一步的依赖库问题

如果按照上面的方法分开打包,每个Lambda对应的zip文件都会变得很小,但有些Lambda在运行时会报错:

bash
"errorType": "Runtime.ImportModuleError",
    "errorMessage": "Error: Cannot find module 'ms'
    Require stack:
    - /var/task/node_modules/@opensearch-project/opensearch/lib/Transport.js

这时就需要单独为该Lambda指定依赖库了:

yaml
search:
  handler: src/functions/OpenSearch.handler
  package:
    patterns:
      - 'node_modules/@opensearch-project/opensearch/**'
      - 'node_modules/debug/**'
      - 'node_modules/ms/**'
      - 'node_modules/hpagent/**'
      - 'node_modules/secure-json-parse/**'

Lambda存储空间限制的问题

在默认情况下,Lambda只能使用512MB临时存储空间,但在2022年的时候,AWS允许Lambda使用最多10 GB的空间:

https://aws.amazon.com/blogs/compute/using-larger-ephemeral-storage-for-aws-lambda/

在serverless中可以这样:
https://www.serverless.com/framework/docs/providers/aws/guide/functions

yaml
functions:
  helloEphemeral:
    handler: handler.handler
    ephemeralStorageSize: 10240

Graviton2 Powered AWS Lambda Functions

基于Graviton2的Lambda说白了就是更快,更省钱。要想在serverless中使用Graviton2 Lambda,需要:

安装/配置插件

bash
npm i [email protected]:teamgenie/serverless-aws-lambda-arch.git

或者:

bash
npm install @cloudwiry/[email protected]

更新serverless.yml:

yaml
plugins:
  - serverless-aws-lambda-arch

# ......
functions:
  dummyFn2_RunningInARM64:
    handler: handler
    description: "Test function running on Graviton2"
    architectures: arm64  

文章作者: 逻思
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 逻思 !