当前位置: 首页 > 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.lryc.cn/news/586972.html

相关文章:

  • 【LeetCode100】--- 5.盛水最多的容器【复习回顾】
  • 1.1.5 模块与包——AI教你学Django
  • 【SCI 4区推荐】《Journal of Visual Communication and Image Representation》
  • 反激变换器设计全流程(一)——电路拓扑及工作流程
  • fatal: active `post-checkout` hook found during `git clone`
  • 小车避障功能的实现(第七天)
  • 零基础入门物联网-远程门禁开关:软件安装
  • RabbitMQ 之仲裁队列
  • C++进阶-多态2
  • 全星质量管理QMS软件系统——汽车零部件制造业数字化转型的质量管理中枢
  • Redis 基础详细介绍(Redis简单介绍,命令行客户端,Redis 命令,Java客户端)
  • axios拦截器
  • 牛客周赛 Round 100
  • duckdb和pyarrow读写arrow格式的方法
  • 1.1.1+1.1.3 操作系统的概念、功能
  • 新手向:使用Python构建高效的日志处理系统
  • 深入理解Java中的hashCode方法
  • 磁悬浮轴承控制全攻略:从原理到实战案例深度解析
  • Python自动化:每日销售数据可视化
  • 闲庭信步使用图像验证平台加速FPGA的开发:第十二课——图像增强的FPGA实现
  • java+vue+SpringBoo中小型制造企业质量管理系统(程序+数据库+报告+部署教程+答辩指导)
  • Git Commit Message写错后如何修改?已Push的提交如何安全修复?
  • NoSQL 介绍
  • 前端-CSS-day3
  • 20250713-`Seaborn.pairplot` 的使用注意事项
  • Spring Boot 安全登录系统:前后端分离实现
  • [Subtitle Edit] 语言文件管理.xml | 测试框架(VSTest) | 构建流程(MSBuild) | AppVeyor(CI/CD)
  • Augment AI 0.502.0版本深度解析:Task、Guidelines、Memory三大核心功能实战指南
  • 海豚远程控制APP:随时随地,轻松掌控手机
  • iOS高级开发工程师面试——关于优化