Serverless Framework `dev` 命令实战指南:基于 AWS IoT Core 的 Lambda 本地实时联调与开发模式
本指南围绕 Serverless Framework 的 serverless dev 命令展开。它以部署在 AWS Lambda 上的真实基础设施为运行底座,通过 AWS IoT Core 建立的 WebSocket 通道把线上函数收到的每一次真实事件实时转发到本地代码中执行——从而让开发者在不做模拟器、不反复部署的前提下获得"改代码即生效"的开发体验。阅读本文后,你将掌握 serverless dev 的全部选项用法、其 IoT 事件回传机制的底层原理、Node.js/TypeScript 本地执行的实现方式,以及 Sandboxes(AWS Lambda MicroVMs)容器级本地开发模式的启动方法,并能独立定位开发会话中常见的故障。
serverless dev 能解决什么问题
在传统的 Serverless 开发流程里,每修改一次函数代码都要执行一次 serverless deploy 才能真正触发一次调试,云函数异步事件(例如 S3 上传、SQS 消息、EventBridge 规则触发)很难在本地完整模拟。serverless dev 改变了这一循环:
- 它在指定的 stage 与 region 上激活一个长驻的 CLI 开发会话;
- 你的函数仍以真实代码部署在 AWS Lambda 上接收真实事件,但事件会被"截获"并通过 WebSocket 转发到本机;
- 本机在近乎真实的环境变量、IAM 权限与上下文条件下执行你的函数代码,日志直接打印在终端,结果与错误实时回传 Lambda;
- 本机源码一旦变化,热重载即时生效,无需反复部署,也不需要任何本地模拟/仿真。
不过需要再次强调原文档中的告诫:虽然技术上可行,官方不建议在 prod stage 上激活开发会话。
快速开始:激活一个开发会话
在服务目录下直接运行:
serverless dev
命令执行后会经历一次"插桩 + 部署"过程:CLI 先为你的服务打上 Dev Mode 所需的运行时垫片(shim),执行一次部署把垫片发布到 AWS,随后 CLI 保持长连接并输出 Connected (Ctrl+C to cancel),此时终端开始实时转发线上事件、日志与响应。按下 Ctrl+C 可退出会话(详见下文"退出与清理"小节)。
该命令要求服务依赖(serviceDependencyMode: 'required'),并且属于带 AWS 扩展的主命令(hasAwsExtension: true),其命令生命周期定义位于 commands-schema.js,dev 顶层插件入口在 lib/plugins/dev.js,而核心实现全部位于 packages/serverless/lib/plugins/aws/dev/index.js。
选项(Options)
原文档列出的常用选项与命令 schema(commands-schema.js)中实际支持的选项合并整理如下:
| 选项 | 简写 | 类型 | 说明 |
|---|---|---|---|
--stage |
-s |
string | 要在哪个 stage 激活开发会话,例如 dev、local 或个人命名 stage |
--region |
-r |
string | 在指定 stage 中于哪个区域激活会话,例如 us-east-2 |
--aws-profile |
— | string | 使用的 AWS 命名 Profile |
--detailed |
— | boolean | 在终端完整展示每一次调用的请求事件与响应体(默认仅打印一行摘要) |
--mode |
-m |
string | 开发模式类型:functions(默认)、agents(AgentCore 运行时)、sandboxes(AWS Lambda MicroVMs)。未指定时按服务配置自动检测 |
--agent |
-a |
string | agents 模式下要运行的 Agent 名称,默认取第一个 runtime Agent |
--sandbox |
— | string | sandboxes 模式下要运行的 Sandbox 名称(本地 Docker 容器方式) |
--assume-role |
— | boolean | sandbox 模式下默认以沙箱部署的执行角色运行本地容器;传 --no-assume-role 则改用你的本地 AWS 凭据 |
--port |
-p |
string | 本地 dev 端口:agents 模式对应 AgentCore 容器端口(默认 8080),sandboxes 模式对应本地 MicroVMs 控制面 API 端口(默认 9100) |
--on-exit |
— | string | 退出开发会话时的行为,合法值仅 remove(Ctrl+C 退出时询问是否移除已部署的栈) |
其中 --mode 的合法取值在插件层通过 validateModeOption() 强制校验,非法值会以 INVALID_DEV_MODE_OPTION 错误码拒绝;--on-exit 也类似,非 remove 的值会被 INVALID_DEV_ON_EXIT_OPTION 拒绝,见 index.js 与 index.js。
注意: 会话区域必须处于 AWS IoT Core 支持的区域范围内。插件内 validateRegion() 维护了一份受支持区域清单(涵盖 us-east-1/2、us-west-1/2、eu-*、ap-*、sa-east-1 等),若所选区域不在其中会以 UNSUPPORTED_REGION 错误直接中止,详见 index.js。这也解释了为何 dev 无法在 IoT Core 不可用的区域使用。
支持的语言运行时
目前该命令只对 Node.js(JavaScript 与 TypeScript) 提供开箱即用的支持:
- Node.js (JS & TS) —— 已支持
- Python —— 即将支持
- Go —— 即将支持
- Ruby —— 即将支持
- Java —— 即将支持
这一限制有明确的源码依据:在会话准备阶段,插件会扫描服务内所有函数,一旦发现 runtime 不以 nodejs 开头便抛出 DEV_MODE_UNSUPPORTED_RUNTIME 错误并给出说明,见 index.js。垫片部署端只接受 AWS Lambda 当前可用的 Node 运行时列表 nodejs18.x / nodejs20.x / nodejs22.x / nodejs24.x;本地执行端(LocalLambda)的 Node wrapper 则额外兼容 nodejs14.x/16.x 并支持 .js/.mjs/.cjs/.ts/.mts/.cts 扩展名,见 local-lambda/index.js。
工作原理:IoT Core 通道与 shim 插桩
serverless dev 并不使用 API Gateway 之类的 WebSocket 方案,而是利用每个 AWS 账号默认即具备、无需部署任何额外基础设施的 AWS IoT Core 终端节点建立安全通道。整条链路在代码中清晰可分,主要分为四步:
1. 为函数注入访问 IoT 的 IAM 权限
CLI 会为服务中所有 Lambda 函数补充一条 IAM 声明,使函数具备向 IoT Core 发布/订阅的能力:
Effect: 'Allow',
Action: ['iot:*'],
Resource: '*'
实际实现中,插件在保留你原有 IAM 配置的前提下(兼容旧版 provider.iamRoleStatements 与新版 provider.iam.role.statements 两种写法),把上述声明追加进去,并额外兼容 serverless-iam-roles-per-function 插件的逐函数 IAM 写法,见 index.js。函数自身的 IAM 配置会在会话开始时被深拷贝备份,供会话结束后恢复。
2. 将函数 handler 替换为 shim 并注入环境变量
插件会先把 shim.js 用 esbuild 打包、压缩,并以 index.js 为入口写进一个 zip 文件,覆盖设置到服务级与函数级的 package.artifact 上——也就是说,本次部署上传的"函数代码"其实就是 shim(详见 pack(),index.js)。随后每个函数的配置被原地改写:
handler被替换为index.handler(原来的 handler 暂存到originalHandler供构建插件使用);runtime被调整为与你本地 Node 版本匹配、且 AWS Lambda 支持的版本(本机为 v18/20/22/24 时原样映射,否则回退nodejs20.x);- 注入四组关键环境变量,告知 shim 应该连接哪里、服务属于谁:
SLS_IOT_ENDPOINT = <账号专属 IoT Data-ATS 终端地址>
SLS_SERVICE = <服务名>
SLS_STAGE = <会话 stage>
SLS_FUNCTION = <函数名>
其中 IoT 终端地址通过 Iot.describeEndpoint({ endpointType: 'iot:Data-ATS' }) 获取,同一账号下各区域唯一,且默认可用,见 index.js。
3. 以"无业务代码"的方式部署
完成配置改写后,插件通过内部 spawn('deploy') 触发一次标准部署——但这次部署的内容只有 shim,不含你的业务代码。所有函数因此变成"事件转发器"。部署结束后,内存中的服务配置会被立刻还原(restore() 恢复原始 handler、runtime、environment 与 IAM),并清理 package.artifact,保证后续本地 dev-build 不会因残留产物而跳过构建,见 index.js。
4. 建立 WebSocket 长连接并订阅事件主题
CLI 使用 MQTT over WSS 连接 IoT 终端,按 sls/{region}/{service}/{stage}/{functionName} 的命名规则为每个函数订阅 request 与 error 主题,并每秒向 sls/.../_heartbeat 发布一次心跳(QoS 1)以维持连接与探活,见 index.js。
线上 shim 与本地 CLI 的职责对称:
- shim(云端):收到 Lambda 调用后,从
context提取非函数字段(awsRequestId、functionName、functionVersion、memoryLimitInMB、logGroupName、logStreamName等),连同完整event与环境变量发布到…/{function}/request主题;随后阻塞等待…/{function}/response主题上的结果,并把它作为 handler 的返回值交还给 Lambda 运行时;若超过约 2 秒未收到本机心跳,则抛出"Dev Mode Disconnected"错误提示重新执行serverless dev,见 shim.js 与 shim.js。 - CLI(本地):订阅到
request消息后解析出functionName,进入本地执行流程,再把结果发布回…/{function}/response。
事件主题中还做了事件来源识别——本地终端对 API Gateway v1/v2、EventBridge、S3、SQS、SNS 事件会打印出可读的一行摘要(如 → λ handler ── aws:sqs:queueName:msgId),配合 --detailed 可查看完整事件体,见 index.js。
本地执行环境:Child Process + Runtime Wrapper
当开发会话处于活跃状态,收到函数调用事件后,CLI 会创建子进程尽可能还原线上 Lambda 的执行环境:
- 环境变量:透传线上函数环境(shim 剔除
PATH、NODE_PATH、LD_LIBRARY_PATH、PWD、SHLVL等宿主无关变量后原样发送); - IAM 权限:真实函数携带的临时 AWS 凭据随环境变量一并生效,本地代码对 AWS 的调用走真实 IAM;
- 上下文与事件:包括
awsRequestId、函数名、内存限制等真实context元数据以及原始事件; - 超时:以函数配置的
timeout(缺省 6 秒)为依据计算getRemainingTimeInMillis(),本地执行若超过该时限会输出超时告警,见 index.js。
本地执行交由 local-lambda/index.js 中的 LocalLambda 类完成:它以 cwd 指向服务目录,spawn 一个 node 子进程执行 runtime-wrappers/node.js,并把 { handlerFileAbsolutePath, handlerName, event, partialContext } 序列化后作为参数传入。wrapper 内会:
- 动态
import()用户的 handler 文件(同时兼容 CommonJS 与 ESM); - 为
context补充succeed / fail / done / getRemainingTimeInMillis等函数——这些函数无法通过 WebSocket 序列化,因此必须在本地补齐; - 拦截
console.log/info与stdout/stderr,把对象参数以彩色、无限深度的util.inspect格式输出,使本地日志可读性接近真实 Lambda; - 执行完毕后把
{ response, error }写入系统临时目录下以子进程 PID 命名的sls_<pid>.json,由父进程读取并删除,见 runtime-wrappers/node.js 相关实现。你可以在本机源码树中对照 runtime-wrappers/node.js 阅读完整的上下文注入与结果回传逻辑。
执行结果与错误随后封装回 JSON,通过 …/{function}/response 主题回传云端 shim;本地抛出的错误会携带原有的 name / message / stack 在远端重新构造,从而保持错误形态一致。需要注意的是,事件与响应都经由 MQTT 传输,受约 125 KB 载荷上限约束(两端分别以 MQTT_PAYLOAD_LIMIT = 125 * 1024 定义),超限时本地会打印 PayloadTooLargeError 提示,见 index.js 与 shim.js。
退出与清理:还原线上代码
开发会话中的函数运行的是 shim 而非你的真实代码,因此关闭会话后若不还原,线上函数将无法正常工作。退出时 CLI 会明确提醒:请立即执行一次常规部署来移除 Dev Mode 插桩、恢复原始代码:
serverless deploy
若你在启动时传了 --on-exit=remove,则 Ctrl+C 退出时会先弹出确认询问是否移除当前 stage/region 的服务栈(确认后内部 spawn('remove') 拆除资源),见 index.js。无论选择哪种退出方式,把"结束后部署还原"纳入你的工作流都是安全使用 Dev Mode 的前提。
实践示例
在原文档基础上,以下命令覆盖了最常见的几种用法。
在默认 dev stage 快速激活会话:
serverless dev
在 dev stage、us-east-2 区域激活会话:
serverless dev --stage dev --region us-east-2
在个人专属 stage 上激活(便于与他人隔离):
serverless dev --stage austen
维护一个专门的 local stage:
serverless dev --stage local
原文档指出,维护一个专用的本地 stage 非常有益:每次执行命令时不必反复改动基础设施,就能快速进入会话。若希望每次调用都查看完整事件与响应体,可追加 --detailed。
Sandboxes(AWS Lambda MicroVMs)的本地开发
对于 sandboxes(Lambda MicroVMs),serverless dev 走的是与上述函数级 IoT 会话完全不同的机制:它把 Sandbox 以 Docker 容器方式跑在本地。CLI 会构建 Sandbox 的 Dockerfile,启动一个本地、与 AWS SDK 兼容的 Lambda MicroVMs 控制面;这个控制面按需把 MicroVM 实例作为 Docker 容器拉起、转发它们的日志,并在文件变化时热重载镜像。
serverless dev --sandbox <name> # 仅定义了一个 sandbox 时可省略 --sandbox
serverless dev --mode sandboxes # 等价写法;当服务只含 sandboxes 时会被自动检测
serverless dev --sandbox <name> --port 9300 # 本地控制面端点端口(默认 9100)
几点关键约束与行为(可对照原文档以及 sandboxes.md 的 Local development 章节阅读):
- 需要本机存在可用的 Docker daemon,且 Sandbox 的
artifact必须是本地目录(内含Dockerfile)——artifact为s3://zip 的 Sandbox 无法用dev本地运行; - 默认情况下,本地容器以 Sandbox 已部署的执行角色运行,因此代码里的 AWS 调用走真实 IAM;传入
--no-assume-role可跳过角色扮演,改用你本地环境的 AWS 凭据; - 控制面端点地址跨会话保持稳定(默认
http://127.0.0.1:9100),可用--port调整;它可被任何使用LambdaMicrovmsClient的编排代码当作 AWS 端点调用。
模式选择(functions / agents / sandboxes)支持自动检测:插件会依据服务中是否定义了函数、runtime Agent、Sandbox 来推断,也可用 --mode/--sandbox/--agent 显式指定,相关判断逻辑见 index.js。Sandbox 模式的实现位于 packages/serverless/lib/plugins/aws/sandboxes/dev。
故障排查(Troubleshooting)
TypeScript 无法工作
Dev Mode 底层调用本地函数时依赖 ts-node 来执行 TypeScript,因此服务中必须存在合法的 tsconfig.json。请确认配置文件有效后重试。本地 wrapper 的动态 import() 支持 .ts/.mts/.cts 扩展名文件,但解析与转译依赖项目自身的 TypeScript 工具链配置。
VPC 内的 Lambda 无法工作
serverless dev 无法直接支持运行在 VPC 内的 Lambda 函数,原因在于函数需要能访问 AWS IoT Core 终端节点。两种解决思路:
- 临时移除函数上的 VPC 配置再启动开发会话;
- 调整 VPC 网络,使其可通过 interface VPC endpoints(终端节点)与 AWS IoT Core 建立连通,具体可参考 AWS 官方文档中"通过 interface VPC endpoints 使用 AWS IoT Core"一节。
区域或载荷受限
- 若所选 region 不在 AWS IoT Core 支持清单内,命令会以
UNSUPPORTED_REGION报错——请切换到列表中的区域; - 若单个事件或响应超过约 125 KB,会收到
PayloadTooLargeError:Dev Mode 的 MQTT 通道不承载大载荷,请改为常规部署后直接invoke验证大载荷场景。
修改了基础设施配置
开发会话的配置热重载只针对函数代码。若你在会话期间修改了 serverless.yml 中的基础设施定义,终端会提示"如已做基础设施变更,请重启 serverless dev"——请退出后重新运行命令使其生效,见 index.js。
深入阅读
本文涉及的机制均可在仓库源码与测试中继续追踪:
- 命令定义与全量选项:commands-schema.js
- 顶层命令注册:lib/plugins/dev.js
- 核心实现(配置改写、部署、IoT 连接、事件分发、退出清理):packages/serverless/lib/plugins/aws/dev/index.js
- 云端 shim(事件转发与心跳机制):packages/serverless/lib/plugins/aws/dev/shim.js
- 本地执行引擎与运行时 wrapper:packages/serverless/lib/plugins/aws/dev/local-lambda/index.js、runtime-wrappers/node.js
- 相关单元测试(含对 shim 配置改写与错误码的断言):packages/serverless/test/unit/lib/plugins/aws/dev.test.js
- Sandbox 本地开发完整说明:docs/sf/providers/aws/guide/sandboxes.md
一句话总结:serverless dev 把"真实云事件 + 本地代码执行"组合为近乎零部署成本的联调闭环——对纯 Node.js/TypeScript 服务,它是替代本地仿真与高频部署的高效方案;对容器型 Sandbox 负载,则应切换 --sandbox 的 Docker 本地运行模式。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00