不存在适用于所有情况的最佳解决方案。一家公司认为好的解决方案可能并不适用于
其他团队的工作流程。而且每个应用的需求也各不相同。小项目可以只使用 virtualenv
或 venv,比较简单,但大型项目可能还需要 buildout 的帮助,以便进行更复杂的装配。
之前没有详细说明的是,在 buildout 早期版本(2.0.0 版之前)中,可以在隔离环境中
对项目进行装配,其结果与 Virtualenv 给出的结果类似。不幸的是,这个项目的 1.x 分支不
再受到维护,所以不建议因为这个原因使用它。
我推荐尽可能使用 venv 模块,而不是 virtualenv。因此,对于面向 Python 3.4 或更高
版本的项目,应该默认选择 venv。在 Python 3.3 中使用 venv 可能不太方便,因为没有内
setuptools 和 pip 的支持。对于面向更多 Python 版本(包括其他解释器和 2.x 分支)
的项目,virtualenv 似乎是最佳选择。
系统级环境隔离
在大多数情况下,软件实现之所以可以快速迭代,是因为开发人员复用了大量现有组
件。不要重复你自己(Don't Repeat Yourself),这已经成为许多程序员的通用准则和座右铭。
将其他包和模块用在代码库中只是这种文化的一部分。二进制库、数据库、系统服务、第
三方 API 等也应该被当作“可复用组件”。甚至整个操作系统都是可复用的。
基于 Web 应用的后端服务是一个超级复杂的应用实例。最简单的软件栈(software
stack)通常由几层组成(从最底层开始):
• 数据库或其他类型的存储。
• Python 实现的应用程序代码。
• HTTP 服务器,例如 Apache 或 NGINX。
当然,这些软件可以进一步简化,但实际上是不可能的。事实上,大型应用往往复杂
到难以区分每一层。大型应用会用到多种不同的数据库,被分为多个独立进程,还会用到
许多其他系统服务来进行缓存、队列、记录日志、服务发现等等。遗憾的是,复杂度没有
上限,代码似乎只是遵循热力学第二定律而已。
真正重要的是,并非所有的软件栈元素都可以在 Python 运行环境的层面进行隔离。无
论是 HTTP 服务器(例如 NGINX)还是关系型数据库管理系统(RDBMS,例如 PostgreSQL),
在不同的系统上通常都有不同的版本。如果没有合适的工具,很难保证开发团队中每个人
使用的每个组件的版本完全相同。如果团队中所有开发人员都在开发同一个项目的话,那
么所有人可能会在开发工具箱上获得相同版本的服务,这在理论上是可能的。但如果他们
使用的操作系统与生产环境不同的话,所有这些努力都是徒劳的。当然也不可能强迫程序
员在并非本人最喜欢的系统上工作。
问题在于,可移植性仍然是一项巨大的挑战。在生产环境中,并非所有服务的运行方
式都和在开发人员电脑上完全相同,而且这一点不可能改变。即使是 Python 在跨平台方面
付出了巨大的努力,但在不同系统上的行为也会有所不同。通常来说,这些情况都有详细
的文档,只有直接进行系统调用时才会发生。但是,靠程序员的记忆力来记住一长串兼容
性问题,是很容易出错的。
这个问题的常见解决方法就是将整个系统隔离为应用程序环境。一般可以利用各种类
型的系统虚拟化工具来实现。当然,虚拟化会降低性能,但是现代计算机的硬件都支持虚
拟化,性能损失通常可以忽略不计。另一方面,可能的好处却有很多,如下所示。
• 开发环境可以完全匹配生产环境中使用的系统版本和服务,这有助于解决兼容性
问题。
• 系统配置工具(如 Puppet、Chef 或 Ansible,如果用的到的话)可以复用于开发环
境配置。
• 如果可以自动创建这样的环境,那么新来的团队成员就可以轻松上手项目。
• 开发人员可以直接调用系统底层特性,在工作机的操作系统上可能没有这些特性,举个
例子,在 Windows 中不可用的用户空间文件系统(File System in User Space,FSUS)。
使用 Vagrant 的虚拟开发环境
目前,Vagrant 似乎是最流行的工具,用一种简单方便的方法来创建并管理开发环境。
它可用于 Windows、Mac OS 和一些常见的 Linux 发行版,没有任何其他依赖。Vagrant 以
虚拟机或容器的形式来创建新的开发环境。具体实现取决于虚拟化供应商(provider)。
VirtualBox 是与 Vagrant 安装程序绑定的默认供应商,但也有其他供应商。最有名的供应商
是 VMware、Docker、LXC(Linux Containers)和 Hyper-V。
Vagrant 最重要的配置是一个名为 Vagrantfile 的文件。每个项目的这个文件都应该
是独立的。该文件中最重要的内容如下所示。
• 选择虚拟化供应商。
• 用作虚拟机镜像的 box 文件。
• 选择环境搭建(provisioning)方法。
• 虚拟机(VM)和虚拟机主机之间的共享存储。
• 虚拟机与主机之间的转发端口。
Vagrantfile 的语法语言是 Ruby。示例配置文件提供了用于启动项目的优秀模板,并
且还有详细的文档,因此无需掌握这种语言的知识。用一行命令就可以创建模板配置文件:
vagrant init
这一命令会在当前工作目录下创建一个名为 Vagrantfile 的新文件。通常最好将这
个文件保存在相关项目的根目录下。这个文件已经是一个有效配置,可以利用默认供应商
和基础镜像文件(base box)来创建新的虚拟机。默认不启用环境搭建(provisioning)。添
加完 Vagrantfile 后,利用下面这个命令可以启动新的虚拟机:
vagrant up
初始启动可能需要几分钟的时间,因为需要从网上下载 box 文件。还有一些初始化过
程可能要花费一些时间,这取决于使用的供应商、box 文件和每次打开现有虚拟机时的系
统性能。通常来说,这个过程只需要几秒。一旦启动并运行了新的 Vagrant 环境,开发者可以利用下面这个简短的命令连接 SSH:
vagrant ssh
在项目源代码树中,在 Vagrantfile 之下的任何位置都可以运行这一命令。为了方
便开发人员,我们会在上层目录中查找配置文件,并与相应的虚拟机实例进行匹配。然后
它会建立安全的 shell 连接,可以像任何普通远程机器一样与开发环境进行交互。唯一的区
别在于,整个项目的源代码树(根目录是 Vagrantfile 所在的位置)是在虚拟机文件系
统的/vagrant/目录下。