当前位置: 首页 > news >正文

AWS Lambda Container 方式部署 Flask 应用并通过 API Gateway 提供访问

前言

一年前写过一篇 Lambda 运行 Flask 应用的博文:
https://lpwmm.blog.csdn.net/article/details/139756140

当时使用的是 ZIP 包方式部署应用代码, 对于简单的 API 开发用起来还是可以的, 但是如果需要集成到 CI/CD pipeline 里面就有点不太优雅. 本文将介绍使用容器方式部署 Flask 应用到 Lambda, 并实现通过 API Gateway 进行访问.

开发一个简单的 Flask 应用

使用 uv 作为项目管理工具, 如果你还不了解 uv, 可以参考之前的这篇文章:
https://lpwmm.blog.csdn.net/article/details/146774376

完整的项目代码开源在 Gitee:
https://gitee.com/lpwm/lambda-flask

主要涉及到以下常用的场景:

  • 静态文件访问, 模板中引入了自定义的 CSS 样式文件
  • 表单处理
  • 路由重定向

实现效果:
在这里插入图片描述

容器化封装

Dockerfile

# 使用 ECR 提供的 Alpine 环境的 Python 3.12
FROM public.ecr.aws/docker/library/python:3.12-alpine
# [重要] 添加 Lambda Web Adapter (LWA)
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter# 使用清华源安装 uv
RUN sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.tuna.tsinghua.edu.cn/alpine#g' /etc/apk/repositories \&& apk add --no-cache uv# [重要] 配置 uv 的缓存文件夹路径, Lambda 中只有 /tmp 具有 RW 权限
ENV UV_CACHE_DIR="/tmp"
# 配置 uv 使用清华源
ENV UV_DEFAULT_INDEX="https://pypi.tuna.tsinghua.edu.cn/simple"WORKDIR /var/task# 先将 uv 项目相关的文件复制并初始化 .venv 和依赖
COPY pyproject.toml uv.lock .python-version ./
RUN uv sync# 再将其他文件复制, 这样可以有效减少后面代码发生更新时重新 build 镜像所需要的操作时间
COPY static ./static
COPY templates ./templates
COPY app.py ./# Lambda 执行时只能在一个运行环境中跑一个 Worker, 所以注意加参数 -w=1, 监听端口直接用 LWA 默认的 8080, 不用再改 LWA 的参数了
CMD ["uv", "run", "gunicorn", "-b=:8080", "-w=1", "app:app"]

测试容器

docker build -t flask-on-lambda .
docker run -it --rm -p 8080:8080 flask-on-lambda

AWS 资源创建

ECR & Lambda

REPO_NAME=flask-on-lambda
# 创建 ECR repository
aws ecr create-repository --repository-name $REPO_NAME# 将 ECR repository 的 URI 存入变量, 方便后面调用
REPO_URI=$(aws ecr describe-repositories --repository-names $REPO_NAME --query 'repositories[0].repositoryUri' --output text)# 从 URI 拆分出来 ECR 的主域名, 用于 Docker 登录访问
ECR_HOST=$(echo $REPO_URI | awk -F'/' '{print $1}')# Docker 登录 ECR
aws ecr get-login-password --region cn-northwest-1 | docker login --username AWS --password-stdin $ECR_HOST# 推送 Docker image 到 ECR
docker tag $REPO_NAME:latest $REPO_URI:latest
docker push $REPO_URI:latest# [可选] 获取最新 Image 的哈希值
LATEST_DIGEST=$(aws ecr describe-images --repository-name $REPO_NAME --query 'sort_by(imageDetails,& imagePushedAt)[-1].imageDigest' --output text)# [可选] 更新 Lambda
aws lambda update-function-code --function-name $REPO_NAME --image-uri $REPO_URI@$LATEST_DIGEST --no-cli-pager# 创建 IAM Role
aws iam create-role \--role-name lambda-execution-role-$REPO_NAME \--assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}' \
&& aws iam attach-role-policy \--role-name lambda-execution-role-$REPO_NAME \--policy-arn arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole# 获取 Role ARN
ROLE_ARN=$(aws iam get-role --role-name lambda-execution-role-$REPO_NAME --query 'Role.Arn' --output text)# 创建和 REPO 相同名称的 Lambda
aws lambda create-function \--function-name $REPO_NAME \--package-type Image \--code ImageUri=$REPO_URI:latest \--role $ROLE_ARN

