三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Vercel部署Python后端API:从Flask项目到Serverless实战指南

Vercel部署Python后端API:从Flask项目到Serverless实战指南

1. 项目概述:为什么选择Vercel托管Python后端?

如果你正在开发一个Python后端API,比如一个简单的数据查询接口、一个机器学习模型的推理服务,或者一个为你的小程序提供数据的后端,那么部署上线往往是项目从“玩具”走向“服务”的关键一步。传统上,我们可能会想到租用云服务器,然后配置Nginx、Gunicorn,再处理SSL证书和防火墙,一套流程下来,半天时间就没了,而且后续的运维、监控、扩展都是头疼事。

最近几年,Serverless(无服务器)架构的兴起,让部署后端API变得前所未有的简单。Vercel,这个以托管前端项目(尤其是Next.js)而闻名的平台,其实对Python后端API的支持也相当出色。它最大的吸引力在于:零配置部署、自动HTTPS、全球CDN加速,以及最重要的——免费额度足够个人项目和小型应用折腾。你只需要把代码推送到Git仓库(GitHub, GitLab, Bitbucket),Vercel就能自动识别你的Python项目,安装依赖,并启动你的API服务。

听起来很美好,对吧?但实际操作中,很多朋友会卡在“环境依赖”这一步。Vercel的Serverless环境是“无状态”的,每次请求都可能是一个全新的、临时的容器。这意味着你不能像在本地或自己的服务器上那样,随意pip install一些包就完事了。你必须通过一个标准的requirements.txt文件来明确声明所有依赖,并且要确保这些依赖与Vercel提供的Python运行时环境兼容。

这篇文章,我就以一个真实的Python Flask API项目为例,带你走一遍从本地开发到成功部署到Vercel的全过程。我会重点拆解“引包引环境”这个核心难题,分享我踩过的坑和总结的实用技巧,确保你也能一次部署成功。

2. 核心思路与项目结构设计

在动手写代码之前,我们先要理解Vercel运行Python项目的逻辑,这决定了我们的项目结构应该如何设计。

2.1 Vercel的Python运行时工作原理

Vercel本质上将你的API部署为一个个Serverless Function(无服务器函数)。当你访问你的API地址时,Vercel会启动一个包含你代码和依赖的临时环境来执行对应的函数,处理完请求后,环境可能会被销毁。因此,你的代码必须是一个可以被“调用”的入口。

对于Python,Vercel官方支持将符合WSGI(Web Server Gateway Interface)ASGI(Asynchronous Server Gateway Interface)规范的应用作为入口。最常见的WSGI应用框架就是Flask和Django,而FastAPI则是ASGI框架的代表。

我们的核心任务就是创建一个符合规范的入口文件(通常是api/index.py或根目录的app.py),并确保Vercel在构建时能正确安装所有依赖。

2.2 推荐的项目结构

一个清晰的项目结构能避免很多部署时的诡异问题。下面是我推荐的结构,适用于大多数中小型Python API项目:

my-python-api/ ├── api/ │ └── index.py # Vercel Serverless Function 入口文件 ├── requirements.txt # Python依赖清单(核心!) ├── vercel.json # Vercel项目配置文件(可选,但推荐) └── (其他项目文件,如 utils/, models/ 等)

关键点解析:

  1. api/index.py:这是Vercel的默认约定。任何放在api目录下的.py文件,都会被Vercel视为一个独立的Serverless Function。访问路径就是/文件名。例如,api/index.py对应的API地址就是https://your-project.vercel.app/apiapi/hello.py对应的地址就是https://your-project.vercel.app/api/hello。这种结构非常适合构建由多个独立函数组成的API。
  2. requirements.txt:这是Python项目的“身份证”。Vercel的构建系统在部署时,会读取这个文件,并使用pip install -r requirements.txt命令在它的云端环境中安装所有依赖。这个文件的内容和格式至关重要。
  3. vercel.json:这个配置文件允许你自定义构建命令、输出目录、路由规则等。对于纯Python API,我们通常用它来指定使用哪个Python版本,或者重写一些默认行为。

注意:你也可以使用根目录的app.py作为入口,但这需要在vercel.json中进行额外配置,告诉Vercel你的应用对象在哪里。对于新手,我强烈建议先从api/index.py这种标准结构开始,它能让你更直观地理解Vercel的函数模型。

