到目前为止,你已经知道怎么基于 FaaS 开发 Serverless 应用了。不过你应该可以发现,04讲中的应用很简单(只有一个函数)。而实际情况中,绝大部分应用都是由多个函数组成的,应用部署时需要将所有函数一同部署,并且函数运行依赖 FaaS 环境,这就导致函数代码不能直接在本地运行。这些限制就给我们的应用开发、调试和部署流程带来了挑战,比如:
函数太多如何管理?
本地开发时如何进行调试?
代码开发完成后如何部署?
……
要解决这些问题,你就需要一套完整的开发、调试和部署 Serverless 的解决方案,这个解决方案最终可以抽象为一个开发框架。那么什么是 Serverless 开发框架呢?我认为Serverless 的开发框架不是传统的 Express.js、Spring Boot 等代码框架,而是集成 Serverless 思想、贯穿 Serverless 应用从开发到上线全流程的工具。 基于 Serverless 开发框架,你很容易开发一个 Serverless 架构的应用,企业也能轻松把现有业务演化为 Serverless 架构。对 Serverless 来说,除了底层的 FaaS、BaaS 等基础设施, 上层的开发框架也是一个非常重要的部分,这直接决定了开发者使用 Serverless 的成本。
这一讲,我以框架开发者的视角,带你思考如何设计并实现一个 Serverless 开发框架,以此让你深入理解开发框架底层设计思路和实现原理,进而明白为什么开发框架能提高 Serverless 应用开发效率。这样一来,当你学完这一讲内容之后,再面对任何开发框架,都能快速上手了,甚至你也能成为一个框架开发者,为广大 Serverless 用户提供更好用的开发工具。
如何设计 Serverless 的开发框架
前面我已经提到,Serverless 开发框架是贯穿 Serverless 应用从开发到上线全流程的工具。基于这个定义,一个开发框架至少要满足应用管理、应用开发、应用调试和应用部署这几个基本功能。 从形态上来看,Serverless 开发框架可以抽象为一个平台或服务,最终以终端工具(命令行工具)或控制台的方式提供给开发者使用。接下来我就从这几个方面进行讲解。
应用管理
Serverless 应用是由函数组成的,所以应用的管理主要就是函数的管理。各个 FaaS 平台其实也考虑到了这一点,比如函数计算的 “服务”功能或 Lambda 的 “应用” 功能。你可以把一个应用的函数都创建在同一个 “服务” 下,一个 “服务” 即代表一个应用。
那么如何去描述 “服务” 和 “函数” 的关系呢?因为二者是静态的,不会在代码运行时改变,所以你可以用 YAML 或 JSON 配置文件来表示(我推荐 YAML,因为它可以编写注释,可读性更好)。在创建函数时,你还要指定函数的入口、编程语言、触发器等信息。所以 YAML 文件的内容可能是这样的:
serverless.yaml# 应用名称service:myservice# 函数列表functions:# 函数1hello:handler:hello.main# 函数入口runtime:nodejs12events:# 函数触发器,一个函数可能有多个触发器-http-timer# 函数2goodbye:handler:goodbye.mainruntime:nodejs12events:-http
我来简单讲一下其中的关键属性。
service:应用名称 。
functions:它的属性就是组成应用的函数,一个应用可以有多个函数,并且函数名不能重复,例如 hello 和 goodbye 就是 myservice 应用的两个函数。
handler :函数的入口。
runtime :函数运行环境(在 Serverless 中函数的运行环境是预先定义好的)。
events:一个函数可能有多个触发器,所以使用 events 数组来表示触发器,例如 hello 函数有 http 和 timer两个触发器。
这里你要注意, 当用户不了解你的 YAML 结构时,让用户去编写这个配置文件就很困难了,所以你要为用户提供一个初始化的功能。假设你的终端工具是一个可执行脚本,命令为 serverless,那么你就可以提供 serverless init 命令自动为用户创建一个管理 Serverless 应用的 YAML 配置文件。
应用开发
有了应用配置文件之后,开发者就可以开始开发代码了。为了进一步简化用户操作,你甚至可以提供一些代码模板,然后提供 init 命令让开发者基于模板一键生成一个 Serverless 应用。例如:
$ serverless init —template hello-world```
|— hello.js |— serverless.yaml
# serverless.yamlservice:myservicefunctions:hello:handler:hello.mainevents:-http
这样开发者就能直接在本地用自己喜欢的编辑器编写代码了。
此外你也可以提供一个 Web IDE,在 Web IDE 中为开发者初始化运行环境,这样开发者不用在本地安装编辑器就能开发,也不用关心运行环境的差异。目前各个 FaaS 平台也有提供了自己的 Web IDE,比如 AWS 的 Cloud9、函数计算 Web IDE。
**这里我多说几句,丰富一下你的知识面。** Cloud9 原本是一家创业公司,提供云上应用开发、部署能力,其 Web IDE 功能很强大,不过它在 2016 年 7 月就被 AWS 收购了。另一个比较有趣的事情是,腾讯在 2018 年投资了 Coding,Coding 的重点产品就是 Cloud Studio 这款 Web IDE,之后 Cloud Studio 就集成到了腾讯云中,腾讯云函数的 Web IDE 就是 Cloud Studio。2019 年腾讯则收购了 Coding。函数计算 Web IDE 则是基于 VS Code 开发的。微软在 2019 年 5 月发布了 Visual Studio Online,让开发者可以轻松实现一个 Web IDE。**所以你也能感受到 Serverless 时代 Web IDE 的重要性。**
现在你基于 Visual Studio Online 也可以开发一个 Web IDE,当然要做一个完善的 Web IDE 还是很难的,我觉得目前体验最好的 Web IDE 还是 Cloud9,但相比本地开发还是有一定差距,不过我也相信,未来 Web IDE 的体验会越来越好。
<a name="72329f15"></a>
#### 应用调试
接着说回来,应用开发结束后,就要进入调试阶段了,**毫不夸张地说,在 Serverless 应用开发中,应用调试十分麻烦。**
以《03 | 基础入门:开发你的第一个 Serverless 应用》中的函数为例,函数代码如下:
(event, context, callback) =>// 从 event 中获取 URL query 参数const// 定义 HTTP ResponseconststatusCode200headers"Content-Type""application/json"bodyJSONmessage`Hello ${name}!`null
exports.handler = {
{ name } = event.queryStringParameters;
response = {
: ,
: {
:
},
: .stringify({ : } ),
};
callback(, response);
};
要想调试这个函数,就需要手动构造 event、context 和 callback 三个对象,然后以它们为参数来调用函数,测试函数运行情况。**麻烦点就在于:** 这些参数运行依赖于 FaaS 环境,不同运行环境、不同触发器函数的入参都有差异,手动模拟这些对象很麻烦,且容易出错。<br />
**那如何让开发者方便调试呢?** 有两种方式。
-
远程调试:将代码部署到 FaaS 平台,然后直接调用 FaaS 平台的接口执行函数,再得到函数运行日志及返回结果。
-
本地调试:由开发框架模拟函数运行时环境,构造函数参数来执行函数。
**我认为对一个 Serverless 开发框架来说,这两种调试方式都需要,** 也就是需要实现 serverless invoke 和 serverless local invoke 两个命令。虽然远程调试最接近代码真实运行情况,但需要进行网络调用,大部分情况还是本地调试效率更高。
<a name="079fb964"></a>
#### 应用部署
当代码开发完成后,就要部署应用了,过程是:根据 YAML 的配置,解析出应用的服务名称和函数列表,然后调用 FaaS 平台的接口来创建或更新服务和函数。
需要注意的是,在创建函数时,FaaS 平台中的函数代码通常以压缩包的方式存储在文件存储服务中,所以部署函数前需要先在本地把代码压缩成 .zip 文件。**而在部署应用时,大部分同学会有这样一个担忧:** 代码调试可以把函数部署到 FaaS 中进行调试,可能每次代码改动都会影响到线上服务,如果有版本控制就可以避免了。FaaS 平台当然考虑了这个问题,提供了函数版本功能(默认是 LATEST 版本)。通过版本控制,你在开发时可以使用 LATEST 版本进行开发,测试稳定了再发布稳定的新版本,用稳定版本提供线上服务。
所以在远程调试时,就不能更新函数版本,一直用 LATEST,而当应用部署时,就需要更新函数版本。
![](https://s0.lgstatic.com/i/image/M00/8B/FD/CgqCHl_jCFuACP1uAAGWw6OtS60066.png#alt=Drawing%201.png)
<a name="3b177521"></a>
#### 账号设置与多平台支持
**讲到这儿,我们已经完成了应用部署功能设计,** 但还有个问题没有解决,就是应用要部署到哪个云账号上?这时就需要账号功能了。
在账号方面,对于接口编程访问,云厂商都是通过 AccessKeyId 和 AccessKeySecret 来授权的。你可以提供一个命令,让用户输入自己的云账号 AccessKeyId 和 AccessKeySecret,然后把账号信息保存到用户自己的磁盘上。这样应用部署的时候,你就可以从磁盘上读取账号信息,以用户的账号去调用 FaaS 平台的接口。这样就可以把应用部署到用户云账号下了,并且账号信息是存储在用户自己的磁盘上,也没有安全问题。
除此之外,我认为一个优秀的 Serverless 开发框架,还应该具备多种不同 Serverless 平台或云厂商的支持,因为你不能限制用户只使用某一种云服务。Serverless 开发框架最好还要抹平不同 Serverless 平台的差异,让应用能够在不同 Serverless 平台中进行平滑迁移,甚至让开发者使用一个开发框架、一套开发流程就能实现多云部署。**而这,正是一个 Serverless 开发框架的核心竞争力之一。**
![](https://s0.lgstatic.com/i/image/M00/8C/B3/CgqCHl_y8F-AKiNdAAGE8ajjCCA752.png#alt=Lark20210104-183828.png)
当然,要支持多种 Serverless 平台,就需要在框架层去适配各个不同的平台。但现在各个 Serverless 平台都没有遵循统一的标准,**这也是实现一个多平台支持的难点所在。** 所以你在实现一个开发框架时,需要先抽象出各个平台公共部分,然后制订一套你的开发标准,再以适配器的方式去适配不同的 Serverless 平台。
![](https://s0.lgstatic.com/i/image/M00/8B/FD/CgqCHl_jCG6AHdZPAAOd4PqLzmk757.png#alt=Drawing%202.png)
Serverless 框架架构
至此,你就完成了一个完整的 Serverless 开发框架的设计。在 Serverless 框架架构中,你通过一个 serverless 命令对开发者提供所有服务,贯穿 Serverless 应用开发到上线的全流程。
**这其实也是现在主流 Serverless 开发框架的实现原理**。接下来,就让我带你看一下主流 Serverless 开发框架的实现细节、功能特性和使用方法,从而让你对 Serverless 开发框架的原理和实现有更多了解。
<a name="64102ca0"></a>
### 主流 Serverless 开发框架的实现
整体而言,国外 Serverless 生态要比国内领先一到两年,因此国外有很多优秀的开发框架,但国内这方面还不够成熟,所以我就讲解一下国内外最具代表性的两个开发框架,[Serverless Framework](https://github.com/serverless/serverless)和[函数计算 Fun](https://github.com/alibaba/funcraft)。
我会按照以下顺序讲解这部分内容:
![](https://s0.lgstatic.com/i/image/M00/8B/F2/Ciqc1F_jCHeASYj3AAGl95hTVl4092.png#alt=Drawing%203.png)
<a name="227fe747"></a>
### Serverless Framework
说到 Serverless,就不得不说 Serverless Framework ,我认为这是目前最完善的 Serverless 开发框架之一。它不仅实现了前面提到的应用开发、调试、部署等基础功能,还实现了多个 Serverless 平台的支持(因为它不仅支持 AWS、Azure、Google、阿里云、腾讯云等公有云 Serverless 平台,还支持 CloudFlare、Kubeless、OpenWhisk 等私有 Serverless 平台)。
-
**安装**
Serverless Framework 是使用 Node.js 开发的,所以你可以使用 npm 直接来安装它,安装后你就可以直接使用 serverless 这个命令了(好巧,跟我们前面设计的开发框架的命令一样)。
$ npm install -g serverless$ serverless --version
Framework Core: 2.15.0 Plugin: 4.2.0 SDK: 2.3.2 Components: 3.4.3
-
**账号设置**
你在使用 Serverless 时,第一件事就进行账号设置,假设你使用 AWS 则命令如下:
$ serverless config credentials --provider aws --key key --secret secret
-
--provider 具体的 Serverless 平台
-
--key AWS 账号的 aws_access_key_id
-
--secret AWS 账号的 aws_secret_access_key
Serverless Framework 会把 AWS 的 aws_access_key_id 和 aws_secret_access_key 保存在 ~/.aws/credentials文件中。
在 Serverless Framework 中,通过 provider 来实现不同平台的支持。在不同 provider 中, Serverless Framework 的命令大部分相同,但不同 provider 的功能完整性还是有一定差异,比如它虽然支持阿里云 provider,但目前只支持 HTTP 触发器和 OSS 触发器。
-
**应用配置**
接下来你就可以使用 serverless 初始化一个应用了:
$ serverless create --template aws-nodejs$ ls
Serverless: Generating boilerplate…
| .——-.——.—.—.——-.——| .——-.——-.——-. | |__| -| _| | | -| | | -| —| —| |__ |__|| _/|_|| ||_|_|_| | | | The Serverless Application Framework | | serverless.com, v2.15.0 ———-‘ Serverless: Successfully generated boilerplate for template: “aws-nodejs” Serverless: NOTE: Please update the “service” property in serverless.yml with your service name
handler.js serverless.yml
Serverless Framework 也是通过 YAML 配置文件来定义应用和函数的,其 YAML 格式如下:
service:myserviceframeworkVersion:'2'provider:name:awsruntime:nodejs12.xfunctions:hello:handler:handler.helloevents:-http:path:users/createmethod:get-websocket:$connect-s3:${env:BUCKET}resources:Resources:NewResource:Type:AWS::S3::BucketProperties:BucketName:my-new-bucketOutputs:NewOutput:Description:"Description for the output"Value:"Some output value"
**在这份 YAML 中,有一个前面没有提到过的属性 resources**,resources 的作用就是帮你创建或更新资源(如果资源不存在则创建,如果资源已存在则更新)。比如示例中,就是为你创建一个 AWS 的 S3 存储桶。之所以会有 resources 属性,是因为通常一个应用都需要依赖其他云服务,比如存储、数据库等,之前你可能都是手动在云厂商的控制台去购买这些资源,一旦应用需要扩容的时候,手动创建资源就非常复杂且容易出错。Serverless Framework 则给你一种新的方式去管理云上资源,即通过代码来描述基础基础设施。
-
**应用调试**
在 Serverless Framework 中,调试函数也很简单,直接通过 invoke 命令即可:
# 远程调试$ serverless invoke --function hello# 本地调试$ serverless invoke local --function hello
{ “statusCode”: 200, “body”: “{\n \”message\”: \”Go Serverless v1.0! Your function executed successfully!\”,\n \”input\”: \”\”\n}” }
{ “statusCode”: 200, “body”: “{\n \”message\”: \”Go Serverless v1.0! Your function executed successfully!\”,\n \”input\”: \”\”\n}” }
当然了,你也可以通过 serverless invoke local 来实现本地调试。
-
**应用部署**
应用开发完成后,你就可以使用 deploy 命令来部署应用,如下所示:
$ serverless deploy
$ tree
Serverless: Packaging service… Serverless: Excluding development dependencies… Serverless: Service files not changed. Skipping deployment… Service Information service: myservice stage: dev region: us-east-1 stack: myservice-dev resources: 6 functions: hello: myservice-dev-hello layers: None
|— .serverless |— myservice.zip |— handler.js |— serverless.yaml
在将函数部署到 Lambda 之前,serverless 会先在本地将代码打包,最终代码是一个压缩包,路径为 .serverles/[serviceName].zip 。你也可以通过 serverless deploy function -f functionName 来单独部署某个函数。
除此之外,Serverless Framework 还提供了日志查询功能,当然,Serverless Framework 的功能不止于此(但因为不是这一讲的重点,所以我想留给你去探索),接下来我们再来了解一下,函数计算 Fun 的基本功能和使用,**看看它和 Serverless Framework 有什么差异。**
<a name="44563187"></a>
### 函数计算 Fun
Fun 是阿里云函数计算团队开发维护的一个 Serverles 开发框架,因此只支持函数计算。因为它是用 Node.js 编写的,所以也可以使用 npm 安装:
@alicloud3.6.20
$ npm install /fun -g $ fun —version
**安装后第一步同样是设置账号:**
'https://******.cn-hangzhou.fc.aliyuncs.com''2016-08-15'''false103truefalse
$ fun config ? Aliyun Account ID * ? Aliyun Access Key ID ** ? Aliyun Access Key Secret ** ? Default region name cn-hangzhou $ cat ~/.fcli/config.yaml endpoint: api_version: access_key_id: ** access_key_secret: ** security_token: debug: timeout: retries: sls_endpoint: cn-hangzhou.log.aliyuncs.com report: enable_custom_endpoint:
配置完成后,fun 会将账号信息存储在.fcli/config.yaml 文件中。除此之外,你也可以在项目根目录中使用 .env文件来配置账号信息,其优先级更高。
项目根目录 .env
ACCOUNT_ID=* REGION=cn-hangzhou ACCESS_KEY_ID=** ACCESS_KEY_SECRET=** TIMEOUT=10 RETRIES=3 FC_ENDPOINT=** ENABLE_CUSTOM_ENDPOINT=false
**在应用配置上,** 你可以通过 fun init `<templateName>`命令来初始化一个项目:
$ fun init event-nodejs12 $ ls -l index.js template.yaml
其中 template.yaml 功能与 serverless.yaml 一致,但内容定义有较大差异。template.yaml 的定义如下:
'2015-09-01''Aliyun::Serverless-2018-04-03''Aliyun::Serverless::Service''helloworld''Aliyun::Serverless::Function''./'
ROSTemplateFormatVersion: Transform: Resources: myservice: Type: Properties: Description: hello: Type: Properties: Handler: index.handler Runtime: nodejs12 CodeUri:
其中 Resources 是表示资源的对象,对象的属性就是具体资源,对象属性中的Type 用来表示资源的类型。比如 Aliyun::Serverless::Service就表示是“服务”,Aliyun::Serverless::Function 表示函数。因此 template.yaml 中你可以定义多个服务,一个服务中也可以定义多个函数。
**接下来,你可以通过 fun invoke functionName 命令对函数进行远程调试:**
9494935e4for202012061911.3539494935e49494935e413.9910012817.30
$ fun invoke hello using template: template.yml ========= FC invoke Logs begin ========= FC Invoke Start RequestId: c6abf471-a-bbc-edf-c312a974 load code handler:index.handler —T14::Z c6abf471-a-bbc-edf-c312a974 [verbose] hello world FC Invoke End RequestId: c6abf471-a-bbc-edf-c312a974 Duration: ms, Billed Duration: ms, Memory Size: MB, Max Memory Used: MB ========= FC invoke Logs end ========= FC Invoke Result: hello world
当然,你也可以通过fun local invoke function 在本地调试。但本地调试前,你必须先安装 Docker,因为 fun 的本地调试原理是通过 Docker 在本地启动一个代码运行环境来执行代码,而不是直接模拟函数参数。这样的好处是,更接近 FaaS 平台的运行环境。
**应用开发完毕后,你可以通过 fun deploy 进行应用部署。**
469810forforfor1final318
$ fun fun deploy using template: template.yml using region: cn-hangzhou using accountId: * using accessKeyId: *QliF using timeout: Waiting service myservice to be deployed… Waiting function hello to be deployed… Waiting packaging function hello code… The function hello has been packaged. A total of file were compressed and the size was B function hello deploy success service myservice deploy success ```
总结
在我看来,开发框架的意义就在于帮助开发者提升 Serverless 应用的开发效率, 因此一个 Serverless 开发框架最主要的就是要实现应用管理、应用调试、应用部署等功能。而在Serverless Framework 和 Fun 这两个开发框架中:
Serverless Framework 特点是功能完善、支持平台丰富。但由于 Serverless 平台尚且没有统一标准,所以支持多平台难度极大,也导致其对国内产品支持不够友好;
Fun 的特点是只为函数计算服务,因此有很多针对函数计算的特定功能。
除了 Serverless Framework 外,国外还有 Vercel、Apex Up 等非常优秀的 Serverless 开发框架,Vercel 的前生就是 now.sh ,Apex Up 则是 Node.js 领域的 TJ 大神 2016 年就开始开发的产品。而国内除了 Fun,现在也有 Midway FaaS、Malagu 等新兴产品出现,这些产品虽然没有国外产品功能完善,但相信随着时间的推移,国内的 Serverless 开发体验也一定会越来越好。也是由于国内 Serverless 生态还不够完善,所以我们才有更多的机会,也希望你也能一起参与进来,为国内 Serverless 开发生态做出贡献。
另一方面,现在绝大部分 Serverless 开发框架都是基于 Node.js 实现,并且大部分都是对 Node.js 编写语言支持最友好,甚至只支持 Node.js,这也能看出 Node.js 在 Serverless 领域的重要性。
关于这节课,我想强调这几个点:
与 FaaS、BaaS 等基础技术一样,Serverless 开发框架也是 Serverless 领域中的非常重要的一部分;
一个优秀的 Serverless 开发框架,可以让开发者很容易开发一个 Serverless 架构的应用,也能让企业轻易将现有业务演化到 Serverless 架构;
Serverless 开发框架需要具备的基本能力是应用管理、应用调试和应用部署。
最后,这节课我留给你的作业时,亲自体验一下本文中提到的各个开发框架,进而对 Serverless 的开发体系有更深的了解。