部署完整指南:从 AWS Lambda 到 Google Cloud Run 的多平台实战)
Fastify 无服务器Serverless部署完整指南从 AWS Lambda 到 Google Cloud Run 的多平台实战【免费下载链接】fastifyFast and low overhead web framework, for Node.js项目地址: https://gitcode.com/GitHub_Trending/fa/fastify在无服务器Serverless / FaaS平台上运行 Node.js Web 框架时最大的挑战往往不在应用本身而在于如何让“常驻监听端口的服务”适配“按事件触发、用完即回收的函数”。Fastify 官方仓库中的 docs/Guides/Serverless.md 就是一份面向这一场景的官方实战指南它不要求你重写应用而是用“同一套本地可直接运行的应用代码 一小段平台适配代码”的方式逐一讲解 AWS Lambda、Genezio、Google Cloud Functions、Firebase Functions、Google Cloud Run、Netlify Lambda 与 Vercel 的接入方法。读完本文你将掌握每种平台下 Fastify 的启动模式、请求注入方式、Content-TypeParser 的避坑配置、本地调试与部署命令并能直接套用文中的完整代码骨架。提示本文全部内容以当前仓库Fastify v6 系开发版本见 package.json及其官方 Serverless 指南为准示例代码均可复制运行仓库仅用于讲解请勿修改其中文件。到底要不要在 Serverless 平台跑 Fastify这取决于你的取舍。FaaS函数即服务的最佳实践是“小而专注”但拿一个完整的 Web 应用去跑也不是不可以。需要清醒认识到应用越大冷启动initial boot就越慢。官方指南明确指出如果你坚持在 Serverless 环境运行 Fastify优先选择Google Cloud Run、AWS Fargate、Azure Container Instances、Vercel这类“服务器可同时处理多个请求”的平台——它们能充分发挥 Fastify 的多请求并发与长连接特性而不是被单个事件/单请求的执行模型所限制。选择 Serverless 的另一个核心价值是开发体验一致本地开发时你直接node app.js把 Fastify 跑起来无需任何额外工具同一份代码部署到目标平台时只需补上“一小段适配代码”。这正是下文所有平台共通的代码组织思路——用require.main module或.mjs中的等价判断区分“本地直跑”与“被平台加载”两种模式。AWS两条路线怎么选AWS 官方指南推荐了两套集成库可按需取舍甚至两者都测一遍再定库优点代价fastify/aws-lambda仅面向 API Gateway 事件对 Fastify 做了深度优化开销低只支持 API Gateway无法直接处理 SQS、SNS 等其他 AWS 事件h4ad/serverless-adapter支持更多 AWS 服务SQS、SNS 等会为每个 AWS 事件构造一次 HTTP 请求性能略低方式一使用 fastify/aws-lambda该库让你可以在 AWS Lambda Amazon API Gateway 之上用近乎原生的方式构建 REST API。核心是拆成两个文件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) { // called directly i.e. node app init().listen({ port: 3000 }, (err) { if (err) console.error(err); console.log(server listening on 3000); }); } else { // required as a module executed on aws lambda module.exports init; }要点解析在 Lambda 中不需要监听任何端口因此这里只导出工厂函数init由下面的lambda.js调用在本地直接node app.js时require.main module为真应用正常监听 3000 端口开发和联调体验与普通 Fastify 项目完全一致路由注册全部收敛在init()工厂内保证“一次创建、多处使用”。lambda.js—— 平台适配层const awsLambdaFastify require(fastify/aws-lambda) const init require(./app); const proxy awsLambdaFastify(init()) // or // const proxy awsLambdaFastify(init(), { binaryMimeTypes: [application/octet-stream] }) exports.handler proxy; // or // exports.handler (event, context, callback) proxy(event, context, callback); // or // exports.handler (event, context) proxy(event, context); // or // exports.handler async (event, context) proxy(event, context);记得先安装依赖npm i fastify/aws-lambdaawsLambdaFastify(app)会返回一个签名与 Lambdahandler完全兼容的proxy函数之后所有 API Gateway 事件都会被交给该proxy若需要返回二进制响应如图片、文件通过第二参数{ binaryMimeTypes: [...] }声明 MIME 类型四种导出写法覆盖了回调式与 async 式 handler任选其一即可。AWS 平台注意事项API Gateway 目前不支持流式响应因此在 AWS 场景下无法使用 Fastify 的 streams 流式发送 能力API Gateway 有 29 秒超时限制handler 必须在这个时间窗内完成reply否则网关会直接掐断请求。超越 API Gateway更多 AWS 服务如果需要接入 SQS、SNS 等更多 AWS 服务官方指引是改用h4ad/serverless-adapter并参考其针对 Fastify 的集成文档在serverless-adapter.viniciusl.com.br的 Fastify 框架页面它会为每个 AWS 事件构造一次内部 HTTP 请求再交给 Fastify 处理。GenezioGenezio 是致力于简化 serverless 应用上云部署的平台。官方文档为 Fastify 提供了专门的部署指南位于其deployapps.dev文档站的 frameworks/fastify 页面按其步骤操作即可本文不再重复。Google Cloud Functions手动注入请求事件Google Cloud Functions 与 Fastify 的结合最“绕”因为平台在请求到达你的函数前已经自行解析了 Body需要做额外处理。创建 Fastify 实例const fastify require(fastify)({ logger: true // you can also define the level passing an object configuration to logger: {level: debug} });开启logger便于在 Cloud Logging 中查看结构化日志想调日志级别可传入对象logger: { level: debug }。Fastify 的日志能力基于 pino 构建仓库 package.json 依赖中包含pino具体见 Logging 参考文档。必须添加自定义 Content-Type Parser这是 GCF 场景下最关键的“坑”由于Google Cloud Functions 平台会在请求到达 Fastify 之前就解析 BodyPOST/PATCH请求的正文会被“二次包装”例如原始 JSON 被放到了某个外层对象的body字段中。官方指南在 issue #946 中给出了缓解方案——注册自定义Content-Type Parserfastify.addContentTypeParser(application/json, {}, (req, body, done) { done(null, body.body); });这里的第二个参数{}是解析器选项未配置时继承实例默认bodyLimit源码见 lib/content-type-parser.js 中addContentTypeParser的实现if (!opts.bodyLimit) opts.bodyLimit this[kBodyLimit]第三个参数是形如(req, body, done)的解析回调done(null, 解析后的对象)表示解析成功。关于Content-TypeParser 的完整用法含hasContentTypeParser、removeContentTypeParser、catch-all 正则注册等参见 ContentTypeParser 参考文档。定义端点简单的GET端点fastify.get(/, async (request, reply) { reply.send({message: Hello World!}) })更完整、带 schema 校验的POST端点fastify.route({ method: POST, url: /hello, schema: { body: { type: object, properties: { name: { type: string} }, required: [name] }, response: { 200: { type: object, properties: { message: {type: string} } } }, }, handler: async (request, reply) { const { name } request.body; reply.code(200).send({ message: Hello ${name}! }) } })schema同时承担两层职责输入校验经fastify/ajv-compiler编译成校验器与响应序列化经fast-json-stringify编译成专属序列化器两者都在仓库 package.json 的依赖中体现。schema 校验失败时 Fastify 会自动返回 4xx无需手写判断逻辑。实现并导出函数由于 GCF 不是把请求直接交给你启动的 HTTP Server而是调用你的导出函数因此需要把平台的(request, reply)手动“喂”给 Fastify——通过向fastify.server触发request事件实现const fastifyFunction async (request, reply) { await fastify.ready(); fastify.server.emit(request, request, reply) } exports.fastifyFunction fastifyFunction;await fastify.ready()的作用是确保实例启动流程完成在 Fastify 中插件、Hook、装饰器等都通过 avvio 的启动图按序加载参见 生命周期 Lifecycle 文档 与 插件 Plugins 文档。只有ready()之后fastify.server才处于可接收request事件的状态。本地测试安装 Google Functions Framework for Node.js可全局安装npm i -g google-cloud/functions-framework或作为开发依赖npm i -D google-cloud/functions-framework然后用它本地启动函数npx google-cloud/functions-framework --targetfastifyFunction也可以写进package.json的 scripts 中通过npm run dev运行scripts: { ... dev: npx google-cloud/functions-framework --targetfastifyFunction ... }注意--target必须与导出函数名一致此处为fastifyFunction。部署、日志与请求验证gcloud functions deploy fastifyFunction \ --runtime nodejs14 --trigger-http --region $GOOGLE_REGION --allow-unauthenticated读取日志gcloud functions logs read向/hello端点发起请求注意curl 的 URL 末尾是函数名并非文档示例中的路径本身示例仅为演示行为curl -X POST https://$GOOGLE_REGION-$GOOGLE_PROJECT.cloudfunctions.net/me \ -H Content-Type: application/json \ -d { name: Fastify } {message:Hello Fastify!}按路由区分日志Per-route logging一个 Cloud Function 只暴露一个入口Cloud Console 会把所有 HTTP 流量都归到该函数名下难以按端点拆分统计。官方推荐的解法是保留单个 Fastify 实例在onResponseHook 中针对匹配到的路由输出一条结构化日志fastify.addHook(onResponse, async (request, reply) { request.log.info({ route: request.routeOptions.url, method: request.method, statusCode: reply.statusCode, responseTime: reply.elapsedTime }, request completed) })这条技巧可以从仓库源码中找到底层依据request.routeOptions.url返回的是路由模式而非实际 URL查看 lib/request.jsrouteOptionsgetter 会从 route context 中读取context.config.url因此/users/123与/users/456都会被归并到/users/:id天然完成按端点聚合reply.elapsedTime在 lib/reply.js 中定义为 getter(this[kReplyEndTime] || now()) - this[kReplyStartTime]即响应耗时毫秒数开启logger: true后这些字段会作为结构化日志字段输出与 Cloud Logging 的结构化日志格式兼容见官方“Structured Logging”“Functions logging”文档文末 References 部分该 Hook 回调会在响应已发出后执行无法再向客户端写数据官方 Hooks 参考文档 也明确说明此语义但非常适合统计采集。事实上 Fastify 内部的标准请求日志也正是基于elapsedTime输出的——仓库 lib/log-controller.js 中就有reply.log.info({ res: reply, responseTime: reply.elapsedTime }, request completed)的实现可对照。随后可在 Cloud Logging 中创建基于日志的指标log-based metric或直接按jsonPayload.route过滤即可得到每个路由的请求量、延迟与错误率。同一个 Hook 在 Firebase Functions 的onRequest场景下同样适用。本节的更多官方参考资料英文集中在原文档 References 小节Cloud Run 快速上手、Cloud Logging 结构化日志与日志指标、Cloud Functions 日志。Firebase Functions用 onRequest 拥抱 Fastify如果你希望用 Fastify 替代 Firebase Functions 自带的简易 JS 路由即默认的onRequest(async (req, res) {})按下面步骤做。引入 onRequestFirebase 函数 SDK v2 从firebase-functions/v2/https暴露onRequestconst { onRequest } require(firebase-functions/v2/https)创建 Fastify 实例并封装启动函数创建实例后把“注册路由、等待插件/Hook/配置就绪”的逻辑收敛进registerRoutes()再用一个fastifyApp包装函数统一完成启动与请求注入const fastify require(fastify)({ logger: true, }) const fastifyApp async (request, reply) { await registerRoutes(fastify) await fastify.ready() fastify.server.emit(request, request, reply) }这种“把实例创建与路由注册分离、注册动作可被重复安全调用”的组织方式契合 Fastify 的封装Encapsulation 与 插件Plugins 设计理念每次请求都先registerRoutes(fastify)再ready()是保守但稳妥的写法重复注册同样路由在 Fastify 中会被幂等处理可确保首请求前路由已就绪。自定义 Content-Type Parser 与端点Firebase Functions 的 HTTP 层同样会预解析请求解析后的 JSON 放在payload.body同时保留未解析的原始 Bodypayload.rawBody——后者对计算 Webhook 签名如 HMAC非常有用。在registerRoutes()内完成注册async function registerRoutes (fastify) { fastify.addContentTypeParser(application/json, {}, (req, payload, done) { // useful to include the requests raw body on the req object that will // later be available in your other routes so you can calculate the HMAC // if needed req.rawBody payload.rawBody // payload.body is already the parsed JSON so we just fire the done callback // with it done(null, payload.body) }) // define your endpoints here... fastify.post(/some-route-here, async (request, reply) {}) fastify.get(/, async (request, reply) { reply.send({message: Hello World!}) }) }⚠️ 重要警告原文档原文强调如果漏掉这个ContentTypeParserFastify 进程在收到一个Content-Type: application/json的请求后可能一直卡住不再处理任何后续请求。这是因为平台的预解析包装与 Fastify 内置 JSON Parser 的预期输入不一致导致解析回调永不结束、请求队列被阻塞。TypeScript 用户注意由于payload的类型是原生IncomingMessage被 Firebase 运行时动态改造TS 无法静态得知payload.body存在。可用模块声明补齐类型以消除编译错误declare module http { interface IncomingMessage { body?: unknown; } }Firebase 官方参考项目见原文档 ReferencesLiran Tal 维护的lemon-squeezy-firebase-webhook-fastify以及同名实战文章《HTTP Webhooks on Firebase Functions and Fastify: A Practical Case Study with Lemon Squeezy》。导出函数并完成本地测试与部署把包装函数交给 Firebase 的onRequestexports.app onRequest(fastifyApp)安装 Firebase CLI 并本地起模拟器npm i -g firebase-tools firebase emulators:start --only functions部署与查看日志firebase deploy --only functions firebase functions:logGoogle Cloud Run几乎零改造的容器化 ServerlessCloud Run 与前两者有本质区别它不是“函数”而是无服务器容器环境——目的就是为任意容器提供免运维基础设施。因此Fastify 几乎无需改动即可直接部署通常只需处理端口与监听地址。调整服务器监听参数Cloud Run 会通过环境变量注入运行配置因此代码要动态适配function build() { const fastify Fastify({ trustProxy: true }) return fastify } async function start() { // Google Cloud Run will set this environment variable for you, so // you can also use it to detect if you are running in Cloud Run const IS_GOOGLE_CLOUD_RUN process.env.K_SERVICE ! undefined // You must listen on the port Cloud Run provides const port process.env.PORT || 3000 // You must listen on all IPV4 addresses in Cloud Run const host IS_GOOGLE_CLOUD_RUN ? 0.0.0.0 : undefined try { const server build() const address await server.listen({ port, host }) console.log(Listening on ${address}) } catch (err) { console.error(err) process.exit(1) } } module.exports build if (require.main module) { start() }要点K_SERVICE是 Cloud Run 注入的环境变量可用来判断是否运行在 Cloud RunIS_GOOGLE_CLOUD_RUN端口必须使用process.env.PORTCloud Run 提供默认 8080示例代码回退到 3000不能写死Host 必须监听0.0.0.0所有 IPv4否则容器外部无法访问本地开发时保持undefined即默认监听行为即可trustProxy: true让 Fastify 信任平台的反向代理层从而正确计算客户端 IP / 协议等详见 Server 参考文档中的trustProxy小节依旧沿用build()/start()require.main module的“本地直跑/平台加载”双模式本地开发时 Cloud Run 专属变量不存在就走host undefined的普通监听。Dockerfile 与 .dockerignore任意能打包并运行 Node 应用的Dockerfile都可用。下面是最小可用版# Use the official Node.js LTS image. # https://hub.docker.com/_/node FROM node:lts # Create and change to the app directory. WORKDIR /usr/src/app # Copy application dependency manifests to the container image. # A wildcard is used to ensure both package.json AND package-lock.json are copied. # Copying this separately prevents re-running npm install on every code change. COPY package*.json ./ # Install production dependencies. RUN npm i --production # Copy local code to the container image. COPY . . # Run the web service on container startup. CMD [ npm, start ]配套.dockerignore可把构建产物挡在镜像之外镜像更小、构建更快Dockerfile README.md node_modules npm-debug.log提交构建并部署将PROJECT-ID、APP-NAME替换为你的 GCP 项目 ID 与应用名gcloud builds submit --tag gcr.io/PROJECT-ID/APP-NAME镜像构建完成后部署到 Cloud Runmanaged 平台gcloud beta run deploy --image gcr.io/PROJECT-ID/APP-NAME --platform managed部署完成后应用会通过 GCP 提供的 URL 对外提供服务。Netlify Lambda复用 AWS 适配层Netlify 的无服务器函数底层同样是 Lambda因此请先完整完成上文 “AWS Lambda” 一节的准备工作尤其是app.js与lambda.js两个文件。然后创建functions目录在其中新建server.js你最终的端点路径即对应server.jsexport { handler } from ../lambda.js; // Change lambda.js path to your lambda.js path即把lambda.js中的handler再导出给 Netlify 的函数入口使用注意把../lambda.js改成你lambda.js的实际相对路径。netlify.toml在项目根目录配置 Netlify 构建行为[build] # This will be run the site build command npm run build:functions # This is the directory is publishing to netlifys CDN # and this is directory of your front of your app # publish build # functions build directory functions functions-build # always appends -build folder to your functions folder for builds注意functions指向functions-build——Netlify 构建函数时总会为源目录追加-build后缀因此源目录是functions构建产物目录是functions-build。publish项被注释掉了如果你的应用还有前端静态资源取消注释并指向对应目录即可。webpack.config.netlify.js必加官方文档明确强调不要漏掉这份 Webpack 配置否则可能出现问题。const nodeExternals require(webpack-node-externals); const dotenv require(dotenv-safe); const webpack require(webpack); const env process.env.NODE_ENV || production; const dev env development; if (dev) { dotenv.config({ allowEmptyValues: true }); } module.exports { mode: env, devtool: dev ? eval-source-map : none, externals: [nodeExternals()], devServer: { proxy: { /.netlify: { target: http://localhost:9000, pathRewrite: { ^/.netlify/functions: } } } }, module: { rules: [] }, plugins: [ new webpack.DefinePlugin({ process.env.APP_ROOT_PATH: JSON.stringify(/), process.env.NETLIFY_ENV: true, process.env.CONTEXT: env }) ] };它做了几件关键事通过webpack-node-externals把 Node 原生模块与依赖排除在打包之外、区分开发/生产模式开发模式加载.env并启用eval-source-map、用DefinePlugin注入 Netlify 需要的构建期环境常量并为本地开发提供/.netlify代理转发。package.json scripts把下面的构建脚本加入package.jsonscripts: { ... build:functions: netlify-lambda build functions --config ./webpack.config.netlify.js ... }netlify-lambda build会读取functions源目录、套用指定 Webpack 配置并输出functions-build。配好后整体即可正常工作。Vercel官方模板 Fluid ComputeVercel 对 Fastify 提供了完善支持。更进一步借助 Vercel 的Fluid Compute能力可以把“类似常驻服务器的并发处理”与“传统 serverless 函数的自动扩缩容”结合起来。官方提供了Fastify on Vercel后端模板可通过 Vercel 模板中心搜索 “fastify” 直接使用一键完成环境初始化Fluid Compute 目前需要显式开启opt-in开启方式见 Vercel 官方文档“Enabling Fluid Compute”一节由于该能力处于演进中是否启用建议结合自身流量模型评估。横向小结一次编写四处适配纵观全篇可以提炼出 Fastify 上 Serverless 的三条通用心法也是动手前最值得记住的判断框架优先选择“容器/长驻型”平台Cloud Run、AWS Fargate、ACI、Vercel Fluid。它们能同时服务多个请求与 Fastify 的并发模型天然匹配代码改动也最少——Cloud Run 一节甚至近乎零改造凡是“函数型”平台GCF、Firebase Functions、AWS Lambda都遵循同一适配范式业务代码放进工厂函数导出平台侧通过fastify.ready()fastify.server.emit(request, req, res)或专用适配库fastify/aws-lambda等注入请求并复用require.main module保证本地可直跑凡平台预解析了 Body都必须显式注册自定义Content-Type Parser对齐数据结构GCF 的body.body、Firebase 的payload.bodypayload.rawBody否则会出现解析错乱甚至进程卡死。搭配仓库内的 ContentTypeParser 参考文档、Reply 参考文档 与 Hooks 参考文档 使用本节代码即可在不改动业务逻辑的前提下把同一套 Fastify 应用平滑迁往你选定的无服务器平台。【免费下载链接】fastifyFast and low overhead web framework, for Node.js项目地址: https://gitcode.com/GitHub_Trending/fa/fastify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考