查看原文
其他

美丽联合集团NoOps实践:持续发布和部署

2017-09-29 赵成,志强,库瑞 DevOps时代

本文转载自公众号「Forrest随想录」(forrest_thinking),高效运维社区致力于陪伴您的职业生涯,与您一起愉快的成长。

导言:DevOps也好,NoOps也罢,关键看是否解决了业务问题,消除了痛点,这才是价值。

作者介绍:

赵成(谦益)
美丽联合集团 运维经理

在华为和蘑菇街,近10年研发和运维经验,期间积累了非常丰富的电信级和互联网业务研发和运维经验。
现在负责美丽联合集团(原蘑菇街、美丽说和淘世界)运维团队的管理以及运维体系建设工作,专注于运维创造价值,以及云计算时代运维的转型和突破。

志强
美丽联合集团 运维架构师
前阿里资深工程师,多年DevOps工具开发实践经验。本文发布系统的主要设计和开发者

库瑞
美丽联合集团 高级运维工程师
本文发布系统的主要开发者

前言

上周分享了一篇文章《有了CMDB,为什么还要应用配置管理》,主要讲了基础层面应该怎么做,那基础的东西做好了,如果用不起来,就没有价值,那我们今天就来看看在此基础之上的一些实践。

为什么要先做持续发布和部署?

首先,根本原因还是为了提升代码的交付效率(好像是句正确的废话),从技术上,主要原因还是因为从单体工程拆分成了服务化的应用。

单体工程的历史原因也很好理解,在创业初期,技术人员有限的情况下,为了快速找到正确的业务发展方向,技术人员必定会把全部的精力放到业务需求的实现上,这时技术或架构层面的事情是次要的,且在创业早期,用户量和业务模型也没有这么复杂,也没有必要搞复杂的架构出来,LNMP足矣,从我们的实践来看这样的技术选择是无比正确的。

但是,随着业务量和业务复杂度的增加(如电商的用户、商家、店铺、商品、交易和支付体系),业务服务质量要求变得越来越苛刻(低时延、高可用),需求的交付周期却要求越来越短(脑补下产品经理站在程序员身后的场景)。

同时工程师数量也已经到了几百人规模,这时单体工程就暴露出很多问题,比如代码逻辑耦合严重、底层公共方法不敢动,代码量巨大已经没有任何一个人能对整个工程代码熟悉,上百个工程师同时向同一个工程提交代码,代码维护耗费的精力非常大。最终一张图看下当时的场景:

所以,解决方案向着Java服务化的方向演进(具体原因就不解释了),简单示意如下:

单体应用拆分成服务化应用后,应用的情况就变成了许许多多、大大小小的应用模式,下图示意:

这个时候遇到的最棘手的问题,就是发布问题,这么多的应用对应的代码都是不同的,且Java服务化之后涉及编译构建、服务优雅上下线等一系列等问题,环节也多了很多(后面会看到),这些环节单纯的靠手工执行脚本和人为的串联已经成为不可能的事情,这个直接影响到开发、测试和运维整个研发体系的效率,所以是摆在面前必须要首要解决的。

好的,接下来我们就开始做发布系统了,提炼一下,发布做的事情就是,将提交后的应用代码,进行编译打包,然后发布到应用对应IP主机的指定目录下,并且做到应用服务的优雅上下线(或者叫做优雅启停)。

理解起来不复杂,其实我们可以抽象出发布的最重要的三步,我们也是始终围绕着这三步在不断的完善,如下:

下面就分阶段详细描述三个主要环节~

环节一、 代码提交环节(分支合并管理)

代码管理工具是Gitlab,代码提交过程中最重要的就是对于分支合并的管理(这里又涉及到标准规范的制定),我们的策略示意大致如下:

描述如下:

  1. master分支,跟线上应用代码保持同步,也就是说随时可以发布到线上进行部署运行。

  2. 开发分支,通常以feature/defect来表示,比如开发一个新的需求,就会以当前master为基线,拉一个feature分支出来进行开发,同一个应用可以同时存在多个feature分支并行开发。

  3. 发布分支,以release表示,在发布时会将所有提交集成的代码commit合并,形成release环境时间戳为分支名称,比如release_dev_01_29_20_52_10 就代表该分支是在1月29号20:52:10在dev环境发布时创建的临时发布分支。

  4. 从预发进入线上时,会以当前预发环境的发布分支release_pre_xxxx为基线创建一个release_online分支,作为线上的发布分支,线上发布结束后会把release_online分支合并到master中,这也就保证了发布到线上的代码最终一定会跟master的代码保持一致。

环节二、 编译构建环节

上面讲完了代码提交环节,下一个环节就是要构建了,以Java为例,构建我们用到了两个工具,Docker和Maven。Docker主要用来提供一个干净独立的编译环境,Maven作为我们的依赖管理和打包工具。整个构建过程如下:

