本篇文章并不是教你怎么用阿里云、谷歌云等等云框架的FaaS服务,而是教你如何从0开始打造一个自己的FaaS服务。
受限于人力及成本原因,现在的FaaS服务基本上可以说是“大厂玩物”,而网上鲜有相关代码的实现,因此我才打算写一篇文章,和大家分享如何从0开始打造一个Nodejs的FaaS服务。
本篇文章中的项目tiny-node-faas已经上线并开源至GitHub,欢迎各位使用。
源码地址:https://github.com/shadowings-zy/tiny-node-faas
一、背景及相关概念介绍
1-1、Serverless
Serverless,直译就是无服务器,是指构建和运行不需要服务器管理的应用程序的概念。Serverless能够按需提供后端服务,用户可以直接编写和部署代码,而不必担心底层基础架构。
简单来说,如何判断一个服务是不是Serverless的呢?如果你在开发这个服务的时候,完全不知道“服务器多少”、“容器环境”等等和基础架构相关的东西的时候,那么这个服务就是Serverless的。
1-2、FaaS
FaaS是Function as a Service的缩写,可以简单理解为功能服务化。FaaS提供了一种服务碎片化的软件架构范式。FaaS可以让研发只需要关注业务代码逻辑,不再关注技术架构。
举个例子吧,如果我们要使用koa框架写一个hello world服务,我们得这样写:
const Koa = require('koa');
const app = new Koa();const handler = async (ctx) => {ctx.body = 'hello world'
}app.use(handler);
app.listen(8080,() => {console.log('8080端口已启动')
});
而如果使用FaaS服务,我们只需要关注下面这部分,其余的工作,比如服务创建、冷启动、负载均衡等等都由FaaS的提供方做了。
const handler = async (ctx) => {ctx.body = 'hello world'
}
1-3、为什么要做Nodejs的FaaS服务
之所以做Nodejs的FaaS服务,是因为有越来越多的开发者,以及越来越多的后台服务,选择使用Nodejs。
这其中当然有Nodejs本身“单线程异步非阻塞”的特性带给我们的简单直接的编程体验,也有一部分原因在于“前端全栈化”趋势会促使一部分前端工程师写后端代码逻辑。
而更值得关注的是,由于技术方向不同,不少前端开发工程师对运维、服务器的相关知识比较薄弱,因此为他们提供一个Nodejs的FaaS服务,能让其从运维中脱离出来,使开发者更聚焦于业务代码逻辑,是非常有前景的事情。
二、代码实现
2-1、vm模块
“如何实现Nodejs的FaaS服务”,这个问题可以换成下面这个问题:
如何才能在Nodejs中新建一个sandbox并在这个sandbox中执行指定的代码,并拿到返回值呢?
Nodejs实际上已经提供了这样一个模块——vm模块,下面是文档:
https://nodejs.org/api/vm.html
使用起来也很简单:
const vm = require('vm');const code = 'console.log("hello world!")'const sandbox = { console };
vm.createContext(sandbox);const data = vm.runInNewContext(code, sandbox);
通过vm模块,我们就能在一个新的sandbox中执行JavaScript代码并拿到返回值了
2-2、整体架构
如图所示,tiny-node-faas由四部分组成:
1、管理平台前端
2、函数管理功能
3、函数存储功能
4、函数执行功能
2、3、4三者则是FaaS服务中最重要的三个组成部分。
2-3、核心代码
const vm = require('vm')const runFunction = async (code) => {let timer = nullconst result = await new Promise((resolve, reject) => {const sandbox = {require,console}try {timer = setTimeout(() => {reject(new Error('Execute function time out'))}, 10000)vm.createContext(sandbox)const data = vm.runInNewContext(code, sandbox)resolve(data)} catch (error) {reject(error)}}).catch((err) => {return err instanceof Error ? err : new Error(err.stack)})if (timer) {clearTimeout(timer)timer = null}return result
}
上面代码中的runFunction
方法就是最核心的执行FaaS函数的方法,我们只需要传入code,即需要运行的代码,这个方法就会新建一个sandbox并运行代码,然后会将运行结果返回出来。
2-4、安全相关
当然,直接使用vm做serverless是有一定的安全隐患的。
比如使用require('os')
会直接拿到物理机的操作系统权限,在这方面其实有很多有趣的涉及到诸如“沙箱逃逸”的话题。
GitHub上也有“更加安全的vm模块的实现”,如vm2
。
但受限于篇幅,以及本人的确不是什么攻防大神,这里就不再展开了。