本篇文章并不是教你怎么用阿里云、谷歌云等等云框架的 FaaS 服务,而是教你如何从 0 开始打造一个自己的 FaaS 服务。
受限于人力及成本原因,现在的 FaaS 服务基本上可以说是“大厂玩物”,而网上鲜有相关代码的实现,因此我才打算写一篇文章,和大家分享如何从 0 开始打造一个 Nodejs 的 FaaS 服务。
本篇文章中的项目 tiny-node-faas 已经上线并开源至 GitHub,欢迎各位使用。
一、背景及相关概念介绍
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 模块,下面是文档: 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 = null;
const 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
。
但受限于篇幅,以及本人的确不是什么攻防大神,这里就不再展开了。