Serverless

使用现有的 Fastify 应用运行无服务器 (serverless) 应用与 REST API。

目录

读者须知:

Fastify 并不是为无服务器环境准备的。 Fastify 框架的设计初衷是轻松地实现一个传统的 HTTP/S 服务器。 无服务器环境则与此不同。 因此,我们不保证在无服务器环境下,Fastify 也能如预期般运转。 尽管如此,参照本文中的示例,你仍然可以在无服务器环境中运行 Fastify。 再次提醒,无服务器环境不是 Fastify 的目标所在,我们不会在这样的集成情景下进行测试。

AWS Lambda

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

注:使用 aws-lambda-fastify 仅是一种可行方案。

app.js

  1. const fastify = require('fastify');
  2. function init() {
  3. const app = fastify();
  4. app.get('/', (request, reply) => reply.send({ hello: 'world' }));
  5. return app;
  6. }
  7. if (require.main === module) {
  8. // 直接调用,即执行 "node app"
  9. init().listen(3000, (err) => {
  10. if (err) console.error(err);
  11. console.log('server listening on 3000');
  12. });
  13. } else {
  14. // 作为模块引入 => 用于 aws lambda
  15. module.exports = init;
  16. }

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

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

当像往常一样运行 Fastify 应用, 比如执行 node app.js(可以用 require.main === module 来判断), 你可以监听某个端口,如此便能本地运行应用了。

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.js,并使用 app 作为唯一参数调用导出的 awsLambdaFastify 函数。 以上步骤返回的 proxy 函数拥有正确的签名,可作为 lambda 的处理函数。 如此,所有的请求事件 (API Gateway 的请求) 都会被代理到 aws-lambda-fastifyproxy 函数。

示例

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

注意事项

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

Google Cloud Run

与 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. // 因此,你可以使用它判断程序是否运行在 Cloud Run 之中
  8. const IS_GOOGLE_CLOUD_RUN = process.env.K_SERVICE !== undefined
  9. // 监听 Cloud Run 提供的端口
  10. const port = process.env.PORT || 3000
  11. // 监听 Cloud Run 中所有的 IPV4 地址
  12. const address = IS_GOOGLE_CLOUD_RUN ? "0.0.0.0" : undefined
  13. try {
  14. const server = build()
  15. const address = await server.listen(port, address)
  16. console.log(`Listening on ${address}`)
  17. } catch (err) {
  18. console.error(err)
  19. process.exit(1)
  20. }
  21. }
  22. module.exports = build
  23. if (require.main === module) {
  24. start()
  25. }

添加 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. Dockerfile
  2. README.md
  3. node_modules
  4. npm-debug.log

提交构建

接下来,使用以下命令将你的应用构建成一个 Docker 镜像 (将 PROJECT-IDAPP-NAME 替换为 Google 云平台的项目 id 和 app 名称):

  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 云平台提供的链接访问你的应用了。

Zeit Now

now 针对 Node.js 应用提供了零配置部署方案。要使用 now,只需要如下配置你的 now.json 文件:

  1. {
  2. "version": 2,
  3. "builds": [
  4. {
  5. "src": "api/serverless.js",
  6. "use": "@now/node",
  7. "config": {
  8. "helpers": false
  9. }
  10. }
  11. ],
  12. "routes": [
  13. { "src": "/.*", "dest": "/api/serverless.js"}
  14. ]
  15. }

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

  1. 'use strict'
  2. const build = require('./index')
  3. const app = build()
  4. module.exports = async function (req, res) {
  5. await app.ready()
  6. app.server.emit('request', req, res)
  7. }

以及一个 api/index.js 文件:

  1. 'use strict'
  2. const fastify = require('fastify')
  3. function build () {
  4. const app = fastify({
  5. logger: true
  6. })
  7. app.get('/', async (req, res) => {
  8. const { name = 'World' } = req.query
  9. req.log.info({ name }, 'hello world!')
  10. return `Hello ${name}!`
  11. })
  12. return app
  13. }
  14. module.exports = build

要注意的是,你得在 package.json 中使用 Node 10:

  1. "engines": {
  2. "node": "10.x"
  3. },