Serverless Framework Prune 指南:AWS Lambda 函数与 Layer 版本的自动清理与制品回收
【免费下载链接】serverless⚡ Serverless Framework – Effortlessly build apps that auto-scale, incur zero costs when idle, and require minimal maintenance using AWS Lambda and other managed cloud services.项目地址: https://gitcode.com/GitHub_Trending/se/serverless
本篇技术指南以 Serverless Framework 官方 AWS 指南中的 Prune 章节(docs/sf/providers/aws/guide/prune.md)为核心,讲解如何使用内置的版本清理能力控制 AWS Lambda 函数版本与 Layer 版本的无序增长,并延伸讲解其背后的部署制品回收机制。读完本文,你将掌握custom.prune配置、serverless prune命令行全部参数、别名与 Layer 附着的保护规则、--dryRun预演方式,以及--includeArtifacts在自管理存储(reference mode)下的真实工作语义。
为什么需要清理 Lambda 版本
AWS Lambda 会保留你发布的每一个函数版本(version)。每次serverless deploy默认都会发布一个新版本(versionFunctions: true),Layer 同理。随着版本持续累积,会产生三类负面效应:
- 消耗存储配额:每个版本的代码/SHA 快照都会占用账户级配额;
- 让 Lambda 控制台变得混乱:版本列表被历史快照淹没,难以辨认线上正在运行的版本;
- 拖慢依赖“枚举版本”的部署工具:
ListVersionsByFunction、ListLayerVersions等 API 需要分页、发出额外的 API 调用。
版本清理(pruning)功能正是为解决这一问题而内置在 Serverless Framework 中的:自动删除旧版本,同时保留一个可配置数量的最新版本。该实现源自 serverless-prune-plugin 的文件头注释明确标注了这一渊源,并保留 MIT 许可声明)。
配置:custom.prune
在serverless.yml的custom块中加入prune段落即可启用部署后自动清理:
custom: prune: automatic: true # Prune after each deploy number: 3 # Keep 3 most recent versions includeLayers: true # Also prune layer versions includeArtifacts: true # Also mark deployment artifacts no longer backed by any version配置选项说明
| Option | Type | Default | Description |
|---|---|---|---|
automatic | boolean | false | 每次 deploy 后是否自动执行清理 |
number | integer | - | 需要保留的版本数量(automatic 模式下必填) |
includeLayers | boolean | false | 是否把 Lambda Layer 也纳入清理范围 |
includeArtifacts | boolean | false | 同时把不再被任何版本引用的部署制品标记为删除 |
从配置解析实现(index.js)可以确认如下细节:
number会被parseInt后使用,若解析为NaN则忽略该值;automatic、includeLayers、includeArtifacts只有类型恰为boolean时才会生效,其它类型会被静默丢弃;- 在
serverless.yml校验阶段,插件通过configSchemaHandler.defineCustomProperties注册了custom.prune的结构化 schema(index.js):四个字段分别约束为boolean/boolean/integer(minimum: 0)/boolean,且additionalProperties: false,因此拼错字段名或给number传负数都会在部署/执行前被配置校验拦截,而不是运行时才发现。
automatic 自动清理的真正触发条件
自动清理挂在after:deploy:deploy钩子上(index.js)。也就是说它只在serverless deploy成功之后执行,与手动执行的prune命令(prune:prune生命周期)是同一套逻辑复用。
从postDeploy()(index.js)的源码可以看到三个必要条件,缺一不可:
custom.prune.automatic为true;custom.prune.number已定义且>= 0;- 本次 deploy 未使用
--noDeploy(该选项会直接短路返回)。
当includeLayers为true时,函数版本与 Layer 版本会并发清理(Promise.all),这与手动命令的行为一致。
手动清理:serverless prune 命令
即便没有开启automatic,你随时可以用prune命令手动回收:
# Keep 5 most recent versions serverless prune -n 5 # Preview what would be deleted serverless prune -n 3 --dryRun --verbose # Prune a specific function serverless prune -n 3 -f myFunction # Prune layers only serverless prune -n 3 -l myLayer # Prune functions and layers serverless prune -n 3 --includeLayers # Also mark unreferenced deployment artifacts (most useful in reference mode) serverless prune -n 3 --includeArtifacts命令级参数(CLI Schema)
该命令的完整选项定义位于 commands-schema.js,除了原文档示例中使用的写法,schema 还注册了更多简写与说明:
| 选项 | 简写 | 类型 | 说明 |
|---|---|---|---|
--number | -n | 必填 | 保留的旧版本数量 |
--function | -f | 可选 | 只清理指定函数 |
--layer | -l | 可选 | 只清理指定 Layer |
--includeLayers | -i | boolean | 把 Lambda Layer 一并纳入清理 |
--includeArtifacts | -a | boolean | 同时标记不再被任何函数/Layer 版本引用的部署制品 |
--dryRun | -d | boolean | 模拟清理,不真正执行删除;需配合--verbose查看待删除列表 |
--verbose | - | boolean | 输出详细日志 |
该命令声明了serviceDependencyMode: 'required',因此必须在服务目录内、且带 AWS 凭证的环境下执行。命令级number被标记为必填;在实现中getNumber()(index.js)会优先取--number,取不到时回退到custom.prune.number,因此若你已在配置里写了number,手动运行时也可以省略-n。
从cliPrune()(index.js)的分支逻辑可以精确归纳三条“作用域”规则:
- 带
--includeLayers:同时清理函数与 Layer(并发执行); - 只带
--layer(不带--function):只清理 Layer; - 其它情况:只清理函数。
别名保护:被 Alias 引用的版本永不删除
无论number设置得多小,被 Lambda Alias 引用的版本都永远不会被删除,以保证稳定部署与流量切换(traffic shifting)等场景不被打断。
对应算法在selectPruneVersionsForFunction()(index.js)中清晰可见,候选版本依次经过四步过滤:
- 跳过
$LATEST(未发布版本,无法也不应删除); - 跳过所有 Alias 当前指向的版本(
listAliases返回的FunctionVersion集合); - 按版本号数值降序排序(新版本在前);
slice(getNumber())截取,只保留最靠前的 N 个,其余进入删除候选。
因此prune -n 3的真实语义是:在排除$LATEST与所有别名指向版本后,对剩余版本按从新到旧排列并仅保留 3 个。若某函数只有 4 个已发布版本且别名指向其中 3 个,则实际只会删除 1 个——这保证了别名路由的稳定性。
Layer 版本保护:仍被本服务函数附着的版本保留
当使用--includeLayers(或-l)清理 Layer 时,存在一条额外的保护规则:
只要某个 Layer 版本仍附着在本服务自身某个现存函数版本上,即使它已经落在
number窗口之外,也会被保留。
实现上,pruneLayers()(index.js)会先枚举本服务所有函数的所有版本(getFunctionVersionLists),收集每个版本Layers[].Arn构成“附着集合”attachedLayerArns,再交给selectPruneVersionsForLayer()(index.js)逐层判定:超过保留窗口的版本若其 ARN 命中附着集合,则打印Retaining layer version ... — attached to existing function versions.并跳过。
关于这条规则有两个值得注意的边界:
- 附着集合刻意做成“超集”:当
--includeLayers让函数与 Layer 并发清理时,某些正在被删除的函数版本仍可能短暂计入“附着”,导致个别 Layer 版本多保留一个清理周期。源码注释明确说明这是有意为之的保守设计(多保护一轮是安全的,下一次 prune 就会收敛)。 - 它只保护本服务的函数:
getFunctionVersionLists枚举的是serverless.service.getAllFunctions()。附着集合的推导同样依赖本地服务配置(index.js),所以被其它服务或账户引用、由prune无法感知的 Layer 版本,仍然需要你自行负责管理。这正是原文档强调“务必使用与产生部署时相同的服务配置来运行 prune”的根本原因。
部署制品清理:includeArtifacts 的完整故事
includeArtifacts是全部选项中最容易产生疑问的一个,原文档给出了两条关键定性描述,结合仓库源码可还原其完整语义:
includeArtifacts在**自管理代码存储(reference mode)**下价值最大——此时部署制品(S3 中的 zip 对象)承载着在线的 Lambda 版本,会一直保留到对应版本被清理为止。该清扫(sweep)在任何存储模式下都可运行。
与 maxPreviousDeploymentArtifacts 的关系
清扫逻辑在 index.js 的sweepDeploymentArtifacts()与 artifact-sweep.js 中实现,其保留窗口由两层规则叠加:
- 无条件保留窗口:尊重
provider.deploymentBucket.maxPreviousDeploymentArtifacts(默认5)。最近的 N 次部署无条件保留,无论其制品是否仍被某个版本“钉住”(pinned); - 有条件的钉住保留:窗口之外的部署目录,只有在其制品仍被存活版本引用时才保留。
这里需要补充一个原文档未展开、但对理解很关键的存储模式差异:
- 在默认的 copy 模式(Lambda 托管代码存储)下,部署后的自动清理已经会执行
maxPreviousDeploymentArtifacts保留窗口,因此稳态下 sweep 通常找不到新目标——它主要用于回收“从 reference mode 切回 copy 模式后遗留的历史制品目录”; - 在reference mode下,部署时的自动制品清理不会运行(否则会破坏在线的版本),
maxPreviousDeploymentArtifacts窗口只在 sweep 运行时才生效。
清扫如何判定一个目录“可删”
artifact-sweep.js的sweepArtifacts()用 S3 分页列出部署桶中deploymentPrefix/service/stage下的对象,通过findAndGroupDeployments按部署目录分组,然后保留最后keepCount组(slice(0, -keepCount),与 deploy 清理逻辑同源),对其余候选目录逐一检验:
- 目录里没有
.zip制品→ 无法证明其“无引用”,fail-safe 保留; - 制品的
filesha256元数据缺失或 headObject 失败→ fail-safe 保留; filesha256命中存活函数版本的CodeSha256(buildPinnedShaSet构造)→ 保留,日志注明backs a surviving function version;- 制品对象命中存活 Layer 版本的
ResolvedS3Object.S3Key/S3ObjectVersion→ 保留,注明backs a surviving layer version(collectLayerArns负责收集本服务函数与 Layer 的全部附着 ARN;对已删除但仍被引用的 Layer ARN 还有基于名字的 fail-safe 兜底layerBasenameFromArn)。
它到底怎么“删”
在版本化桶(Framework 托管桶、reference mode 所要求的桶都满足)上,sweep只写 S3 delete marker,绝不硬删除某个具体VersionId:通过deleteObjects每批最多 1000 个 key(artifact-sweep.js)。marker 会让旧制品“隐藏”起来但不会真正销毁任何数据,真正的空间回收交由 S3 生命周期规则(如NoncurrentVersionExpiration,配合ExpiredObjectDeleteMarker清理遗留 marker)完成。由于被钉住的制品始终是所属 key 的当前版本,非当前版本过期规则永远不会误删仍在服务在线版本的制品。若你在非版本化的自定义桶上使用 copy 模式运行 sweep,删除才是永久性的——这与自动部署清理在该桶上的行为一致。完整的保留机制与回收建议见 Artifact retention in reference mode。
两个使用前提
原文档特别提醒的两点,在源码中也有对应落点:
- 用同一份服务配置运行:清扫的保护集合(存活函数/Layer 版本、制品 basename)全部派生自本地
serverless.yml(函数与 Layer 定义、resolveLayerArtifactName等),配置不一致会导致保护集合失真; -f/-l作用域下不会执行 sweep:shouldSweepArtifacts()(index.js)在函数/Layer 限定清理时打印Skipping deployment artifact sweep: it is not available on function/layer-scoped prune runs.并返回false。
Dry Run 模式:先预演,再动手
--dryRun只模拟删除过程、输出将被删除的候选版本,不执行任何真实删除。它需要与--verbose配合才能看到逐版本日志:
serverless prune -n 3 --dryRun --verbose输出示例:
Prune: myFunction:4 selected for deletion. Prune: myFunction:3 selected for deletion. Prune: Dry-run enabled, no pruning actions will be performed.在源码中 dry-run 的分支非常明确:cliPrune()检测到dryRun时先打印Dry-run enabled, no pruning actions will be performed.(index.js),随后pruneFunctions/pruneLayers走printPruningCandidates只打印候选而不调用deleteFunction/deleteLayerVersion(index.js)。
值得留意的一个工程细节:dry-run 会通过recordPlannedPruneVersions把“计划删除的版本”记录进与真实删除相同的映射中。因此当你在 dry-run 后再叠加--includeArtifacts时,sweep 预演的是删除后的世界——它不会因为刚“计划删除”的版本仍然存活就把所有目录误报为被钉住,这与真实 run 内部通过excludePrunedVersions抵消 Lambda 列表 API 最终一致性造成的误报是同一设计思路(index.js 与 index.js 的注释对此有详尽说明)。
底层实现要点:从 CLI 到 AWS API
对于希望深入理解该功能运作方式的读者,核心实现集中在两个文件:
- index.js:
Prune类,负责配置加载、命令/hook 注册与删除编排; - artifact-sweep.js:部署制品清扫的纯实现。
值得强调的实现事实(均可对照源码验证):
1. 三个 AWS 读接口统一做分页。makeLambdaRequest()(index.js)按NextMarker循环拉取完整列表,分别用于listVersionsByFunction、listLayerVersions与listAliases,避免大版本列表被截断误删。
2. 未部署函数被优雅跳过。三个列出接口都捕获 404(Lambda was unable to ...之外的状态码为 404 的异常),函数/Layer 尚未部署时返回空数组而不中断整个清理流程(index.js)。
3. 对 Lambda@Edge 复制版本单独降级。deleteVersionsForFunction删除时会遇到Lambda was unable to delete ... because it is a replicated function.(400),此时只打印 warning 并跳过该版本,而不是让整批清理失败(index.js)。
4. 跨函数版本列表按“次”记忆化复用。getFunctionVersionLists()在单次运行内缓存所有函数的版本列表,避免--includeLayers并发清理时每个函数被listVersionsByFunction请求两次;但 sweep 阶段故意绕过该缓存重新拉取,因为需要删除后的最新视角(index.js)。
5. 每次运行开始都会重置内部状态。resetPrunedVersionTracking()在每次cliPrune()/postDeploy()开头清空“本次删除记录”与记忆化缓存,确保 dry-run 与连续多次运行之间不泄漏状态。
对应的单元测试位于 packages/serverless/test/unit/lib/plugins/prune/index.test.js 与 packages/serverless/test/unit/lib/plugins/prune/artifact-sweep.test.js,可作为理解各分支行为(别名保护、附着 Layer 保留、fail-safe 保留、dry-run 与 sweep 联动)的补充素材。
使用建议
综合文档与实现,下面是一份可直接落地的实践清单:
- 先预演后执行:任何批量清理前先跑
serverless prune -n <N> --dryRun --verbose,核对待删除列表是否符合预期; - 自动清理从 3 起步:
automatic: true+number: 3是兼顾回滚空间与存储成本的常见起点;需要更长的回滚窗口再调大number; - 别让 Alias 裸奔:删除行为天然保护 Alias 指向的版本,因此建议为稳定的生产函数配置指向指定版本的 Alias,作为“免删保险”;
- 在 reference mode 下把 prune 当保留杠杆:该模式下部署桶列表与
serverless deploy list会随版本持续增长,reference-mode 文档 明确把prune称为该模式下的保留(retention)工具——用automatic: true + includeArtifacts: true即可让版本与制品同步回收; - 存储回收交给生命周期规则:对版本化桶,sweep 只写 delete marker,别手动硬删被钉住的 object version(会导致在线函数无预警失效),让
NoncurrentVersionExpiration规则去真正回收空间; - 保持配置一致:清理(尤其是 Layer 附着保护与制品清扫)依赖本地服务配置推导保护集合,务必用产生这些部署时的那份
serverless.yml执行prune,跨服务/跨账户被引用的 Layer 需自行管理。
【免费下载链接】serverless⚡ Serverless Framework – Effortlessly build apps that auto-scale, incur zero costs when idle, and require minimal maintenance using AWS Lambda and other managed cloud services.项目地址: https://gitcode.com/GitHub_Trending/se/serverless
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考