3. 手把手实操:从零构建并部署一个Flask API

理论说再多不如动手做一遍。我们一起来创建一个简单的“待办事项(Todo)”API,它支持获取列表和添加新事项。

3.1 第一步:初始化本地项目与环境

首先,在你的本地电脑上创建一个新目录,并初始化一个干净的Python虚拟环境。虚拟环境能隔离项目依赖,是Python开发的最佳实践。

# 创建项目目录 mkdir vercel-python-todo-api cd vercel-python-todo-api # 创建虚拟环境(假设你使用Python3) python3 -m venv venv # 激活虚拟环境 # 在 macOS/Linux 上: source venv/bin/activate # 在 Windows 上: # venv\Scripts\activate # 激活后,命令行提示符前通常会显示 (venv)

3.2 第二步:编写核心API代码

现在,创建api目录和入口文件index.py

# api/index.py from flask import Flask, jsonify, request from flask_cors import CORS # 处理跨域请求,如果前端需要调用的话 app = Flask(__name__) CORS(app) # 允许所有域的跨域请求,生产环境建议配置具体域名 # 用一个内存列表模拟数据库 todos = [ {"id": 1, "task": "学习Vercel部署", "completed": False}, {"id": 2, "task": "写一篇技术博客", "completed": True}, ] @app.route('/api', methods=['GET']) def get_todos(): """获取所有待办事项""" return jsonify({"success": True, "data": todos}) @app.route('/api', methods=['POST']) def add_todo(): """添加一个新的待办事项""" data = request.get_json() if not data or 'task' not in data: return jsonify({"success": False, "error": "缺少任务内容"}), 400 new_id = max([todo['id'] for todo in todos], default=0) + 1 new_todo = { "id": new_id, "task": data['task'], "completed": data.get('completed', False) } todos.append(new_todo) return jsonify({"success": True, "data": new_todo}), 201 # 这个入口点是为了兼容Vercel的无服务器函数格式 # Vercel会寻找名为 `app` 或 `application` 的WSGI/ASGI应用对象 # 对于Flask,直接导出 `app` 即可。 app = app

代码要点说明:

  • 我们创建了一个Flask应用,并定义了两个端点:GET /apiPOST /api
  • 使用内存列表todos来存储数据。注意:在真实的Serverless环境中,由于函数实例是无状态的,内存数据在请求结束后就会丢失,且不同请求可能由不同实例处理,因此绝对不能用内存做持久化存储。这里仅作演示,真实项目需要连接数据库(如Vercel Postgres、Supabase或外部数据库)。
  • 最后一行app = app看起来有点多余,但这是为了明确导出一个名为app的对象,这是Vercel识别WSGI应用的常见方式之一。你也可以写application = app

3.3 第三步:创建并管理requirements.txt(核心环节)

这是整个部署过程中最容易出错的一步。我们需要生成一个精确的依赖列表。

首先,在虚拟环境中安装项目所需的包:

pip install flask flask-cors

安装完成后,使用pip freeze命令生成依赖列表。但直接使用pip freeze > requirements.txt可能会引入很多不必要的、来自你本地全局环境的依赖,导致文件臃肿且可能冲突。

更推荐的做法是,仅记录你主动安装的核心包及其版本范围。手动创建一个requirements.txt文件:

# requirements.txt Flask>=2.3.0,<3.0.0 Werkzeug>=2.3.0,<3.0.0 flask-cors>=4.0.0,<5.0.0

为什么这么做?

  1. 精确控制pip freeze会列出所有包,包括pipsetuptools等底层工具,这些在Vercel的构建环境中可能已经存在或版本不同,强行指定可能导致冲突。
  2. 版本范围:使用>=<指定一个兼容的版本范围,比固定死一个具体版本(如Flask==2.3.2)更灵活。这既能保证核心功能,又能让Vercel的包解析器在一定范围内选择最兼容的版本,提高部署成功率。
  3. 简洁明了:只列出你的代码直接导入的包(Flask,flask-cors)及其直接依赖(Werkzeug是Flask的核心依赖)。这通常就够了。

实操心得:我遇到过无数次部署失败,都是因为requirements.txt里包含了不兼容的包版本。一个黄金法则是:在本地开发时,尽量使用与Vercel官方支持的Python版本相近的环境。你可以在Vercel项目设置的“Build & Development Settings”中查看支持的版本(如Python 3.9, 3.10, 3.11)。在本地使用pyenvconda创建对应版本的虚拟环境,能极大减少环境差异。

3.4 第四步:配置vercel.json(可选但推荐)

虽然Vercel能自动检测Python项目,但显式配置可以避免歧义,尤其是当项目根目录还有其他文件时。在项目根目录创建vercel.json

{ "functions": { "api/*.py": { "runtime": "python@3.11" } }, "builds": [ { "src": "api/*.py", "use": "@vercel/python" } ], "routes": [ { "src": "/(.*)", "dest": "/api" } ] }

配置解析:

  • "functions": 指定api目录下的所有.py文件都使用Python 3.11运行时。你可以根据需要改为3.93.10
  • "builds": 告诉Vercel,对于api目录下的Python文件,使用官方的Python构建器@vercel/python
  • "routes": 这是一个重写规则。它将所有发送到根路径/的请求,都重定向到/api这个函数。这样,你访问https://your-project.vercel.app/就相当于访问https://your-project.vercel.app/api。如果你有多个函数(如api/index.py,api/hello.py),这个规则可能不需要,或者需要更复杂的配置。

3.5 第五步:本地测试与调试

在部署前,务必在本地测试API是否能正常运行。

  1. 本地运行Flask应用

    # 确保在项目根目录,且虚拟环境已激活 export FLASK_APP=api/index.py # macOS/Linux # 在Windows CMD中:set FLASK_APP=api\index.py # 在Windows PowerShell中:$env:FLASK_APP = "api\index.py" flask run

    访问http://127.0.0.1:5000/api,你应该能看到JSON格式的待办事项列表。

  2. 使用vercel dev进行仿真测试(强烈推荐): Vercel CLI提供了一个本地开发服务器,能模拟真实的Vercel生产环境。

    # 全局安装Vercel CLI npm i -g vercel # 在项目根目录登录(会打开浏览器) vercel login # 在项目根目录链接项目(选择“Link to existing project”或创建新项目) vercel link # 启动本地仿真服务器 vercel dev

    访问http://localhost:3000/api。这个环境会读取你的vercel.json配置,并使用与线上更接近的构建流程,是排查部署问题的最佳工具。

3.6 第六步:部署到Vercel

确保代码已经提交到Git仓库(如GitHub)。这是最推荐的部署方式。

  1. 通过Vercel网站部署

    • 访问 vercel.com ,用GitHub账号登录。
    • 点击“Add New...” -> “Project”。
    • 导入你的GitHub仓库。
    • Vercel会自动检测为Python项目。检查配置(根目录、构建命令等),通常无需修改。
    • 点击“Deploy”。等待几分钟,构建和部署就完成了。
  2. 通过Vercel CLI部署

    # 在项目根目录执行 vercel --prod

    按照提示操作即可。

部署成功后,你会获得一个类似https://your-project.vercel.app的URL。访问https://your-project.vercel.app/api即可测试你的线上API。

4. 深度解析:requirements.txt的陷阱与高级技巧

requirements.txt是部署成败的关键。下面我展开讲讲这里面的门道和高级用法。

4.1 依赖冲突与版本锁定

Python的包依赖有时像一团乱麻。包A依赖包B的1.0版本,包C依赖包B的2.0版本,这就产生了冲突。在本地,pip可能会通过复杂的解析找到一个可行解,但Vercel的构建环境可能不同。

策略一:使用pip-tools进行精确编译pip-tools提供了pip-compile命令,可以根据一个顶层的requirements.in文件,生成一个锁定所有次级依赖版本的requirements.txt

# 安装 pip-tools pip install pip-tools # 创建 requirements.in,只写你直接需要的包 # requirements.in 内容: Flask flask-cors # 编译生成 requirements.txt pip-compile requirements.in --output-file=requirements.txt

生成的requirements.txt会包含所有依赖及其精确版本(如Flask==2.3.2Werkzeug==2.3.6)。这能保证环境的一致性。但缺点是,如果某个次级依赖更新了不兼容的版本,你可能需要手动干预或定期重新编译。

