亚马逊AWS官方博客

AWS Lambda 的新功能 — 容器映像支持

借助 AWS Lambda,您可以在不考虑服务器的情况下上传代码并运行。许多客户喜欢这种工作方式,但是如果您为开发工作流投资了容器工具,那么使用同样的方法来通过 Lambda 构建应用程序并不容易。

为了帮助您实现这一目标,您现在可以将 Lambda 函数打包和部署为最大容量为 10 GB容器映像。通过这种方式,您还可以轻松构建和部署依赖于大量依赖项的更大型工作负载,例如机器学习或数据密集型工作负载。就像作为 ZIP 存档打包的函数一样,作为容器映像部署的函数也可以获益于操作简单性、自动扩展、高可用性以及与许多服务的原生集成。

我们为所有支持的 Lambda 运行时(Python、Node.js、Java、.NET、Go、Ruby)提供 基础映像,以便您可以轻松添加代码和依赖项。我们还有针对自定义运行时基于 Amazon Linux 的基础映像,您可以通过实现 Lambda Runtime API 对其进行扩展以纳入自己的运行时 。

您可以将自己的任意基础映像部署到 Lambda,例如基于 AlpineDebian Linux 的映像。要使用 Lambda,这些映像必须实现 Lambda Runtime API。为了让您可以更轻松地构建自己的基础映像,我们即将发布 Lambda Runtime Interface Client,为所有支持的运行时实现 Runtime API。这些实现可通过原生软件包管理器获得,因此您可以轻松地在映像中获取它们,并且这些软件包使用开源许可证与社区共享。

我们还将以开源的方式发布 Lambda Runtime Interface Emulator,让您能够对容器映像执行本地测试,以检查在部署到 Lambda 后是否可正常运行。Lambda Runtime Interface Emulator 包含在 AWS 提供的所有基础映像中,也可以与任意映像一起使用。

您的容器映像还可以使用 Lambda Extensions API 将监控、安全和其他工具与 Lambda 执行环境集成。

要部署容器映像,需从 Amazon Elastic Container Registry 存储库中选择一个容器映像。让我们通过几个示例来看看如何进行实际操作。首先使用 AWS 提供的 Node.js 映像,然后为 Python 构建自定义映像。

使用 AWS 提供的 Node.js 基础映像
以下是使用 PDFKit 模块生成 PDF 文件的简单 Node.js Lambda 函数的代码 (app.js)。每次调用它时,它都会创建一封包含 faker.js 模块生成的随机数据的新邮件。该函数的输出使用 Amazon API Gateway 的语法返回 PDF 文件。

const PDFDocument = require('pdfkit');
const faker = require('faker');
const getStream = require('get-stream');

exports.lambdaHandler = async (event) => {

    const doc = new PDFDocument();

    const randomName = faker.name.findName();

    doc.text(randomName, { align: 'right' });
    doc.text(faker.address.streetAddress(), { align: 'right' });
    doc.text(faker.address.secondaryAddress(), { align: 'right' });
    doc.text(faker.address.zipCode() + ' ' + faker.address.city(), { align: 'right' });
    doc.moveDown();
    doc.text('Dear ' + randomName + ',');
    doc.moveDown();
    for(let i = 0; i < 3; i++) {
        doc.text(faker.lorem.paragraph());
        doc.moveDown();
    }
    doc.text(faker.name.findName(), { align: 'right' });
    doc.end();

    pdfBuffer = await getStream.buffer(doc);
    pdfBase64 = pdfBuffer.toString('base64');

    const response = {
        statusCode: 200,
        headers: {
            'Content-Length': Buffer.byteLength(pdfBase64),
            'Content-Type': 'application/pdf',
            'Content-disposition': 'attachment;filename=test.pdf'
        },
        isBase64Encoded: true,
        body: pdfBase64
    };
    return response;
};

我使用 npm 来初始化软件包并在 package.json 文件中添加我需要的三个依赖项。由此,我还创建了 package-lock.json 文件。我将把它添加到容器映像中,以获得更可预测的结果。

$ npm init
$ npm install pdfkit
$ npm install faker
$ npm install get-stream

现在,我将创建 Dockerfile 来为我的 Lambda 函数创建容器映像,从 AWS 为 nodejs12.x 运行时提供的基础映像开始:

FROM amazon/aws-lambda-nodejs:12
COPY app.js package*.json ./
RUN npm install
CMD [ "app.lambdaHandler" ]

Dockerfile 正在将源代码 (app.js) 和描述软件包和依赖项的文件(package.jsonpackagelock.json)添加到基础映像中。然后,我运行 npm 来安装依赖项。我将 CMD 设置为函数处理程序,但是也可以稍后在配置 Lambda 函数时作为参数覆盖来完成。

我使用 Docker CLI 在本地构建 random-letter 容器映像:

$ docker build -t random-letter

为了检查是否能够正常运行,我使用 Lambda Runtime Interface Emulator 在本地启动容器映像:

$ docker run -p 9000:8080 random-letter:latest

