AWS Lambda 层(^注:层在这里指的是AWS Lambda的功能扩展。)
Lambda 函数现在已经成为无服务器架构中的常见元素,我们大多数人在各种情况下与它们打过交道。在用您喜欢的编程语言编写 Lambda 代码时,您可能遇到过这样的情况:外部依赖项可以帮助您节省开发时间,或者为您处理复杂的操作。例如,如果您要将 Lambda 函数连接到像 Amazon RDS 这样的数据库(例如 PostgreSQL 或 MySQL),您需要一个库来高效管理这些连接。
这引发了一个重要问题:将这些外部库包含在您的Lambda函数中的最好方式是什么?例如,如果你正在使用Node.js,你可能会想知道是否将node_modules
直接打包进最终的包中,或者是否在运行时安装所需的库。
这里就是 AWS Lambda 层大显身手的地方。Lambda 层让你可以在 Lambda 函数中用到外部包,不再需要在主要部署包中管理依赖项,从而让你的开发流程更加简单和高效。
用Lambda 层管理您的依赖项
Lambda 层 (Lambda Layer) 是一个包含额外代码或数据的 .zip
文件。通常包含库、自定义运行时或配置文件,这可以帮助你更有效地管理依赖项。
如果你在使用 Go 或 Rust 的 Lambda 函数,建议不要 使用分层。对于 Go 和 Rust 函数,你提供的是一个可执行程序,包含编译后的函数代码及其所有依赖项。将依赖项放在分层中会强制函数在初始化阶段手动加载额外的组件和依赖,从而增加冷启动延迟。为了达到 Go 和 Rust 函数的最佳性能,请将所有依赖项包含在部署包中。
以下对比说明了两个共享相同库的函数在管理依赖上的区别。
Lambda 无层级结构
考虑两个依赖于 pg
、Axios
和 Moment.js
这样的库的函数。在你需要维护这两个函数并且某个库发布了新版本的情况下,你需要分别更新每个函数的依赖,再重新部署每个函数——这个过程既耗时又容易出错,十分麻烦。
使用 Lambda 层,您可以集中管理依赖项并简化管理流程。每个层可以包含多个依赖项,您可以根据需要将这些依赖项附加到 Lambda 函数上。一旦附加了层,AWS Lambda 会在运行时加载这些依赖项,从而提高维护效率,减少重复更新。
带有层次的Lambda
通过使用Lambda Layers,你可以提升Lambda函数的效率和易维护性,尤其是在复杂的情况里,依赖项频繁更新或在多个函数间共享。
使用Lambda层的好处有, 主要包括你可能出于多种原因考虑使用层次:
- 减少部署包大小
通过将依赖项放在一个层中,你可以让部署包更小且更整洁,因为主包中只保留核心功能逻辑。 - 逻辑与依赖分离
层允许你将功能逻辑与依赖项分离。你可以更新依赖项而不更改函数代码,从而促进清晰的职责分离。 - 跨函数共享依赖
创建层后,你可以将其附加到您账户中的多个函数。如果没有层,你需要在每个函数的部署包中包含相同的依赖项,从而造成重复。 - 启用Lambda控制台代码编辑器
Lambda控制台代码编辑器是一个方便的小代码更新工具。但是,如果您的部署包过大,编辑器将无法正常工作。使用层可以减小包的大小,并允许您直接在控制台中快速更新代码。
当你给Lambda函数添加一个层时,该层的内容会被提取到函数执行环境中的 /opt
目录下。所有受支持的Lambda运行环境都包含了指向 /opt
目录下特定路径的引用,从而可以直接访问你在层中添加的内容。
如果有多个带有相同目录结构的 Lambda 层(例如,带有 node_modules
文件夹的层),Lambda 会将这些层合并,让你可以无缝访问所有依赖项而不会互相覆盖。
- 层数限制:每个功能最多可以附加每个功能五层。
- 部署格式:层仅支持以
.zip
归档形式部署的 Lambda 函数。对于基于容器的 Lambda 函数,这些依赖项和运行时库必须包含在容器镜像中。
每个层版本都是特定层状态的不可更改的快照。当你创建一个新层时,Lambda 会创建第一个版本号为 1
的层版本。每次更新层时,都会递增版本号。
每个层版本都有一个唯一的 Amazon 资源名称 (ARN),用于标识。当您将层添加到 Lambda 函数时,请指定确切的层版本,以确保函数引用正确的依赖关系。
打包你的图层内容Lambda 层(Lambda layer)是一个包含辅助代码或数据的 .zip
文件存档,比如 node_modules
和配置文件。要创建层,只需将所有必需内容打包进一个 .zip
文件。
因为 Lambda 函数运行在 Amazon Linux 上,请确保任何编译依赖项都与 Linux 环境相兼容。
每个 Lambda 运行时的层的路径信息当你将一个层附加到函数上后,Lambda 会将层内容加载到函数执行环境的 /opt
目录下。对于每个运行时,PATH
环境变量会包含 /opt
目录中的特定路径,使运行时可以自动找到依赖项。为了确保 Lambda 能找到你的层内容,请按照特定于运行时的路径来组织你的 .zip
文件。
假设你正在为 axios
包在 NodeJs 运行环境创建一个层。这个层的 .zip
文件应该具有如下结构:
axios.zip
└ 这里是 nodejs/node_modules/axios 目录
基于示例学习:Terraform 和 Node.js 项目由于你在使用 Node.js,所以应该从顶层的
nodejs
目录开始,并接着使用库路径 (nodejs/node_modules/
)。这样的目录结构可以让 Lambda 正确提取和使用层的内容。如果结构不正确,运行时可能无法找到所需的依赖项,导致执行出错。
学习变得更有吸引力,尤其是在应用所学知识时,让我们通过构建一个小项目来更好地理解Lambda Layers的工作原理。我们将使用两个在无服务器工作流中常见的技术:Terraform(作为我们的代码定义基础设施工具)和 Node.js。
项目 GitHub 地址: https://github.com/JoudAwad97/lambda-layer-terraform-nodejs/tree/main
Terraform 项目的结构安装说明可在
README.md
文件中找到。
这里是对项目结构的简要说明,让你对项目有个大概了解:
📦项目根文件夹
┣ 📂lambda-functions # 包含用于部署的Lambda函数及其层
┃ ┣ 📂function-1
┃ ┃ ┗ 📜function-1.mjs
┃ ┣ 📂function-2
┃ ┃ ┗ 📜function-2.mjs
┃ ┗ 📂layers # 包含依赖或共享代码的层
┃ ┃ ┗ 📂axios
┃ ┃ ┃ ┗ 📂nodejs
┃ ┃ ┃ ┃ ┣ 📂node_modules
┃ ┃ ┃ ┃ ┣ 📜package-lock.json
┃ ┃ ┃ ┃ ┗ 📜package.json
┃ ┃ ┗ 📂pg
┃ ┃ ┃ ┗ 📂nodejs
┃ ┃ ┃ ┃ ┣ 📂node_modules
┃ ┃ ┃ ┃ ┣ 📜package-lock.json
┃ ┃ ┃ ┃ ┗ 📜package.json
┣ 📂modules # Terraform模块
┃ ┣ 📂lambda
┃ ┃ ┣ 📜main.tf
┃ ┃ ┣ 📜output.tf
┃ ┃ ┗ 📜variables.tf
┃ ┗ 📂lambda-layers
┃ ┃ ┣ 📜main.tf
┃ ┃ ┣ 📜output.tf
┃ ┃ ┗ 📜variables.tf
┣ 📂scripts # 用于安装层依赖项的脚本
┃ ┗ 📂lambda-layer-dependencies-installer
┃ ┃ ┗ 📜run.sh
┣ 📜lambda-layers.tf # 使用lambda-layers模块提供层
┣ 📜lambda.tf # 使用lambda模块提供Lambda函数
┣ 📜locals.tf
┣ 📜providers.tf
┗ 📜providers2.tf
了解层结构是怎么样的
让我们来看看这个项目中的一层结构是怎样的。
📦项目根目录文件夹
┣ 📂lambda-functions文件夹
┃ ┗ 📂layers文件夹
┃ ┃ ┣ 📂axios文件夹
┃ ┃ ┃ ┗ 📂nodejs文件夹
┃ ┃ ┃ ┃ ┣ 📂node_modules文件夹
┃ ┃ ┃ ┃ ┣ 📜package-lock.json文件
┃ ┃ ┃ ┃ ┗ 📜package.json文件
如下所示,每一层都是从一个以依赖项命名的顶层文件夹开始(例如,axios
)。在每个层文件夹内,你会在这个 nodejs
目录中找到 node_modules
、package.json
和 package-lock.json
这些文件。具体的文件夹结构非常关键;对于 Node.js,你需要确保有一个顶层的 nodejs
文件夹,这样才能让 Lambda 正确识别并加载层的内容。错误的结构可能会在运行时引发问题。
这是功能目录如下:
📦项目根目录文件夹
┣ 📂lambda函数
┃ ┣ 📂函数1文件夹
┃ ┃ └ 📜function-1.mjs
每个 Lambda 函数目录中包含一个 .mjs
JavaScript 文件,该文件作为函数的入口。请注意,这里没有 node_modules
目录这一项,依赖项则是通过 Lambda 层来管理的。
在 function-1.mjs
脚本中有代码:
import axios from "axios";
import pg from "pg";
export async function handler(_event) {
console.log("function-1 正在运行...");
注释: 'pg' 包已导入。此设置非常基础,没有实际建立数据库连接。如果 'pg' 包不可用,函数将抛出一个错误。
console.log("模拟数据库连接...");
const response = await axios.get(
"https://jsonplaceholder.typicode.com/todos/1"
);
console.log("function-1 运行结束...");
return {
statusCode: 200,
body: JSON.stringify(response.data),
};
}
在这个基础功能中,我们导入 axios
来发起一个示例 API 调用,并导入 pg
来模拟数据库连接的过程。如果缺少这些包,该函数会抛出错误,但是由于已经加载了附加的层,axios
和 pg
在运行时是可用的。
设置完成后,你应该能在 AWS Lambda 控制台中找到 function-1
。在控制台的概览页面上,你会看到已附加的图层——比如这里就有两个图层。
功能 1 控制台简介
如果你点击控制台中的“层”,你将看到附属于该函数的两层及其版本号。如果这些层有更新,AWS Lambda 会自动更新每个层的最新版本。
Lambda 层功能控制台界面简介
有了这个设置,你现在就可以高效地管理多个组件之间的依赖,并且这些依赖无需手动干预就能维护。
最后
AWS Lambda 层提供了一种强大的方式来简化无服务器应用程序的依赖管理。通过使用层,您可以保持部署包精简,跨函数共享通用库,使这些库可以被多个函数重用,并在核心函数逻辑和其依赖项之间保持分离。在我们的项目示例中,我们看到 Terraform 和 Node.js 如何协同工作来部署具备可重用、版本化依赖的功能,使过程既高效又可扩展。
无论你是在管理几个函数还是大规模的无服务器架构,利用Lambda层可以节省时间并简化更新过程。通过遵循最佳实践进行打包和组织层,你就能很好地管理函数间的依赖关系,改善冷启动性能,让未来的维护变得轻松。
关注我,获得更多内容共同学习,写下你的评论
评论加载中...
作者其他优质文章