Serverless Framework Prune 指南:AWS Lambda 函数与 Layer 版本的自动清理与制品回收
2026/9/9 20:14:32 网站建设 项目流程

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 控制台变得混乱:版本列表被历史快照淹没,难以辨认线上正在运行的版本;
  • 拖慢依赖“枚举版本”的部署工具ListVersionsByFunctionListLayerVersions等 API 需要分页、发出额外的 API 调用。

版本清理(pruning)功能正是为解决这一问题而内置在 Serverless Framework 中的:自动删除旧版本,同时保留一个可配置数量的最新版本。该实现源自 serverless-prune-plugin 的文件头注释明确标注了这一渊源,并保留 MIT 许可声明)。

配置:custom.prune

serverless.ymlcustom块中加入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

配置选项说明

OptionTypeDefaultDescription
automaticbooleanfalse每次 deploy 后是否自动执行清理
numberinteger-需要保留的版本数量(automatic 模式下必填)
includeLayersbooleanfalse是否把 Lambda Layer 也纳入清理范围
includeArtifactsbooleanfalse同时把不再被任何版本引用的部署制品标记为删除

从配置解析实现(index.js)可以确认如下细节:

  • number会被parseInt后使用,若解析为NaN则忽略该值;
  • automaticincludeLayersincludeArtifacts只有类型恰为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)的源码可以看到三个必要条件,缺一不可:

  1. custom.prune.automatictrue
  2. custom.prune.number已定义且>= 0
  3. 本次 deploy 未使用--noDeploy(该选项会直接短路返回)。

includeLayerstrue时,函数版本与 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-iboolean把 Lambda Layer 一并纳入清理
--includeArtifacts-aboolean同时标记不再被任何函数/Layer 版本引用的部署制品
--dryRun-dboolean模拟清理,不真正执行删除;需配合--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)中清晰可见,候选版本依次经过四步过滤:

  1. 跳过$LATEST(未发布版本,无法也不应删除);
  2. 跳过所有 Alias 当前指向的版本(listAliases返回的FunctionVersion集合);
  3. 按版本号数值降序排序(新版本在前);
  4. 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 中实现,其保留窗口由两层规则叠加:

  1. 无条件保留窗口:尊重provider.deploymentBucket.maxPreviousDeploymentArtifacts(默认5)。最近的 N 次部署无条件保留,无论其制品是否仍被某个版本“钉住”(pinned);
  2. 有条件的钉住保留:窗口之外的部署目录,只有在其制品仍被存活版本引用时才保留。

这里需要补充一个原文档未展开、但对理解很关键的存储模式差异:

  • 默认的 copy 模式(Lambda 托管代码存储)下,部署后的自动清理已经会执行maxPreviousDeploymentArtifacts保留窗口,因此稳态下 sweep 通常找不到新目标——它主要用于回收“从 reference mode 切回 copy 模式后遗留的历史制品目录”;
  • reference mode下,部署时的自动制品清理不会运行(否则会破坏在线的版本),maxPreviousDeploymentArtifacts窗口只在 sweep 运行时才生效。

清扫如何判定一个目录“可删”

artifact-sweep.jssweepArtifacts()用 S3 分页列出部署桶中deploymentPrefix/service/stage下的对象,通过findAndGroupDeployments按部署目录分组,然后保留最后keepCount组(slice(0, -keepCount),与 deploy 清理逻辑同源),对其余候选目录逐一检验:

  • 目录里没有.zip制品→ 无法证明其“无引用”,fail-safe 保留;
  • 制品的filesha256元数据缺失或 headObject 失败→ fail-safe 保留;
  • filesha256命中存活函数版本的CodeSha256buildPinnedShaSet构造)→ 保留,日志注明backs a surviving function version
  • 制品对象命中存活 Layer 版本的ResolvedS3Object.S3Key/S3ObjectVersion→ 保留,注明backs a surviving layer versioncollectLayerArns负责收集本服务函数与 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作用域下不会执行 sweepshouldSweepArtifacts()(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/pruneLayersprintPruningCandidates只打印候选而不调用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循环拉取完整列表,分别用于listVersionsByFunctionlistLayerVersionslistAliases,避免大版本列表被截断误删。

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 联动)的补充素材。

使用建议

综合文档与实现,下面是一份可直接落地的实践清单:

  1. 先预演后执行:任何批量清理前先跑serverless prune -n <N> --dryRun --verbose,核对待删除列表是否符合预期;
  2. 自动清理从 3 起步automatic: true+number: 3是兼顾回滚空间与存储成本的常见起点;需要更长的回滚窗口再调大number
  3. 别让 Alias 裸奔:删除行为天然保护 Alias 指向的版本,因此建议为稳定的生产函数配置指向指定版本的 Alias,作为“免删保险”;
  4. 在 reference mode 下把 prune 当保留杠杆:该模式下部署桶列表与serverless deploy list会随版本持续增长,reference-mode 文档 明确把prune称为该模式下的保留(retention)工具——用automatic: true + includeArtifacts: true即可让版本与制品同步回收;
  5. 存储回收交给生命周期规则:对版本化桶,sweep 只写 delete marker,别手动硬删被钉住的 object version(会导致在线函数无预警失效),让NoncurrentVersionExpiration规则去真正回收空间;
  6. 保持配置一致:清理(尤其是 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询