为何Heroku在新部署中安装旧的Python(pip)依赖?
(在这里提问是根据Heroku的支持团队的指示)
我们最近在一个项目中发现了依赖问题,主要是因为开发环境中的库不匹配。具体情况不重要,但根本原因是某个依赖在它的setup.py
文件中使用了“>=”的版本匹配方式。这意味着当某个开发者重建他的环境时,他突然得到了最新版本(0.4.0),而不是之前的旧版本(0.3.11),结果开始出现了DeprecationWarning
的警告。
在调试过程中,我以为每次把代码推送到Heroku时,都会重建一个干净的环境,这让我错误地认为我们的开发环境(每天重建一次)会安装最新版本。因为我们在开发环境中没有看到这个问题,我决定深入调查,于是在远程环境中运行了heroku run pip list
。
我(非常)惊讶地发现,输出的结果是一些旧的和过期的依赖,根本不是一个干净的环境。结果发现,我们正在调试的问题可能在我们的生产环境中安然无恙,因为它是旧安装的一部分。
最简单的解释是BeautifulSoup库。我们最近从版本3更新到了版本4,作为这次更新的一部分,这个库在PyPI上的名字也从BeautifulSoup
改成了beautifulsoup4
。我们更新了requirements.txt
文件来反映这个变化,但如果我现在在Heroku环境中运行pip list
,我会看到两个库:
~ $ heroku run bash
~ $ pip list
BeautifulSoup (3.2.1)
beautifulsoup4 (4.3.2)
所以,旧的依赖没有被清除,它就静静地待在那儿。我可以通过启动一个python会话轻松证明这一点:
~ $ python
Python 2.7.4 (default, Apr 6 2013, 22:14:13)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import bs4
>>> import BeautifulSoup
>>>
这真是有点震惊,我很惊讶这没有在某个时候导致我们的应用崩溃?
所以,问题是 - Heroku是如何在后台管理依赖的 - 显然它并不会在每次部署时都清空python环境并重新运行pip install
,有没有办法强制这种行为?
[编辑 1]
顺便说一下,这是执行安装的构建包 - https://github.com/heroku/heroku-buildpack-python/blob/master/bin/compile
[编辑 2]
根据Heroku Buildpack的文档:
CACHE_DIR中的内容将在构建之间被保留。你可以在这里缓存像依赖解析这样耗时的过程的结果,以加快未来的构建速度。
而且在更下面:
bin/compile脚本会将CACHE_DIR作为第二个参数传递,这可以用来在构建之间存储工件。存储在这个目录中的工件将在后续构建中可用。CACHE_DIR仅在slug编译期间可用,并且是特定于正在构建的应用的。
推荐的做法是:
Heroku用户可以使用heroku-repo插件来清除他们应用所使用的构建包创建的构建缓存。
话虽如此 - 虽然我理解缓存是用来加快未来编译的速度,但我不明白为什么缓存中的所有内容都会被安装。这一点我不太理解?
1 个回答
Heroku会把你的Python环境安装在/app/.heroku/python
这个地方,并且每次构建开始时,整个.heroku
目录会从CACHE_DIR复制过来,构建结束后又会复制回去。(如果你在那个构建包的脚本里搜索restore_cache
和dump_cache
,你会看到相关的代码。)
所以,一旦你在Heroku应用里安装了某个东西,它就会一直留在那里,除非CACHE_DIR被清空。显然,这样并不是最理想的做法,但这样做是为了避免每次部署时都要重新编译和安装所有依赖,导致构建时间过长。(顺便说一下,我觉得现在有更好的方法可以使用轮子来缓存已编译的包,这样每次都能得到一个新的环境,而不需要等所有东西重新构建。我可能会尝试做个例子,并问问Kenneth Reitz的意见。)
在短期内,听起来你应该明确锁定所有依赖的版本(包括依赖的依赖等等)。简单地运行pip freeze > requirements.txt
是个不错的开始,不过你也可以看看pip-tools。你真的要避免你刚才描述的情况,也就是开发者从requirements.txt
重建环境时,结果却得到了不同版本的包。
至于BeautifulSoup更改包名(但假设你导入的模块名没有变),这听起来真糟糕,正是Heroku缓存失效的那种情况!我觉得唯一的解决办法就是使用那个heroku-repo插件,彻底清除你的缓存。