一个简单的Python部署问题 - 一整个痛苦的世界
我们有几个在Linux上运行的Python 2.6应用程序。其中一些是Pylons网页应用,其他的则是我们通过命令行用nohup
运行的长时间进程。我们在开发和生产环境中都使用virtualenv
。那么,最好的方法是什么来把这些应用程序部署到生产服务器上呢?
在开发环境中,我们只需把源代码放到任何目录,设置一个虚拟环境,然后运行——这很简单。我们也可以在生产环境中这样做,或许这确实是最实用的解决方案,但在生产环境中运行svn update
总让人觉得不太对劲。我们也试过用fab
,但它总是第一次就不成功。每个应用程序总会有不同的问题出现。我觉得整个过程实在是太复杂了,因为我们想要实现的其实非常简单。以下是我们对部署过程的期望。
- 我们应该能够运行一个简单的命令来部署应用程序的更新版本。(如果初次部署需要一些额外的步骤,那也没关系。)
- 当我们运行这个命令时,它应该把某些文件从Subversion仓库或本地工作副本复制到服务器上的指定“环境”,这可能意味着不同的虚拟环境。我们在同一台服务器上有应用程序的预发布和生产版本,所以它们需要以某种方式保持分开。如果它安装到site-packages里也没问题,只要能正常工作就行。
- 我们在服务器上有一些配置文件需要保留(也就是说,部署过程中不能被覆盖或删除)。
- 其中一些应用程序需要从其他应用程序导入模块,所以它们需要能够以某种方式相互引用作为包。这是我们遇到的最大麻烦!我不在乎它是通过相对导入、site-packages还是其他方式,只要在开发和生产环境中都能可靠地工作就行。
- 理想情况下,部署过程应该自动安装我们应用程序所依赖的外部包(例如,psycopg2)。
就这些了!这有多难呢?
8 个回答
我一直在为我们的工作项目实现这个功能,涉及到几个不同的部分。
首先,我们通过定制virtualenv.py,利用它的引导功能,添加自己想要的创建后操作和标志。这些功能让我们可以定义常见的项目类型,并且只需一个命令就能创建一个新的虚拟环境,从git仓库中检出项目,并使用pip和requirements.txt文件安装所需的依赖。
所以我们的命令看起来像这样: python venv.py --no-site-packages -g $git_proj -t $tag_num $venv_dir
http://pypi.python.org/pypi/virtualenv http://pip.openplans.org/
这样我们就能顺利完成对现有项目的初步检出。在我们工作和更新项目时,我们会在每个项目中使用fabric命令来构建发布版本,然后进行部署:
http://docs.fabfile.org/0.9.0/
我有一个fab命令:make_tag,它会检查未使用的提交,打开需要更新版本字符串的文件,构建并上传sphinx文档,然后将最终的标签提交到仓库。
另一方面,还有一个fab deploy命令,它会通过ssh执行指定标签的git检出,更新任何新的依赖,运行所需的数据库迁移,然后如果这是一个web应用程序,还会重启web服务器。
以下是标记功能的一个示例: http://www.google.com/codesearch/p?hl=en#9tLIXCbI4vU/fabfile.py&q=fabfile.py%20git%20tag_new_version&sa=N&cd=1&ct=rc&l=143
你可以通过谷歌代码搜索浏览很多优秀的fabric文件。我自己也参考了一些来使用。
这确实有点复杂,需要多个部分才能让事情顺利运行。不过,一旦你把它搞定,灵活性和速度都非常棒。
这其实并不难。你主要需要玩弄一下 buildout 和 supervisord,这是我的看法。
虽然学习 buildout 可能需要一点时间,但考虑到它能减少重复设置时的麻烦,这些时间是值得的。
关于 nohup: nohup 的方法不太适合正式的部署。我对 supervisord 的使用体验非常好。它是运行生产环境下 Python 应用的优秀解决方案,设置起来也非常简单。
下面是一些具体的回答。
- 部署只需一个命令:Buildout 就是答案。我们已经使用它好几年了,没遇到太多问题。
- 通常,你会先获取源代码,然后运行 buildout。之后,让设置直接复制到 site-packages 可能不是个好主意,最好保持环境的独立性。
- 配置文件不会被覆盖。
- 你可以考虑为常用的包构建 egg 文件。比如,你可以为一个包(比如 commonlib)构建 egg 文件,然后把它上传到你的代码库。接着,你可以在 buildout.cfg 中把它指定为依赖项。
- Buildout 能够完全独立于中央/顶层安装来构建大多数必要的包。不过根据我的经验,如果将带有 C 扩展的 Python 包作为操作系统包安装,会简单得多。
使用 setuptools、virtualenv 和 pip,可以让Python代码的开发和部署变得简单很多。
核心思想
我发现最棘手的部分是如何运行一个开发环境,让它尽可能地和实际部署的环境相似,同时又要遵循Python的工具和习惯。不过,使用pip和setuptools,这个目标其实很容易实现。它们可以让你在Python环境中“安装”一个开发树,而不需要移动文件。(实际上,setuptools自己就能做到这一点,但pip在处理依赖关系时更方便。)
另一个关键问题是如何准备一个干净的环境,确保两个环境中使用的包是一致的。Python的virtualenv在这方面非常有用,它允许你配置一个完全自定义的Python环境,选择你想要的包,而不需要管理员权限,也不需要操作系统的包(比如rpm或dpkg),而且不受你所使用的发行版上已安装的包和版本的限制。
最后,一个让人头疼的问题是创建与PYTHON_PATH兼容的命令行脚本。这一点setuptools也提供了很好的解决方案。
设置环境
(为了简单起见,这里给出的是比较具体的步骤。你可以根据需要进行调整。)
- 准备一个工作目录,让你的Python项目有个“家”。
- 从这个页面底部下载最新的virtualenv,并解压(解压到哪里都可以)。
在工作目录中,设置一个新的Python虚拟环境:
$ python <untarred_directory>/virtualenv.py venv
你会希望在这个虚拟环境中进行大部分工作。使用这个命令来进入虚拟环境(
.
是source
的快捷方式):$ . venv/bin/activate
安装pip:
$ easy_install pip
为你想要创建的每个可安装包创建一个目录。
- 在每个目录中,你需要一个setup.py文件,用来定义包的内容和结构。setuptools的文档是一个很好的入门资源,值得花时间去仔细阅读。
开发
一旦你的目录结构准备好了,你就几乎可以开始编码了。不过,现在相互依赖的包在开发环境中是看不到彼此的,和实际部署的环境不一样。这个问题可以通过setuptools提供的一个小技巧来解决,pip也会利用这个技巧。对于你正在开发的每个包,运行以下命令(确保你在项目的虚拟环境中,如上面的第3步所述):
$ pip install -e pkg1
这个命令会将pkg1
安装到你的虚拟环境中,而且不会复制任何文件。它只是添加了一个指向包开发根目录的链接到site-packages
目录,并在该根目录下创建了一个egg-info目录。你也可以不使用pip,像这样做:
$ cd pkg1
$ python setup.py develop
这样通常也能工作,但如果你有第三方依赖(这些应该在setup.py中列出,具体说明可以参考setuptools文档中的这里),pip在查找这些依赖时会更聪明。
需要注意的是,setuptools和pip都不能自动找到你自己包之间的依赖关系。如果目录B中的PkgB依赖于目录A中的PkgA,那么pip install -e B
会失败,因为pip不知道PkgA在目录A中;它会尝试从在线仓库下载PkgA,但会失败。解决方法就是在安装每个包时,先安装它的依赖包。
此时,你可以开始python,加载你的模块并开始玩耍。你可以编辑代码,下次导入时就能立即看到更改。
最后,如果你想用你的包创建命令行工具,不要手动编写。这样会导致一堆PYTHON_PATH的混乱,永远无法正常工作。只需阅读setuptools文档中的自动脚本创建部分,这样可以省去很多麻烦。
部署
当你的包准备好使用时,可以用setup.py创建部署包。这里有太多选项不便一一列举,但以下内容可以帮助你入门:
$ cd pkg1
$ python setup.py --help
$ python setup.py --help-commands
未完事项
由于问题的广泛性,这个回答自然是不完整的。我没有涉及长时间运行的服务器、网络框架或实际的部署过程(特别是使用pip install的--index-url
来管理私有的第三方和内部包,以及-e vcs+...
,这可以从svn、git、hg或bzr中拉取包)。但我希望我给了你足够的信息,让你能把这些内容结合起来(只要别因此而困扰自己 :-)。