看看360的同学日常是怎么使用python的

打算写这样一个系列,说说我们在360里面,如何使用Python。在360,除非是需要包含在360客户端软件当中的功能,技术人员使用什么语言进行开发,更多的是一种个人,最多是项目团队的决定。因此我们的同事会使用php写页面,用python的工具,用c/c++写模块,甚至用易语言写界面。

也正是因为这个原因,虽然这个标题写得很大,但其实我也只能写一下我所在的团队如何使用Python。我们目前有Python代码约6万行,程序运行在Linux下,使用Python2.5Python 2.7环境。

6万行Python代码被被分成80余个项目进行组织。每个项目提供一个或一组完整的功能集合,每个项目都有自己的setup.py文件用来将项目代码打包成Python 发布包(Distribution),部分项目还有自动文档生成,我们使用的是 Sphinx 和 reST格式的文本。打包好的Python包被发布到我们自己搭建的内网的与 pypi.python.org 兼容的私有 pypi 服务器上,而文档保存在内网的类似于 readthedocs 的服务器上。

后台团队的代码主要运行我们自己的Linux服务器集群上,开发和部署的成本比较低,因此我们使用比较敏捷的开发流程。流程大体上可以分为下面几个步骤:

1. 1开发:顾名思义,这个步骤当中,开发人员在开发机上面写代码实现功能,不同的开发人员的开发环境使用 扩展过的virtualenv 脚本进行隔离;

2. 2单元测试:开发人员负责对代码当中的关键部分进行单元测试,通常使用 unittest,我们使用 nose 将测试用例聚合和进行回归测试,不定期使用 coverage 确定代码测试覆盖率(集成在nose当中)。这一步还会使用 PyLint对代码进行扫描;

3. 3构建:使用 python distribute 将Python代码构建成包,同时将这个包发布到测试版pypi服务器(pypi-testing),测试版pypi服务器是我们搭建的若干个私有pypi其中之一,发布工作使用的是我们扩展的distribute命令;

4. 4测试:在测试机器或测试流程当中,从测试版pypi当中获取最新的库并测试,这个部署过程我们使用distribute提供的easy_install工具进行;

5. 5发布:将经过测试的包从测试版pypi服务器移到发布版pypi服务器,这个同样通过扩展的distribute命令;

6. 6部署:运维人员从发布版pypi服务器上获取最新的库,并更新到真实的服务器上,并应用新的变更。这个过程当中任何一台单机部署使用的都是easy_install,在分布式环境下,我们使用 Fabric 进行多机部署;

7. 7监控:新版本上线之后,会持续通过日志和报警系统进行系统监控。我们专门扩展了 logging 模块以便适应我们的监控需求。

熟悉Python的朋友们可能看到这些名词和包都很熟悉,因为我们所使用的都是业界广泛使用的开发、测试和运维的工具。但这些工具很多都适合于开源软件(Open Source Software)而非私有软件(Proprietary Software),例如 distributepypi.python.org的结合是天衣无缝的,Sphinxreadthedocs也是很容易结合,但是作为一个私有软件,我们无法将代码和文档放到pypireadthedocs上面。为此我们几乎复制了整套的基础架构,包括pypi服务,readthedocs服务等,后续我会介绍我们如何做到这点的。希望这个系列对于其他正在使用Python开发私有软件的同仁能有些帮助。

这个系列的前面一部分,我们简单介绍了我们的项目的基本结构和开发流程。整个开发流程都是基于 distribute 的。关于我们如何使用distribute,会在后面一部分当中介绍。基于distribute,我们具有了从代码构建到发布,测试和部署的基本框架。但是在实际的开发当中,我们还有一些问题需要解决,最重要的一点就是开发和运行环境的隔离和低权限。

所谓环境的隔离,是指在同一台机器上,能够并行(side-by-side)地部署多个python环境,每个环境之间互相独立,拥有自己的python程序,库和可执行程序。这样,我们既可以在一台机器上为多名开发者提供互不影响的开发环境,也能在同一台服务器上为多个应用提供互不影响的执行环境。同时,为了能够让多名开发者或多个应用实现真正意义上的隔离,还需要让每个环境能够在各自的非root且不能sudo的用户手里进行管理,包括升级包,安装新的包等等,否则需要为每个人都提供sudo权限,会破坏隔离性。

一个直觉的解决方案是在机器上安装为每个人安装一个python,彼此在不同的目录当中,每个目录只授权给对应的人员权限。但这种方案会导致大量的冗余的文件,比如python的标准库,就需要每个安装的目录里面一份。而且如果我们需要做小版本升级(比如将python 2.7.2 升级到python 2.7.3)的时候,需要对每个目录重新执行一次install操作。

