PyPI很慢。如何搭建自己的服务器?
当一个新开发者加入团队,或者Jenkins进行完整构建时,我需要创建一个新的虚拟环境(virtualenv)。我发现,用Pip设置一个虚拟环境并安装很多(超过10个)依赖包时,从PyPI下载所有内容的速度非常慢。很多时候,安装过程会完全失败,出现如下错误:
Downloading/unpacking Django==1.4.5 (from -r requirements.pip (line 1))
Exception:
Traceback (most recent call last):
File "/var/lib/jenkins/jobs/hermes-web/workspace/web/.venv/lib/python2.6/site-packages/pip-1.2.1-py2.6.egg/pip/basecommand.py", line 107, in main
status = self.run(options, args)
File "/var/lib/jenkins/jobs/hermes-web/workspace/web/.venv/lib/python2.6/site-packages/pip-1.2.1-py2.6.egg/pip/commands/install.py", line 256, in run
requirement_set.prepare_files(finder, force_root_egg_info=self.bundle, bundle=self.bundle)
File "/var/lib/jenkins/jobs/hermes-web/workspace/web/.venv/lib/python2.6/site-packages/pip-1.2.1-py2.6.egg/pip/req.py", line 1018, in prepare_files
self.unpack_url(url, location, self.is_download)
File "/var/lib/jenkins/jobs/hermes-web/workspace/web/.venv/lib/python2.6/site-packages/pip-1.2.1-py2.6.egg/pip/req.py", line 1142, in unpack_url
retval = unpack_http_url(link, location, self.download_cache, self.download_dir)
File "/var/lib/jenkins/jobs/hermes-web/workspace/web/.venv/lib/python2.6/site-packages/pip-1.2.1-py2.6.egg/pip/download.py", line 463, in unpack_http_url
download_hash = _download_url(resp, link, temp_location)
File "/var/lib/jenkins/jobs/hermes-web/workspace/web/.venv/lib/python2.6/site-packages/pip-1.2.1-py2.6.egg/pip/download.py", line 380, in _download_url
chunk = resp.read(4096)
File "/usr/lib64/python2.6/socket.py", line 353, in read
data = self._sock.recv(left)
File "/usr/lib64/python2.6/httplib.py", line 538, in read
s = self.fp.read(amt)
File "/usr/lib64/python2.6/socket.py", line 353, in read
data = self._sock.recv(left)
timeout: timed out
我知道Pip有一个--use-mirrors
的选项,有时我的团队成员会通过使用--index-url http://f.pypi.python.org/simple
(或者其他镜像)来解决这个问题,直到他们找到一个响应速度快的镜像。我们在英国,但德国有一个PyPI镜像,我们从其他网站下载数据没有问题。
所以,我在考虑为我们的团队在内部搭建一个PyPI镜像。
我考虑过的选项有:
运行我自己的PyPI实例。官方的PyPI实现是:CheeseShop,还有一些第三方实现,比如:djangopypi和pypiserver(见脚注)
这个方法的问题是,我并不需要完整的PyPI功能,比如文件上传,我只想镜像它提供的内容。
使用pep381client或pypi-mirror来运行一个PyPI镜像。
这个方法看起来可行,但需要我的镜像先从PyPI下载所有内容。我已经设置了一个pep381client的测试实例,但我的下载速度在5 Kb/s到200 Kb/s之间波动(是比特,不是字节)。除非有完整的PyPI存档的副本,否则我需要几周才能有一个有用的镜像。
使用像yopypi这样的PyPI轮询代理。
这个方法现在不相关,因为http://pypi.python.org本身就由几个地理位置不同的服务器组成。
在开发者之间复制虚拟环境,或者托管一个当前项目依赖的文件夹。
这个方法不太可行:我们有几个不同的Python项目,它们的依赖会随着时间慢慢变化。一旦任何项目的依赖发生变化,这个中央文件夹就必须更新以添加新的依赖。不过,复制虚拟环境比复制包更麻烦,因为任何带有C模块的Python包都需要为目标系统编译。我们的团队有Linux和OS X的用户。
(不过,这看起来还是一堆糟糕选项中最好的。)
使用智能的PyPI缓存代理:collective.eggproxy
这似乎是一个很好的解决方案,但PyPI上的最后一个版本是2009年的,而且讨论的是mod_python。
其他大型Python团队是怎么做的?有什么好的解决方案可以快速安装相同的Python包?
脚注:
- 我看到过这个问题如何搭建自己的PyPI?,但那个问题是关于托管私有代码的。
- Python维基列出了替代的PyPI实现
- 我最近还发现了Crate.io,但我认为这对我使用Pip没有帮助。
- 有一个网站监控PyPI镜像状态
- PyPI上的一些包的文件托管在其他地方,所以即使是完美的镜像也无法解决所有依赖问题。
5 个回答
我最近在我们开发团队的Vagrant配置中安装了devpi,让它的包缓存存放在主机的文件系统上。这样,每个虚拟机(VM)都可以有自己的devpi-server守护进程,作为virtualenv/pip的索引网址。当这些虚拟机被销毁并重新配置时,之前下载的包就不需要一次又一次地下载了。每个开发者只需下载一次,建立自己的本地缓存,只要这些缓存还在主机的文件系统上,就可以一直使用。
我们还有一个内部的PyPi索引,用于存放我们的私有包,目前只是一个由Apache提供服务的目录。最终,我打算把它转换成一个devpi代理服务器,这样我们的构建服务器不仅可以维护Python依赖的包缓存,还能托管我们的私有库。这将为我们的开发环境、生产部署和公共PyPi之间增加一个缓冲层。
到目前为止,这似乎是我找到的最稳健的解决方案。
虽然这并不能解决你在PyPI上的问题,但可以通过Terrarium将构建好的虚拟环境交给开发者或用于部署。
使用terrarium可以打包、压缩并保存虚拟环境。你可以把它们保存在本地,甚至可以存储到S3上。根据GitHub上的文档:
$ pip install terrarium
$ terrarium --target testenv --storage-dir /mnt/storage install requirements.txt
在创建一个新的环境后,terrarium会将这个环境打包并压缩,然后把它复制到你指定的存储位置。
在后续安装相同需求集时,如果指定了相同的存储位置,terrarium会从/mnt/storage复制并解压缩之前的压缩包。
如果你想查看terrarium将如何命名这个压缩包,可以运行以下命令:
$ terrarium key requirements.txt more_requirements.txt
x86_64-2.6-c33a239222ddb1f47fcff08f3ea1b5e1
你们有共享的文件系统吗?
如果有的话,我建议使用pip的缓存设置。这非常简单。比如在/mnt下创建一个叫做pip-cache的文件夹。
mkdir /mnt/pip-cache
然后每个开发者都需要在他们的pip配置文件里加上一行代码(在Unix系统中是$HOME/.pip/pip.conf,在Windows系统中是%HOME%\pip\pip.ini)。
[global]
download-cache = /mnt/pip-cache
这样做之后,pip还是会去检查PyPi,看看有没有最新版本。然后它会查看这个版本是否已经在缓存里。如果在缓存里,就直接从那里安装;如果不在,就会下载这个版本。下载后会把它存到缓存里,然后再安装。所以每个包在新版本发布时只会下载一次。