现在,我使用 cURL 测试函数调用。在这里,我将传递了一个空的 JSON 有效负载。

$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'

如果存在错误,我可以在本地进行修复。假如一切正常,我就可以转到下一步。

为了上传容器映像,我在账户中创建了一个新的 ECR 存储库,然后标记本地映像以将其推送到 ECR。为了识别容器映像中的软件漏洞,我启用了 ECR 映像扫描

$ aws ecr create-repository --repository-name random-letter --image-scanning-configuration scanOnPush=true
$ docker tag random-letter:latest 123412341234.dkr.ecr.sa-east-1.amazonaws.com/random-letter:latest
$ aws ecr get-login-password | docker login --username AWS --password-stdin 123412341234.dkr.ecr.sa-east-1.amazonaws.com
$ docker push 123412341234.dkr.ecr.sa-east-1.amazonaws.com/random-letter:latest

在这里,我使用 AWS 管理控制台来完成函数的创建。您还可以使用 AWS 无服务器应用程序模型,该模型已更新以添加对容器映像的支持。

Lambda 控制台中,单击创建函数。选择容器映像,为函数命名,然后选择浏览映像以在我的 ECR 存储库中查找合适的映像。

控制台的屏幕截图

选择存储库后,我使用我上传的最新映像。当我选择映像时,Lambda 会将其转换为底层映像摘要(位于下图中标签的右侧)。您可以使用 docker images --digests 命令在本地查看映像摘要。这样,即使最新的标签分配给了新的映像,该函数也会使用相同的映像,并且可以免受意外部署的影响。您可以在函数代码中更新要使用的映像。更新函数配置对使用的映像没有影响,即使在此期间将标签重新分配给另一个映像也是如此。

控制台的屏幕截图

我也可以选择覆盖一些容器映像值。我现在不这样做,但通过这种方式(例如通过覆盖 CMD 值中的函数处理程序),我可以创建可用于不同函数的映像。

控制台的屏幕截图

我将所有其他选项保留为默认选项,然后选择创建函数

创建或更新函数的代码时,Lambda 平台会优化新的和更新的容器映像,以便为接收调用做好准备。此优化需要几秒钟或几分钟,具体取决于映像的大小。之后,该函数就可以被调用了。我在控制台中对该函数进行测试。

控制台的屏幕截图

它可以正常工作! 现在让我们将 API Gateway 添加为触发器。选择添加触发器使用 HTTP API 添加 API Gateway。为简单起见,我让 API 的身份验证保持开放状态。

控制台的屏幕截图

现在,我点击几次 API 终端节点,以下载几封随机邮件。

控制台的屏幕截图

它能够按预期工作! 以下是一些使用 faker.js 模块中的随机数据生成的 PDF 文件。

示例应用程序的输出。

 

为 Python 构建自定义映像
有时,您需要使用自定义容器映像,例如为了遵循公司的指导原则,或是为了使用我们不支持的运行时版本。

在此,我想构建一个映像以使用 Python 3.9。我的函数的代码 (app.py) 非常简单,我只想打个招呼并显示正在使用的 Python 版本。

import sys
def handler(event, context): 
    return 'Hello from AWS Lambda using Python' + sys.version + '!'

正如之前提到的,我们将与您分享适用于所有受支持运行时的 Lambda Runtime Interface Client(实现 Runtime API)开源实现。在此,我以基于 Alpine Linux 的 Python 映像作为基础。然后,我将适用于 Python 的 Lambda Runtime Interface Client(链接即将发布)添加到映像中。以下是 Dockerfile 的内容:

# 定义全局参数
ARG FUNCTION_DIR="/home/app/"
ARG RUNTIME_VERSION="3.9"
ARG DISTRO_VERSION="3.12"

# 阶段 1 - 捆绑基础映像 + 运行时
# 获取映像的新副本并安装 GCC
FROM python:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} AS python-alpine
# 安装 GCC(Alpine 使用 musl,但我们使用 GCC 进行编译和关联依赖项)
RUN apk add --no-cache \
    libstdc++

# 阶段 2 - 构建函数和依赖项
FROM python-alpine AS build-image
# 安装 aws-lambda-cpp 构建依赖项
RUN apk add --no-cache \
    build-base \
    libtool \
    autoconf \
    automake \
    libexecinfo-dev \
    make \
    cmake \
    libcurl
