Serverless

2020-02-06 15:45 更新

Serverless

使用現(xiàn)有的 Fastify 應(yīng)用運(yùn)行無(wú)服務(wù)器 (serverless) 應(yīng)用與 REST API。

讀者須知:

Fastify 并不是為無(wú)服務(wù)器環(huán)境準(zhǔn)備的。 Fastify 框架的設(shè)計(jì)初衷是輕松地實(shí)現(xiàn)一個(gè)傳統(tǒng)的 HTTP/S 服務(wù)器。 無(wú)服務(wù)器環(huán)境則與此不同。 因此,我們不保證在無(wú)服務(wù)器環(huán)境下,F(xiàn)astify 也能如預(yù)期般運(yùn)轉(zhuǎn)。 盡管如此,參照本文中的示例,你仍然可以在無(wú)服務(wù)器環(huán)境中運(yùn)行 Fastify。 再次提醒,無(wú)服務(wù)器環(huán)境不是 Fastify 的目標(biāo)所在,我們不會(huì)在這樣的集成情景下進(jìn)行測(cè)試。

AWS Lambda

以下是使用 Fastify 在 AWS Lambda 和 Amazon API Gateway 架構(gòu)上構(gòu)建無(wú)服務(wù)器 web 應(yīng)用/服務(wù)的示例。

注:使用 aws-lambda-fastify 僅是一種可行方案。

app.js

const fastify = require('fastify');

function init() {
  const app = fastify();
  app.get('/', (request, reply) => reply.send({ hello: 'world' }));
  return app;
}

if (require.main === module) {
  // 直接調(diào)用,即執(zhí)行 "node app"
  init().listen(3000, (err) => {
    if (err) console.error(err);
    console.log('server listening on 3000');
  });
} else {
  // 作為模塊引入 => 用于 aws lambda
  module.exports = init;
}

你可以簡(jiǎn)單地把初始化代碼包裹于可選的 serverFactory 選項(xiàng)里。

當(dāng)執(zhí)行 lambda 函數(shù)時(shí),我們不需要監(jiān)聽(tīng)特定的端口,因此,在這個(gè)例子里我們只要導(dǎo)出 init 函數(shù)即可。 在 lambda.js 里,我們會(huì)用到它。

當(dāng)像往常一樣運(yùn)行 Fastify 應(yīng)用, 比如執(zhí)行 node app.js 時(shí) (可以用 require.main === module 來(lái)判斷), 你可以監(jiān)聽(tīng)某個(gè)端口,如此便能本地運(yùn)行應(yīng)用了。

lambda.js

const awsLambdaFastify = require('aws-lambda-fastify')
const init = require('./app');

const proxy = awsLambdaFastify(init())
// 或
// const proxy = awsLambdaFastify(init(), { binaryMimeTypes: ['application/octet-stream'] })

exports.handler = proxy;
// 或
// exports.handler = (event, context, callback) => proxy(event, context, callback);
// 或
// exports.handler = (event, context) => proxy(event, context);
// 或
// exports.handler = async (event, context) => proxy(event, context);

我們只需要引入 aws-lambda-fastify (請(qǐng)確保安裝了該依賴 npm i --save aws-lambda-fastify) 以及我們寫的 app.js,并使用 app 作為唯一參數(shù)調(diào)用導(dǎo)出的 awsLambdaFastify 函數(shù)。 以上步驟返回的 proxy 函數(shù)擁有正確的簽名,可作為 lambda 的處理函數(shù)。 如此,所有的請(qǐng)求事件 (API Gateway 的請(qǐng)求) 都會(huì)被代理到 aws-lambda-fastify 的 proxy 函數(shù)。

示例

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

注意事項(xiàng)

  • 你沒(méi)法操作 stream,因?yàn)?API Gateway 還不支持它。
  • API Gateway 的超時(shí)時(shí)間為 29 秒,請(qǐng)務(wù)必在此時(shí)限內(nèi)回復(fù)。

Google Cloud Run

與 AWS Lambda 和 Google Cloud Functions 不同,Google Cloud Run 是一個(gè)無(wú)服務(wù)器容器環(huán)境。它的首要目的是提供一個(gè)能運(yùn)行任意容器的底層抽象 (infrastucture-abstracted) 的環(huán)境。因此,你能將 Fastify 部署在 Google Cloud Run 上,而且相比正常的寫法,只需要改動(dòng)極少的代碼。

