Serverless

Fastify 无法直接运行在无服务器平台上,需要做一点修改。本文为如何在知名的无服务器平台上运行 Fastify 应用提供指导。

我应该在无服务器平台上使用 Fastify 吗?

取决于你自己!FaaS 通常使用精简专注的函数,但你依然可以运行完整的 web 应用。但请牢记,应用越繁重,初始化越漫长。在无服务器环境中运行 Fastify,最好的方式便是使用例如 Google Cloud Run、AWS Fargate 以及 Azure Container Instances 之类的平台,它们能同时处理多个请求,因此能充分利用 Fastify 的特性。

开发便利是通过 Fastify 构建无服务器应用的优势之一。在本地环境,Fastify 应用无需任何额外工具即可运作,而相同的代码加上一些额外内容便能在无服务器平台上运行。

以下是使用 Fastify 在 AWS Lambda 和 Amazon API Gateway 架构上构建无服务器 web 应用/服务的示例。

注:使用 仅是一种可行方案。

app.js

你可以简单地把初始化代码包裹于可选的 选项里。

当执行 lambda 函数时,我们不需要监听特定的端口,因此,在这个例子里我们只要导出 函数即可。 在 lambda.js 里,我们会用到它。

lambda.js

  1. const awsLambdaFastify = require('aws-lambda-fastify')
  2. const init = require('./app');
  3. const proxy = awsLambdaFastify(init())
  4. // 或
  5. // const proxy = awsLambdaFastify(init(), { binaryMimeTypes: ['application/octet-stream'] })
  6. exports.handler = proxy;
  7. // 或
  8. // exports.handler = (event, context, callback) => proxy(event, context, callback);
  9. // 或
  10. // exports.handler = (event, context) => proxy(event, context);
  11. // 或
  12. // exports.handler = async (event, context) => proxy(event, context);

我们只需要引入 aws-lambda-fastify (请确保安装了该依赖 npm i --save aws-lambda-fastify) 以及我们写的 ,并使用 app 作为唯一参数调用导出的 awsLambdaFastify 函数。 以上步骤返回的 proxy 函数拥有正确的签名,可作为 lambda 的处理函数。 如此,所有的请求事件 (API Gateway 的请求) 都会被代理到 aws-lambda-fastifyproxy 函数。

示例

你可以在这里找到使用 的可部署的例子。

  • 你没法操作 stream,因为 API Gateway 还不支持它。
  • API Gateway 的超时时间为 29 秒,请务必在此时限内回复。

与 AWS Lambda 和 Google Cloud Functions 不同,Google Cloud Run 是一个无服务器容器环境。它的首要目的是提供一个能运行任意容器的底层抽象 (infrastucture-abstracted) 的环境。因此,你能将 Fastify 部署在 Google Cloud Run 上,而且相比正常的写法,只需要改动极少的代码。

参照以下步骤部署 Google Cloud Run。如果你对 gcloud 还不熟悉,请看其

调整 Fastify 服务器

为了让 Fastify 能正确地在容器里监听请求,请确保设置了正确的端口与地址:

  1. function build() {
  2. const fastify = Fastify({ trustProxy: true })
  3. return fastify
  4. }
  5. async function start() {
  6. // Google Cloud Run 会设置这一环境变量,
  7. const IS_GOOGLE_CLOUD_RUN = process.env.K_SERVICE !== undefined
  8. // 监听 Cloud Run 提供的端口
  9. const port = process.env.PORT || 3000
  10. // 监听 Cloud Run 中所有的 IPV4 地址
  11. const address = IS_GOOGLE_CLOUD_RUN ? "0.0.0.0" : undefined
  12. try {
  13. const server = build()
  14. const address = await server.listen(port, address)
  15. console.log(`Listening on ${address}`)
  16. } catch (err) {
  17. process.exit(1)
  18. }
  19. }
  20. module.exports = build
  21. if (require.main === module) {
  22. start()
  23. }

添加 Dockerfile

你可以添加任意合法的 Dockerfile,用于打包运行 Node 程序。在 gcloud 官方文档中,你能找到一份基本的 Dockerfile

  1. # 使用官方 Node.js 10 镜像。
  2. # https://hub.docker.com/_/node
  3. FROM node:10
  4. # 创建并切换到应用目录。
  5. WORKDIR /usr/src/app
  6. # 拷贝应用依赖清单至容器镜像。
  7. # 使用通配符来确保 package.json 和 package-lock.json 均被复制。
  8. # 独立地拷贝这些文件,能防止代码改变时重复执行 npm install。
  9. COPY package*.json ./
  10. # 安装生产环境依赖。
  11. RUN npm install --only=production
  12. # 复制本地代码到容器镜像。
  13. COPY . .
  14. # 启动容器时运行服务。
  15. CMD [ "npm", "start" ]

添加 .dockerignore

添加一份如下的 .dockerignore,可以将仅用于构建的文件排除在容器之外 (能减小容器大小,加快构建速度):

  1. gcloud builds submit --tag gcr.io/PROJECT-ID/APP-NAME

部署镜像

镜像构建之后,使用如下命令部署它:

  1. gcloud beta run deploy --image gcr.io/PROJECT-ID/APP-NAME --platform managed

如此,便能从 Google 云平台提供的链接访问你的应用了。

首先,完成与 AWS Lambda 有关的准备工作。

新建 functions 文件夹,在其中创建 server.js (应用的入口文件)。

functions/server.js

  1. export { handler } from '../lambda.js'; // 记得将路径修改为你的应用中对应的 `lambda.js` 的路径

netlify.toml

别忘记添加这个文件,否则会有不少问题

  1. const dotenv = require('dotenv-safe');
  2. const webpack = require('webpack');
  3. const env = process.env.NODE_ENV || 'production';
  4. if (dev) {
  5. dotenv.config({ allowEmptyValues: true });
  6. }
  7. module.exports = {
  8. mode: env,
  9. devtool: dev ? 'eval-source-map' : 'none',
  10. externals: [nodeExternals()],
  11. devServer: {
  12. proxy: {
  13. '/.netlify': {
  14. target: 'http://localhost:9000',
  15. pathRewrite: { '^/.netlify/functions': '' }
  16. }
  17. }
  18. },
  19. module: {
  20. rules: []
  21. },
  22. plugins: [
  23. new webpack.DefinePlugin({
  24. 'process.env.APP_ROOT_PATH': JSON.stringify('/'),
  25. 'process.env.NETLIFY_ENV': true,
  26. 'process.env.CONTEXT': env
  27. })
  28. ]
  29. };

Scripts

package.jsonscripts 里加上这一命令

  1. "scripts": {
  2. ...
  3. "build:functions": "netlify-lambda build functions --config ./webpack.config.netlify.js"
  4. ...
  5. }

这样就完成了。

  1. {
  2. "rewrites": [
  3. {
  4. "source": "/(.*)",
  5. "destination": "/api/serverless.js"
  6. }
  7. ]
  8. }

之后,写一个 api/serverless.js 文件: