Heroku部署Flask应用报FileNotFoundError: [Errno 2]问题求助
问题描述
你正在开发托管在Heroku平台的Flask应用,核心需求是读写本地存储的文件:
- 本地开发环境运行正常,可正常访问指定目录
/project/files下的文件 - 部署到Heroku生产环境后,访问本地目录持续抛出如下报错:
FileNotFoundError: [Errno 2] No such file or directory
相关业务代码
s = sftp.Connection(host=SFTP_HOSTNAME, username=SFTP_USERNAME, password=SFTP_PASSWORD, cnopts=cnopts) localFilePath = '/app/app/sftpfiles' remoteFilePath = '/files/client' files = s.listdir_attr(remoteFilePath) if files == []: return "empty" else: for f in files: if f.filename.endswith('.csv'): randomint = str(random.randint(1000,9999)) datenow = str(datetime.now().date()) lPath = localFilePath + '/' + f.filename newlPath = localFilePath + '/' + f.filename + '-' + datenow + '-' + randomint s.get(remoteFilePath + '/' + f.filename, lPath) os.rename(lPath, newlPath) [...]
运行日志
2022-07-13T09:16:34.603426+00:00 app[web.1]: Connection succesfully established... 2022-07-13T09:16:34.603590+00:00 app[web.1]: Root Path: /app/app 2022-07-13T09:16:34.603669+00:00 app[web.1]: CONNECTION IS: <pysftp.Connection object at 0x7f93566e5960> 2022-07-13T09:16:34.706901+00:00 app[web.1]: Connection succesfully established... 2022-07-13T09:16:34.707053+00:00 app[web.1]: Root Path: /app/app 2022-07-13T09:16:34.707092+00:00 app[web.1]: CONNECTION IS: <pysftp.Connection object at 0x7f9578c6b30> 2022-07-13T09:16:35.926913+00:00 app[web.1]: BEFORE DOWNLOADING THE FILE app/sftpfiles/testagainok.csv-2022-07-13-6651 2022-07-13T09:16:35.943975+00:00 app[web.1]: BEFORE DOWNLOADING THE FILE app/sftpfiles/testagainok.csv-2022-07-13-9316 2022-07-13T09:16:36.082532+00:00 app[web.1]: AFTER DOWNLOADING THE FILE, HERE IS THE RESPONSE: 2022-07-13T09:16:36.094290+00:00 app[web.1]: Job "sync_work_days (trigger: interval[0:00:05], next run at: 2022-07-13 09:16:39 UTC)" raised an exception 2022-07-13T09:16:36.094292+00:00 app[web.1]: Traceback (most recent call last): 2022-07-13T09:16:36.094292+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.10/site-packages/apscheduler/executors/base.py", line 125, in run_job 2022-07-13T09:16:36.094295+00:00 app[web.1]: retval = job.func(*job.args, **job.kwargs) 2022-07-13T09:16:36.094296+00:00 app[web.1]: File "/app/app/sftp.py", line 32, in sync_work_days 2022-07-13T09:16:36.094296+00:00 app[web.1]: s.get(remoteFilePath + '/' + f.filename, lPath) 2022-07-13T09:16:36.094297+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.10/site-packages/pysftp/__init__.py", line 249, in get 2022-07-13T09:16:36.094297+00:00 app[web.1]: self._sftp.get(remotepath, localpath, callback=callback) 2022-07-13T09:16:36.094297+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.10/site-packages/paramiko/sftp_client.py", line 812, in get 2022-07-13T09:16:36.094298+00:00 app[web.1]: s = os.stat(localpath) 2022-07-13T09:16:36.094298+00:00 app[web.1]: FileNotFoundError: [Errno 2] No such file or directory: 'app/sftpfiles/testagainok.csv' 2022-07-13T09:16:39.610429+00:00 app[web.1]: Connection succesfully established... 2022-07-13T09:16:39.610592+00:00 app[web.1]: Root Path: /app/app 2022-07-13T09:16:39.610658+00:00 app[web.1]: CONNECTION IS: <pysftp.Connection object at 0x7f93568520b0> 2022-07-13T09:16:39.692308+00:00 app[web.1]: Connection succesfully established... 2022-07-13T09:16:39.692435+00:00 app[web.1]: Root Path: /app/app
排查过程
- 目标访问目录为
/app/app/sftpfiles,目录结构如下:
- 添加测试代码尝试直接读取指定路径文件:
print("TRYING OPENING A FILE") f = open("/app/app/sftpfiles/testfile.csv") print("results of the file are: ", f)
- 通过命令
touch /app/app/sftpfiles/testfile.csv手动创建测试文件,文件存在截图如下:
- 重新运行应用后仍然抛出相同报错:
2022-07-13T13:19:15.443344+00:00 app[web.1]: f = open("/app/app/sftpfiles/testfile.csv") 2022-07-13T13:19:15.443345+00:00 app[web.1]: FileNotFoundError: [Errno 2] No such file or directory: '/app/app/sftpfiles/testfile.csv'
问题原因
核心问题来自Heroku平台的运行机制和代码路径处理的疏漏:
- Heroku的dyno文件系统是临时、非持久化的,每次部署、重启、缩放实例时都会完全重置文件系统。通过
heroku run bash手动执行touch创建的文件,只会存在于那次命令启动的一次性独立dyno中,和实际运行web服务的dyno是完全隔离的环境,web服务所在的dyno里根本不存在手动创建的文件 - Git本身不会跟踪空目录,如果代码仓库里的
sftpfiles是空目录、没有提交任何占位文件,部署到Heroku时这个目录不会被创建,访问时自然会报路径不存在 - Heroku dyno的文件系统除了
/tmp临时目录外其他路径都是只读的,即使目录存在,写入的文件也会在dyno生命周期结束时被完全清除,无法持久保存 - 从报错日志能看到路径不一致问题:代码里定义的是绝对路径
/app/app/sftpfiles,但实际报错时访问的是相对路径app/sftpfiles/testagainok.csv,说明代码运行时工作目录和预期不符,手动拼接字符串生成路径的方式很容易出现这类解析错误。
修复方案
- 不要依赖Heroku本地文件系统做持久化存储:如果需要长期保存下载的文件,替换为第三方持久化存储服务,比如AWS S3、Heroku自带的Postgres/Redis或其他对象存储服务,文件下载后直接上传到对象存储,不要存在本地磁盘
- 如果只是临时处理文件(处理完就删除、不需要持久保存),统一使用
/tmp目录作为临时文件存储路径,这个目录在dyno运行期间是可写的,启动时自动创建目标目录避免不存在报错:import os localFilePath = '/tmp/sftpfiles' os.makedirs(localFilePath, exist_ok=True) - 路径拼接不要手动用字符串加
/,用os.path.join避免跨平台、工作目录不一致导致的路径错误:lPath = os.path.join(localFilePath, f.filename) newlPath = os.path.join(localFilePath, f"{f.filename}-{datenow}-{randomint}") remote_file_full_path = os.path.join(remoteFilePath, f.filename) s.get(remote_file_full_path, lPath) - 不要通过
heroku run bash手动创建文件验证路径,这种方式启动的是独立的一次性dyno,和运行web服务的dyno文件系统不互通,验证逻辑要直接写在应用代码中,比如启动时打印目标目录的文件列表。
注意:存放在
/tmp目录的文件会在dyno停止、重启、重新部署时被完全删除,仅适合存放临时处理的文件,绝对不能存需要持久保留的业务数据。
内容的提问来源于stack exchange,提问作者Kaiss B.
相关产品推荐
相关产品推荐