策略二:在Vercel上使用Python版本和构建命令如果遇到棘手的依赖冲突,可以在Vercel项目设置的“Build & Development Settings”中尝试:

  • 切换Python版本(如从3.11降到3.9)。
  • 在“Build Command”中覆盖默认行为,例如:
    pip install --upgrade pip setuptools wheel && pip install -r requirements.txt
    先升级打包工具,有时能解决一些古老的包安装问题。

4.2 处理二进制依赖与系统库

有些Python包(如psycopg2(PostgreSQL驱动)、Pillow(图像处理)、cryptography)依赖系统级别的C库。Vercel的构建环境基于Linux,如果你的包需要编译,通常没问题,因为Vercel提供了编译工具链。

但是,你需要确保requirements.txt里的是这些包的纯Python轮子(wheel)或源码版本,而不是预编译的、针对特定平台(如macOS)的版本。通常,直接写包名(如psycopg2-binary)即可,pip会在构建时自动获取适合Linux的版本进行编译或安装。

一个常见错误:在macOS上开发,使用了某些包的macOS特定版本,然后pip freeze到了requirements.txt里。部署到Vercel的Linux环境时就会失败。这就是为什么建议在requirements.in里只写顶级包名,让pip-compile在部署时根据目标环境解析依赖。

4.3 使用runtime.txt指定Python版本

除了在vercel.json中指定,你还可以在项目根目录创建一个runtime.txt文件来指定Python版本,这是Heroku等平台的传统,Vercel也支持。

# runtime.txt python-3.11.0

这比在vercel.json中指定更直观,但注意两者如果同时存在,Vercel可能会优先使用vercel.json中的配置。

5. 进阶部署场景与优化

5.1 部署FastAPI(ASGI)应用

FastAPI是当下非常流行的异步Python框架。部署到Vercel需要一点小改动,因为Vercel的Python构建器默认寻找WSGI应用。

api/index.py示例:

from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI() # 添加CORS中间件 app.add_middleware( CORSMiddleware, allow_origins=["*"], # 生产环境请替换为具体前端域名 allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get("/api") async def read_root(): return {"message": "Hello from FastAPI on Vercel"} # 关键:为了兼容Vercel,需要将ASGI应用包装成WSGI兼容的格式 # 但更简单的方式是,使用 `uvicorn` 或 `hypercorn` 的ASGI适配器。 # 实际上,Vercel的 `@vercel/python` 构建器现在能自动检测ASGI应用。 # 你只需要确保导出的应用对象名是 `app`。 app = app

对于FastAPI,依赖文件requirements.txt需要包含:

fastapi>=0.100.0,<0.101.0 uvicorn[standard]>=0.23.0,<0.24.0

uvicorn是ASGI服务器,Vercel在运行时会使用它来启动你的FastAPI应用。

5.2 处理静态文件与大型依赖

Serverless函数有执行时间和包大小的限制(Vercel免费计划是10秒和50MB)。如果你的API需要加载大型模型(如机器学习模型),需要注意:

  1. 将模型文件放在项目内:模型文件会被打包进函数。确保总大小在限制内。
  2. 使用外部存储:更推荐的做法是将大型文件(如模型.pkl文件)存储在云存储(如AWS S3、Google Cloud Storage,或Vercel自家的Blob存储)中。在函数启动时(或首次请求时)下载到临时目录。虽然这会增加冷启动时间,但能避免包体积超标。
  3. 利用层(Layers)或全局变量:在多次调用间,函数的容器可能被复用(热启动)。你可以将加载好的模型对象存储在全局变量中,这样在热启动时就不需要重新加载。但这不是百分百可靠的,要做好失败重试的逻辑。

5.3 配置环境变量与密钥

绝对不要将API密钥、数据库连接字符串等敏感信息硬编码在代码中!Vercel提供了环境变量管理。

  1. 在Vercel控制台设置:进入项目设置 -> Environment Variables。添加你的变量,例如DATABASE_URL
  2. 在代码中读取
    import os database_url = os.environ.get('DATABASE_URL') if not database_url: # 可以提供一个本地开发用的默认值 database_url = 'sqlite:///local.db'
  3. 本地开发:使用python-dotenv包。在项目根目录创建.env文件(务必加入.gitignore),内容如DATABASE_URL=your_local_db_url。在代码开头加载:
    from dotenv import load_dotenv load_dotenv() # 这会读取 .env 文件中的变量到 os.environ

