作为一名接触 Python 有一段时间的初学者,越来越体会到 Python 的方便之处,它使人能更 多的关注业务本身的逻辑,而不用太纠结语言层面的技巧与细节。然而,随着服务的规模 变得越来越大,如何方便快速地制作与发布一个 Python 软件包则越来越成为一个让人头疼 地问题,特别是像 Openstack 这种相对复杂、各种依赖也很多的 Python 项目,到目前也没有 发现特别完美的解决方案。这里将尝试对Python的包管理工具做一个小结,为将来更好的 解决这个问题提供思路。
setuptools1 是Python提供的最基本的包管理工具,后面提到的其他更高层次的Python 包管理工具都是在它的基础上实现的。曾经Python还有其他各种各样类似功能的包管理工 具,比如distribute、distutils等等,对有选择困难症的同学非常不友好,不过目前绝大 多数Python项目都开始统一使用setuptools,其他工具已经被慢慢合并/遗弃了。
setuptools的常见使用方式是在Python项目中新建一个setup.py:
- from setuptools import setup, find_packages
- setup(
- name = "HelloWorld",
- version = "0.1",
- packages = find_packages("helloworld"),
- )
-
在这个文件中通过setuptools提供的各种关键字描述项目的元信息,包括名字、版本、 文件等等。写好后就可以通过python setup.py [command]执行setuptools提供的各种子 命令,比如编译、安装、测试、制作各种格式的软件安装包等等。
当你拿到一份Python项目的源代码,如果它已经写好了setup.py,你就可以方便的通过 setuptools将它安装到系统:
- $ python setup.py install
-
目前,使用setuptools制作的软件安装包常见格式有:
source distro
- $ python setup.py sdist
-
binary distro: egg
- $ python setup.py bdist_egg
-
binary distro: wheel
- $ python setup.py bdist_wheel
-
以openstack/nova为例,对应的三种格式软件安装包分别为:
- nova-12.0.0.0b2.dev251.tar.gz
- nova-12.0.0.0b2.dev251-py2.7.egg
- nova-12.0.0.0b2.dev251-py2.py3-none-any.whl
-
sdist和wheel格式的安装包都可以使用最常见的pip命令安装,egg还只能通过 比较老得easy_install命令安装。
- $ pip install nova-12.0.0.0b2.dev251.tar.gz
- $ easy_install nova-12.0.0.0b2.dev251-py2.7.egg
- $ pip install nova-12.0.0.0b2.dev251-py2.py3-none-any.whl
-
从上面的例子里可以看到,setup.py本质上还是一个Python脚本,有诸多语法的限制。 当Python项目比较庞大和复杂时,setup.py就会变的非常难写与维护。 在Openstack中,新建了一个pbr2 项目来处理这个问题。通过使用pbr项目提供的维护与 setup.py对应的setup.cfg文本配置文件来自动生成setup.py从而解决这个问题。
比如上面的例子,切换到pbr就会变成这样:
setup.py
- from setuptools import setup
-
- setup(
- setup_requires=['pbr'],
- pbr=True,
- )
-
setup.cfg
- [metadata]
- name = HelloWorld
- version = 0.1
-
- [files]
- packages =
- helloworld
-
当然,实际工程中,setup.cfg文件的内容会比这个例子复杂很多。
setuptools解决了Python软件包的制作问题,那如何分发它们呢?Python本身提供了PyPI 基础设施与相应的pip3 命令行工具来做这件事。
你可以将生成好的软件包通过
- $ python setup.py upload
-
通过网络上传到PyPI。 上传成功后,其他人就可以直接通过pip命令安装你上传的软件包:
- $ pip install ${package_name}
-
提到了pip,就不得不提一下virtualenv4 ,这两个工具经常是在一起搭配使用的。
virtualenv的功能有点像Linux系统下的chroot命令,运行
- $ virtualenv .venv
-
后,他会将最小安装一个Python运行环境到当前目录的.venv文件夹,内容包括.venv/bin 下的python、pip命令,.venv/lib下的python库目录等等。当你使用
- $ . .venv/bin/activate
-
激活刚刚创建出来的这个virtualenv环境后,你执行的python相关的二进制命令其实都是 .venv/bin目录下(而不是系统路径下的),安装/引用的python库也都是在.venv/lib目录 下的。
这样子,使用virtualenv后可以让每个Python项目使用独立的Python运行环境,不会产生 几个项目间依赖冲突、污染操作系统Python库的问题。
wheel5 是众多 Python 软件安装包格式的一种,这里专门要把它单独拿出来说是因为它的 优点和好处确实很多,是Python官方推荐的新一代Python项目发布格式,个人也非常希望 目前所有PyPI上项目都能尽快提供wheel格式的安装包。新版本的pip已经可以直接从PyPI 上下载安装wheel格式的安装包了。
和老的 egg6 格式相比,wheel 能让人直观感受到的先进之处:
和egg格式一样,wheel拥有的优点:
这里有一份官方文档详细比较了wheel和egg: 7
上面总结了Python打包社区PyPA(Python Packaging Authority)本身提供的各种工具, 但是在各个Linux发行版中,出于和系统集成(比如各种配置文件、脚本的管理)、加强 包的管理(PyPI上的软件包都是开发者自行自由上传的)等等原因,很多Linux发行版通常 也会制作维护各自平台上专有的Python项目安装包,比如Debian系的.deb格式安装包 等等。
由于各个Linux发行版的管理风格、系统路径、甚至默认的Python版本等等都大相径庭, 所以各种安装包的规格、内容也差别很大。
常见的Linux发行版里,Debian系的.deb格式和Redhat系的.rpm格式安装包属于比较严格和 保守的一派。它们通常会做严格的版本、依赖控制;维护项目相关的服务配置文件、 SysVinit/systemd、syslog、logrotate脚本等等;安装包会包含任何需要编译的二进制 文件(如.pyc文件、各种需要编译的c extension等),不需要在安装时进行本地编译; 使用Python2作为Python的默认版本,甚至在某些较老的Debian、CentOS的发行版中, 还在使用Python2.6/2.3。
而像ArchLinux这样比较激进的Linux发行版,安装包的管理就非常奔放。安装包通常只是 帮你指定一下依赖、任何软件和库都用最新版本(非常令人讨厌的,Python也默认使用 Python3)、Python项目的PKGBUILD里package()通常就一行 “python2 setup.py install ...”。
上面基本把最近自己接触到的和Python相关的包管理工具介绍了一遍,而在实际操作中去 在服务器上批量发布部署Python项目,利用上面提到的这些工具,能想到的通常有下面 几种方式。
每次到这个时候,真的是非常羡慕Java/Go这些不用纠结这种问题的编程语言。
适合在自己的开发调试机器上瞎搞的时候使用。
由于Python/pip本身的包管理功能比较弱,直接在服务器上用这种方法会给服务器的系统 安装一系列不可控制的Python依赖,并且可能破坏其他Python程序的依赖,导致其他 Python程序无法正常运行。装的时候方便,当你碰到问题想清理的时候可就蛋疼了,相信 维护服务器的SA也不太可能会让你这样做的。
比较靠谱的办法,但是只适合单机部署。当服务器数量众多,如何实现批量部署发布就是 个困难的问题了。
在virtualenv环境里安装好所有的Python文件,把整个virtualenv目录打包成一个单独的 tar包。发布时直接将这个tar包拷贝到目标服务器上解压安装。
目前一些知名的Openstack厂商,如Rackspace,就是通过这种方式在服务器上部署Python 项目的。
然而,这样做的缺点是,还需要做很多额外的工作去维护服务的配置文件、脚本、系统上 非Python相关的软件依赖(如kvm、libvirt、openvswitch)等等。
类似方法还有自建私有PyPI源,提前将Python项目打包上传到自建的PyPI源里。然后在 服务器上的virtualenv环境里通过自建的PyPI源安装。
比如,Debian系的.deb安装包、Redhat系的.rpm安装包。它们能比较好的维护各种服务 配置文件、脚本,以及像kvm这样的非Python相关的依赖,我们目前也在使用这种方式。
然而,它也有很多问题,以我比较熟悉的Debian发行版为例:
在virtualenv环境里安装好所有的Python文件,然后将整个virtualenv目录打包到单独的 一个Linux发行版的安装包内,同时在这个安装包内设置好相关的配置文件、脚本、系统 依赖。
理论上这种方法综合了上面两种方式的优点,目前正在调研中,然而相信实践过程中肯定 会踩到不少坑。
spotify公布了他们在Debian下使用这种方式解决Python项目发布部署问题的工具 dh-virtualenv8 ,感兴趣的同学可以参考一下。