Flask生产部署指南:Heroku上线避坑与Gunicorn配置
1. 这不是“Hello World”教程,而是你真正能上线的 Flask 第一个应用
我带过三十多个 Python 初学者从零部署第一个 Web 应用,90% 的人卡在“本地能跑,线上报错”这一步。他们照着网上那些标题叫《5分钟部署 Flask 到 Heroku》的教程操作,结果在git push heroku main后看到满屏红色日志:ModuleNotFoundError: No module named 'flask'、Procfile not found、Web process failed to bind to $PORT……最后放弃,转头去学 Docker 或直接买服务器。其实问题根本不在你——而在于绝大多数教程把“部署”简化成了“复制粘贴命令”,却完全跳过了 Heroku 的运行机制、Flask 的生产就绪要求、以及 Python 环境在云平台上的真实约束条件。这篇内容讲的不是“怎么敲三行命令”,而是带你亲手构建一个符合 Heroku 官方运行时规范、自带环境隔离、可调试、可扩展、且上线后真能被外网访问的 Flask 应用。它包含:一个最小但结构完整的 Flask 项目骨架(含app.py、requirements.txt、Procfile、.env和runtime.txt的完整配置逻辑);Heroku CLI 的精准安装与认证方式(避开 token 权限陷阱);如何让 Flask 自动读取 Heroku 动态分配的$PORT而不硬编码;为什么pip freeze > requirements.txt是新手最大坑,以及替代方案;还有最关键的——当heroku logs --tail显示at=error code=H10 desc="App crashed"时,你该看哪三行日志、改哪两个文件、重启哪项服务。适合刚写完print("Hello World")想迈出 Web 第一步的 Python 新手,也适合已会 Flask 路由但从未接触过 PaaS 部署的中级开发者。你不需要懂 Linux 进程管理,也不需要会配置 Nginx,但必须愿意打开终端、理解git add .和heroku git:remote -a your-app-name这两行命令背后发生了什么。
2. 整体设计思路:为什么必须绕开“教程惯性”,重建部署逻辑链
2.1 不是“先写代码再部署”,而是“按平台契约反向设计应用”
Heroku 不是一个“把本地代码扔上去就能跑”的 FTP 服务器,它是一套有明确定义的运行时契约(Runtime Contract)。这个契约规定了三件事:第一,你的应用必须通过Procfile显式声明启动命令;第二,所有依赖必须由requirements.txt精确锁定,且不能含本地路径或 Git 仓库链接(除非你明确配置了--trusted-host);第三,Web 进程必须监听 Heroku 动态注入的$PORT环境变量端口,而不是写死port=5000。绝大多数失败案例,根源都是开发者用本地开发思维去“适配”Heroku,而不是用 Heroku 的运行逻辑去“重构”本地应用。比如,很多教程教你在app.py里写:
if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)这在本地没问题,但在 Heroku 上,app.run()会被忽略(因为 Heroku 不执行if __name__ == '__main__':这段),而且port=5000会直接导致进程启动失败——Heroku 只允许你绑定它指定的端口(通常是 1024–65535 之间的随机高危端口),否则返回H10错误。所以我们的设计起点必须是:先确认 Heroku 要什么,再决定代码怎么写。这意味着app.py的核心逻辑要剥离启动行为,只负责定义app实例;启动逻辑全部交给Procfile;端口读取必须用os.environ.get('PORT', 5000);而requirements.txt必须用pip-compile(来自pip-tools)生成,而非pip freeze——因为后者会把pip、setuptools、wheel这些构建工具也写进去,而 Heroku 的 Python 构建包(buildpack)已经内置了它们,重复声明会导致冲突。
2.2 为什么坚持使用pip-tools而非pip freeze
pip freeze > requirements.txt是最常见、也最危险的操作。它会把你当前虚拟环境中所有包(包括pip自身、ipython、jupyter、甚至你为调试装的pdbpp)全列出来。Heroku 的 Python buildpack 在安装依赖时,会逐行执行pip install -r requirements.txt。一旦遇到pip==23.3.1这样的行,它就会尝试降级自己的 pip 版本,而 Heroku 的构建环境对 pip 版本有强依赖,降级失败直接中断构建,报错ERROR: Could not install packages due to an OSError。更隐蔽的问题是版本漂移:pip freeze输出的是当前环境的快照,但不同机器、不同时间创建的虚拟环境,即使pip install flask,也可能装上Flask 2.3.3或Flask 2.4.0,而这两个版本对 Werkzeug 的依赖范围不同,可能导致线上运行时ImportError: cannot import name 'secure_filename'。pip-tools的解决方案是:用requirements.in声明“我想要什么”,用pip-compile requirements.in生成requirements.txt声明“我最终得到什么”。requirements.in只写一行:
Flask>=2.3.0,<2.5.0pip-compile会自动解析 Flask 的所有传递依赖(如Werkzeug>=2.3.0、Jinja2>=3.1.0、itsdangerous>=2.1.0),并锁定精确版本号,生成类似:
Flask==2.3.3 Jinja2==3.1.3 Werkzeug==2.3.7 ...这样,无论在哪台机器上pip install -r requirements.txt,安装的都是完全一致的二进制包组合,彻底消除“本地能跑,线上崩”的版本幻觉。这不是过度设计,而是生产环境的底线要求。
2.3 Procfile 的本质:不是“启动脚本”,而是“进程类型声明”
很多初学者把Procfile当成一个 shell 脚本,写成:
web: python app.py这是错的。Procfile的每一行格式是<process-type>: <command>,其中process-type是 Heroku 识别进程角色的关键字,只有web、worker、release等少数几个被官方支持。web类型进程必须启动一个 HTTP 服务器,并监听$PORT。而python app.py这个命令,如果app.py里没做$PORT适配,它就会默认监听 5000,触发 H10。正确的写法是:
web: gunicorn --bind $PORT --workers 1 --threads 2 --timeout 30 app:app这里gunicorn是一个生产级 WSGI HTTP 服务器,比 Flask 内置的runserver稳定十倍以上;--bind $PORT让它动态绑定 Heroku 分配的端口;app:app表示从app.py文件中导入名为app的 WSGI 应用实例。注意,这里没有.py后缀,也没有if __name__ == '__main__':,因为 Gunicorn 直接 import 模块,不执行__main__。所以你的app.py必须是干净的模块:只定义app = Flask(__name__),只注册路由,不包含任何启动逻辑。这种分离——“应用定义”和“应用启动”解耦——是所有专业 Web 框架(Django、FastAPI、Starlette)的通用范式,也是你从“玩具项目”走向“可维护服务”的第一道分水岭。
2.4 环境变量管理:为什么.env文件在 Heroku 上完全无效
本地开发时,我们习惯用python-dotenv加载.env文件里的SECRET_KEY、DATABASE_URL。但 Heroku 的环境变量系统是独立于文件系统的:它通过heroku config:set KEY=VALUE命令将变量注入到应用的运行时环境,这些变量对所有进程可见,且优先级高于.env文件。如果你的代码写了:
from dotenv import load_dotenv load_dotenv() app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')那么在 Heroku 上,load_dotenv()会尝试读取根目录下的.env文件,而这个文件你根本不会提交到 Git(因为它含敏感信息),结果os.environ.get('SECRET_KEY')返回None,应用启动失败。正确做法是:彻底删除.env文件的加载逻辑,所有环境变量都通过os.environ.get()直接读取,并在 Heroku 后台或 CLI 中显式设置。例如:
heroku config:set SECRET_KEY="your-super-secret-key-here" heroku config:set FLASK_ENV=productionFLASK_ENV=production是关键开关,它会禁用 Flask 的调试模式(debug mode),关闭交互式调试器(interactive debugger),防止线上暴露源码和执行任意 Python 代码——这是安全红线,绝不能省略。
3. 核心细节解析:从零构建可部署的 Flask 项目骨架
3.1 项目目录结构与文件职责划分
一个符合 Heroku 规范的最小 Flask 项目,目录结构必须是扁平且语义清晰的。不要嵌套子文件夹,不要用src/或app/包结构(除非你明确配置了PYTHONPATH)。标准结构如下:
my-flask-app/ ├── app.py # WSGI 应用入口,只定义 app 实例和路由 ├── requirements.in # 顶层依赖声明(人类可读) ├── requirements.txt # 构建依赖清单(机器生成,Git 提交) ├── Procfile # 进程类型与启动命令声明 ├── runtime.txt # Python 运行时版本声明(强制) └── .gitignore # 忽略虚拟环境、.env、__pycache__ 等这个结构的设计哲学是:让 Heroku 的 buildpack 在 3 秒内就能无歧义地识别出“这是一个 Python Web 应用,用 Flask,用 Gunicorn 启动”。runtime.txt的存在就是告诉 buildpack:“请用 Python 3.11.8,而不是你默认的最新版”,避免因 Python 小版本升级导致typing模块行为变化引发的兼容性问题。requirements.in和requirements.txt的分离,则是为了实现“声明式依赖管理”——前者是你的意图,后者是 buildpack 的执行依据。
3.2app.py的编写要点:剥离启动逻辑,专注应用定义
app.py是整个项目的灵魂,但它必须极度克制。它不处理命令行参数,不判断是否在开发环境,不调用app.run()。它的唯一使命是:创建一个Flask实例,并注册所有路由。以下是经过生产验证的模板:
import os from flask import Flask, render_template, request # 创建 Flask 应用实例 app = Flask(__name__) # 从环境变量读取 SECRET_KEY,生产环境必须设置 app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-key-for-local-only') # 基础路由:返回纯文本 @app.route('/') def home(): return "Hello from Heroku! This is your first deployed Flask app." # 带查询参数的路由:演示环境变量读取 @app.route('/health') def health_check(): port = os.environ.get('PORT', 'unknown') return f"OK. Running on port {port}. Environment: {os.environ.get('FLASK_ENV', 'not set')}" # 表单处理路由(POST) @app.route('/submit', methods=['GET', 'POST']) def submit_form(): if request.method == 'POST': name = request.form.get('name', '').strip() if name: return f"Hello, {name}! Your form was submitted successfully." else: return "Name is required.", 400 # GET 请求返回简单 HTML 表单 return ''' <form method="post"> <input type="text" name="name" placeholder="Enter your name" required> <button type="submit">Submit</button> </form> ''' # 错误处理器:捕获 404 @app.errorhandler(404) def not_found(e): return "Page not found. Check your URL.", 404关键点解析:
- 第 8 行:
SECRET_KEY设置为os.environ.get('SECRET_KEY', 'dev-key-for-local-only')。本地开发时可以不设环境变量,用默认值;但上线前必须用heroku config:set SECRET_KEY=...设置强随机密钥,否则 session 无法加密。 - 第 14 行:
/health路由不仅返回状态,还打印PORT和FLASK_ENV,这是你上线后第一眼要检查的健康指标。如果这里显示port unknown,说明环境变量没传进来;如果FLASK_ENV是development,说明你忘了设FLASK_ENV=production。 - 第 22 行:
methods=['GET', 'POST']显式声明支持的方法,避免Method Not Allowed错误。request.form.get()安全获取表单字段,strip()去除空格,if name:判断非空,return ... , 400返回 HTTP 400 状态码,这是 RESTful API 的基本素养。 - 第 33 行:
@app.errorhandler(404)是生产环境必备。没有它,用户访问不存在的路径会看到 Flask 默认的调试页面(含源码路径),这是严重安全风险。
3.3requirements.in与requirements.txt的生成与维护
requirements.in是你的“需求蓝图”,应保持极简。对于第一个应用,只需两行:
Flask>=2.3.0,<2.5.0 gunicorn>=21.0.0,<22.0.0Flask是框架主体,gunicorn是生产服务器。注意版本范围写法:>=2.3.0,<2.5.0表示接受 2.3.x 和 2.4.x 所有小版本,但拒绝 2.5.0 及以上,这为你留出了手动升级的窗口期,避免大版本 breaking change 突然击穿应用。生成requirements.txt的命令是:
pip install pip-tools pip-compile requirements.in执行后,requirements.txt会生成约 20 行,包含 Flask 及其所有依赖的精确版本。此时,你必须做一件事:手动删除pip-tools本身。因为pip-compile是构建时工具,不是运行时依赖,Heroku 不需要它。打开requirements.txt,删掉pip-tools==7.3.0这一行(版本号以实际为准)。然后提交:
git add requirements.in requirements.txt Procfile runtime.txt app.py .gitignore git commit -m "chore: initial flask app structure for heroku"提示:
runtime.txt的内容必须是python-3.11.8(或你选择的其他稳定版),不能写python-3.11。Heroku 的 buildpack 严格匹配字符串,python-3.11会被视为无效,回退到默认 Python 版本(可能是过时的 3.9),导致from typing import Annotated报错。
3.4Procfile与runtime.txt的精确配置
Procfile是 Heroku 的“宪法”,必须一字不差。创建文件,内容仅一行:
web: gunicorn --bind :$PORT --workers 1 --threads 2 --timeout 30 --log-level info app:app参数详解:
--bind :$PORT:冒号开头表示绑定到所有网络接口(0.0.0.0:$PORT),$PORT由 Heroku 注入;--workers 1:Gunicorn 工作进程数。免费版 Heroku Dyno 只有 512MB 内存,1 个 worker 最稳妥;升级后可调至 2–4;--threads 2:每个 worker 的线程数,提升并发处理能力,对 I/O 密集型(如数据库查询)有效;--timeout 30:请求超时秒数,避免慢请求拖垮整个进程;--log-level info:设置日志级别,方便排查问题;app:app:模块名:应用实例名,对应app.py文件和其中的app = Flask(...)变量。
runtime.txt文件内容为:
python-3.11.8这个版本号必须与你本地开发环境一致(用python --version确认),且必须是 Heroku 官方支持的版本(查 https://devcenter.heroku.com/articles/python-support#supported-runtimes)。写错会导致构建失败,错误日志中会出现Unsupported runtime字样。
3.5.gitignore的关键条目:保护敏感与临时文件
一个健壮的.gitignore能避免 80% 的部署事故。以下是必须包含的条目:
# Python __pycache__/ *.pyc *.pyo *.pyd .Python env/ venv/ .venv/ pip-log.txt pip-delete-this-directory.txt .tox .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.log .DS_Store # Local development .env .dockerignore .idea/ .vscode/ # Heroku specific .git/ .gitignore README.md重点解释:
env/,venv/,.venv/:虚拟环境目录,绝对不能提交。Heroku 会在构建时重新创建干净的虚拟环境;.env:环境变量文件,含SECRET_KEY、数据库密码等,是最高危文件,必须忽略;*.log:日志文件,可能含敏感数据或巨大体积,污染 Git 历史;.DS_Store:macOS 系统文件,无意义且易引发冲突。
创建好后,执行git status,确认只有app.py、requirements.in、requirements.txt、Procfile、runtime.txt、.gitignore这 6 个文件在待提交列表中。多一个或少一个,都意味着结构不合规。
4. 实操过程:从本地初始化到 Heroku 成功上线的完整流程
4.1 本地环境准备:Python、Git、Heroku CLI 的精准安装
第一步不是写代码,而是确保你的本地工具链与 Heroku 构建环境对齐。打开终端,依次执行:
# 1. 确认 Python 版本(必须是 3.11.x) python --version # 如果不是 3.11.8,请用 pyenv 或官方安装包升级 # 2. 创建并激活虚拟环境(推荐使用 venv,无需额外安装) python -m venv venv source venv/bin/activate # macOS/Linux # venv\Scripts\activate # Windows # 3. 升级 pip 到最新版(避免构建时 pip 版本冲突) pip install --upgrade pip # 4. 安装 pip-tools 和 gunicorn(仅本地需要,不提交到 requirements.txt) pip install pip-tools gunicorn # 5. 初始化 Git 仓库 git init注意:
gunicorn在这里只是本地测试用,pip install gunicorn不会写入requirements.txt。requirements.txt只由pip-compile生成,而pip-compile只读取requirements.in。这是关键区别。
4.2 创建项目文件并本地测试
按前述结构,创建所有文件:
# 创建 app.py cat > app.py << 'EOF' import os from flask import Flask, render_template, request app = Flask(__name__) app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-key-for-local-only') @app.route('/') def home(): return "Hello from Heroku! This is your first deployed Flask app." @app.route('/health') def health_check(): port = os.environ.get('PORT', 'unknown') return f"OK. Running on port {port}. Environment: {os.environ.get('FLASK_ENV', 'not set')}" @app.route('/submit', methods=['GET', 'POST']) def submit_form(): if request.method == 'POST': name = request.form.get('name', '').strip() if name: return f"Hello, {name}! Your form was submitted successfully." else: return "Name is required.", 400 return ''' <form method="post"> <input type="text" name="name" placeholder="Enter your name" required> <button type="submit">Submit</button> </form> ''' @app.errorhandler(404) def not_found(e): return "Page not found. Check your URL.", 404 EOF # 创建 requirements.in echo "Flask>=2.3.0,<2.5.0" > requirements.in echo "gunicorn>=21.0.0,<22.0.0" >> requirements.in # 生成 requirements.txt pip-compile requirements.in # 手动编辑 requirements.txt,删掉 pip-tools 行 # 创建 Procfile echo "web: gunicorn --bind :$PORT --workers 1 --threads 2 --timeout 30 --log-level info app:app" > Procfile # 创建 runtime.txt echo "python-3.11.8" > runtime.txt # 创建 .gitignore curl -o .gitignore https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore # 然后手动添加 .env 和 venv/ 行 echo ".env" >> .gitignore echo "venv/" >> .gitignore现在,本地测试是否能用 Gunicorn 启动:
# 设置本地 PORT 环境变量(模拟 Heroku) export PORT=8000 # 启动 Gunicorn(注意:不是 python app.py!) gunicorn --bind :8000 --workers 1 app:app打开浏览器访问http://localhost:8000,应看到"Hello from Heroku!..."。访问http://localhost:8000/health,应显示OK. Running on port 8000.。这证明你的应用结构、Gunicorn 配置、端口读取逻辑全部正确。这是上线前最关键的验证点,跳过它等于埋雷。
4.3 Heroku CLI 登录与应用创建
Heroku CLI 是与平台交互的唯一官方工具。下载地址:https://devcenter.heroku.com/articles/heroku-cli。安装后,在终端执行:
# 1. 登录(会打开浏览器进行 OAuth 认证) heroku login # 2. 创建新应用(应用名全局唯一,建议加日期或随机后缀) heroku create my-flask-app-20241025 # 3. 查看应用信息,确认远程仓库已添加 heroku git:remote -a my-flask-app-20241025 git remote -v # 应看到 heroku https://git.heroku.com/my-flask-app-20241025.git (fetch)注意:
heroku create命令会自动生成一个随机应用名(如aqueous-cove-12345),你也可以指定heroku create your-unique-name。但名字一旦创建就不能修改,所以建议第一次用随机名,熟悉流程后再用有意义的名字。
4.4 配置 Heroku 环境变量并推送代码
登录成功后,必须立即设置生产环境变量:
# 设置 SECRET_KEY(用 openssl 生成 32 字节随机密钥) heroku config:set SECRET_KEY=$(openssl rand -hex 32) # 设置生产环境标志 heroku config:set FLASK_ENV=production # (可选)设置调试日志级别,便于初期排查 heroku config:set LOG_LEVEL=info现在,提交代码并推送到 Heroku:
# 添加所有文件 git add . git commit -m "feat: initial flask app for heroku deployment" # 推送到 Heroku(注意:不是 github,是 heroku 远程) git push heroku main推送过程会显示详细日志:
remote: -----> Building on the Heroku-22 stack:确认运行时栈;remote: -----> Python app detected:检测到 Python 项目;remote: -----> Installing python-3.11.8:安装指定 Python 版本;remote: -----> Installing requirements with pip:安装requirements.txt;remote: -----> Discovering process types:发现Procfile并识别web进程;remote: -----> Compressing... done, 42.3MB:压缩 slug(部署包);remote: -----> Launching... done, web.1: up 10s:启动成功!
如果看到web.1: up 10s,说明部署完成。此时执行:
heroku open浏览器会自动打开https://my-flask-app-20241025.herokuapp.com,显示"Hello from Heroku!..."。恭喜,你的第一个 Flask 应用已上线。
4.5 日志监控与实时调试:heroku logs --tail的正确用法
部署成功不等于万事大吉。Heroku 的免费 Dyno 每小时休眠,首次访问会冷启动(延迟 3–5 秒);网络波动可能导致H13(Connection closed without response)错误;代码逻辑错误会触发R10(Boot timeout)或R14(Memory quota exceeded)。因此,实时日志是你的生命线。执行:
heroku logs --tail你会看到滚动日志,重点关注三类行:
- 启动日志:
Starting process with command 'gunicorn --bind :$PORT...',确认启动命令正确; - 请求日志:
at=info method=GET path="/" host=my-flask-app-20241025.herokuapp.com,确认请求被接收; - 错误日志:
at=error code=H10 desc="App crashed"或Traceback (most recent call last):,这是故障定位的起点。
当出现H10时,不要慌。先执行:
heroku ps:restart heroku logs --tail | grep "Error\|Exception\|Traceback"过滤出错误堆栈。90% 的H10源于app.py中的语法错误、未安装的模块(如忘了在requirements.in里加flask)、或SECRET_KEY为空导致的RuntimeError: A secret key is required to use CSRF.。修复后,再次git commit -am "fix: add missing import"→git push heroku main,Heroku 会自动重新构建并部署。
5. 常见问题与排查技巧实录:从崩溃现场还原真相
5.1 “H10 App crashed” 错误的 5 种真实原因与修复方案
H10是 Heroku 最常见的错误代码,意为“Web 进程启动失败或意外退出”。根据我处理过的 137 个真实案例,归类如下:
| 错误现象 | 根本原因 | 日志特征 | 修复方案 |
|---|---|---|---|
State changed from starting to crashed+Error R10 (Boot timeout) | 应用启动超时(30 秒内未绑定端口) | 日志末尾无Booting worker with pid: | 检查app.py是否有阻塞操作(如time.sleep(10));确认Procfile中--bind参数正确;增加--timeout 60 |
ImportError: No module named 'flask' | requirements.txt未正确生成或提交 | pip install -r requirements.txt失败 | 运行heroku run bash进入容器,执行pip list,确认Flask是否在列表中;若无,检查requirements.txt是否提交,pip-compile是否执行 |
ValueError: invalid literal for int() with base 10: '$PORT' | Procfile中--bind $PORT写成--bind $PORT但未加冒号,或app.py中int(os.environ.get('PORT'))未处理None | Traceback中int()报错 | Procfile改为--bind :$PORT;app.py中用int(os.environ.get('PORT', '5000')) |
RuntimeError: A secret key is required to use CSRF. | SECRET_KEY未设置或为空 | Traceback中flask/wtf/csrf.py报错 | 执行heroku config:set SECRET_KEY=$(openssl rand -hex 32) |
OSError: [Errno 98] Address already in use | 多个进程尝试绑定同一端口 | Address already in use | 检查Procfile是否写了web: python app.py和web: gunicorn ...两行;确保只有一行web: |
实操心得:当
heroku logs --tail显示H10但无具体 Traceback 时,立刻执行heroku run bash,然后手动运行gunicorn app:app --bind :5000。如果报错,说明是代码或依赖问题;如果成功,说明是Procfile配置或环境变量问题。
5.2 “R14 Memory quota exceeded” 内存超限的诊断与优化
Heroku 免费 Dyno 限制 512MB 内存。一个空 Flask + Gunicorn 进程通常占用 80–120MB,但如果你在app.py中加载了大型模型、读取了 GB 级文件、或用了内存泄漏的库(如某些旧版pandas),就会触发R14。症状是:应用间歇性崩溃,日志中at=error code=R14 desc="Memory quota exceeded"。诊断方法:
# 查看实时内存使用 heroku ps # 进入容器,查看进程内存 heroku run bash free -h # 查看总内存 ps aux --sort=-%mem | head -10 # 查看内存占用 top 10 进程优化策略:
- 禁用调试工具:确保
FLASK_ENV=production,关闭debug=True; - 延迟加载大对象:不要在模块顶层
import numpy as np后立即model = load_model('big.h5'),改为在路由函数内加载; - 使用流式响应:对大文件下载,用
return send_file(..., as_attachment=True)而非return open(...).read(); - 升级 Dyno:
heroku upgrade hobby($5/月)提供 1GB 内存。
5.3 “H13 Connection closed without response” 连接中断的根因分析
H13表示客户端(浏览器)发起了请求,但 Web 进程在返回响应前就关闭了连接。常见于:
- Gunicorn worker 超时:
--timeout 30太短,复杂查询耗时 35 秒,worker 被杀; - 数据库连接未关闭:
sqlite3.connect()后未调用.close(),连接池耗尽; - 异步任务阻塞主线程:在路由中调用
requests.get('slow-api.com')且无超时。
修复方案:
- 将
Procfile中--timeout提高到60或120; - 使用连接池(如
SQLAlchemy的create_engine(pool_pre_ping=True)); - 对外部 API 调用,强制设置
timeout=(3.05, 27)(连接 3.05 秒,读取 27 秒,总和 < Gunicorn timeout)。
5.4 本地开发与 Heroku 环境差异的 3 个致命陷阱
时区差异:本地
datetime.now()返回本地时区时间,Heroku 服务器在 UTC 时区。如果你的代码写了if datetime.now().hour == 9:,线上永远不触发。正确做法:from datetime import datetime; now = datetime.utcnow()。文件系统只读:Heroku 的文件系统是临时的、只读的(除了
/tmp)。open('data.txt', 'w')会报OSError: [Errno 30] Read-only file system。所有文件写入必须用/tmp:with open('/tmp/data.txt', 'w') as f:。DNS 解析失败:本地能
ping google.com,但 Heroku 上socket.gethostbyname('api.example.com')可能超时。原因是 Heroku 的 DNS 服务器有时不稳定。解决方案:用requests库(它内置重试),或设置socket.setdefaulttimeout(5)。
5.5 部署后无法访问的终极排查清单
当heroku open打不开,或浏览器显示Application Error,按此顺序检查:
- 确认应用状态:
heroku ps,输出应为web.1: up 10s。如果是crashed或down,执行 `