6. 常见部署问题与排查实录

即使按照教程操作,你也可能会遇到一些问题。这里记录了几个我亲自踩过的坑和解决方法。

6.1 构建失败:ModuleNotFoundErrorImportError

这是最常见的问题,意味着Vercel构建环境没有成功安装你的某个依赖,或者安装的版本/路径不对。

排查步骤:

  1. 检查requirements.txt格式:确保没有拼写错误,版本号语法正确。每行一个包。
  2. 查看构建日志:在Vercel项目的“Deployments”页面,点击失败的部署,查看详细的构建日志。日志会显示pip install的过程。仔细看是否有红色的错误信息,比如某个包编译失败、版本不兼容等。
  3. 简化依赖:如果依赖复杂,尝试先只部署一个最简单的Flask应用(只有Flask一个依赖)。成功后再逐步添加其他包,定位是哪个包引起的问题。
  4. 检查Python版本兼容性:有些较新或较旧的包可能不支持你指定的Python版本。尝试在vercel.json中切换Python版本(如从3.11切到3.9)。

6.2 运行时错误:502 BAD_GATEWAYFunction Invocation Error

部署成功,但访问API时返回5xx错误。

排查步骤:

  1. 检查函数日志:在Vercel项目的“Functions”标签页下,找到对应的函数(如api/index),查看其运行时日志。这里会打印出Python应用的错误堆栈信息,是调试的黄金线索。
  2. 检查入口点:确保api/index.py中导出的应用变量名是app(或application)。这是Vercel Python构建器的默认约定。
  3. 检查路径:你的代码里是否使用了绝对路径读写文件?在Serverless环境中,当前工作目录和文件系统权限可能与本地不同。尽量使用相对路径,并做好异常处理。
  4. 冷启动超时:如果函数代码初始化(如加载大模型)时间超过Vercel的限制(免费版10秒),会导致函数超时并返回错误。优化初始化逻辑,或考虑使用更快的启动方案(如减小模型尺寸、使用外部存储按需加载)。

6.3 跨域(CORS)问题

如果你的前端(在your-frontend.com)调用部署在Vercel上的API(your-api.vercel.app),浏览器会因同源策略而阻止请求。

解决方案:

  • 在Flask中,使用flask-cors扩展(如本文示例)。
  • 在FastAPI中,使用CORSMiddleware
  • 生产环境注意:不要使用allow_origins=["*"],而应该明确指定前端域名,如allow_origins=["https://your-frontend.com"],这样更安全。

6.4 如何连接数据库?

内存存储不可靠。你需要一个外部数据库。Vercel推荐使用其集成的Vercel PostgresVercel KV (Redis)。你也可以使用任何提供公开访问地址的数据库服务(如Supabase, PlanetScale, MongoDB Atlas等)。

以连接Vercel Postgres为例:

  1. 在Vercel项目仪表板,进入“Storage”标签页,创建Postgres数据库。
  2. 它会自动生成连接字符串,并作为环境变量POSTGRES_URL添加到你的项目中。
  3. 在代码中,使用psycopg2asyncpg等驱动连接。记得将psycopg2-binary添加到requirements.txt

一个重要的提醒:Serverless函数会创建大量短暂的数据库连接。务必使用连接池或在每个函数调用内部创建并关闭连接,避免耗尽数据库连接数。许多ORM(如SQLAlchemy)和驱动都支持连接池配置。

部署Python API到Vercel,核心在于理解其Serverless函数模型,并妥善管理依赖和环境。从简单的Flask应用开始,遵循api/index.py的结构,精心维护requirements.txt,利用好vercel dev进行本地仿真,大部分问题都能迎刃而解。当你的API流量增长,开始关心性能、冷启动和成本时,再深入探索数据库连接优化、函数配置调优和监控告警,那就是另一个阶段的旅程了。至少现在,你已经拥有了一个免费、自动伸缩、全球可访问的API服务起点。

← 返回列表