# 在构建的这个阶段纳入全局参数
ARG FUNCTION_DIR
ARG RUNTIME_VERSION
# 创建函数目录
RUN mkdir -p ${FUNCTION_DIR}
# 复制处理程序函数
COPY app/* ${FUNCTION_DIR}
# 可选 – 安装函数的依赖项
# RUN python${RUNTIME_VERSION} -m pip install -r requirements.txt --target ${FUNCTION_DIR}
# 为 Python 安装 Lambda 运行时接口客户端
RUN python${RUNTIME_VERSION} -m pip install awslambdaric --target ${FUNCTION_DIR}

# 阶段 3 - 最终的运行时映像
# 获取 Python 映像的新副本
FROM python-alpine
# 在构建的这个阶段纳入全局参数
ARG FUNCTION_DIR
# 将工作目录设置为函数的根目录
WORKDIR ${FUNCTION_DIR}
# 复制构建的依赖项
COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR}
# (可选)添加 Lambda Runtime Interface Emulator 并在 ENTRYPOINT 中使用脚本实现更简单的本地运行
COPY https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /usr/bin/aws-lambda-rie
RUN chmod 755 /usr/bin/aws-lambda-rie
COPY entry.sh /
ENTRYPOINT [ "/entry.sh" ]
CMD [ "app.handler" ]

这次的 Dockerfile 更加清晰,遵循多阶段构建 Docker 最佳实践,分三个阶段构建最终映像。您可以使用这种三阶段方法来构建自己的自定义映像:

  • 阶段 1 是使用运行时(本例中的运行时为 Python 3.9)和 GCC(我们在阶段 2 中用于编译和关联依赖项)构建基础映像。
  • 阶段 2 是安装 Lambda Runtime Interface Client 并构建函数和依赖项。
  • 阶段 3 是将阶段 2 的输出添加到阶段 1 中构建的基础映像,以创建最终映像。这里我还添加了 Lambda Runtime Interface Emulator,但这是可选的,请参阅下文。

我在下面创建了 entry.sh 脚本将其用作 ENTRYPOINT。它执行适用于 Python 的 Lambda Runtime Interface Client。如果是在本地执行,则 Lambda Runtime Interface Emulator 将包装 Runtime Interface Client。

#!/bin/sh
if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
    exec /usr/bin/aws-lambda-rie /usr/local/bin/python -m awslambdaric
else
    exec /usr/local/bin/python -m awslambdaric
fi

现在,我可以使用 Lambda Runtime Interface Emulator 在本地检查函数和容器映像是否正常工作:

$ docker run -p 9000:8080 lambda/python:3.9-alpine3.12

不将 Lambda Runtime Interface Emulator 纳入容器映像中
将 Lambda Runtime Interface Emulator 添加到自定义容器映像为可选项。如果不将其纳入其中,则可以按照以下步骤在本地机器上安装 Lambda Runtime Interface Emulato 以进行本地测试:

  • 我删除 Dockerfile 阶段 3 中复制 Lambda Runtime Interface Emulator (aws-lambda-rie) 和 entry.sh 脚本的命令。在此,我不需要 entry.sh 脚本。
  • 我使用这个 ENTRYPOINT 默认启动 Lambda Runtime Interface Client:
    ENTRYPOINT [ "/usr/local/bin/python", “-m”, “awslambdaric” ]
  • 我运行这些命令来在我的本地机器上安装 Lambda Runtime Interface Emulator,例如在 ~/.aws-lambda-rie 下:
mkdir -p ~/.aws-lambda-rie
curl -Lo ~/.aws-lambda-rie/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie
chmod +x ~/.aws-lambda-rie/aws-lambda-rie

在 Lambda Runtime Interface Emulator 安装到我的本地机器上后,我可以在启动容器时挂载它。现在在本地启动容器的命令是(假设 Lambda Runtime Interface Emulator 位于 ~/.aws-lambda-rie

docker run -d -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 \
       --entrypoint /aws-lambda/aws-lambda-rie lambda/python:3.9-alpine3.12
       /lambda-entrypoint.sh app.handler

测试 Python 自定义映像
无论使用哪种方式,当容器在本地运行时,我都可以使用 cURL 测试函数调用:

curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'

输出符合我的预期!

"Hello from AWS Lambda using Python3.9.0 (default, Oct 22 2020, 05:03:39) \n[GCC 9.3.0]!"

我将映像推送到 ECR 并像以前一样创建函数。以下是我的测试在控制台中显示的内容:

控制台的屏幕截图

我的基于 Alpine 的自定义容器映像可以在 Lambda 上运行 Python 3.9 了!

现已推出
您现在可以使用容器映像在以下区域部署 Lambda 函数:美国东部(弗吉尼亚北部)美国东部(俄亥俄)美国西部(俄勒冈)亚太地区(东京)亚太地区(新加坡)欧洲(爱尔兰)欧洲(法兰克福)南美洲(圣保罗)。我们正在努力尽快在更多区域增加支持。除了 ZIP 存档外,还提供容器映像支持,我们将继续支持 ZIP 打包格式。

使用此功能不会产生额外费用。您需要为 ECR 存储库 Lambda(按正常的定价)付费。

您可以将 AWS Lambda 中的容器映像支持与控制台AWS 命令行界面 (CLI)AWS 开发工具包AWS 无服务器应用程序模型以及来自 AWS 合作伙伴的解决方案一起使用,这些解决方案包括 Aqua SecurityDatadogEpsagonHashiCorp TerraformHoneycombLumigoPulumiStackerySumo LogicThundra

这项新功能创造了新的使用场景,简化了与开发管道的集成,并让您可以轻松使用自定义映像和您喜欢的编程平台构建无服务器应用程序。

了解在 AWS Lambda 中使用容器映像的更多信息并开始使用。

Danilo