幸好我们不是第一个遇到这种需求的人,于是我们找到了 virtualenvvirtualenv官网(http://virtualenv.org/)是这样描述virtualenv的:

virtualenv 是一个创建隔离的Python环境的工具。

virtualenv要解决的根本问题是库的版本和依赖,以及权限问题。假设你有一个程序,需要LibFoo的版本1,而另一个程序需要版本2,如何同时使用两个应用程序呢?如果将所有的库都安装在/usr/lib/python2.7/site-packages(或者你的系统的标准包安装路径),非常容易出现将不该升级的库升级的问题。

另外,在一台共享的机器上,如果没有全局的 site-packages目录的权限(例如一个共享的主机),如何安装Python库呢?

在这些情况下,就是该用到virtualenv的地方。它能够创建一个自己的安装目录,形成一个独立的环境,不会影响其他的virtualenv环境,甚至可以不受全局的site-packages当中安装的包的影响。

简直就是为我们的需求量身定做的一样。但仔细想来,virtualenv并不能完全满足我们的需求。virtualenv默认只能生成一个干净Python环境,其中只有python及其标准库。而我们需要有一个脚本,可以一键做到:

1. 在开发环境当中,生成基本环境之后,自动安装基本的开发库,如 PyLint, nose, coverage等,并将源代码目录当中的所有项目注册到 site-packages(执行python setup.py develop

2. 在线上环境当中,环境生成完毕之后,自动安装应用程序及其依赖库。

幸好virtualenv支持生成一个定制化的脚本,可以用来生成自己的虚拟环境,关键是virtualenv.create_bootstrap_script方法,这个方法支持在生成的定制化脚本的末尾添加一段自己的代码,而且支持在默认的环境创建完成之后调用自定义的after_install方法。我们写了自己的一个after_install方法,加载同一目录下的after_install.py文件并执行其中的main函数。

在这个main函数当中,我们将after_install的需求整理成下面几种类型的动作:

· 用 easy_install 安装指定的包,支持指定pypi服务器地址。

· 将未打包的一些脚本或二进制文件复制到新的 python环境当中,比如一些私有C库的python绑定。

· 枚举当前目录下的代码目录,并对其中的 setup.py执行develop操作

第一个和第二个问题都容易解决。我们建立了一个配置文件,在其中指定 pypi 服务器的地址,easy_install需要安装的包列表,需要复制到python环境当中的文件的文件名称,并将这些文件放在与after_install.py同一目录的data子目录下,而after_install.py只要忠实地按照配置文件当中的配置进行easy_install或复制的操作即可。

比较复杂的是第三点,因为当前目录当中可能有多个项目,而这些项目之间是具有相互依赖的,同时他们可能都会依赖于一些外部的python包。如果不谨慎地考虑develop的顺序,可能导致develop一个较为高层的库的时候,将它依赖的底层库从pypi上面获取到并安装到环境当中,而后续develop这个底层库的时候,有可能造成两个库的冲突。

对于这种情况,我们需要先枚举出当前目录下的所有包含有 setup.py文件的目录,然后获取到其中的依赖关系,并根据依赖关系进行排序,最后根据这个顺序依次进行develop

获取一个项目(python包)的依赖关系,只能从setup.py文件当中抽取。关于setup.py当中定义包和包的依赖关系的内容,请参见distribute文档。我们使用monkey patch 的方法,hooksetup函数,在枚举出所有项目的setup.py文件之后,动态加载每个setup.py(使用imp.load_source),也就获取到了相应的依赖关系。下面的代码当中,_get_project_depends接受一个project名称,和一个project所在目录的名字,即可获取这个项目所依赖的Python包的列表。

图片1.jpg

随后我们根据每个项目的依赖关系,做成一个字典,key就是Python包的名字,而value是该包所依赖的包的列表。根据依赖关系进行排序这个问题,也常常被我拿来做面试题,有兴趣的同仁也可以自己实现一下:

图片2.jpg

至此,after_install.py 扩展脚本的功能就不是问题了。

这样,我们有了一个通过 virtualenv.create_bootstrap_script生成的bootstrap.py脚本,一个after_install.py作为扩展脚本,一个配置文件用来配置after_install,以及一个目录用来保存所有需要直接复制的文件。他们的入口是bootstrap.py,直接用python执行它即可创建一个virtaulenv环境,然后自动调用after_install.py当中的逻辑。一切看起来很完美,但是我们需要的可能更多:我们需要一个独立的可执行的脚本,以便大家方便的创建起来一个新环境,而不是每次复制一堆文件。

为此我们使用了类似 eggsecutable 的方式。

这种方式依赖于Linux unzip程序的一个特性。unzip程序在试图解压一个zip文件的时候,并不要求一定要从第一个字节开始就是zip文件,它会在一定的区域内搜索zip文件的头,并从对应的位置开始解压。这样一来,我们可以在一个zip文件的之前,附加上一个简短的shell脚本,在其中将自身解压,并将后续的控制权交给解压出来的文件。比如我们所做出的一个附加了zip文件的shell脚本:

图片3.jpg

其中前面部分就是我们的加载脚本:

图片4.jpg

这之后就是一个zip文件,通过unzip可以方便地将其解压。

明白了原理,我们就可以很容易地写出一个 bootstrap.sh文件的生成器make-bootstrap。借助这个生成器,我们可以方便地生成一个自包含的bootstrap.sh文件。对应于需要的场景,我们有两种方式使用这个脚本:

方式一:预先生成好一个 bootstrap.sh,并将这个文件签入到svn当中,放在开发环境的根目录下;每个开发人员签出代码之后,立即执行这个脚本构建一个自己的开发环境:

1

./bootstrap.sh dev-sharelib

后续开始开发之前,可以运行

1

source dev-sharelib/bin/activate

激活这个开发环境,随后即可在其中进行开发和测试了。

方式二:在应用程序的构建过程当中,调用 make-bootstrap脚本生成一个指定配置的bootstrap.sh(为此我们还写了一个distribute扩展,后续介绍distribute的时候会提到),并将其发布到更新服务器。在部署阶段,运维人员获取这个bootstrap.sh并在服务器上建立起来一个隔离环境用来部署整个应用。

至此,我们就有了一套基于virtualenv的环境建立脚本及其生成方案,基本的环境隔离和低权限化已经完成。但真正的开发工作,以及发布和部署,都还需要另一个python大杀器– distribute 才行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值