测试 Lambda 调用

aws lambda invoke \--function-name flask-on-lambda \--payload '{"httpMethod": "GET","path": "/","headers": {"Host": "example.com","User-Agent": "curl/7.68.0"},"requestContext": {"resourcePath": "/","httpMethod": "GET"},"body": null,"isBase64Encoded": false}' \--cli-binary-format raw-in-base64-out \/dev/stdout

预期响应:

{"statusCode": 200,"headers": {},"multiValueHeaders": {"server": ["gunicorn"],"date": ["Sun, 13 Jul 2025 12:02:04 GMT"],"connection": ["close"],"content-type": ["text/html; charset=utf-8"],"content-length": ["585"]},"body": "<html>\n\n<head>\n    <title>Flask on Lambda</title>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"/static/style.css\">\n</head>\n\n<body>\n    <section>\n        <h1>Welcome to the Flask on Lambda</h1>\n        <p>This is a simple Flask application powered by Lambda.</p>\n    </section>\n    <section>\n        <form action=\"\" method=\"post\">\n            <label for=\"name\">Name:</label>\n            <input type=\"text\" id=\"name\" name=\"name\" required placeholder=\"Enter your name\">\n            <br>\n            <button type=\"submit\">Submit</button>\n        </form>\n    </section>\n</body>\n\n</html>","isBase64Encoded": false
}

API Gateway

这部分配置用 CLI 会很麻烦, 还是在 Console 操作吧

  • 创建 HTTP API
    注意, 一定要使用 HTTP API, REST API 会在 URL 中包含 Stage 名称, 导致后端的 Flask 无法正常处理路由. 踩坑过程归档在文章末尾了, 有兴趣继续看完
    在这里插入图片描述
    在这里插入图片描述
  • 添加 Lambda 集成
    在这里插入图片描述
  • 修改路由:
    Method: ANY
    Resource path: /{proxy+}
    在这里插入图片描述
  • 使用默认 Stage
    在这里插入图片描述
  • 完成创建
    在这里插入图片描述
  • 在 Deploy > Stages 中找到 Invoke URL
    在这里插入图片描述
  • 使用浏览器访问测试, 受到 Lambda 的 Code start 机制的影响, 首次加载和交互的速度会有点慢.
    在这里插入图片描述
    后面刷新后再次交互速度就很快了.
    在这里插入图片描述

性能优化

为了保证用户能在首次访问的时候也有友好的体验, 我们可以为 Lambda 配置 Provisioned concurrency (额外收费的哟)

  • 首先为 Lambda function 创建 Version
    在这里插入图片描述
    在这里插入图片描述
  • 在 Version 视图中编辑 Provisioned concurrency
    在这里插入图片描述
    在这里插入图片描述
  • 此时 Status 为 In progress, 需要等几分钟
    在这里插入图片描述
    状态变成 Ready 就好了
    在这里插入图片描述
  • 复制当前 Version 界面的 Function ARN
    在这里插入图片描述
  • 回到 HTTP API 控制台修改 Integration, 将 Lambda function 对应的 ARN 更新为上面复制的带有 Version 信息的
    在这里插入图片描述
  • 确认目前使用的集成设置中 Lambda 包含了版本信息(后面多了 :1)
    在这里插入图片描述
    因为 HTTP API 默认开启了 Auto deploy 的选项, 所以这种修改都不需要手动重新 Deploy 操作. 再次使用浏览器访问测试, 速度嘎嘎的~

当然, 我们前面配置的 Provisioned concurrency = 1, 对于生产环境业务负载较高的场景, 可以酌情提升.

结尾

