发布于 2026-01-06 0 阅读
0

停止使用 Lambda 层(请改用此方法)

停止使用 Lambda 层(请改用此方法)

本文也可在 YouTube 上观看:

Lambda 层是 AWS Lambda 提供的一种特殊打包机制,用于管理基于 zip 文件的 Lambda 函数的依赖项。Lambda 层本身就是一个精简的zip 文件,但它有一些有趣的特性,在某些情况下非常有用。然而,对于开发人员来说,Lambda 层也难以使用,安全部署也比较棘手,而且通常并不比原生包管理器更有优势。这些缺点往往大于优点,我们将详细探讨它们的优缺点。

读完这篇文章,您将了解 Lambda 层一般使用的弊端,以及在哪些特殊情况下使用 Lambda 层可能更有意义。

揭穿Lambda层迷思

当我询问开发者为何使用 Lambda 层时,我经常发现他们背后的原因存在误解。这并非完全是他们的错,文档中一些不够精确的说法可能会助长这些误解。

Lambda 层并不能绕过 250MB 的大小限制。

我经常听到有人说他们利用 Lambda 层来“突破基于 zip 的 Lambda 函数 250MB 的大小限制”。这完全是错误的。解压后的函数及其所有附加层 的大小必须小于 250MB

这种误解源于文档的第一点,即 Lambda 层“可以减小部署包的大小”。虽然从技术上讲,使用层确实可以减小部署的特定函数代码的大小,但函数在 Lambda 中运行时的整体大小并不会改变。

这引出了我的下一个观点。

Lambda 层不会改善或减少冷启动初始化持续时间

开发者常常误以为减小部署包的大小就能降低冷启动延迟。这种说法并不正确,因为我们已经知道,加载的代码才是造成冷启动延迟的最大因素。这些字节是来自某个层,还是仅仅来自 zip 函数本身,与最终的初始化时间无关。

分层开发中的痛点

使用 Lambda 层的开发者面临的最大挑战之一是,它们会magically在处理程序执行时出现。虽然从技术角度来看,这令人印象深刻,但对开发者而言却是个问题,因为文本编辑器和 IDE 都期望依赖项在本地可用,打包工具、测试运行器和代码检查工具也是如此。如果您在本地运行函数代码或使用模拟器,则只有一部分工具能够与 Lambda 层兼容。尽管这些问题可以解决,但 Lambda 层提供的外部依赖项需要特别考虑和处理,而收益却十分有限。

通常情况下,分别构建和部署层的过程足以避免使用 Lambda 层,但还有其他原因需要避免使用 Lambda 层。

跨架构难题

我们正在为一个日益依赖 ARM 芯片的世界编写软件。这可能是你崭新的 M3 笔记本电脑,也可能是亚马逊自家(不得不承认,性能卓越)的Graviton处理器。如今,你的 Lambda 函数很可能运行在 x86 或 ARM 与 x86 混合架构的处理器上。

Lambda 层确实支持名为“支持的运行时”和“支持的架构”的元数据属性,但这些仅仅是标签。它们不会阻止或强制任何运行时或部署时的兼容性。想象一下,当你将为 x86 编译的二进制文件附加到基于 arm 的 Lambda 函数时收到exec format错误,你会多么惊讶!

我现场演示了这一失败。

部署困难

Lambda 层不支持语义化版本控制。相反,它们是不可变的,并采用增量版本控制。虽然这有助于防止意外升级,但增量版本控制无法提供关于向后兼容性或更新层包中变更的任何信息。此外,Lambda 层完全与运行时无关,不提供清单文件、锁定文件或打包提示。层不提供 `<filename>` package.json、 `<filename> pyproject.toml` 或 ` gemspec<filename>` 文件来确保充分的依赖关系解析。因此,作者有责任确保只打包兼容的代码。

Lambda 层的主要卖点之一是它们可以在多个函数之间共享公共依赖项,这在每个函数都需要完全相同的兼容版本依赖项时非常有用。但是,当您想要升级主版本时会发生什么呢?

您需要发布新版本的层及其新主版本,确保没有开发人员意外应用增量调整的层(记住——没有语义版本控制、清单文件或锁定文件!),然后同时升级 Lambda 函数代码和层。

但即便如此,也无法自动完成,正如我之前记录的那样。部署函数和层会导致两个独立的异步 API 调用。一个调用updateFunction会更新函数代码updateFunctionConfiguration,另一个调用会更新已配置的层,这两个操作都是独立的控制平面操作,可以并行执行。这意味着,$LATEST在两个调用都完成之前,调用将会失败。为了避免这种情况,您需要创建一个新的函数版本,应用新的层,然后在两个步骤完成后更新您的集成(例如:ApiGateway)以指向新的别名。

语义化版本控制并不完美,灵活的版本控制(例如,~使用^相对版本号)意味着执行 Lambda 函数的各个组件可能在测试或生产环境中首次同时运行。这已经引发了不少问题,以至于包管理器提供了诸如 `package.json` 之类的解决方案npm shrinkwrap,但对于 Lambda 层来说,情况可能会更糟。

这就是我的观点——这才是你的包管理器应该做的。

依赖冲突

