一种部署 Python 代码的新方法
在Nylas,我们喜欢使用Python进行开发。它的语法简单并富有表现力,拥有大量可用的开源模块和框架,而且这个社区既受欢迎又有多样性。我们的后台是纯用 Python 写的,团队也经常在 PyCon 和 meetups 上演讲。你可以认为我们是 Python 的超级粉。
然而,Python 的一个大缺陷是没有一个明确的工具来部署 Python 服务端应用。工作的情况就像是“执行 git 的 pull 命令后剩下的就只有祈祷了”,但这并不是一个好的方式,尤其当用户依赖于我们的应用。当你的应用引用了很多仍在变化的依赖时,这会让 Python 的部署工作变得更加复杂。下面 HN 上的评论概括了 Python 部署的糟糕情况。
为什么这么多年了,仍没有一个有效的办法帮我将 Python 编写的软件转换成 deb 格式?
—— 来自一个受挫的 HN 用户
在 Nylas,我们开发了一种更好的方法可以将 Python 代码连同其依赖一起部署,使得我们能够轻松地对轻量级包进行安装、升级或者删除。该方式的实现并不需要将我们的整个栈迁移到像 Docker、 CoreOS 或者AMIs 这样的系统上。
新手才用 GIT 和 PIP 进行 Python 部署
Python 提供了丰富的模块。不管你搭建的是一个web 服务器还是机器学习分类器,总会有一个合适的模块帮你启动项目。现在获取这些模块的标准方法是通过 pip 从 Python 包索引 (亦称 PyPI)中下载和安装。这就跟 apt,yum,rubgem 等命令操作是一样。
大多数人搭建开发环境的第一步就是用 git 克隆份代码,然后通过 pip 安装其依赖。这也是大多数人第一次尝试部署代码的做法。部署脚本大致如下:
Shell
1
2
3
4
|
git clone https://github.com/company/somerepo.git
cd /opt/myproject
pip install -r requirements.txt
python start_server.py
|
但是当部署大量生产服务时,这种策略有下面几个原因可能导致失败:
PIP 并没有提供“部署回滚”策略
pip unistall
并不是每次都能正常工作,也没有办法“回滚”到上一个状态。虽然可以用Virtualenv 实现,但它并不是用来管理历史环境的。
用 pip 安装依赖会让部署变得极其慢
使用pip install 来安装一个由C语言编写的模块经常需要从源码进行编译,对于一个新建立的virtualenv环境,这将花费几分钟的时间。但部署应用应该是一个以秒计算的快速而轻量的过程。
在每台主机上分别构建代码会有一致性问题
当你使用 pip 部署时,无法保证不同服务器上运行的应用版本是一致的。构建过程或现有依赖中的错误导致的不一致,这是很难去调试的。
如果 PyPI 或者你的 git 服务器挂掉会导致部署失败
pip install
和 git pull
通常依赖于外部服务器。你可以选择使用第三方平台 (如 Github,PyPI)或者自己搭建服务器,重要的是确保部署过程满足正常运行时间和规模的预期。当扩展自己的基础设施,尤其是在大型部署时,外部服务往往是第一个挂掉的。
如果你在运营一个人们依赖的应用系统,而且它又是部署在多个服务器上,那么采用 git + pip 部署策略只会让你更加头痛。我们需要的部署策略应该是快速的、一致而且可靠的。更具体地说:
- 能够构建代码成单一、有版本控制的工件
- 受版本控制的工件可以进行单元测试和系统测试
- 一种简单的机制可以从远程主机完全的安装或卸载工件
有了这三样东西可以让我们将更多的时间花在功能的构建上,以更少的时间进行代码一致性迁移。
“干脆用DOCKER吧”
初看下,这似乎最适合用 Docker了,Docker 是当前盛行的容器管理工具。在一个 Dockerfile里,只要简单地添加代码仓库的引用,安装必要的库和依赖。那么我们就建好 Docker 镜像,将它作为受版本控制的工件发往到远程主机。
然而,当我们尝试这样操作时遇到了几个问题:
- 我们的内核版本 (3.2)并不完美支持 Docker,如果为了更快的迁移代码而升级内核,我们觉得这有点像在用牛刀杀鸡。
- 在专用网络里分发 Docker 镜像要有一个独立服务,这样我们又需要对服务进行配置,测试和维护。
- 将 ansible 自动化安装转换成 Dockerfile 的过程是痛苦的,需要对日志配置、用户权限和秘钥管理等进行大量繁琐的编辑。
即便我们顺利的解决了这些问题,为了调试生产上的问题,我们的工程师团队又不得不学习如何跟 Docker 连接交互。我们认为更快地迁移代码并不应该重新执行整个基础架构自动化和编排层。所以我们继续调查其他方案。
PEX
PEX 是 Twitter 开发的一个智能工具,它允许 Python 代码以可执行压缩文件进行传送。这是一个很酷的想法,关于这个主题我们建议去看Brian Wickman 在推特大学的演讲。
设置 PEX 比 Docker 还简单,因为他只需要运行生成好的可执行压缩文件,但是构建 PEX 文件却是一项巨大的工程。我们在构建第三方库需求时遇到问题,特别是当中包含了静态文件。我们也遭遇到 PEX 源代码产生的混乱的堆栈跟踪,使得调试工作变得更加困难。这是个异份子,因为我们主要目标是改善工程效率,让事情更加易懂。
使用 Docker 会增加运行时的复杂度。而 PEX 会增加构建时的复杂度。我们需要一个方案可以最小化整体复杂度,同时提供可靠的部署,所以我们继续调查其他方案。
包:原始的“容器”
几年前,Spotify 悄悄发布了一个工具叫 dh-virtualenv,你可以用来构建内含 virtualenv 的 debian 包。我们觉得这很有意思,也已经有了很多 Debian 相关经验并在生产环境上运行。(Christine,我们的联合创始人之一,就是一个 Debian 的开发者。)
用dh-virtualenv
可以很简单的创建一个 debian 包,它包含了 virtualenv 和罗列在requirement.txt 文件里的所有依赖。当在主机上安装这个 debian 包时, 它会将 virtualenv 放置在/usr/share/pyton/
路径下。就是它了。
这就是我们在 Nylas 上部署代码的关键。我们的持续集成服务器 (Jenkins) 运行 dh-virtualenv 来构建包, 用 Python 的wheel缓存来避免对依赖重新构建。这就创建一个捆绑式工件(debian 包),然后对其进行大量的单元测试和系统测试。如果通过测试,则可认为生产上是安全的,可以上传到 s3.
这个过程的关键部分是通过均衡 Debian 的内置包管理器 dpkg,我们可以最小化部署脚本的复杂度。部署脚本大致如下:
Shell
1
2
3
4
|
temp=$(mktemp /tmp/deploy.deb.XXXXX)
curl “https://artifacts.nylas.net/sync-engine-3k48dls.deb” -o $temp
dpkg -i $temp
sv reload sync-engine
|
要回滚的话,我们只需要部署上一版本的工件。dpkg 工具可以免费帮你清除旧代码。
这个策略最重要的一方面是它实现了一致性和可靠性,同时匹配了我们的开发环境。我们的工程师已经在用virtualenv,而dh-virtualenv 只是一个将其迁往远程主机的方式。如果我们选择 Docker 或者 PEX,明显需要改变我们本地开发的方式又增加了复杂度。我们同样不希望给使用我们开源代码的开发者带来复杂度负担。
现在,我们用 Debian 包来迁移我们所有的 Python 代码。完整构建我们代码库(包含数十个依赖)只要不到2分钟时间,部署更是数秒便完成。
DH-VIRTUALENV 入门
如果你受够了 Python 部署的折磨,那就试试 dh-virtualenv吧。它会是你不错的选择!
配置 Debian 包对于初学者而言是棘手的,所以我们构建了 make-deb 工具来帮你入手。它会基于你 Python 项目里的 setup.py 文件生成 Debian 配置。
首先安装make-deb
工具,然后在你项目的根目录下运行它:
Shell
1
2
3
|
cd /my/project
pip install make-deb
make-deb
|
如果你的 setup.py 文件有缺失信息的话,make-deb
会要求你添加。一旦它收齐所需的信息,make-deb
会在你项目的根目录下创建一个 debian 目录,包含了 dh-virtualenv 需要的所有配置。
构建 Debian 包需要你在装有 dh-virtualenv 的 Debian 系统上进行。如果你没有 Debian 环境,我们建议你在 Mac 或者 Windows 上用 Vagrant 和 Virtualbox 安装一个 Debian 的虚拟机。你也可以参考我们放在Git 仓库下 sync-engine 项目里的Vagrantfile 来作配置。
最后,运行dpkg-builpackage -us -uc
来创建 Debian 包。你不需要直接调用 dh-virtualenv,因为它已经在之前make-deb
创建好的配置规则里了。当这条命令执行好后,你就构建好一个可部署的漂亮的工件。
一段简单的部署脚本如下:
Shell
1
2
3
4
5
6
7
8
|
scp my-package.deb remote-host.example.org:
ssh remote-host.example.org
# Run the next commands on remote-host.example.org
dpkg -i my-package.deb
/usr/share/python/myproject/bin/python
>>> import myproject # it works!
|
部署时,你需要将这个工件上传到生产服务器上。运行dpkg -i my-package.deb
命令来安装。virtualenv 会被放在/usr/share/python/
目录下,所有预设在 setup.py文件里的脚本文件则会在bin
目录里。就是它了!你这就走上了简易部署的光明大道。
总结
在构建大型系统时,项目难点往往是在寻求合适的工具,而非从头开始重构一个新的系统。我们认为使用 Debian 基于包的部署是部署 Python 应用的一个极佳方案,最重要的是它可以帮我们平稳而快速的迁移代码。
声明:
本文转载自:http://python.jobbole.com/85057/
一种部署 Python 代码的新方法的更多相关文章
- 【Android】一种提高Android应用进程存活率新方法
[Android]一种提高Android应用进程存活率新方法 SkySeraph Jun. 19st 2016 Email:skyseraph00@163.com 更多精彩请直接访问SkySeraph ...
- 在Linux下调试Python代码的各种方法
这是一个我用于调试或分析工具概述,不一定是完整全面,如果你知道更好的工具,请在评论处标记. 日志 是的,的确,不得不强调足够的日志记录对应用程序是多么的重要.您应该记录重要的东西,如果你的记录足够好的 ...
- 一种提高Android应用进程存活率新方法
一.基础知识 1.Android 进程优先级 1.1 进程优先级等级一般分法:- Activte process- Visible Process- Service process- Backgrou ...
- 三种安装python第三方库的方法
还记得第一天的时候我们说python拥有丰富的库,那这么多的第三方库,我们如何使用呢?今天我们可以看一下python库的安装. 方法一:使用python命令进行离线安装 我以urllib5库 ...
- 5 种使用 Python 代码轻松实现数据可视化的方法
数据可视化是数据科学家工作中的重要组成部分.在项目的早期阶段,你通常会进行探索性数据分析(Exploratory Data Analysis,EDA)以获取对数据的一些理解.创建可视化方法确实有助于使 ...
- [转]5 种使用 Python 代码轻松实现数据可视化的方法
数据可视化是数据科学家工作中的重要组成部分.在项目的早期阶段,你通常会进行探索性数据分析(Exploratory Data Analysis,EDA)以获取对数据的一些理解.创建可视化方法确实有助于使 ...
- python代码异常范围检查方法(非常实用)
对于python编程的代码,如果需要进行相应的检查其中的错误或者异常,并且确定出现异常语句的大致范围,主要有以下四种方法: 1.第一种方法:遇错即止(告知原因) try ......(所需检查语句) ...
- 实现斐波拉契数列的四种方式python代码
斐波那契数列 1. 斐波拉契数列简介 斐波那契数列(Fibonacci sequence),又称黄金分割数列.因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引 ...
- 计算Python代码运行时间长度方法
在代码中有时要计算某部分代码运行时间,便于分析. import time start = time.clock() run_function() end = time.clock() print st ...
随机推荐
- 回文树(回文自动机) - BZOJ 3676 回文串
BZOJ 3676 回文串 Problem's Link: http://www.lydsy.com/JudgeOnline/problem.php?id=3676 Mean: 略 analyse: ...
- JS调用asp.net后台方法:PageMethods
先帮朋友宣传一下程序人生(http://www.manong123.com)的网站,里面都是开发感悟,开发人员创业,支持一下吧~ 原来是通过PageMethods来实现的. 举个列子: Default ...
- 用 malloc 或 new 申请内存之后,应该立即检查指针值是否为 NULL
用 malloc 或 new 申请内存之后,应该立即检查指针值是否为 NULL. 防止使用指针值为 NULL 的内存. #include <iostream> #include <s ...
- java-I/O File类(5)-Reader和Writer、OutputStreamWriter 、BufferedWriter、字节流和字符流的区别
标签: outputstreamwriterreader字符file方法 2015-05-14 23:06 469人阅读 评论(0) 收藏 举报 分类: 孙鑫-java基础(16) I-O(4 ...
- before伪类的超有用应用技巧——水平菜单竖线分隔符
方法一.li前面加before伪类 <!doctype html> <html dir="ltr" lang="zh-CN"> < ...
- js中的问题(this)(遍历对象中的属性)
for (var i in this) { if (this[i] == null) this[i] = "";//属性如果为null,则默认为""; ...
- jQuery监控文本框事件并作相应处理的方法
本文实例讲述了jQuery监控文本框事件并作相应处理的方法.分享给大家供大家参考.具体如下: //事情委托 $(document) .on('input propertychange', '#que ...
- MFC获取系统当前时间
1.使用CTime类 CString str; //获取系统时间 CTime tm; tm=CTime::GetCurrentTime(); str=tm.Format("现在时间是%Y年% ...
- ItcastOA_设计BaseDao_设计DAO接口和实现类_写DAO实现类中的方法内容
3. 基础功能 3.1. 设计BaseDao接口与BaseDaoImpl类 每个实体都应有一个对应的Dao,他封装了对这个实体的数据库操作.例 实体Dao接口实现类 ================= ...
- 编程之美 最长递增子序列 LIS
1. O(N*logN) 解法 先对序列排序, 然后寻找两个序列的最长公共子序列 2. O(N*N) 的动态规划解法 令 LIST[i] 表示以 i 为结尾的最长子序列的长度, 那么 LIST[J] ...