至此, 我们成功使用 Docker 容器的方式将一个 Flask 应用部署到了 Lambda 上, 并通过 API Gateway (HTTP API) 对外提供了可访问的 URL 地址, 实现了 Serverless 部署传统 Web 应用. 🎉🎉🎉
由于应用全部都封装在了 ECR 镜像, 所以在实际项目中, 也可以很方便的融入到 CI/CD pipeline 中.

关于之前撰稿期间使用 REST API 踩坑的经历, 有兴趣可以继续阅览. 😂

REST API 踩坑归档

  • 添加 Trigger
    在这里插入图片描述
  • 创建新的 REST API
    在这里插入图片描述
  • 打开自动创建好的 API
    在这里插入图片描述
  • 删除自动创建的资源路径
    在这里插入图片描述
  • 在根路径下创建资源
    在这里插入图片描述
  • 创建 Proxy 资源
    在这里插入图片描述
  • 编辑集成
    在这里插入图片描述
  • Execution role 可以留空
    在这里插入图片描述
  • 测试 GET 方法
    在这里插入图片描述
    在这里插入图片描述
  • 部署 API
    在这里插入图片描述

REST API 存在问题

完成上面的配置后, 如果从浏览器直接访问 Stage URL 根路径报错:
在这里插入图片描述
访问子路径 success/变量 可以加载出来页面
在这里插入图片描述
但是静态 CSS 文件加载失败, 因为请求路径中并没有包含 stage 的名称
在这里插入图片描述
先来解决直接访问 Stage 根路径报错的问题. 这是因为前面只给 /{proxy+} 创建了 ANY 方法和集成, 对于 / 来说, 还是空的设置. 再单独选中 / 资源路径, 创建 ANY 方法, 相同的方式配置 Lambda proxy 集成
在这里插入图片描述

重新部署后就可以访问到了:
在这里插入图片描述
当提交表单后, 重新定向的 URL 又出现了和 CSS 加载相同的问题, Stage 名称丢失了:
在这里插入图片描述

http://www.xdnf.cn/news/1114489.html

相关文章:

  • 手写std::optional:告别空指针的痛苦
  • 系规备考论文:论IT服务知识管理
  • 010_学习资源与社区支持
  • C语言基础教程(002):变量介绍
  • Spring Boot 配置注解处理器 - spring-boot-configuration-processor
  • 初识计算机网络
  • Node.js 聊天内容加密解密实战教程(含缓存密钥优化)
  • python 列表(List) vs. 元组(Tuple):什么时候该用不可变的元组?它们在性能和用途上有什么区别?
  • C++使用Thread实现子线程延时重发
  • 语言模型常用的激活函数(Sigmoid ,GeLU ,SwiGLU,GLU,SiLU,Swish)
  • 【论文阅读】基于注意力机制的冥想脑电分类识别研究(2025)
  • LeetCode第 458 场周赛题解
  • 字符串问题(哈希表解决)
  • 【论文阅读】Think Only When You Need with Large Hybrid-Reasoning Models
  • 【源力觉醒 创作者计划】文心开源大模型ERNIE-4.5私有化部署保姆级教程与多功能界面窗口部署
  • 编译器优化——LLVM IR,零基础入门
  • 我做了一个windows端口占用查看跟释放工具
  • Spring AI 项目实战(十六):Spring + AI + 通义万相图像生成工具全栈项目实战(附完整源码)
  • linux-shell脚本
  • SpringCloud云间剑歌 第四章:藏经阁与信鸽传书
  • 打造你的专属智能生活:鸿蒙系统自定义场景开发全流程详解
  • package.json 与 package-lock.json
  • Redis缓存设计与性能优化指南
  • Web攻防-PHP反序列化原生内置类Exception类SoapClient类SimpleXMLElement
  • 分类问题-机器学习
  • 011_视觉能力与图像处理
  • 力扣面试150题--单词搜索
  • MySQL 分表功能应用场景实现全方位详解与示例
  • Flink学习笔记:整体架构
  • Docker(02) Docker-Compose、Dockerfile镜像构建、Portainer