Lambda 函数的层级结构可能会导致一个棘手的 bug,这源于 Lambda 如何根据部署工件创建文件系统。如果您一直关注本博客,就会知道,zip 压缩文件本身在解压到文件系统时就可能出现一些棘手的极端情况,而 Lambda 也无法避免。创建 Lambda 函数沙箱时,主函数包会被复制到沙箱中,然后每个层级会按顺序复制到同一个文件系统目录中。这意味着包含路径和文件名相同的文件的层级会被合并。

尽管 Lambda 处理程序代码与层代码被复制到不同的目录中,但运行时会决定首先从哪里查找依赖项。这通常由环境变量中列出的目录顺序决定PATH,或者由运行时特定的变体(例如NODE_PATHRuby 的 `<version>`GEM_PATH或 Java 的 `<version>`)决定CLASS_PATH,具体文档请参见此处

假设有一个 Lambda 函数和两个层,它们都依赖于同一个库的不同版本。这些层不提供锁定文件或内容元数据,因此作为开发人员,您可能在构建时或部署时不会意识到这种依赖冲突。
Lambda 函数代码需要 A @ 1.0,第 1 层需要 A @ 2.0,第 2 层需要 A @ 3.0

运行时,层代码和函数代码会被复制到各自的目录中,但是当处理程序开始处理请求时,却因为语法错误而崩溃了!可是你的代码在本地运行正常啊?!这是怎么回事?

Lambda 层中的代码和依赖项需要访问库 ABC 的版本 2,但运行时已从函数 zip 文件加载了库 ABC 的版本 1!

Lambda 函数代码加载库 A @ 3.0!

如果这听起来匪夷所思,但它也可能发生在你身上——因为它就发生在我身上

Lambda 层能为您做什么

Lambda 层可以提高函数部署速度(但 CI 流水线也可以)。

假设有两个依赖关系相同的 Lambda 函数,一个使用了分层(A),另一个没有使用分层(B)。
如果你没有修改和部署相关的分层,那么 A 的部署时间确实会相对较短。然而,绝大多数 CI/CD 流水线都支持依赖缓存,因此大多数用户无论是否使用分层,都能轻松实现快速部署。没错,使用分层部署 CloudFormation 会稍微长一些,但最终并没有明显的优势。

Lambda 层可以在函数之间共享代码

在同一区域内,一个层可以被不同的 Lambda 函数使用。这在共享身份验证库或其他跨功能依赖项时非常有用。如果您(像我一样)需要与其他用户共享层(即使是公开共享),这一点尤其有用。

我不太认同文档中的另外两点。分层或许可以“将核心功能逻辑与依赖项分离”,但这仅仅相当于把依赖项放在另一个文件中并import进行编译。你的运行时环境已经实现了这一点,所以这一点显得有些牵强。

最后,我认为最好不要在控制台编辑器中实时编辑生产环境的 Lambda 函数代码,尤其应该为了支持这种做法而修改软件开发流程。(Cloud9 IDE 是个不错的产品,但不要使用 Lambda 控制台中的版本。)

何时应该使用 Lambda 层

Lambda 层并非一无是处,它只是一个功能强大的工具,但也存在一些不足之处(AWS 应该修复这些问题!)。不过,在某些情况下,您可以而且应该使用 Lambda 层。

  • 共享二进制文件

如果您有常用的二进制文件,例如 `<module>`ffmpeg或 `<module> sharp`,那么将这些项目编译一次并将其部署为一个层可能更方便。这样便于在不同函数之间共享,而且这个特定的层很少需要重新构建和更新。层最适合包含完善 API 接口的成熟二进制文件,这样您就无需处理我之前提到的与主要版本升级相关的部署难题。

  • 自定义运行时

广受欢迎的Bref PHP 运行时环境可以作为 Layer 提供。Bref 已预编译为 arm 和 x86 架构,因此将其用作 Layer 是一个不错的选择。Bun JavaScript 运行时环境也是如此。话虽如此,容器镜像的性能最近有了显著提升,值得重新考虑,但这又是另一个话题了。

  • Lambda 扩展

扩展是一种特殊的层,它能够访问常规 Lambda 处理程序无法访问的额外生命周期事件、异步工作和后处理功能。扩展可以与主处理程序异步执行工作,并且可以在处理程序向调用者返回结果执行代码。这使得 Lambda 扩展成为规避上述风险的一个值得考虑的例外,尤其是在它们是预编译、静态链接的二进制可执行文件时,不会出现依赖冲突。

总结

在某些特定情况下,使用 Lambda 层可能很有价值。特别是对于 Lambda 扩展或编译量大的二进制文件。但是,Lambda 层不应取代您现有的运行时特定的打包和生态系统。Lambda 层不提供语义化的版本控制,会使破坏性变更难以同步,在开发过程中造成诸多麻烦,并使您的软件容易受到依赖冲突的影响。

如果 AWS 能够提供语义化版本控制、支持层锁定文件以及与原生包管理器集成,我将乐于重新考虑这些想法。

尽可能使用软件包管理器,它功能更强大,而且已经可以帮你解决这些问题。

如果你喜欢这类内容,请订阅我的博客或在推上联系我提问。如果我在TwitchYouTube上直播,你也可以直接向我提问。

文章来源:https://dev.to/aws-heroes/stop-using-lambda-layers-use-this-instead-46o0