如何将AWS Node.js Lambda函数迁移至OpenFaaS?

本篇教程,我们将共同了解如何将AWS Lambda函数(Node.js)迁移至OpenFaaS。

为什么要迁移至OpenFaas?

云函数服务确实优点很多,不仅成本低廉,而且适合大部分用例的实际需求。但在另一方面,OpenFaaS相较于云函数服务也拥有不少独特优势。

下面,我先聊聊自己在使用OpenFaaS中的具体感受:

  • 可以在自有基础设施上托管函数以满足本地化标准。
  • 确保函数托管在符合用例特性的资源之上(包括CPU、内存以及GPU密集型任务)。
  • 可以使用现有Kubernetes或者Docker Swarm集群部署OpenFaaS。
  • 对TTL没有任何限制,可以长期保持函数运行。
  • 确保用户不致锁定于特定云服务供应商处。
  • 拥有一整套函数库以及为其提供贡献的活跃社区,能够为项目提供巨大助益。
  • 默认提供自动规模伸缩功能。
  • 支持一系列编程语言选项,甚至能够使用bash脚本,极大提升使用体验!
  • 极易学习,而且使用感受也非常友好。
  • Cli客户端与faas-cil的存在又让OpenFaaS的使用难度进一步降低。
  • Grafana、Prometheus以及ALertManager可在框架中开箱即用,允许大家轻松查看函数指标并设置警报机制。

根据实际体验,我之前已经建立起一套Docker Swarm集群,其中的资源由云服务供应商管理,同时拥有监控、高可用性以及自我修复机制。

现在,我可以在这套集群设置之上使用OpenFaaS,而且完美匹配实际用例。

架构

终极目标是将AWS Lambda Function迁移至OpenFaaS:

应用程序

我们在AWS中的无服务器应用程序包含API网关、DynamoDB以及Lambda(Node.js)。

在示例中,我会尽量控制应用程序的复杂度,因此其功能非常简单:当我在API网关资源上发出GET请求时,在DynamoDB表上执行GetItem。

在这种情况下,我将哈希键值硬编码至 ruan.bekker中。

整个流程如下所示:

-> API: /dev/person,
-> Lambda calls DynamoDB: {"id": "ruan.bekker"},
-> Response: {"id": "ruan.bekker", "name": "ruan", ...}

AWS设置

为了完全透明,我将使用无服务器方式设置整个AWS栈:

$ mkdir -p ~/dev/aws-node-get-dynamodb \
&& cd ~/dev/aws-node-get-dynamodb
$ npm install -g serverless
$ serverless create --template aws-nodejs 

创建Lambda函数:

$ mkdir function/handler.js
$ cat function/handler.js
'use strict';
const AWS = require('aws-sdk');
const dynamoDb = new AWS.DynamoDB.DocumentClient();
module.exports.identity = (event, context, callback) => {
const params = {
TableName: process.env.DYNAMODB_TABLE,
Key: {
     id: 'ruan.bekker',
   },
 };
dynamoDb.get(params, (error, result) => {
    if (error) {
     console.error(error);
      callback(null, {
        statusCode: error.statusCode || 501,
       headers: { 'Content-Type': 'text/plain' },
       body: 'GetItem Failed',
      });
      return;
    }
    const response = {
      statusCode: 200,
      body: JSON.stringify(result.Item),
    };
    callback(null, response);
  });
};

无服务器定义文件:

$ cat serverless.yml
service: aws-node-get-dynamodb
frameworkVersion: ">=1.1.0 <2.0.0"
provider:
  name: aws
  runtime: nodejs10.x
  environment:
    DYNAMODB_TABLE: my-dynamodb-table
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:GetItem
      Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"
functions:
  get:
    handler: functions/handler.identity
    events:
      - http:
          path: person
          method: get
          cors: true
resources:
  Resources:
    TodosDynamoDbTable:
      Type: 'AWS::DynamoDB::Table'
      DeletionPolicy: Retain
      Properties:
        AttributeDefinitions:
          -
            AttributeName: id
            AttributeType: S
        KeySchema:
          -
            AttributeName: id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
        TableName: ${self:provider.environment.DYNAMODB_TABLE}

部署该栈:

$ serverless deploy --region eu-west-1
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service aws-node-get-dynamodb.zip file to S3 (7.38 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............
Serverless: Stack update finished...
Service Information
service: aws-node-get-dynamodb
stage: dev
region: eu-west-1
stack: aws-node-get-dynamodb-dev
resources: 12
api keys:
  None
endpoints:
  GET - https://xx.execute-api.eu-west-1.amazonaws.com/dev/person
functions:
  get: aws-node-get-dynamodb-dev-get
layers:
  None
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.

现在我们的技术栈已经部署完成,接下来就是向DynamoDB中写入一个条目。

由于本文的重点在于迁移,因此我将哈希键硬编码至ruan.bekker当中,下面在DynamoDB中创建该条目:

$ aws dynamodb put-item \
  --table-name my-dynamodb-table --item \
'
{
    "id": {"S": "ruan.bekker"},
    "name": {"S": "ruan"},
    "surname": {"S": "bekker"},
    "country": {"S": "south africa"},
    "age": {"N": "32"}
}

发送一条指向该API网关URL的GET请求:

$ curl https://xx.execute-api.eu-west-1.amazonaws.com/dev/person
{"id":"ruan.bekker","surname":"bekker","name":"ruan","country":"south africa","age":32}

可以看到,现在我们已经能够在DynamoDB中检索到该条目。

设置OpenFaaZS函数

创建一个新的Node.js OpenFaaS函数(请注意,设置当中使用了镜像前缀与网关url,如下所示):

$ mkdir -p ~/dev/lambda-to-openfaas-migration \
  && cd ~/dev/lambda-to-openfaas-migration
$ faas-cli new \
  --lang node person \
  --prefix=ruanbekker \
  --gateway https://openfaas.ruan.dev
$ mv person.yml stack.yml

在本示例中,我会将AWS Access Keys与Secret Keys创建为OpenFaaS secrets:

$ faas-cli secret create my-aws-secret-key --from-literal="your-access-key"
$ faas-cli secret create my-aws-access-key --from-literal="your-secret-key"

在我们的package.json当中提供aws-sdk依赖项,并借此与AWS进行交互:

$ cat person/package.json
{
  "name": "function",
  "version": "1.0.0",
  "description": "",
  "main": "handler.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "aws-sdk": "latest"
  }
}

我们的栈定义:

$ cat stack.yml
provider:
  name: openfaas
  gateway: https://openfaas.ruan.dev
functions:
  person:
    lang: node
    handler: ./person
    image: ruanbekker/person:latest
    environment:
      content_type: application/json
      DYNAMODB_TABLE: my-dynamodb-table
      AWS_REGION: eu-west-1
    secrets:
      - my-aws-access-key
      - my-aws-secret-key

我们的初始设置中仍包含AWS Lambda函数代码,但到这里的栈已经设置完成,而且无需任何本地复本。

下面,我们需要下载Lambda部署软件包:

$ mkdir aws-lambda \
  && cd aws-lambda
$ lambda_url=$(aws lambda get-function --function-name serverless-rest-api-with-dynamodb-dev-get  | jq -r .Code.Location)
$ curl -o deployment_package.zip "${lambda_url}"

提取该部署软件包并利用由此得到的OpenFaaS处理程序替换原Lambda函数处理程序:

$ unzip deployment_package.zip
$ cd ..
$ mv aws-lambda/function/handler.js person/handler.js

接下来,我们需要修改处理程序以纳入各secrets与环境变量:

$ cat person/handler.js
'use strict';
const fs = require('fs');
const secretAK = "/var/openfaas/secrets/my-aws-access-key";
const secretSK = "/var/openfaas/secrets/my-aws-secret-key";
const accessKey = fs.readFileSync(secretAK, "utf-8");
const secretKey = fs.readFileSync(secretSK, "utf-8");
const AWS = require('aws-sdk');
AWS.config.update({
  credentials: new AWS.Credentials ({
    region: process.env.AWS_REGION,
    accessKeyId: accessKey,
    secretAccessKey: secretKey
  })
})
const dynamoDb = new AWS.DynamoDB.DocumentClient();
module.exports = (context, callback) => {
  const params = {
    TableName: process.env.DYNAMODB_TABLE,
    Key: {
      id: 'ruan.bekker',
    },
  };
  dynamoDb.get(params, (error, result) => {
    if (error) {
      console.error(error);
      callback(null, {
        statusCode: error.statusCode || 501,
        headers: { 'Content-Type': 'text/plain' },
        body: 'GetItem Failed',
      });
      return;
    }
    const response = result.Item;
    callback(null, response);
  });
};

部署OpenFaaS函数:

$ export OPENFAAS_URL=https://openfaas.ruan.dev
$ faas-cli up
Deploying: person.
Deployed. 202 Accepted.
URL: https://openfaas.ruan.dev/function/person

现在,我们需要在OpenFaaS API网关URL上通过GET请求测试新创建的函数:

$ curl https://openfaas.ruan.dev/function/person
{"id":"ruan.bekker","surname":"bekker","name":"ruan","country":"south africa","age":32}

搞定,现在我们已经将AWS Lambda Function迁移至OpenFaaS。

原文链接:

https://sysadmins.co.za/migrate-your-aws-node-js-lambda-function-to-openfaas/

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/UaPJDrfklqL7xT0hANV5
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券