參照以下步驟部署 Google Cloud Run。如果你對(duì) gcloud 還不熟悉,請(qǐng)看其入門文檔。

調(diào)整 Fastfiy 服務(wù)器

為了讓 Fastify 能正確地在容器里監(jiān)聽(tīng)請(qǐng)求,請(qǐng)確保設(shè)置了正確的端口與地址:

function build() {
  const fastify = Fastify({ trustProxy: true })
  return fastify
}

async function start() {
  // Google Cloud Run 會(huì)設(shè)置這一環(huán)境變量,
  // 因此,你可以使用它判斷程序是否運(yùn)行在 Cloud Run 之中
  const IS_GOOGLE_CLOUD_RUN = process.env.K_SERVICE !== undefined

  // 監(jiān)聽(tīng) Cloud Run 提供的端口
  const port = process.env.PORT || 3000

  // 監(jiān)聽(tīng) Cloud Run 中所有的 IPV4 地址
  const address = IS_GOOGLE_CLOUD_RUN ? "0.0.0.0" : undefined

  try {
    const server = build()
    const address = await server.listen(port, address)
    console.log(`Listening on ${address}`)
  } catch (err) {
    console.error(err)
    process.exit(1)
  }
}

module.exports = build

if (require.main === module) {
  start()
}

添加 Dockerfile

你可以添加任意合法的 Dockerfile,用于打包運(yùn)行 Node 程序。在 gcloud 官方文檔中,你能找到一份基本的 Dockerfile。

# 使用官方 Node.js 10 鏡像。
# https://hub.docker.com/_/node
FROM node:10

# 創(chuàng)建并切換到應(yīng)用目錄。
WORKDIR /usr/src/app

# 拷貝應(yīng)用依賴清單至容器鏡像。
# 使用通配符來(lái)確保 package.json 和 package-lock.json 均被復(fù)制。
# 獨(dú)立地拷貝這些文件,能防止代碼改變時(shí)重復(fù)執(zhí)行 npm install。
COPY package*.json ./

# 安裝生產(chǎn)環(huán)境依賴。
RUN npm install --only=production

# 復(fù)制本地代碼到容器鏡像。
COPY . .

# 啟動(dòng)容器時(shí)運(yùn)行服務(wù)。
CMD [ "npm", "start" ]

添加 .dockerignore

添加一份如下的 .dockerignore,可以將僅用于構(gòu)建的文件排除在容器之外 (能減小容器大小,加快構(gòu)建速度):

Dockerfile
README.md
node_modules
npm-debug.log

提交構(gòu)建

接下來(lái),使用以下命令將你的應(yīng)用構(gòu)建成一個(gè) Docker 鏡像 (將 PROJECT-ID 和 APP-NAME 替換為 Google 云平臺(tái)的項(xiàng)目 id 和 app 名稱):

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

部署鏡像

鏡像構(gòu)建之后,使用如下命令部署它:

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

如此,便能從 Google 云平臺(tái)提供的鏈接訪問(wèn)你的應(yīng)用了。

Zeit Now

now 針對(duì) Node.js 應(yīng)用提供了零配置部署方案。要使用 now,只需要如下配置你的 now.json 文件:

{
  "version": 2,
  "builds": [
    {
      "src": "api/serverless.js",
      "use": "@now/node",
      "config": {
        "helpers": false
      }
    }
  ],
  "routes": [
    { "src": "/.*", "dest": "/api/serverless.js"}
  ]
}

之后,寫一個(gè) api/serverless.js 文件:

'use strict'
const build = require('./index')
const app = build()
module.exports = async function (req, res) {
  await app.ready()
  app.server.emit('request', req, res)
}

以及一個(gè) api/index.js 文件:

'use strict'
const fastify = require('fastify')
function build () {
  const app = fastify({
    logger: true
  })
  app.get('/', async (req, res) => {
    const { name = 'World' } = req.query
    req.log.info({ name }, 'hello world!')
    return `Hello ${name}!`
  })
  return app
}
module.exports = build

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

  "engines": {
    "node": "10.x"
  },


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)