添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

在AWS Lambda中使用uv

AWS Lambda 是一项无服务器计算服务,可让您无需预置或管理服务器即可运行代码。

你可以使用uv与AWS Lambda来管理你的Python依赖项,构建你的部署包,并部署你的Lambda函数。

查看 uv-aws-lambda-example 项目,了解使用uv将应用程序部署到AWS Lambda时的最佳实践示例。

首先,假设我们有一个结构如下的最小化FastAPI应用:

project
├── pyproject.toml
└── app
    ├── __init__.py
    └── main.py

其中 pyproject.toml 文件包含:

pyproject.toml
[project]
name = "uv-aws-lambda-example"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
    # FastAPI is a modern web framework for building APIs with Python.
    "fastapi",
    # Mangum is a library that adapts ASGI applications to AWS Lambda and API Gateway.
    "mangum",
[dependency-groups]
dev = [
    # In development mode, include the FastAPI development server.
    "fastapi[standard]>=0.115",

main.py 文件包含:

app/main.py
import logging
from fastapi import FastAPI
from mangum import Mangum
logger = logging.getLogger()
logger.setLevel(logging.INFO)
app = FastAPI()
handler = Mangum(app)
@app.get("/")
async def root() -> str:
    return "Hello, world!"

我们可以在本地运行此应用程序:

$ uv run fastapi dev

从这里开始,在网页浏览器中打开 http://127.0.0.1:8000/ 将会显示"Hello, world!"

部署Docker镜像

要部署到AWS Lambda,我们需要构建一个容器镜像,该镜像需包含应用程序代码和所有依赖项,并输出到单一目录中。

我们将遵循Docker指南中概述的原则(特别是多阶段构建),以确保最终镜像尽可能小巧且缓存友好。

在第一阶段,我们将用一个目录存放所有应用程序代码和依赖项。在第二阶段,我们会将这个目录复制到最终镜像中,同时省略构建工具和其他不必要的文件。

Dockerfile
FROM ghcr.io/astral-sh/uv:0.7.6 AS uv
# First, bundle the dependencies into the task root.
FROM public.ecr.aws/lambda/python:3.13 AS builder
# Enable bytecode compilation, to improve cold-start performance.
ENV UV_COMPILE_BYTECODE=1
# Disable installer metadata, to create a deterministic layer.
ENV UV_NO_INSTALLER_METADATA=1
# Enable copy mode to support bind mount caching.
ENV UV_LINK_MODE=copy
# Bundle the dependencies into the Lambda task root via `uv pip install --target`.
# Omit any local packages (`--no-emit-workspace`) and development dependencies (`--no-dev`).
# This ensures that the Docker layer cache is only invalidated when the `pyproject.toml` or `uv.lock`
# files change, but remains robust to changes in the application code.
RUN --mount=from=uv,source=/uv,target=/bin/uv \
    --mount=type=cache,target=/root/.cache/uv \
    --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    uv export --frozen --no-emit-workspace --no-dev --no-editable -o requirements.txt && \
    uv pip install -r requirements.txt --target "${LAMBDA_TASK_ROOT}"
FROM public.ecr.aws/lambda/python:3.13
# Copy the runtime dependencies from the builder stage.
COPY --from=builder ${LAMBDA_TASK_ROOT} ${LAMBDA_TASK_ROOT}
# Copy the application code.
COPY ./app ${LAMBDA_TASK_ROOT}/app
# Set the AWS Lambda handler.
CMD ["app.main.handler"]

要部署到基于ARM架构的AWS Lambda运行时,请将public.ecr.aws/lambda/python:3.13替换为public.ecr.aws/lambda/python:3.13-arm64

我们可以使用以下命令构建镜像,例如:

$ uv lock
$ docker build -t fastapi-app .

该Dockerfile结构的核心优势如下:

  • 最小化镜像体积。 通过采用多阶段构建技术,我们可以确保最终镜像仅包含应用程序代码和依赖项。例如,uv二进制文件本身并不会打包进最终镜像。
  • 最大化缓存复用。 通过将应用程序依赖项与应用程序代码分开安装,我们可以确保仅在依赖项变更时才会使Docker层缓存失效。
  • 具体来说,在修改应用程序源代码后重新构建镜像可以复用缓存的层,从而实现毫秒级的构建速度:

     => [internal] load build definition from Dockerfile                                                                 0.0s
     => => transferring dockerfile: 1.31kB                                                                               0.0s
     => [internal] load metadata for public.ecr.aws/lambda/python:3.13                                                   0.3s
     => [internal] load metadata for ghcr.io/astral-sh/uv:latest                                                         0.3s
     => [internal] load .dockerignore                                                                                    0.0s
     => => transferring context: 106B                                                                                    0.0s
     => [uv 1/1] FROM ghcr.io/astral-sh/uv:latest@sha256:ea61e006cfec0e8d81fae901ad703e09d2c6cf1aa58abcb6507d124b50286f  0.0s
     => [builder 1/2] FROM public.ecr.aws/lambda/python:3.13@sha256:f5b51b377b80bd303fe8055084e2763336ea8920d12955b23ef  0.0s
     => [internal] load build context                                                                                    0.0s
     => => transferring context: 185B                                                                                    0.0s
     => CACHED [builder 2/2] RUN --mount=from=uv,source=/uv,target=/bin/uv     --mount=type=cache,target=/root/.cache/u  0.0s
     => CACHED [stage-2 2/3] COPY --from=builder /var/task /var/task                                                     0.0s
     => CACHED [stage-2 3/3] COPY ./app /var/task                                                                        0.0s
     => exporting to image                                                                                               0.0s
     => => exporting layers                                                                                              0.0s
     => => writing image sha256:6f8f9ef715a7cda466b677a9df4046ebbb90c8e88595242ade3b4771f547652d                         0.0
    

    构建完成后,我们可以将镜像推送到 Elastic Container Registry (ECR),例如:

    $ aws ecr get-login-password --region 
    
    
    
    
        
    region | docker login --username AWS --password-stdin aws_account_id.dkr.ecr.region.amazonaws.com
    $ docker tag fastapi-app:latest aws_account_id.dkr.ecr.region.amazonaws.com/fastapi-app:latest
    $ docker push aws_account_id.dkr.ecr.region.amazonaws.com/fastapi-app:latest
    

    最后,我们可以使用AWS管理控制台或AWS CLI将镜像部署到AWS Lambda,例如:

    $ aws lambda create-function \
       --function-name myFunction \
       --package-type Image \
       --code ImageUri=aws_account_id.dkr.ecr.region.amazonaws.com/fastapi-app:latest \
       --role arn:aws:iam::111122223333:role/my-lambda-role
    是通过以下方式创建的:

    $ aws iam create-role \
       --role-name my-lambda-role \
       --assume-role-policy-document '{"Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}'
    

    或者,使用以下方式更新现有函数:

    $ aws lambda update-function-code \
       --function-name myFunction \
       --image-uri aws_account_id.dkr.ecr.region.amazonaws.com/fastapi-app:latest \
       --publish
    

    要测试Lambda函数,我们可以通过AWS管理控制台或AWS CLI来调用它,例如:

    $ aws lambda invoke \
       --function-name myFunction \
       --payload file://event.json \
       --cli-binary-format raw-in-base64-out \
       response.json
      "StatusCode": 200,
      "ExecutedVersion": "$LATEST"
    

    其中 event.json 包含要传递给 Lambda 函数的事件负载:

    event.json
    {
      "httpMethod": "GET",
      "path": "/",
      "requestContext": {},
      "version": "1.0"
    

    response.json 包含来自 Lambda 函数的响应:

    response.json
    {
      "statusCode": 200,
      "headers": {
        "content-length": "14",
        "content-type": "application/json"
      "multiValueHeaders": {},
      "body": "\"Hello, world!\"",
      "isBase64Encoded": false
    

    详情请参阅 AWS Lambda文档

    工作区支持

    如果项目包含本地依赖项(例如通过Workspaces),这些依赖项也必须包含在部署包中。

    我们将从扩展上述示例开始,加入对本地开发的名为library的库的依赖。

    首先,我们将创建这个库本身:

    $ uv init --lib library
    $ uv add ./library
    

    project目录中运行uv init会自动将project转换为工作区,并将library添加为工作区成员:

    pyproject.toml
    [project]
    name = "uv-aws-lambda-example"
    version = "0.1.0"
    requires-python = ">=3.13"
    dependencies = [
        # FastAPI is a modern web framework for building APIs with Python.
        "fastapi",
        # A local library.
        "library",
        # Mangum is a library that adapts ASGI applications to AWS Lambda and API Gateway.
        "mangum",
    [dependency-groups]
    dev = [
        # In development mode, include the FastAPI development server.
        "fastapi[standard]",
    [tool.uv.workspace]
    members = ["library"]
    [tool.uv.sources]
    lib = { workspace = true }
    

    默认情况下,uv init --lib会创建一个导出hello函数的包。我们将修改应用程序源代码来调用该函数:

    app/main.py
    import logging
    from fastapi import FastAPI
    from mangum import Mangum
    from library import hello
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    app = FastAPI()
    handler = Mangum(app)
    @app.get("/")
    async def root() -> str:
        return hello()
    

    我们可以在本地运行修改后的应用程序:

    $ uv run fastapi dev
    

    并确认在网页浏览器中打开http://127.0.0.1:8000/会显示"来自库的问候!" (而不是"Hello, World!")

    最后,我们将更新Dockerfile,将本地库包含在部署包中:

    Dockerfile
    FROM ghcr.io/astral-sh/uv:0.7.6 AS uv
    # First, bundle the dependencies into the task root.
    FROM public.ecr.aws/lambda/python:3.13 AS builder
    # Enable bytecode compilation, to improve cold-start performance.
    ENV UV_COMPILE_BYTECODE=1
    # Disable installer metadata, to create a deterministic layer.
    ENV
    
    
    
    
        
     UV_NO_INSTALLER_METADATA=1
    # Enable copy mode to support bind mount caching.
    ENV UV_LINK_MODE=copy
    # Bundle the dependencies into the Lambda task root via `uv pip install --target`.
    # Omit any local packages (`--no-emit-workspace`) and development dependencies (`--no-dev`).
    # This ensures that the Docker layer cache is only invalidated when the `pyproject.toml` or `uv.lock`
    # files change, but remains robust to changes in the application code.
    RUN --mount=from=uv,source=/uv,target=/bin/uv \
        --mount=type=cache,target=/root/.cache/uv \
        --mount=type=bind,source=uv.lock,target=uv.lock \
        --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
        uv export --frozen --no-emit-workspace --no-dev --no-editable -o requirements.txt && \
        uv pip install -r requirements.txt --target "${LAMBDA_TASK_ROOT}"
    # If you have a workspace, copy it over and install it too.
    # By omitting `--no-emit-workspace`, `library` will be copied into the task root. Using a separate
    # `RUN` command ensures that all third-party dependencies are cached separately and remain
    # robust to changes in the workspace.
    RUN --mount=from=uv,source=/uv,target=/bin/uv \
        --mount=type=cache,target=/root/.cache/uv \
        --mount=type=bind,source=uv.lock,target=uv.lock \
        --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
        --mount=type=bind,source=library,target=library \
        uv export --frozen --no-dev --no-editable -o requirements.txt && \
        uv pip install -r requirements.txt --target "${LAMBDA_TASK_ROOT}"
    FROM public.ecr.aws/lambda/python:3.13
    # Copy the runtime dependencies from the builder stage.
    COPY --from=builder ${LAMBDA_TASK_ROOT} ${LAMBDA_TASK_ROOT}
    # Copy the application code.
    COPY ./app ${LAMBDA_TASK_ROOT}/app
    # Set the AWS Lambda handler.
    CMD ["app.main.handler"]
    

    要部署到基于ARM架构的AWS Lambda运行时,请将public.ecr.aws/lambda/python:3.13替换为public.ecr.aws/lambda/python:3.13-arm64

    从这里开始,我们可以像之前一样构建并部署更新后的镜像。

    部署zip压缩包

    AWS Lambda 也支持通过 zip 归档文件进行部署。对于简单的应用程序,zip 归档文件相比 Docker 镜像可能是一种更直接高效的部署方式;但 zip 归档文件的大小限制为 250 MB

    回到FastAPI示例,我们可以通过以下方式将应用程序依赖项打包到本地目录中,以便在AWS Lambda上使用:

    $ uv export --frozen --no-dev --no-editable -o requirements.txt
    $ uv pip install \
       --no-installer-metadata \
       --no-compile-bytecode \
       --python-platform x86_64-manylinux2014 \
       --python 3.13 \
       --target packages \
       -r requirements.txt
    

    要部署到基于ARM架构的AWS Lambda运行时,请将x86_64-manylinux2014替换为aarch64-manylinux2014

    AWS Lambda文档,我们可以 将这些依赖项打包成zip文件,方法如下:

    $ cd packages
    $ zip -r ../package.zip .
    $ cd ..
    

    最后,我们可以将应用程序代码添加到zip压缩包中:

    $ zip -r package.zip app
    

    然后我们可以通过AWS管理控制台或AWS CLI将zip存档部署到AWS Lambda,例如:

    $ aws lambda create-function \
       --function-name myFunction \
       --runtime python3.13 \
       --zip-file fileb://package.zip \
       --handler app.main.handler \
       --role arn:aws:iam::111122223333:role/service-role/my-lambda-role
    是通过以下方式创建的:

    $ aws iam create-role \
       --role-name my-lambda-role \
       --assume-role-policy-document '{"Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}'
    

    或者,使用以下方式更新现有函数:

    $ aws lambda update-function-code \
       --function-name myFunction \
       --zip-file fileb://package.zip
    

    默认情况下,AWS管理控制台假设Lambda入口点为lambda_function.lambda_handler。 如果您的应用程序使用不同的入口点,则需要在AWS管理控制台中进行修改。 例如,上述FastAPI应用程序使用app.main.handler

    要测试Lambda函数,我们可以通过AWS管理控制台或AWS CLI来调用它,例如:

    $ aws lambda invoke \
       --function-name myFunction \
       --payload file://event.json \
       --cli-binary-format raw-in-base64-out \
       response.json
      "StatusCode": 200,
      "ExecutedVersion": "$LATEST"
    

    其中 event.json 包含要传递给 Lambda 函数的事件负载:

    event.json
    {
      "httpMethod": "GET",
      "path": "/",
      "requestContext": {},
      "version": "1.0"
    

    response.json 包含来自 Lambda 函数的响应:

    response.json
    {
      "statusCode": 200,
      "headers": {
        "content-length": "14",
        "content-type": "application/json"
      "multiValueHeaders": {},
      "body": "\"Hello, world!\"",
      "isBase64Encoded": false
    

    使用Lambda层

    AWS Lambda 还支持在使用 zip 归档文件时部署多个组合的 Lambda 层。这些层在概念上类似于 Docker 镜像中的层,允许您 将应用程序代码与依赖项分离。

    特别是,我们可以为应用程序依赖项创建一个lambda层,并将其附加到Lambda函数上,与应用程序代码本身分离。这种设置可以改善应用程序更新时的冷启动性能,因为依赖项层可以在多个部署中重复使用。

    要创建Lambda层,我们将遵循类似的步骤,但会创建两个独立的zip压缩包:一个用于应用程序代码,另一个用于应用程序依赖项。

    首先,我们将创建依赖层。Lambda层需要遵循略微不同的结构,因此我们将使用--prefix而不是--target

    $ uv export --frozen --no-dev --no-editable -o requirements.txt
    $ uv pip install \
       --no-installer-metadata \
       --no-compile-bytecode \
       --python-platform x86_64-manylinux2014 \
       --python 3.13 \
       --prefix packages \
       -r requirements.txt
    

    我们将按照Lambda层的预期布局压缩依赖项:

    $ mkdir python
    $ cp -r packages/lib python/
    $ zip -r layer_content.zip python
    

    要生成确定性zip压缩包,可以考虑向zip命令传递-X参数,以排除扩展属性和文件系统元数据。

    并发布Lambda层: