Free Arch:将 Koa 服务部署到 Vercel

哈德韦

共 3376字,需浏览 7分钟

 ·

2022-07-17 06:34

多年前写的 Koa Js 服务,本来跑在服务器上,要一点花销。后来决定薅各大云厂商的羊毛,就将它容器化了,跑在免费的 Okteto 提供的 K8S 环境里。但是最近 Okteto 提供的 url 访问不了了,虽然发了邮件请 Okteto 帮忙协助,但是本着狡兔三窟的原则,决定再部署一个实例到其他的服务上。

于是将目光瞄准到 Vercel,Vercel 提供免费的 Serverless Function,虽然类似 AWS Lambda,但又不太一样,所以记录一下。如果只是将 koa 服务部署到 AWS lambda,那么只需要使用一个 serverless-http 的框架转化一下就好。关于薅 AWS Lambda 羊毛的文章已经写过多篇,参见:


一顿操作猛如虎,部署一个万能 BFF


使用 Mocha 和 Chai 测试驱动开发 AWS Lambda API


Koa Js

Koa Js 是由 Express Js 的原班人马设计的 Web 框架,更小巧但是不再捆绑任何中间件,这一点也是我在将它部署到 Vercel 时碰到坑的原因。尽管它比 Express 新,但 Express.js 仍然更加流行,也许这也是 Vercel 内置对 Express.js 的支持的原因吧。

Express Js 目前的星标数在 57.6 K,而 koa 的星标数大约是它的一半多一点:

看了一下 koa 的贡献者列表,我居然也名列其中呢:

Vercel

Vercel 是一个用来部署前端应用的云平台,但也可以用来构建轻量级的事件驱动 API,并部署到它们的全球边缘网络。

Vercel 的 Serverless Function 有些特别

传统 API 托管在运行着的服务器上。当应用需要扩展时,希望更低成本、更灵活、更安全、资源快速分配并启动等等,使用传统服务器很难做到。但是用 Serverless 就比较容易,因为仅仅是一些后端代码片段在无状态环境中运行着,它们由事件(比如 http 请求)触发并只在一次调用中存活。这可以全部自动化并且在毫秒级扩展。更好的是,不用再维护服务器了。开发者只需要关注业务逻辑——返回值的函数。

如果我们部署一个服务器程序到 Serverless Function,我们就为每个请求执行了一个完整的服务器实现,这是一个反模式,因为 Serverless 函数仍然是函数,只应服务于一个目的。将服务器程序部署在 Serverless Function 上,相当于强行将庞大的逻辑混杂在一个函数里。虽然这是一个反模式,但是羊毛在那里,不得不薅。对于有钱的企业级服务器程序,还是建议绕道。

Vercel 的 Serverless Function 和 AWS Lambda 很像,比如都是一个对外暴露 handler 函数的模块,但是特别之处在于其 handler 签名不一样。AWS Lambda 的 handler:

export default const handler = (event, context) => 'Hello World'

但是 Vercel 的 handler 接收一个 req 和 res 参数(https://github.com/Jeff-Tian/v/blob/master/api/test.js):

export default function handler(req, res) {
    const { name = 'World' } = req.query;
    return res.send(`Hello ${name}!`);
}

https://v.pa-pa.me/api/test?name=Jeff

这签名看起来很像是 Express.js 中间件,有意思的是,它真的支持完整的 Express.js 应用,只需要将入口文件放在 /api/index.js 里即可。

Vercel Serverless Function 对 Koa 的支持

经过实验,Vercel Serverless Function 是不支持 Koa.js 应用的。因为它的签名和 (ctx, next) => ctx.body = 'Hello' 这种 Koa.js 风格就不相容。

koa-to-express

想将 Koa 应用搬到 Vercel Serverless Function,但是不希望改已有的 Koa 代码,最自然的方式莫过于增加一个 adapter,将 koa 风格的中间件函数适配成 express.js 的中间件函数。于是找到了 koa-to-express 这个库。

增加 /api/index.js 文件

该 index.js 引用 Koa 应用的入口文件,并将它的中间件做个转换,伪装成一个 Express.js 应用。这样,原来的 Koa 应用在服务器环境中仍然照常运行,同时又可以在 Vercel Serverless Function 环境里运行。如果再使用 serverless-http,再在 AWS Lambda 里部署一个适配,那么就是名副其实的“狡兔三窟”了。

修复 koa-to-express 的一个 BUG

其实没有那么顺利,由于原 Koa 应用使用了 koa-router 这个中间件,触发了 koa-to-express 的一个 BUG。于是只能 Fork 了 koa-to-express ,在自己的版本中做了修复。虽然给原作者提交了 PR,但在他合并并发新版之前,我得临时使用自己的版本,于是要对 package.json 做个修改,在安装 koa-to-express 时,从自己的仓库里下载代码:

https://github.com/Jeff-Tian/v/commit/cd11bf4f7100fd61ef9dda98397eb255b764a396#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519

package.json:

-    "koa-to-express": "^3.1.4",
+    "koa-to-express": "git+https://github.com/jeff-tian/koa-to-express.git",


给它们的 PR 链接:https://github.com/xingxingted/koa-to-express/pull/10/files

完成 /api/index.js

最终的 /api/index.js 文件如下:

https://github.com/Jeff-Tian/v/blob/cd11bf4f7100fd61ef9dda98397eb255b764a396/api/index.js

const k2e = require('koa-to-express');

process.env.ROUTER_PREFIX = '/api';

const app = require('../app');

const expressApp = require('express')();

app.middleware.map(m => {
    expressApp.use(k2e(m));
})

module.exports = expressApp;

重定向

Vercel Serverless Function 默认将 /api/xxx 路由到 /api/xxx.js,所以 /api/xy/z,就不会被 /api/index 处理,所以需要增加一个 vercel.json 文件,将所有 /api/ 下的请求,重定向到 index.js:

{
  "rewrites": [{ "source": "/api/(.*)", "destination": "/api" }]
}

完成

效果和在服务器上运行 koa 一模一样:


浏览 23
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报