以Java为例,简单描述如下:

  1. 首先准备好JDK的编译镜像,这个镜像环境与线上环境保持一致,当有新的构建任务进来时,就创建一个对应的Docker实例进行代码编译;

  2. 构建任务会根据应用配置管理中的Git地址,将代码clone下来放到指定的编译目录,Docker实例启动后,将编译目录挂在到Docker实例中;

  3. 对于Java应用,在这个Docker实例环境中,就可以执行mvn package命令打包了,最终会生成一个可发布xxx.war的软件包;

  4. 同样的,对于C++,Go、NodeJs,也会准备好类似的编译镜像,不同的是打包时,对于C++是cmake&make,Go就是go install等等,最终也会生成一个可发布的软件包;

  5. 构建完成后,Docker实例销毁;
    这里面Docker发挥了一个很大的作用,就是提供了干净的,互不干扰的编译环境,且对于并发打包的情况,Docker快速创建多个并行的实例出来提供编译环境,使用完销毁,这个效率上的优势也是非常大的。

  6. 关于配置管理,当时设计时考虑比较简单,我们的做法是同时做三个配置文件出来,dev_pom.xml,pre_pom.xml,online_pom.xml,分别对应开发、预发和线上三个不同的环境,根据发布的环境不同,将不同的配置文件替换上去。

    这样做其实可扩展性不够,对于多机房、多分组的情况会有更多的配置文件创建出来,且对于有敏感信息的配置项保密也不够(不过这些配置都已经加密挪到中间件的配置中心了),更好的办法可以考虑采用阿里早已开源的auto-config方案,这里就不细讲了。

环节三、 部署环节

以上,代码提交和编译构建完成后,就该进入发布到线上的部署环节了,也就是将代码发布到应用对应IP主机的指定目录下,并且能够优雅的上下线应用服务,貌似很简单,但是,看下图:

这个过程的环节还是比较多的,这些环节内部又会有很多的细节,所以整个部署环节是很复杂的,下面将整体思路介绍一下:

  • 0)从CMDB中,拿到应用-主机IP对应关系,然后再从1开始做,后面的过程可以是针对单台机器做,也可以是分批或分组多台机器同时做。(从第0步开始,原因就是我们上一篇文章里面说的CMDB和应用配置管理的基础要先打好,这个基础没有,下面的环节就无法顺畅地执行);

  • 1)检查每台机器上的服务是否正常运行的,如果是正常服务的,说明可以发布,但是服务本身异常,就要记录或跳过了;

  • 2)下载war包到指定目录(应用的目录信息,应用配置管理又发挥作用了);

  • 3)将监控关闭,以免服务下线和停止产生误告警;

  • 4)优雅下线,包含RPC服务从LB下线或Web服务从Nginx下线(如果不提供http服务就不涉及),下线动作均通过API接口调用方式实现;

  • 5)下线后自动检测无新的流量进入,停止应用,发布代码,然后启动应用(这时应用配置管理里面,启停命令等等就又发挥作用了);

  • 6)优雅上线,进行健康监测,检查进程和应用状态是否正常,如果全部监测通过,开始上线服务,开启监控;

  • 7)分批发布,这里简单提一下,假设我们一个应用有100台主机,这个时候做发布不可能全部一把停掉,这样服务会中断,但是也不能一台台的做,这样效率又太低,所以我们可以折中分批发,比如可以分5批,每批20台,也可以分10批,每批10台,这样既可以保证在线升级,也可以保证效率能够跟的上。当然复杂一点,还有分组分批,或者按步长分批,第一批5台,第二批10台,后面每批20台等等。示例如下:

整个过程下来我们可以看到:

  1. 基于场景入手,将业务流程梳理细致,做到细分环节,每步自动化,流程串联

  2. 上篇文章提到的CMDB和应用配置管理的内容,在这一部分无时无处不在发挥着基础的作用

效果:NoOps or DevOps?

发布系统上线后,整个过程已经可以做到开发全程自助发布,之前运维还在参与审批,后来审批环节也省略,在发布这个过程中,全流程NoOps。效果如下图所示:



END



更多相关文章阅读

华为专家 | 轻量化微服务测试实践

一个理论,三个原则,多个步骤 | 阿里游戏异地多活设计之道

痛点驱动的淘宝直播敏捷实践之路

一个真实的DevOps演进过程是啥样的?

认识高性能Web缓存体系,你需要知道这些

颠覆思想的一堂 DevOps 课

来自Facebook的大规模持续交付实践

性能哥 | 腾讯的专项测试之道



独乐乐不如众乐乐,DevOps 时代社区长期欢迎原创作者投稿,DevOps 时代社区愿陪伴您共同成长。投稿邮箱:liuce@greatops.net


点击阅读原文关注GOPS2017.上海站活动官网

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存