如何禁用Heroku一次性Dyno对崩溃应用的自动重启(或实现优雅停机)
我来帮你拆解下这个问题——其实你碰到的是Heroku对dyno类型和任务调度的一个常见认知偏差,咱们一步步来搞定它:
核心问题根源
Heroku的one-off dyno(比如用heroku run手动启动的)确实是一次性任务,跑完就停;但Heroku Scheduler本质上是帮你创建重复运行的worker dyno,而不是手动的one-off dyno。如果你的任务被识别为web dyno,或者进程退出不符合预期,Heroku就会判定为崩溃并尝试重启。
具体解决方案
1. 把任务部署为worker dyno,而非web dyno
Heroku对web dyno有特殊要求:它期望进程持续监听某个端口(默认5000),如果进程退出或者没有监听端口,就会直接标记为崩溃。这也是你哪怕跑个简单print脚本都会崩溃的原因——如果默认用了web dyno类型,Heroku在脚本结束后发现没有进程监听端口,就判定为异常。
解决方法:
- 在项目根目录创建
Procfile文件,内容指定为worker进程:worker: python your_script.py - 部署后,在Heroku Scheduler的任务配置里,选择worker作为dyno类型(不要选默认的web)。
2. 确保脚本以退出码0正常结束
不管是让脚本自然执行完毕,还是主动退出,都要保证最终的退出状态码是0(正常结束的标志):
- 主动退出时用
sys.exit(0),而不是不带参数的sys.exit()或者quit()(不带参数可能返回非0码)。 - 本地测试时,运行脚本后执行
echo $?(Linux/macOS)或echo %errorlevel%(Windows),确认返回值是0。
举个极简测试脚本:
print("测试任务完成") import sys sys.exit(0)
3. 手动验证worker任务的运行状态
配置好Procfile后,先手动运行worker任务验证:
heroku run worker
运行结束后,查看日志:
heroku logs --tail
如果日志里显示Exit status 0,说明任务正常结束,Heroku不会标记为崩溃,Scheduler调度时也会正常执行后停止。
4. 检查日志排查异常
如果还是出现崩溃,一定要看Heroku日志的详细信息:
- 日志里会明确显示进程的退出码、是否有未捕获的异常、或者Heroku的健康检查报错。
- 比如如果日志里出现
Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds of launch,那说明你还是用了web dyno类型,赶紧切换成worker。
总结
你之前的操作失误大概率是用了web dyno类型来运行一次性任务,导致Heroku误判进程异常。只要把任务配置为worker dyno,确保脚本以退出码0正常结束,Scheduler调度的任务就会执行完毕后自动停止,不会触发崩溃重启。
内容的提问来源于stack exchange,提问作者Pablo Escobar

