从开发者的角度看:打包和部署
如今的互联网软件越来越碎片化(micro services),Queue无处不在,服务依赖越来越多,使得软件功能的开发,到软件功能的部署,中间有很长的一段路。这段路,是持续集成(Continuous Integration)和持续发布(Continous Delivery)的基石,一般由devOps包圆了,对从不涉身其中的dev而言,看上去就像ops们用了黑魔法,设了道传送门一样,让写好的代码biu的一下就变成了运行在浏览器,或者手机上的鲜活页面。本着不懂点devOps的dev不是好pm的态度,本文简单讲讲软件发布过程中的两个黑魔法:打包(packaging)和部署(deploying)。
我们先看「打包」。
打包
打包字面上的理解是把你的应用和其依赖的组件组织在一起,以便于分发到目标系统上。客户端软件的时代,如office 97烧录成一个iso(便于刻在光盘上)就是个典型的打包的过程;互联网时代,一个java项目生成 jar,python项目生成 wheel/egg,也是打包的过程。
打包的意义在于制作可以重复使用的软件。所有琐碎的活儿都在打包的时候完成了,而在要部署的目标系统上,无需使用源代码,无需处理依赖,无需编译,只要把打包好的软件「安装」好即可。我们知道,在计算机领域,合格的程序员倾向于消除一切重复的工作。打包的过程,实际上是一系列手工操作的合集,因此必然有相应的工具来帮助提高打包的效率。
打包软件的元老级人物应该是 make。这个常常伴随C/C++项目的任务管理工具的一大主要用途就是打包。当然,make 接近于 *nix shell 的语法并非人见人爱,于是各个语言都提供语言本身的任务管理工具,如grunt,rake。
简单的应用,打包的过程可以很快,因为只需应用本身的编译和依赖处理,秒级就可以完成;但复杂的应用可能需要数个钟头。比如说,一个嵌入式软件需要用 buildroot 把 OS,dependencies 及应用软件深度整合在一个可以直接烧录在硬件的 firmware 里,其 full build 可能需要几个小时。另外一个例子是一个复杂的系统可能会使用 ansible/puppet/chef 这样的工具将多个代码库的不同部分装进不同的 aws ec2 instances 中,安装依赖,配置系统时钟,配置 nginx,supervisor 等等服务,然后把这些 instances 制作成一个个 AMI(Amazon Machine Images),供日后部署之用。这往往也需要耗费半个小时到几个小时的时间。
打包的过程中,包括之后部署的过程中,还需要一样东西:资源管理工具。因为,就像 micro service 需要 consul 这样的工具提供服务发现,docker 需要 docker registry 提供 repository 一样,打包好的软件需要一个合适的地方放置,便于版本管理,部署以及可能的回退。
这个工具可以是S3,可以是自行开发的 repository,也可以用开源的 archiva 或 artifactory。后两者做java的同学应该有所耳闻。这样的资源管理工具有什么用?以python为例,如果你的软件会打包出很多私有的 egg/wheel 包,这些包无法被公开放置在 pypi 上,那么你可以用 artifactory(或achiva)取代 pypi,成为你 pip install
的 index server。artifactory 上没有的包(公共包)会自动去 pypi 上取。同样的,debian 的 index server,docker 的 registry 都可以用这样的工具构建。
部署
不少人把「打包」和「部署」两件事混在一起,是因为二者经常在一起执行:打包之后,不待喘息,就立刻部署。但部署的动作其实是独立的,一份打包好的软件,按使用场景,可能会有多种部署。互联网软件的部署,往往是相当复杂的,光线上环境而言,就有开发环境,测试环境,以及生产环境。这还不算生产环境中可能存在的各种版本(提供外部API的同学应该心有戚戚焉),所以,部署往往是比打包更让人头疼的事情。
我们举个具体的例子:一个线上的日程系统,运行在 aws 里,主要使用 dynamodb,elasticache,ec2 和 s3。开发环境无需考虑 scaling,以单台服务器承载所有服务,没有 ELB / auto-scaling,数据是线上数据的子集;测试环境有 ELB,服务分布在不同的EC2上,每种服务都有两台服务器做HA,但没有 auto-scaling;线上服务则有 ELB / auto-scaling。
一个新功能的开发和集成的过程中,开发环境可能会被部署多次;当集成完成后,系统会被部署到生产测试环境;而测试结束后,系统可以以蓝绿发布(blue green deployment)的方式部署到生产环境;或者,选取一定样本的用户做灰度发布(gated launch 或者 A/B test)。
在aws的世界里,部署的主要工具是 cloudformation / elastic beanstalk,因为在打包的过程中,已经通过 ansible/puppet/chef/docker 等生成好了 AMI 或者 docker image;在非aws的世界里,ansible等工具也被用于部署。
如之前例子所示,部署主要是做资源的调配。开发环境毋须消耗太多资源,所以分配少一些;生产环境是现金奶牛,必须保证资源的全力供应。
当然,部署并不单单是资源的调配,它还是服务的 ochestration(嗯,这词比较高大上)。拿 logging 为例,如何把分散在各个服务器上的日志集中起来用于查询和分析,就是部署的一项任务。
蓝绿发布的思想其实比较简单,就是提供两套一样的生产环境(production / staging),通过DNS对流量进行切换。如下图:
(图片来自Martin Fowler:http://martinfowler.com/bliki/BlueGreenDeployment.html)
当 staging 足够稳定时,可以通过DNS切换,把流量从 production 转入 staging。待稳定后,staging 变为 production,原有的 production 转为 staging,可以被 shutdown,也可以被用于下一个 release。如果使用AWS,可以通过 route53 进行 DNS redirection,或者 ELB 的 auto-scaling group进行蓝绿发布。
蓝绿发布的好处是一旦发现问题,可以迅速回滚。
灰度发布在王淮的《打造Facebook》一书中有介绍:基本思想就是发布的时候控制发布的人群及其比例。比如先1%的用户,再扩大到5%,直至100%。人群可以根据多种属性来筛选,如:年龄、性别、国家、城市、语言、学历、工作单位等。
灰度发布的缺点是如果系统有不可逆的更改,则不能使用;对蓝绿发布而言,可以使用,但是系统不能回滚。
关于打包和发布的基础知识,就讲这么些。真正操作起来还是挺复杂的。就拿部署的速度而言,就有很多学问:层层缓存,最小化任务集合,对 full build 和 incremental build 采取不同的优化方式等。有同学可能会有疑问:如果打包和部署都已经自动化了,速度快一点,慢一点又有什么影响?殊不知以互联网的速度,如果你做一次部署要两小时,人家只需要五分钟,那么一天八小时内,你能部署四次,人家最多可以部署九十六次。效率提升差出来一到两个量级后,对开发人员的效率而言,会产生质的变化。
- Gerrit日常操作命令收集
- 轻型的ORM类Dapper
- [原创]Gerrit中文乱码问题解决方案分享
- 获奖案例:国航&百度“微笑启航”AI主题航班
- MySQL高可用架构-MMM环境部署记录
- Silverlight之ListBox/Style学习笔记--ListBox版的图片轮换广告
- MySQL高可用架构-MHA环境部署记录
- 分布式监控系统Zabbix-3.0.3-完整安装记录 - 添加shell脚本监控
- Flash/Flex学习笔记(52):使用TweenLite
- 设计一个界面,很简单!
- 配置Quartz.net Cluster以及远程管理
- [原创]CI持续集成系统环境--Gitlab+Gerrit+Jenkins完整对接
- Flash/Flex学习笔记(49):3D基础
- Flash/Flex学习笔记(51):3维旋转与透视变换(PerspectiveProjection)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android自定义网络连接工具类HttpUtil
- Android Studio时间选择器的创建方法
- Android 拦截返回键事件的实例详解
- Android自定义可点击的ImageSpan并在TextView中内置View
- Android开发实现带清空按钮的EditText示例
- Android用于校验集合参数的小封装示例
- TextView中URL等指定特殊字符串与点击事件解析
- Android开发实现仿京东商品搜索选项卡弹窗功能
- Android开发中button按钮的使用及动态添加组件方法示例
- Kotlin开发的一些实用小技巧总结
- Android使用URLConnection提交请求的实现
- android原生JSON解析实例
- iOS新闻类App内容页技术探索
- Android仿iphone自定义滚动选择器
- Android仿iPhone日期时间选择器详解