如何将 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
$npminstall-gserverless
$serverlesscreate--templateaws-nodejs

创建 Lambda 函数:

复制代码

$ mkdirfunction/handler.js
$catfunction/handler.js
'usestrict';
constAWS=require('aws-sdk');
constdynamoDb =newAWS.DynamoDB.DocumentClient();
module.exports.identity =(event, context, callback) =>{
constparams = {
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;
}
constresponse = {
statusCode:200,
body:JSON.stringify(result.Item),
};
callback(null, response);
});
};

无服务器定义文件:

复制代码

$catserverless.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}

部署该栈:

复制代码

$serverlessdeploy--regioneu-west-1
Serverless:Packagingservice...
Serverless:Excludingdevelopmentdependencies...
Serverless:UploadingCloudFormationfiletoS3...
Serverless:Uploadingartifacts...
Serverless:Uploadingserviceaws-node-get-dynamodb.zipfiletoS3(7.38MB)...
Serverless:Validatingtemplate...
Serverless:UpdatingStack...
Serverless:CheckingStackupdateprogress...
..............
Serverless:Stackupdatefinished...
ServiceInformation
service:aws-node-get-dynamodb
stage:dev
region:eu-west-1
stack:aws-node-get-dynamodb-dev
resources:12
apikeys:
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:Runthe"serverless"commandtosetupmonitoring,troubleshootingandtesting.

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

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

复制代码

$ aws dynamodbput-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 \
--langnode person \
--prefix=ruanbekker\
--gatewayhttps://openfaas.ruan.dev
$ mv person.yml stack.yml

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

复制代码

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

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

复制代码

$catperson/package.json
{
"name":"function",
"version":"1.0.0",
"description":"",
"main":"handler.js",
"scripts": {
"test":"echo \"Error:notestspecified\" && 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=$(awslambdaget-function--function-nameserverless-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.jsperson/handler.js

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

复制代码

$ cat person/handler.js
'use strict';
constfs =require('fs');
constsecretAK ="/var/openfaas/secrets/my-aws-access-key";
constsecretSK ="/var/openfaas/secrets/my-aws-secret-key";
constaccessKey = fs.readFileSync(secretAK,"utf-8");
constsecretKey = fs.readFileSync(secretSK,"utf-8");
constAWS =require('aws-sdk');
AWS.config.update({
credentials:newAWS.Credentials ({
region: process.env.AWS_REGION,
accessKeyId: accessKey,
secretAccessKey: secretKey
})
})
constdynamoDb =newAWS.DynamoDB.DocumentClient();
module.exports =(context, callback) =>{
constparams = {
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;
}
constresponse = result.Item;
callback(null, response);
});
};

部署 OpenFaaS 函数:

复制代码

$ export OPENFAAS_URL=https://openfaas.ruan.dev
$ faas-cli up
Deploying:person.
Deployed.202Accepted.
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/