first commit

This commit is contained in:
张乾
2024-10-16 06:37:41 +08:00
parent 633f45ea20
commit 206fad82a2
3590 changed files with 680090 additions and 0 deletions

View File

@ -0,0 +1,109 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
00 开篇词 量身定制你的持续交付体系
你好,我是王潇俊,从今天开始,我将会和你一起聊聊“持续交付”这个话题。
“持续交付”已不再是一个陌生词汇了,绝大多数软件研发企业,都在或多或少地实施“持续交付”,因为大家都清楚,也都曾经体会或者听别人说过,“持续交付”能够提高研发效率。 但是要说实施得多好、多彻底,那我估计很多人都会面面相觑。
做好持续交付并不是件易事,从我的经验来看,它主要难在三个地方。
第一,实施“持续交付”,将会影响整个的研发生命周期,会涉及到流程、团队、工具等多个方面。很可能需要突破当前组织的束缚,引起大量的技术和组织变革。因为,实施“持续交付”需要组织从上到下的认可,需要有大勇气将一些可能属于黑箱操作的工作,公开出来给大家监督。所以,这样的事情很难推进。
第二,实施“持续交付”,对实施者和参与者的要求都很高,他们不仅需要了解开发,还要了解流程,了解测试,了解运维,甚至还需要有一定的架构知识和管理知识。所以,这样的人才很难寻找。
第三,实施“持续交付”,大多数团队都希望能够快速见效,立竿见影。但是,“持续交付”的改进过程本身就是一个持续迭代的过程,需要多次循环才能体现效果。甚至在实施的初期,因为开发习惯和流程变化,团队在适应的过程中效率会有暂时的下降。所以,这样的效果很难度量。
由于这三大难点,很多人对“持续交付”敬而远之,或者爱恨交加。因此,我希望这个专栏能够带你全面、立体地认识持续交付,当你了解得越多,理解得越透彻,你也就越有信心。简单来说,我认为:
无论企业在什么阶段,无论个人的能力如何,都可以去尝试“持续交付”。
在实践中,我还经常看到一些错误的观点。
过度强调自动化。认为只有自动化才能算是“持续”但限于业务逻辑变化快QA能力不足等又无法实现测试自动化而发布自动化更是遥遥无期所以只能放弃。
过度强调流程化。总觉得“持续交付”先要构建强流程来管控,结果就一直限于流程和实现流程的“泥潭”里,却忘了初衷。
过度强调特殊化。比如我们经常会听到,我们的工程师能力特别强,我们的团队有特殊的工作方式,我们的系统有不同的设计,这些往往成了拒绝“持续交付”的借口。
希望在这个专栏里,通过我的讲解能够纠正你的这些错误观点。
同时,我也希望和你之间不是教与学的关系,而是切磋与讨论,在这三个月的时间里,我们一起讨论如何解决现实的问题,讨论如何进一步去做好“持续交付”,讨论那些超出你我边界的所谓的“难题”。
自从决定写这个专栏,我就一直在脑子里“翻箱倒柜”,在网络上收集相关参考资料,整理写作材料。突然,我脑子里蹦出一个问题:我自己当年是怎么接触到持续交付的,是怎么走上“持续”这条“不归路”的?
仔细回想一下接触“持续集成”这个概念其实是挺早的事情了。那时我在第九城市负责用户中心的开发有些与《魔兽世界》相关的功能需要大洋彼岸的老美同学QA进行验收。因此为了利用时差优势我们如果有新功能要测试就会要求整个团队在当天下午冻结代码版本并在6点后向测试环境发布。
晚上我们睡觉的时候,老美们就开始干活了。因为《魔兽世界》的爆红,所以当时开发需求特别多,缺陷也特别多,几乎每天都要提测,我就干脆用按键精灵写了个脚本,实现了每天自动地处理这些事情。现在想想,这不就是每日构建嘛。
你现在可能和当时的我一样,正在采用或借鉴一些“持续集成”或“持续交付”的最佳实践,但还停留在一个个小的、零散的点上,并没有形成统一的体系,还搞不定持续交付。
所以,我希望这个专栏首先能够给你呈现一个体系化的“持续交付”课程,帮助你拓展高度和广度,形成对“持续交付”立体的认识。
其实从这个角度来看,我想通过这个专栏与你分享的内容,不正好就是我自己在实际成长过程中一点一点学到的东西吗?那么,如果你不嫌厌烦,可以继续听一下我的故事。
离开第九城市之后由于经受不住帝都“干燥”的天气2008年我又回到魔都加入了当时还默默无闻的大众点评网。在那里我真正体验了一把“坐火箭”的感觉也是在那里我与“持续交付”真正结缘。
点评是一家工程师文化很浓重的公司一直以来都以工程师的能力为傲。但随着O2O和移动互联网的兴起点评走到了风口浪尖团队在不断扩大而研发效率开始下降了。
起初,大家都觉得是自己的能力跟不上,就开始拼命学习,公司也开始树立专家典型。但结果却事与愿违,个人越牛,杂事越多,不能专注,反而成了瓶颈。总结之后,我们发现,这种情况是研发流程、合作方式等低效造成的。个人再强放在一个低效的环境下,也无力可施。
然后QA团队开始推动“持续交付”试图改变现状。为什么是QA团队呢因为QA在软件研发生命周期的最后一端所有前期的问题他们都得承担。低效的研发模式和体系首先压死的就是QA。但是QA团队最终还是以失败收场了。究其原因
缺乏实践经验多数“持续交付”相关的图书、分享都停留在“what”和“why”上没有具体的“how”
QA团队本身缺乏开发能力无法将“持续交付”通过工具进行落地只能流于表面的流程和理念。
但这场自底向上的革命,却让公司看到了变革的方向。
之后,点评就开始了轰轰烈烈的“精益创业”运动。“持续交付”作为研发线变革的重点,得到了更多资源的支持和高度的关注。也是在这时,我获得了与国内众多的领域专家进行探讨和学习的机会。
最终,点评是以发布系统为切入点,从下游逐步向上游的方式推行“持续交付”。 并且在这个过程中,形成了专职的工程效能团队,从而打造出了一套持续交付平台。
所以,我希望这个专栏的第二个重点是,结合我个人多年的实践经验,与你分享“持续交付”涉及的工具、系统、平台,到底如何去设计,如何去实施,如何去落地。
离开点评之后我加入了携程。携程的规模、体量相比点评又大了许多。比如携程有近20个BU应用数量达到6000+研发人员有3000人同时还有去哪儿、艺龙等兄弟公司在系统上也息息相关而且携程随着多年的业务发展系统复杂度也远远高于点评。要在这么大的平台上推行“持续交付”挑战是巨大的。
其实,携程在“持续交付”方面一直以来都是有所尝试和努力的,引进、自研各种方式都有,但是收效甚微。其中构建的一些工具和平台,由于种种问题,反而给研发人员留下了坏印象。这里面自然有各方面的问题,但我认为最主要的问题是以下三点:
“持续交付”必须以平台化的思想去看待,单点突破是无力的;
“持续交付”的实施,也要顺应技术的变迁,善于利用技术红利;
“持续交付”与系统架构、运维体系息息相关,已经不分彼此。
事实上在携程推进“持续交付”时我们联合了框架、OPS等部门将目标放在支持更未来的容器化、云原生Cloud Native以及微服务上利用这些新兴技术的理念和开源社区的红利从“持续发布”开始逐步推进“持续交付”。
在推进的过程中,我们既兼容了老旧的系统架构,也为迁移到新一代架构做好了准备,并提供了支持。可以说,携程第四代架构的升级本身,就是在坚持“持续交付”,从而获得了成功。
所以在DevOps越来越火的今天我希望这个专栏可以达到的第三个目的是能够让你看到“持续交付”与新兴技术擦出的火花并与你探讨“持续交付”的未来。
除了以上内容,你还将通过我的专栏收获以下四个方面。
“持续交付”的主要组件:配置管理、环境管理、构建集成和测试管理。-
在这一部分里,我会深入浅出地,跟你聊聊“持续交付”的这“四大金刚”,帮你全方位地理解“持续交付”的各项主要活动。
如何实现“灰度发布”。-
如果你对“持续部署”有所期待,希望进一步了解,那么你大多数的问题都可以在这一部分得到解答。
移动App中有所不同的“持续交付”体系。-
移动互联网如火如荼,你一定也想了解下,如何在手机客户端研发中做好“持续交付”。那么这一部分,你就不能错过了。
如何利用开源红利,快速搭建一套持续交付平台。-
在这一部分,我会手把手地,带你真正去搭建一套最小集合的持续交付平台。
学须致用,思须有为。我的这趟“持续交付”班车即将发车,如果你感兴趣,那就赶紧一起来,让我们一同欣赏沿途的风景吧!

View File

@ -0,0 +1,172 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
01 持续交付到底有什么价值?
随着云计算、容器等新兴技术的发展“持续交付”这个老生常谈的问题忽如一夜春风来仿佛找到了从理想通向现实的大门。各类相关工具、产品、服务也是纷纷出现如Jenkins 2.0Jenkins X阿里云效Netflix SpinnakerJfrog Artifactory等等。
到底是什么魔力使得各大公司和厂商对“持续交付”如此趋之若鹜?那么,作为本专栏的第一篇文章,我就先来为你揭示“持续交付”真正的价值。
你了解持续交付吗?
持续交付,到底是什么意思,它的定义是什么?《持续交付:发布可靠软件的系统方法》一书中把“持续交付”定义为:
持续交付是软件研发人员,如何将一个好点子,以最快的速度交付给用户的方法。
是不是听起来有点抽象呢其实这就好像你去问100个哲学家“哲学”的定义是什么你会获得101个答案一样。与马丁 · 福勒Martin Fowler老爷子在2006年提出“持续集成”概念时一样我们可以把 “持续交付”定义为“一套软件工程方法论和许许多多的最佳实践的集合”。
但即使熟知了定义和方法论,其实也还是如海市蜃楼一般,无法落地,因为大家所贡献的最佳实践才是持续交付理论的核心。只有真正在工作中贯彻和使用这些实践工具,才能体会持续交付的真正含义和作用。
持续集成、持续交付和持续部署的关系
了解了持续交付,你可能会说“持续集成”、“持续部署”又是什么意思, 它们和“持续交付”有什么关系呢。那我就给你简单解释一下。
我们通常会把软件研发工作拆解,拆分成不同模块或不同团队后进行编码,编码完成后,进行集成构建和测试。这个从编码到构建再到测试的反复持续过程,就叫作“持续集成”。
“持续集成”一旦完成,则代表产品处在一个可交付状态,但并不代表这是最优状态,还需要根据外部使用者的反馈逐步优化。当然这里的使用者并不一定是真正的用户,还可能是测试人员、产品人员、用户体验工程师、安全工程师、企业领导等等。
这个在“持续集成”之后,获取外部对软件的反馈再通过“持续集成”进行优化的过程就叫作“持续交付”,它是“持续集成”的自然延续。
那“持续部署”又是什么呢?软件的发布和部署通常是最艰难的一个步骤。
传统安装型软件,要现场调试,要用户购买等等,其难度可想而知。即使是可达度最高的互联网应用,由于生产环境的多样性(各种软件安装,配置等)、架构的复杂性(分布式,微服务)、影响的广泛性(需要灰度发布)等等,就算产品已是待交付的状态,要真正达到用户可用的标准,还有大量的问题需要解决。
而“持续部署”就是将可交付产品,快速且安全地交付用户使用的一套方法和系统,它是“持续交付”的最后“一公里”。
可见,“持续交付”是一个承上启下的过程,它使“持续集成”有了实际业务价值,形成了闭环,而又为将来达到“持续部署”的高级目标做好了铺垫。
虽然从概念上你可以这样理解,但从实践和我个人多年的经验来说,往往是从“持续部署”(自动化发布)开始推进“持续交付”,这才是一条优选的路径。这部分内容我会在后续文章中详细介绍。
持续交付的显性价值
持续交付也通常以“发布流水线”的方式来解释,即研发团队从开发,到测试,再到部署,最终将产品交付给最终用户使用的过程。如下图:
虽然持续交付着重打造的是发布流水线的部分,但它所要达到的目标是在“最终用户”和“研发团队”之间建立紧密的反馈环:通过持续交付新的软件版本,以验证新想法和软件改动的正确性,并衡量这些改动对软件价值的影响。
这里说的“软件价值”说白了就是收入、日活、GMV等KPI指标了。
通常我们在实施持续交付后,都能够做到在保证交付质量的前提下,加快交付速度,从而更快地得到市场反馈,引领产品的方向,最终达到扩大收益的目的。
在互联网应用盛行、速度为王的今天,持续交付的价值更是被突显出来。持续交付的能力,正成为评定一家互联网公司研发能力的重要指标。
持续交付的隐性价值
除了上面这些你一眼就能看出来的价值外,如果作为不同的角色、站在不同的角度去看持续交付之后的变化,你还会发现其他一些隐性价值,而其中有一些影响甚至远远超过你的预期。
或者可以这么说,通过介绍持续交付的隐性价值,我希望你能够了解到,无论是什么企业,无论你的职位高低,都可以或者应该去尝试持续交付,它一定会让你觉得物超所值。
如果你是CTO或者是一个较大规模研发团队的管理者
你是不是时常困扰于技术选型的问题?-
技术选型最大的难点在于影响大,又难以验证(或者验证效率低下)。而造成这些困境的绝大多数原因是没有合适的测试环境,比如环境差异造成测试数据缺乏说服力,又比如缺少隔离环境造成服务冲突等等。而这正是持续交付的用武之地。-
持续交付的实施,将全面改善企业对测试环境的管理方法,使得环境管理更合理、更自由。我也将在后续章节里介绍如何做好环境管理。
你是不是经常头痛于已制定的标准难以落地?-
标准、规范、流程的落地,都需要载体,而最好的载体就是平台工具。而持续交付是一整套平台工具的落地,几乎涵盖了研发的整个生命周期,是天然的、最佳的载体。-
另外,持续交付的落地本身就伴随着各类标准、规范、流程的制定和实施,可以说两者相互依存,是非常好的管理思想落地方案。
你是不是时常考虑如何提高跨部门协作的效率?-
我看到的每一个持续交付实施团队,都可以说是最厉害的“拆墙大队”,拆的就是各个研发协作部门间的“隔离墙”。-
持续交付能够向各个协作部门输出统一的标准、流程和工具,提升沟通效率;并且通过大量的自动化,进一步提升各部门工作效率;还可以快速集成,把各个分散的团队,无论是横向的业务研发团队,还是纵向的技术框架团队,紧紧地联系在一起,共同进退。
你是不是担心“黑天鹅”的降临?-
既然叫“黑天鹅”,那就是说明它的产生有一定的必然性。正应了一句老话“是福不是祸,是祸躲不过”,既然躲不过,那就解决它呗。其实任何故障都有一个天敌,叫作:快速恢复。-
假设所有的故障都可以在3分钟内恢复你是不是觉得天下无敌了。那恢复故障最快、最有效的手段又是什么呢当然就是回滚或重新部署而这正是持续交付所包含和着力打造的能力之一。
如果你是Team Leader
你一定希望团队的知识能够传承。-
互联网公司的人才流动之频繁已经远远超过了你我的想象。人来人往,如何将知识传承下来呢?其实在这方面,持续交付也能为团队提供很多帮助。-
首先,持续交付将团队赖以生存的工作流程进行了固化;其次,利用代码静态检查等工具,能够很好地传承团队多年来的代码规范,并作为检查项进行自动化校验;再次,自动化测试的脚本,同样是团队经验的产物。
你一定希望团队专注于业务而非工程。-
目前越来越多的公司或研发组织意识到,持续交付体系也如同中间件一样,能够从日常的业务研发工作中抽象出来,其不同只在于中间件解决架构问题,而持续交付解决工程问题。-
这样研发团队能够全力应付业务的需求,而不用总是重复奔波于一些烦人且耗时的工程问题,比如安装测试机、准备编译服务器等等。
你一定希望以一个较平稳的节奏持续工作。-
虽然在实施持续交付的初期,团队为了适应新的流程和工具,会有一定的效率下降,但之后在自动化的帮助下,团队效率会有一个明显的提升并逐渐稳定下来。-
持续交付就是这样通过稳固的流程、自动化的工具和公开而真实的数据,来避免发布前夕容易发生的“死亡行军”式开发阶段。
如果你是产品经理
你应该是产品真正的第一个用户。-
持续交付不仅仅是可以保证每一个变化都能及时得到测试以及反馈,更多的是解决测试与实际发布时存在差异的问题。-
产品人员再也不会陷入“为什么用户端运行的结果和在测试环境中的不一致”这样的窘境他们将真正成为第一个用户而不再是最后一个QA。
你应该完全知悉当前的进度和质量。-
作为产品人员,你是不是一直有这样的感觉:和研发团队之间总有一扇墙,程序员们似乎并不乐意告诉产品人员项目的真相;而最终总有这样那样的理由造成延期,产品人员往往无话可说。-
那么,持续交付就能够实时地反应当前的开发情况,从而帮助产品人员决策和调整。
你的产品应该随时能发布。-
计划永远赶不上变化,任何产品人员都希望自己的产品能够随时处于可发布状态。这样就能灵活地交付已完成的功能,迎合市场或业务的需要。-
本质上,做到代码上线和业务上线的解耦分离,这也正是持续交付方法论强调的一个重点。
如果你是一个程序员
你可以通过对持续交付的学习,进一步加强自己对整个软件工程的认识。-
持续交付涵盖了软件交付端到端的整个周期,其覆盖面不仅仅包括编码,还包括:设计、测试、部署、运维、运营等等。-
如果你对自己的发展有更高的要求,那么你就应该学习一下持续交付的内容,它能让你看到更多与编码有关的其他东西,比如不同的编码方式等;也能让你站在更高的角度去看待自己的工作:研发效率的提高往往不是个人能力的提高,而是集体协同效率的提高。
你可以利用持续交付的工具或最佳实践,提高自己的工作效率和质量。-
随着持续交付的流行其配套的实践和工具也层出不穷。如果你玩过ping-pong式的结对编程A写测试B写实现然后B写下一个测试A写重构和实现你一定会觉得编程如此轻松有趣而这种TDD的方式也很好的保证了代码质量。
你可以参与到持续交付实施中去,享受为其他程序员提供效率工具的挑战和乐趣。-
试想一下如果你是一个出租车司机而你的乘客却是舒马赫F1世界冠军此时你开车的压力会有多大。其实参与到持续交付的实施中也是一样因为你正在用程序员的方式改造程序员的工作习惯为程序员提供工具。-
虽然挑战和压力巨大,但这又是如此有趣,你将会站在另一个高度去看你曾经的工作,不想试试吗?
如何评估持续交付的价值
我跟你说了这么多持续交付的价值,那如何评估它呢?这是一个非常难的问题,我自己每年在绩效考评时也都会问自己这个问题:我到底应该怎么给老板汇报呢?我可以量化持续交付的价值吗?
首先,你一定会说,我可以衡量产品的交付速度是否变快了。但是,实际情况下影响产品交付速度的因素实在太多,虽然我们一定知道持续交付有积极作用,但到底占比是多少呢?好像非常模糊,难以回答。
然后,你又想到,我们可以衡量各个自动化过程的速度是否变快了,比如:编译速度、发布速度、回滚速度、自动化测试速度等等。
是的,这些指标确实很好地反应了持续交付的价值,但总觉得这些并不是全部,持续交付的标准化、推行的新流程、改革的环境治理架构,好像都没有体现出来。
那到底应该怎么评估持续交付的价值呢?这里和你分享一下我在携程是怎么解决这个问题的。
我除了会评估一些常规的KPI外更多地会换一种思考方式。既然很难量化持续交付的价值那么我们就具象化来看看整个工程生命周期中有多少被开发人员诟病或者阻碍开发人员自助处理的问题点 ,即“不可持续点”:
开发不能按需产生隔离的测试环境;-
生产代码回滚后,要手工处理代码分支;-
预发布Staging流量要能自动分离以便预发布测试。
在携程我们会将所有的“不可持续点”进行记录和分解通过OKR的考评方式将消灭这些点作为目标拆解出来的可行动点作为关键结果以这样的方式来完成绩效考评。
虽然,有些“不可持续点”已经超越了一般传统持续交付的概念,甚至有些已经超越了纯技术改进的范畴,但是持续交付仍会一直关注于消灭这些“不可持续点”。
So what我们就是要持续交付我们的价值
总结
接下来,我给你提炼一下今天内容的要点。
持续交付的价值不仅仅局限于简单地提高产品交付的效率,它还通过统一标准、规范流程、工具化、自动化等等方式,影响着整个研发生命周期。
持续交付最终的使命是打破一切影响研发的“阻碍墙”,为软件研发工作本身赋能。无论你是持续交付的老朋友还是新朋友,无论你在公司担任管理工作还是普通的研发人员,持续交付都会对你的工作产生积极的作用。
思考题
你的团队最希望借助持续交付解决什么现实问题?
好了,今天就聊到这里,欢迎你给我留言,下期见!

View File

@ -0,0 +1,186 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
02 影响持续交付的因素有哪些?
在上一篇文章中,我和你聊了聊“持续交付”的价值。现在,你是不是感觉热血澎湃,似乎找到了解决一些问题的良方?你是不是跃跃欲试,想在团队立刻实施看看效果如何?
但别急,就像我在开篇词里说的一样,“持续交付”可真不是一件简单的事情。你一定会在实施过程中碰到各种各样的问题和困难,但也不要气馁,我现在就和你说说:影响持续交付的各种因素。知己知彼,方可百战不殆。
与绝大多数理论分析一样,影响持续交付的因素也可归结为:人(组织和文化),事(流程),物(架构)。
组织和文化因素
谈到组织,你是不是一下就想到了部门划分,跨部门合作等?的确,这就是我要和你讲的第一个影响因素。因为“持续交付“一定是整个组织层面的事情,是跨部门合作的产物,所以组织和文化因素,是要首先考虑的问题。
什么样的组织文化,才是“持续交付”成长的沃土(当然这也是定义好的组织的标准),我把它分成了三个层次:
第一个层次:紧密配合,这是组织发展,部门合作的基础。
一般企业都会按照职能划分部门。不同的职能产生不同的角色;不同的角色拥有不同的资源;不同的资源又产生不同的工作方式。这些不同的部门紧密配合,协同工作于共同的目标,就能达到成效。
第二个层次:集思广益,这就需要组织内各个不同部门,或不同职能的角色,跳出自身的“舒适区”。
除思考和解决本身职能的问题外,各部门还要为达到组织的共同目标,通盘考虑和解决所遇到问题和困难。这个层次需要增加组织的透明度,需要接受互相批评和帮助。
第三个层次:自我驱动,是理想中的完美组织形式。
如果第二个层次能够持续地运转,就会形成自我学习、自我驱动的飞轮效应,并且越转越快,它甚至能自发式的预见困难,并自驱动解决问题。
这三个层次看起是不是有点眼熟,和我在上一篇文章中讲到的持续集成的三个层次:
分模块编码;
整体集成;
实现以上两个过程的自动化,并形成闭环;
好像是一样的。真是有趣,持续交付其实也是帮企业建立更好的组织形式的一种方法。
那么,在形成理想组织的实际执行中会遇到哪些问题呢?
一般软件企业与交付有关的研发部门包括四个:产品、开发、测试和运维。而这四个部门天然地形成了一个生产流水线,所以形成理想组织的第一层次紧密配合,基本没什么问题。
但是,要达到第二层次集思广益的难度,往往就很大。因为,每个部门有自身的利益,以及自己的工作方式和目标。
比如,产品人员和测试人员就是一对矛盾体:产品人员希望产品尽快上线,而测试人员则希望多留时间进行更完整的测试。
又比如开发人员和运维人员也经常矛盾开发人员希望能有完全权限而运维人员却控制着生产的root。
从各自的小目标的角度看,这些矛盾是正常的。但是,产品、开发、测试和运维这些部门的小目标往往就是实施持续交付的阻碍,只有它们把眼光放到更高地持续交付可用的产品上,有了共同的目标,问题才会迎刃而解。
那么,靠各个部门自己能解决这个问题吗,其实很难。组织的问题,还是需要通过组织变革来解决。通常我们会采用以下三种方案:
成立项目管理办公室Project Manage Office简称PMO这样的监督型组织帮助持续交付落地
独立建立工程效能部门,全面负责包括持续交付在内的研发效率提升工作;
使用敏捷形式如Scrum打破职能部门间的“隔离墙”以产品的形式组织团队各团队自行推进持续交付 。
当然,这三种方案各有利弊。比如:
成立项目管理办公室,虽然会带来非常强大的项目推进力,但它往往需要通过流程把控进行监督,这样就很有可能把流程变得更加复杂;
而独立的工程效能部门,虽然能最大化地去做好持续交付工作,但其研发成本的投入也是需要考虑的,小团队的话,就不太适用了;
敏捷形式是比较适合中小团队的一种组织变革方式,但对个人能力的要求也会比较高,而且往往需要一个很长时间的磨合才能见效。
所以,你需要根据当前组织的情况来选择。总而言之,持续交付必须有与其相适应的组织和文化,否则将很难实施。
流程因素
要说持续交付对企业和组织改变最多的是什么,那么一定是流程。
持续交付一定会打破的这三类流程是:
耗时较长的流程。比如一个功能的研发迭代周期为5天而其中有一个上线审核流程需要花费3天时间那这个流程就严重影响了持续交付必须被打破。
完全人工类的流程。 完全人工操作的流程,一般效率低下,且质量难以保证,持续交付的逐步深入会通过自动化替代这些人工流程的存在。
信息报备类的流程。 持续交付过程中同样会产生各种信息流,这些信息有些需要广播,有些需要定点传递。实施持续交付后,这些信息报备类的流程一定会通过异步消息等方式进行改造。
其中,如何对待审批流程是重点。
在持续交付过程中,其实最让你头痛的应该是一些审批流程。这些流程既然叫做审批,那就代表着授权与责任,代表着严谨与严肃,因此也一定有其存在的价值和意义,不能轻易被去除或打破。
但是,你我都知道,审批往往指的是由人进行审核和批准,既是一个全人工流程,又是一个信息流转类流程。那么如何打破它呢?同样,也有几种思路:
该审批流程是否确实需要,如果能够通过系统来保证,则可以去除;
该审批流程是否可以从事前审批转化为事后审核;
该审批流程是否可以被简化。
但是,每家公司的流程都不太一样,所以我的这几个思路并不一定是放诸四海而皆准,但我希望你可以借鉴,或者从中学习到一些新的思路,并结合你自己的情况进行合理调整。
相对于组织文化和流程因素,架构是真正和技术相关的因素,也是我要和你重点分享的内容。
架构因素
技术架构对于持续交付来说,是万分重要的。如果遇到混乱的架构,那持续交付会处处受制,痛苦不堪。但与之前讨论的组织、文化和流程因素相比,架构的问题解决起来也会相对容易,因为凡是技术上的东西,都比较愿意接受优化,并且可以随着持续交付一起慢慢重构。
影响持续交付的架构因素,主要有两大部分:系统架构和部署架构,接下来我会给你详细展开。
第一,系统架构
系统架构指系统的组成结构,它决定了系统的运行模式,层次结构,调用关系等。我们通常会遇到的系统架构包括:
单体架构,一个部署包,包含了应用所有功能;
SOA架构面向服务通过服务间的接口和契约联系
微服务架构,按业务领域划分为独立的服务单元,可独立部署,松耦合。
那么,这些架构对持续交付又有什么影响和挑战呢?
对单体架构来说:
整个应用使用一个代码仓库,在系统简单的情况下,因为管理简单,可以快速简单地做到持续集成;但是一旦系统复杂起来,仓库就会越变越大,开发团队也会越来越大,多团队维护一个代码仓库简直就是噩梦,会产生大量的冲突;而且持续集成的编译时间也会随着仓库变大而变长,团队再也承受不起一次编译几十分钟,结果最终失败的痛苦。
应用变复杂后,测试需要全回归,因为不管多么小的功能变更,都会引起整个应用的重新编译和打包。即使在有高覆盖率的自动化测试的帮助下,测试所要花费的时间成本仍旧巨大,且错误成本昂贵。
在应用比较小的情况下,可以做到单机部署,简单直接,这有利于持续交付;但是一旦应用复杂起来,每次部署的代价也变得越来越高,这和之前说的构建越来越慢是一个道理。而且部署代价高会直接影响生产稳定性。这显然不是持续交付想要的结果。
总而言之,一个你可以完全驾驭的单体架构应用,是最有容易做到持续交付的,但一旦它变得复杂起来,一切就都会失控。
对SOA架构来说
由于服务的拆分,使得应用的代码管理、构建、测试都变得更轻量,这有利于持续集成的实施。
因为分布式的部署,使得测试环境的治理,测试部署变得非常复杂,这里就需要持续交付过程中考虑服务与服务间的依赖,环境的隔离等等。
一些新技术和组件的引入,比如服务发现、配置中心、路由、网关等,使得持续交付过程中不得不去考虑这些中间件的适配。
总体来说SOA架构要做到持续交付比单体架构要难得多。但也正因架构解耦造成的分散化开发问题持续集成、持续交付能够在这样的架构下发挥更大的威力。
对微服务架构来说:
其实微服务架构是一种SOA架构的演化它给持续交付带来的影响和挑战也基本与SOA架构一致。
当然,如果你采用容器技术来承载你的微服务架构,就另当别论了,这完全是一个持续交付全新的领域,这部分内容我将在后续文章中跟你分享。
第二,部署架构
部署架构指的是,系统在各种环境下的部署方法,验收标准,编排次序等的集合。它将直接影响你持续交付的“最后一公里”。
首先,你需要考虑,是否有统一的部署标准和方式。 在各个环境,不同的设备上,应用的部署方式和标准应该都是一样的,可复用的;除了单个应用以外,最好能做到组织内所有应用的部署方式都是一样的。否则可以想象,每个应用在每个环境上都有不同的部署方式,都要进行持续交付的适配,成本是巨大的。
其次,需要考虑发布的编排次序。 特别是在大集群、多机房的情况下。我们通常会采用金丝雀发布(之后讲到灰度发布时,我会详解这部分内容),或者滚动发布等灰度发布策略。那么就需要持续交付系统或平台能够支持这样的功能了。
再次是markdown与markup机制。 为了应用在部署时做到业务无损,我们需要有完善的服务拉入拉出机制来保证。否则每次持续交付都伴随着异常产生,肯定不是大家愿意见到的。
最后,是预热与自检。 持续交付的目的是交付有效的软件。而有些软件在启动后需要处理加载缓存等预热过程,这些也是持续交付所要考虑的关键点,并不能粗暴启动后就认为交付完成了。同理,如何为应用建立统一的自检体系,也就自然成为持续交付的一项内容了。
关于部署的问题,我也会在之后的篇章中和你详细的讨论。
总结
今天,我和你分享的主题是影响持续交付的因素,为了便于你理解,我将其划分为人(组织和文化),事(流程),物(架构)三个方面:
组织和文化,是最重要的因素,是持续交付推进的基础;
流程因素,实施持续交付也是一次流程改造之旅;
系统架构,与持续交付相互影响,但技术可以解决一切问题;部署架构,千万不要失败在“最后一公里”,这部分你也需要重点关注。
最后,也请你思考一下,如果你的组织实施持续交付,最大的影响因素会是什么?如果是系统架构方面的因素,你将如何应对?
欢迎你给我留言。

View File

@ -0,0 +1,128 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
03 持续交付和DevOps是一对好基友
现在很多人都在困惑持续交付和DevOps到底是什么关系有什么区别或许你也感觉傻傻分不清楚。那么今天我就来和你聊聊持续交付和DevOps以及它们到底是什么关系。
持续交付是什么?
我在专栏的第一篇文章中已经跟你很详细地分享了持续交付是什么为了加深你的印象并与DevOps形成对比我在这里再从另外一个角度给你总结一下
持续交付是,提升软件交付速率的一套工程方法和一系列最佳实践的集合。
它的关注点可以概括为:持续集成构建、测试自动化和部署流水线。
那么DevOps又是什么呢其实一直以来学术界、工业界都对DevOps没有明确的定义所以造成了大家对它的看法也是众说纷纭也难免片面。
在我给出我个人的认识之前我先给你讲讲DevOps是怎么被发明的吧。
DevOps的诞生
DevOps的故事要从一个叫帕特里克 · 德博伊斯Patrick Debois的IT咨询师讲起。2007年帕特里克参与了一个政府下属部门的大型数据中心迁移的项目。
在这个项目中帕特里克发现开发团队Dev和运维团队Ops的工作方式和思维方式有巨大的差异
Dev的工作是为软件增加新功能和修复缺陷这要通过频繁的变更来达到
Ops的工作是保证系统的高稳定性和高性能这代表着变更越少越不容易出错。
因此Dev和Ops长久以来都处于对立和矛盾的状态。
2009年6月23日Flickr公司的运维部门经理约翰 · 阿斯帕尔瓦John Allspaw和工程师保罗 · 哈蒙德在Velocity 大会上做了一个轰动世界的演讲《每天部署10次以上Flickr公司的Dev与Ops的合作》10+ Deploys Per Day: Dev and Ops Cooperation at Flickr
这个演讲中提出了DevOps的核心观点Dev和Ops的矛盾可以通过技术升级和文化构建来解决这标志着DevOps的诞生。
帕特里克也在网上看到了这个演讲,并且十分兴奋,因为这就是长久以来他所想解决的问题。于是,他开始筹备自己的 Velocity 大会。
2009年10月帕特里克的Velocity大会在比利时顺利召开他把会议命名为DevOpsDays。他本来想用 DOD作为 DevOpsDays 的缩写以提醒自己“死在交付上”Dead On Delivery但不知什么原因他最后没有这么做。
这届大会出人意料的成功许多开发工程师和运维工程师参加了这次大会甚至还有各种IT管理人员参加。人们开始在 Twitter上大量讨论DevOpsDays的内容。
由于Twitter对内容长度的限制是140个字符所以大家在Twitter上讨论时去掉了“Days”只保留了 “DevOps”。于是 DevOps这个名称正式诞生。
持续交付的姗姗来迟
在DevOps的这段编年史里持续交付又在哪里呢
2006 年,杰斯 · 亨布尔Jez Humble克里斯 · 里德Chris Read和丹 · 诺斯Dan North在 Agile 大会上发表了一篇名为《部署生产线》Deployment Production Line的文章这也是第一篇描述持续部署核心内容的会议文章。
在后面的三年里又有一系列“持续部署”的文章被发表。2009年这一些系列的文章被编成为了一本叫作《持续交付发布可靠软件的系统方法》的书这一年也正是帕特里克举办DevOpsDays的那一年。
2010 年,《持续交付:发布可靠软件的系统方法》的作者之一杰斯参加了第二届的 DevOpsDays并做了 关于“持续交付”的演讲在这一年“DevOps”与“持续交付”终于有了交集。
从本质上说帕特里克最初遇到的问题在《持续交付发布可靠软件的系统方法》一书中找到了最佳实践。如果这本书可以早两年问世或许今天就不会有DevOps了。
然而DevOps 的概念一直在向外延伸包括了运营和用户以及快速、良好、及时的反馈机制等内容已经超出了“持续交付”本身所涵盖的范畴。而持续交付则一直被视作DevOps的核心实践之一被广泛谈及。
这么看来,持续交付真是打了一个大盹儿。
认识 DevOps
DevOps这几年一直在不断地演化那么它到底是什么呢
目前人们对DevOps的看法可以大致概括为DevOps是一组技术一个职能、一种文化和一种组织架构四种。
第一DevOps是一组技术包括自动化运维、持续交付、高频部署、Docker等内容。
但是如果你仅仅将DevOps认为是一组技术的集合的话就有一些片面。任何技术都是为了解决某些问题而被创造出来的。比如Docker就是为了解决 DevOps 所提倡的“基础设施即代码”这个问题,而被创造出来的。
从这个角度来看的话DevOps的范畴应该远远大于一组技术了。
其实DevOps是一组技术这个观点还是只站在了工程师角度去思考问题而得出的结论。虽然“DevOps”中“Dev”和“Ops”这两个角色都是工程师但是其本质还是希望跳出工程师的惯性思维来看待问题。
第二DevOps是一个职能这也是我在各个场合最常听到的观点。
你的公司有没有或者正准备成立一个叫作DevOps的部门并将这个部门的工程师命名为DevOps工程师至少在各大招聘网站上是随处可见这样的职位而招聘要求往往就是会Ops技能的Dev或者会Dev技能的Ops或者干脆叫全栈工程师。
“DevOps是一个职能”这个观点源于设施的日趋完善云服务的流行以及各类开源工具的广泛使用使传统Ops的工作重心发生了变化使企业产生了不再需要Ops的错觉。
但这个观点也是错误的原因就是忽略了Dev与Ops本质上是不同的也就是他们掌握的技能是不同。
虽然在DevOps看来Dev和Ops的最终目标是一致的都是为了快速向客户提供高质量的产品但其达到目标的手段和方法是不一样的。比如Ops 往往需要更多的在线处理问题的经验而这未必是Dev所具备的。
所以简单地把DevOps看做是一个职能是一个彻底错误的观点。
第三DevOps是一种文化推倒Dev与Ops之间的阻碍墙。
DevOps是通过充分的合作解决责任模糊、相互推诿的问题和矛盾。在著名的演讲《每天部署10次以上Flickr公司的Dev与Ops的合作》 中,就明确的指出工具和文化是他们成功的原因。
其实DevOps通常想要告诉我们的是什么行为是值得被鼓励的而什么行为需要被惩罚。通过这样的方法DevOps可以促使我们形成良好的做事习惯也就是DevOps文化。
所以我们可以发现引入DevOps的组织其实都是希望塑造这样的一种信任、合作、沟通、学习、分享、共担等鼓励协作的文化。
第四DevOps是一种组织架构将Dev和Ops置于一个团队内一同工作同化目标以达到 DevOps文化地彻底贯彻。
这看起来确实没有什么问题而且敏捷团队往往都是这么去做的。但是从另一方面来看Ops作为公司的公共研发资源往往与Dev的配比是不成比例。所以虽然我们希望每一个敏捷团队都有Ops但这可能是一种奢求。
但是敏捷团队也说了不一定是要有一个专职Ops人员只要有负担这个角色职责的成员存在即可。这当然也讲得通但可能真正的执行效果就没有DevOps所设想的那么好了。
所以DevOps是一种组织架构这种说法也对也不对主要视组织的具体情况而定。
总结
今天我和你一起回顾了DevOps产生的历程。同时也顺便带你回顾了一下爱打盹儿的持续交付。我希望通过这篇文章你可以理清持续交付和DevOps的关系
DevOps的本质其实是一种鼓励协作的研发文化
持续交付与 DevOps 所追求的最终目标是一致的,即快速向用户交付高质量的软件产品;
DevOps的概念比持续交付更宽泛是持续交付的继续延伸
持续交付更专注于技术与实践,是 DevOps 的工具及技术实现。
思考题
DevOps大潮袭来企业是不是真的就不需要Ops这个岗位了呢
欢迎你给我留言。

View File

@ -0,0 +1,158 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
04 一切的源头,代码分支策略的选择
记得大概是一年前吧,我与好友老吴喝茶聊天时,讨论到:高效的持续交付体系,必定需要一个合适的代码分支策略。
我告诉老吴:“采用不同的代码分支策略,意味着实施不同的代码集成与上线流程,这会影响整个研发团队每日的协作方式,因此研发团队通常会很认真地选择自己的策略。”
老吴是一名有多年开发经验的资深架构师当时正好要接手一个框架团队从个人贡献者向团队管理者转型。他个人对代码管理工具可谓熟之又熟甚至连“老古董”的CVS都可以跟你聊半天。但他在为团队制定代码分支管理策略时还是慎之又慎足见其重要性。
最后我们发现,要确定选用哪种代码分支管理策略,需要先假设几个问题,这几个问题有了答案,也就代表你找到了适合的方向。
你需要思考的几个问题如下:
Google和Facebook这两个互联网大咖都在用主干开发Trunk Based Development简称TBD我们是不是也参照它俩采用主干开发分支策略
用Google搜索一下会发现有个排名很靠前的分支策略叫“A successful Git branching model”简称 Git Flow它真的好用吗团队可以直接套用吗
GitHub 和 GitLab 这两个当下最流行的代码管理平台,各自推出了 GitHub Flow 和 GitLab Flow它们有什么区别适合我使用吗
像阿里、携程和美团点评这样国内知名的互联网公司,都在用什么样的分支策略?
今天,我想再沿着当时的思考路径,和你一起回顾和总结一下,希望能够带你全面了解代码分支策略,帮助你做出合适的选择。
谈谈主干开发TBD
主干开发是一个源代码控制的分支模型,开发者在一个称为 “trunk” 的分支Git 称 master 中对代码进行协作,除了发布分支外没有其他开发分支。
Google和 Facebook都是采用“主干开发”的方式代码一般直接提交到主干的头部这样可以保证所有用户看到的都是同一份代码的最新版本。
“主干开发”确实避免了合并分支时的麻烦因此像Google这样的公司一般就不采用分支开发分支只用来发布。
大多数时候发布分支是主干某个时点的快照。以后的改Bug和功能增强都是提交到主干必要时 cherry-pick (选择部分变更集合并到其他分支)到发布分支。与主干长期并行的特性分支极为少见。
由于不采用“特性分支开发”所有提交的代码都被集成到了主干为了保证主干上线后的有效性一般会使用特性切换feature toggle。特性切换就像一个开关可以在运行期间隐藏、启用或禁用特定功能项目团队可以借助这种方式加速开发过程。
特性切换在大型项目持续交付中变得越来越重要,因为它有助于将部署从发布中解耦出来。但据吉姆 · 伯德Jim Bird介绍特性切换会导致代码更脆弱、更难测试、更难理解和维护、更难提供技术支持而且更不安全。
他的主要论据是,将未经测试的代码引入生产环境是一个糟糕的主意,它们引发的问题可能会在无意间暴露出来。另外,越来越多的特性切换会使得逻辑越来越混乱。
特性切换需要健壮的工程过程、可靠的技术设计和成熟的特性切换生命周期管理,如果不具备这三个关键的条件,使用特性切换反而会降低生产力。
根据上面的分析,主干开发的分支策略虽然有利于开展持续交付,但是它对开发团队的能力要求也更高。
主干开发的优缺点如表1所示。-
表1 主干开发的优缺点
谈谈特性分支开发
和主干开发相对的是 “特性分支开发” 。在这个大类里面我会给你分析Git Flow、GitHub Flow和 GitLab Flow这三个常用的模型。
第一Git Flow
我们在Google上查关键词“branch model”也就是“分支模型”有一篇排名比较靠前的文章“A successful Git branching model”它介绍了Git Flow模型。
Git刚出来的那些年可参考的模型不多所以Git Flow模型在2011年左右被大家当作了推荐的分支模型至今也还有项目团队在使用。然而Git Flow烦琐的流程也被许多研发团队吐槽大家普遍认为hotfix和 release 分支显得多余,平时都不会去用。
图1 Git Flow示意图
第二GitHub Flow
GitHub Flow 是 GitHub 所使用的一种简单流程。该流程只使用master和特性分支并借助 GitHub 的 pull request 功能。
图2 GitHub Flow示意图
在 GitHub Flow 中master 分支中包含稳定的代码,它已经或即将被部署到生产环境。任何开发人员都不允许把未测试或未审查的代码直接提交到 master 分支。对代码的任何修改包括Bug 修复、热修复、新功能开发等都在单独的分支中进行。不管是一行代码的小改动,还是需要几个星期开发的新功能,都采用同样的方式来管理。
当需要修改时,从 master 分支创建一个新的分支,所有相关的代码修改都在新分支中进行。开发人员可以自由地提交代码和提交到远程仓库。
当新分支中的代码全部完成之后,通过 GitHub 提交一个新的 pull request。团队中的其他人员会对代码进行审查提出相关的修改意见。由持续集成服务器如 Jenkins对新分支进行自动化测试。当代码通过自动化测试和代码审查之后该分支的代码被合并到 master 分支。再从 master 分支部署到生产环境。
GitHub Flow 的好处在于非常简单实用,开发人员需要注意的事项非常少,很容易形成习惯。当需要修改时,只要从 master 分支创建新分支,完成之后通过 pull request 和相关的代码审查,合并回 master 分支就可以了。
第三GitLab Flow
上面提到的GitHub Flow适用于特性分支合入master后就能马上部署到线上的这类项目但并不是所有团队都使用GitHub或使用pull request功能而是使用开源平台GitLab特别是对于公司级别而言代码作为资产不会随意维护在较公开的GitHub上除非采用企业版
GitLab Flow 针对不同的发布场景在GitHub Flow特性分支加master分支的基础上做了改良额外衍生出了三个子类模型如表2所示。-
表2 GitLab Flow 的三个分支
图3 带生产分支的GitLab Flow
图4 带环境分支的GitLab Flow
图5 带发布分支的GitLab Flow
GitLab Flow的特性分支合入master用的是“Merge Request”功能与GitHub Flow的“pull request”相同这里不再赘述。
通过Git Flow、GitHub Flow和 GitLab Flow3个衍生类别 这几个具体模型的介绍我给你总结一下特性分支开发的优缺点。如表3所示。
表3特性分支开发的优缺点
选出最适合的分支策略
上面我跟你讲到的分支模型都是IT研发领域比较流行的。虽然有些策略带上了代码平台的标识如GitHub Flow但并不意味着该策略仅限于GitHub代码平台使用你完全可以在自己搭建的代码平台上使用这些策略。
接下来我就总体归纳一下什么情况下应该选择什么样的分支策略。如表4所示。-
表4 不同情况适用的代码分支策略
国内互联网公司的选择
GitLab作为最优秀的开源代码平台被多数互联网大公司包括阿里、携程和美团点评等所使用这些大厂也都采用特性分支开发策略。当然这些大公司在长期持续交付实践中会结合各自公司的情况做个性化的定制。
比如携程公司在GitHub Flow的基础上通过自行研发的集成加速器Light Merge和持续交付Paas平台一起完成集成和发布。
再比如阿里的AoneFlow采用的是主干分支、特性分支和发布分支三种分支类型再加上自行研发的Aone协同平台实现持续交付。
总结
今天,我主要给你介绍了各种代码分支策略的特性。
你应该已经比较清晰地理解了“主干开发”和“特性分支开发”两种策略的各自特性:
“主干开发”集成效率高,冲突少,但对团队个人的开发能力有较高要求;
“特性分支开发”有利于并行开发,需要一定的流程保证,能保证主干代码质量。
相信在没有绝对自信能力的情况下,面对绝大多数的场景,企业还是会选择“特性分支开发”的策略。所以,我给你介绍了几种主流的特性分支方法,并对比了各类策略的优劣,以及它们适用的场景。
接下来,你就可以根据自己所在项目的具体情况,参考今天的内容,裁剪出最适合自己团队的分支策略了。
思考题
开源性质的项目,为什么不适合用主干开发的分支策略?
如果你所在的团队只有5人而且迭代周期为1周你会采用什么样的分支策略
欢迎你给我留言。

View File

@ -0,0 +1,168 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
05 手把手教你依赖管理
软件工程是多人合作的结果,我们在开发软件的时候经常会使用一些别人编写好的,比较成熟的库。
比如,早期的前端开发用到了 jQuery 库,那么通常的做法是去官网下载一个最新版本的 jQuery然后放在自己本地的项目中。对于简单的前端项目来说这样可以简单粗暴地达到目的。
但当项目越来越庞大,除了 jQuery 之外,你还会依赖一些其他的第三方库。比如 Bootstrap 与 Chosen这两个流行的前端库也都依赖 jQuery如果这些第三方库依赖的jQuery 版本一致还好,但大多数情况并没有这么乐观:
你的项目依赖的 jQuery 版本是 1.0.0 Bootstrap 依赖的版本是 1.1.0,而 Chosen 依赖的版本是 1.2.0,看上去都是小版本不一致,一开始并没有发现任何问题,但是如果到后期发现不兼容,可能就为时已晚了。
所以,你需要在确定依赖之前,就把整个系统的依赖全部梳理一遍,保证每个依赖都不会有冲突问题。
你可能会质疑,这个前端工程师一定是初级的,事先都不会确认 Bootstrap 和 Chosen 依赖的版本吗,直接选择依赖 jQuery 1.0.0 版本的不就行了?
这么说有一定道理,但是手工维护这些依赖是相当麻烦且容易出错的。随便找一个比较流行的开源软件,你都会发现它依赖了大量的第三方库,而这些第三方库又依赖着其他的第三方库,形成了一条十分复杂的依赖链。靠人工去解决这个依赖链一定会让你怀疑人生,因此你需要一些工具去管理项目的依赖。
你见过几种依赖管理工具?
其实,各大平台早已有一套自己的手段来解决上述的问题,仔细看看你常用的软件,你会发现其实工作当中已经充斥着各种各样的依赖管理工具,没有它们你将寸步难行。
操作系统的依赖管理工具比如CentOS 的 yumDebian 的 aptArch 的 PackmanmacOS 的 Homebrew-
编程语言的依赖管理工具比如Java 的 Maven .Net 的 nugetNode.js 的 npmGolang 的 go getPython 的 pipRuby 的 Gem 。
这些平台的解决思路都是将依赖放到共同的仓库,然后管理工具通过依赖描述文件去中央仓库获取相应的包。
一个典型的依赖管理工具通常会有以下几个特性:
统一的命名规则,也可以说是坐标,在仓库中是唯一的,可以被准确定位到;
统一的中心仓库可以存储管理依赖和元数据;
统一的依赖配置描述文件;
本地使用的客户端可以解析上述的文件以及拉取所需的依赖。
接下来我以 Maven 为例,跟你一起探究一下 Maven 会如何管理 Java 项目的依赖。
Maven 如何管理依赖?
Maven 是 Java生态系统里面一款非常强大的构建工具其中一项非常重要的工作就是对项目依赖进行管理。
Maven 使用 XML格式的文件进行依赖配置描述的方式叫作 POMProject Object Model ),以下就是一段简单的 pom.xml 文件片段:
在POM中根元素 project 下的 dependencies 可以包含一个或多个 dependency 元素,以声明一个或者多个项目依赖。每个依赖可以包含的元素有:
groupId、artifactId、version 依赖的基本坐标;
type 依赖的类型默认为jar
scope 依赖的范围;
optional 标记依赖是否可选;
exclusions 用来排除传递性依赖;
要想用好Maven管理依赖你必须理解每一项的含义而新手通常傻傻分不清楚。举个例子依赖范围这一项Maven 在不同的时期会使用不同的 classpath
比如junit 只有在测试的时候有用,那么将其设为 test scope 就可以;
再比如 servlet API这个 jar 包只需要在编译的时候提供接口,但是实际在运行时会有对应的 servlet 容器提供,所以没必要打到 war 包中去,这时候只需要指定在 provided scope 就可以了。通过指定 provided scope的方式可以让每个依赖各司其职不用弄成“一锅粥”。
包管理工具还解决了依赖传递的问题,比如你的项目 A 依赖了 B而 B 依赖了 C和D那么在获取依赖的时候会把 B、C、D 都一起拉下来,这样可以节省大量的时间。
再让我们回到最开始的问题依赖不一致该如何处理通常每个包管理工具都有一套自己的原则Maven 的依赖仲裁原则如下。
第一原则: 最短路径优先原则。 比如A 依赖了 B和C而 B 也依赖了 C那么 Maven 会使用 A 依赖的 C 的版本,因为它的路径是最短的。
第二原则: 第一声明优先原则。 比如A 依赖了 B和CB 和 C 分别依赖了 D那么 Maven 会使用 B 依赖的 D 的版本,因为它是最先声明的。
根据这两个原则Maven 就可以确定一个项目所有依赖的列表,但它处理依赖的方式还是有些简单粗暴。有时 Maven 的决定结果并不是你想要的,所以我们在使用 Maven 的时候还是要多加小心。
Maven 最佳实践
接下来我跟你分享下我平时使用Maven 时总结的一些经验。
生产环境尽量不使用 SNAPSHOT 或者是带有范围的依赖版本,可以减少上线后的不确定性,我们必须保证,测试环境的包和生产环境是一致的。
将 POM分成多个层次的继承关系比如携程的POM继承树一般是这样
corp pom
ctrip pom/qunar pom
bu pom
product pom
project parent pom
project sub module pom
这样做的好处是每一层都可以定义这一级别的依赖。-
其中 ctrip pom/qunar pom 我们叫它为公司的 super-pom每个项目必须直接或间接的继承其所在公司的 super-pom。这样做的好处是每一层都可以定义这一级别的依赖便于各个层次的统一管理。
在父模块多使用 dependencyManagement 来定义依赖,子模块在使用该依赖时,就可以不用指定依赖的版本,这样做可以使多个子模块的依赖版本高度统一,同时还能简化子模块配置。
对于一组依赖的控制可以使用BOMBill of Materials 进行版本定义。一般情况下框架部门有一个统一的BOM 来管理公共组件的版本当用户引用了该BOM后在使用框架提供的组件时无需指定版本。即使使用了多个组件也不会有版本冲突的问题因为框架部门的专家们已经在BOM中为各个组件配置了经过测试的稳定版本。-
BOM是一个非常有用的工具因为面对大量依赖时作为用户你不知道具体应该使用它们的哪些版本、这些版本之间是否有相互依赖、相互依赖是否有冲突使用BOM 就可以让用户规避这些细节问题了。
对于版本相同的依赖使用 properties 定义,可以大大减少重复劳动,且易于改动。上面的 pom.xml 片段,就是使用了 properties 来定义两个一样的版本号的依赖。
不要在在线编译环境中使用 mvn install 命令否则会埋下很多意想不到并且非常难以排查的坑该命令会将同项目中编译产生的jar包缓存在编译系统本地覆盖mvn仓库中真正应该被引用的jar包。
禁止变更了代码不改版本号就上传到中央仓库的行为。否则,会覆盖原有版本,使得一个版本出现二义性的问题。
归根结底,这些经验都是为了两件事:减少重复的配置代码,以及减少不确定的因素发生。
有时候,你会听到来自业务开发部门同事传来报障的声音:“为什么我本地可以编译通过,而你们编译系统编译通不过?”难道 Maven 在工作的时候还看脸? 当然不是!
遇到这样的情况不要急,处理起来通常有如下“三板斧”:
确认开发操作系统Java 版本Maven 版本。通常情况下操作系统对 Java 编译的影响是最小的但是偶尔也会遇到一些比如分隔符冒号与分号之类的问题。Java 和 Maven 的版本应尽量与生产编译系统保持一致,以减少不必要的麻烦。
如果确认了开发操作系统没问题,那么你可以把用户的项目拉到自己的本地,并且删除本地依赖的缓存,也就是删除 .m2 目录下的子目录减少干扰执行编译。若编译通不过说明用户本地就有问题让他也删掉自己本地的缓存找问题。如果可以编译通过说明问题出在编译系统进入第3步。
使用 mvn dependency 命令对比生产编译系统与本地依赖树的区别,检查编译系统本地是否被缓存了错误的 jar 包,从而导致了编译失败。有时候这种错误会隐藏得比较深,非常难查,需要很大的耐心。
总结
今天,我跟你聊了聊依赖的问题。你可以从中:
了解到依赖管理的复杂度是如何产生的;
学习到依赖管理的一些常规思路;
初步掌握通过Maven进行依赖管理的方式方法及一些最佳实践。
同时我也与你一起分享了一些我的实际经验,希望能够对你在实际工作中有所帮助。
后续
理想是美好的,然而现实却很骨感,在实际过程中我们也遇到了一些问题,比如用户不遵守我们推荐的命名规则,或者不继承公司提供的 Super POM或者框架组件升级而用户不愿意升级等等。
为了能够统一管理,我们在构建系统上增加了一些强制手段来做统一的约束,使用 Maven Enforcer 插件以及其他方式对构建过程实行大量检查,欲知详情,请听下回分解。
思考题
你可以对比一下 Maven 其他语言的依赖管理工具,如 npm、pip、gem 等等,看看它们各自都有什么样的优缺点。
欢迎你给我留言。

View File

@ -0,0 +1,206 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
06 代码回滚,你真的理解吗?
什么是代码回滚?
在我正式开始今天的分享前,先给你讲两个核心概念:
包回滚是指,线上运行的系统,从现在的版本回滚到以前稳定的老版本。
代码回滚是指Git分支的指针游标从指向当前有问题的版本改为指向一个该分支历史树上没问题的版本而这个版本可以是曾经的commit也可以是新建的commit。
你是不是也遇到了问题?
在日常的代码管理中,困扰开发工程师最多,也是他们向我咨询得最多的问题就是:代码回滚的问题。这些问题,有的只是影响个人开发,而有的涉及了整个团队。我把这些问题进行了整理汇总,你可以看看是否也遇到过类似的问题?
今天上午我在自己的开发环境上拉了一条新分支提交了5个commit最新提交的3个commit 我不想要了那我该怎么退回到这3个commit之前的那个commit-
答:参考我在下面即将分享的“个人分支回滚”的内容。
我本地的分支通过 reset hard 的方式做了代码回滚想通过push的方式让远端的分支也一起回滚执行push命令时却报错该怎么办-
答:如果不加 -f 参数,执行 reset hard 后push会被拒绝因为你当前分支的最新提交落后于其对应的远程分支。push 时加上 -f 参数代表强制覆盖。
线上产品包已经回滚到昨天的版本了我清清楚楚地记得昨天我把发布分支上的代码也reset hard到对应的commit了怎么那几个有问题的commit今天又带到发布分支上了真是要命-
答:集成分支不能用 reset hard 做回滚应该采用集成分支上新增commit的方式达到回滚的目的。
我刚刚在GitLab上接纳了一个合并请求Merge Request变更已经合入到master上了但现在我发现这个合并出来的commit有较大的质量问题我必须把master回滚到合并之前我该怎么办-
可以在GitLab上找到那个合并请求点击revert按钮。
刚刚线上A产品V6.2的包有问题我已经把A的产品包回退到V6.1 版本了请问发布分支上的代码也要回滚到V6.1对应的commit吗-
答:你可以在下文“哪些情况下需要回滚代码?”和“哪些情况下包的回滚无需回滚代码?”中找到答案。
产品包的回滚可以在我们公司持续交付云平台上执行,平台能不能也提供代码一键回滚的功能?这样我们回滚代码能相对轻松一些。-
答:针对已上线发布的版本,我认为持续交付平台提供一键回滚的方式还是有必要的。这么做可以规范集成分支上线后代码回滚的行为,也能减少人为失误。具体做法可以参考我在下面给你分享的“集成分支上线后回滚”的内容。
上面这六个问题,除了前两个问题外,剩下的四个问题都可能影响到整个团队,因此回滚代码时须站在团队的立场,采用合适的方式进行回滚。
接下来,我就一一为你解答这些问题。
哪些情况下需要回滚代码?
在代码集成前和集成后,都有可能需要回滚代码。
第一种情况开发人员独立使用的分支上如果最近产生的commit都没有价值应该废弃掉此时就需要把代码回滚到以前的版本。 如图1所示。
图1 个人分支回滚
第二种情况:代码集成到团队的集成分支且尚未发布,但在后续测试中发现这部分代码有问题,且一时半会儿解决不掉,为了不把问题传递给下次的集成,此时就需要把有问题的代码从集成分支中回滚掉。 如图2所示。
图2 集成分支上线前回滚
第三种情况代码已经发布到线上线上包回滚后发现是新上线的代码引起的问题且需要一段时间修复此时又有其他功能需要上线那么主干分支必须把代码回滚到产品包V0529对应的commit。 如图3所示。
图3 集成分支上线后回滚
哪些情况下包的回滚无需回滚代码?
线上回滚后,查出并不是因为源代码有问题。
下次线上发布,就是用来修复刚才线上运行的问题。
代码回滚必须遵循的原则
集成分支上的代码回滚坚决不用 reset hard 的方式,原因如下:
集成分支上的 commit 都是项目阶段性的成果,即使最近的发布不需要某些 commit 的功能,但仍然需要保留这些 commit ,以备后续之需。
开发人员会基于集成分支上的commit 拉取新分支,如果集成分支采用 reset 的方式清除了该 commit ,下次开发人员把新分支合并回集成分支时,又会把被清除的 commit 申请合入,很可能导致不需要的功能再次被引入到集成分支。
三种典型回滚场景及回滚策略
在上面的内容中,我给你提到了个人分支回滚、集成分支上线前的回滚,以及集成分支上线后的回滚,这三种需要代码回滚的场景,它们具有一定的代表性。
现在我就先以表1 的形式,针对不同场景为你归纳不同的处理策略。后面的章节中,我再为你具体介绍每种场景的处理步骤。
表1 需要代码回滚的三种场景对应的处理策略
第一,个人分支回滚
同图1 个人分支回滚
针对图1的情况
feature-x分支回滚前HEAD指针指向 C6 。
在个人工作机上,执行下面的命令:
\( git checkout feature-x
\) git reset hard C3的HASH值
如果feature-x已经push到远端代码平台了则远端分支也需要回滚
$ git push -f origin feature-x
第二,集成分支上线前回滚
同图2 集成分支上线前回滚
针对图2中集成分支上线前的情况说明
假定走特性分支开发模式上面的commit都是特性分支通过merge request合入 master 产生的commit。
集成后测试环境中发现C4和C6的功能有问题不能上线需马上回滚代码以便 C5 的功能上线。
团队成员可以在 GitLab 上找到C4和C6合入master的合并请求然后点击 revert 。如图4所示。
图4 用revert方式实现回滚
回滚后master分支变成如图5所示C4是revert C4产生的commitC6是revert C6产生的commit。通过revert操作C4 和 C6 变更的内容在 master 分支上就被清除掉了,而 C5 变更的内容还保留在 master 分支上。
图5 回滚后的示意图
第三,集成分支上线后回滚
同图3 集成分支上线后回滚
我先跟你说明一下图3中的具体情况
C3打包并上线生成线上的版本V0529运行正确。之后C6也打包并上线生成线上版本V0530运行一段时间后发现有问题。C4和C5并没有单独打包上线所以没有对应的线上版本。
项目组把产品包从V0530回滚到V0529经过定位V0530的代码有问题但短时间不能修复于是项目组决定回滚代码。
C4和C5没有单独上过线因此从线上包的角度看不能回滚到C4或C5应该回滚到C3。
考虑到线上包可以回滚到曾发布过的任意一个正确的版本。为了适应线上包的这个特点,线上包回滚触发的代码回滚我们决定不用 一个个revert C4、C5和C6的方式而是直接创建一个新的commit它的内容等于 C3 的内容。
具体回滚步骤:
\( git fetch origin
\) git checkout master
\( git reset --hard V0529 # 把本地的master 分支的指针回退到 V0529此时暂存区(index)里就指向 V0529里的内容了。
\) git reset soft origin/master # soft使得本地的master 分支的指针重新回到 V05javascript:;30而暂存区(index)变成 V0529的内容。
\( git commit -m "rollback to V0529" # 把暂存区里的内容提交这样一来新生成的commit的内容和 V0529 相同。
\) git push origin master # 远端的master也被回滚。
回滚后如图6所示。
图6 回滚后的示意图
C3的内容等于 C3master分支已清除C4、C5和C6的变更。
现在master又回到了正确的状态其他功能可以继续上线。
如果要修复C4、C5和C6的问题可以在开发分支上先revert 掉C3 这样被清除的几个commit的内容又恢复了。
总结
代码回滚在持续交付中与包回滚一样也是不可缺少的一项活动。但它并不是简单地去执行Git的reset或revert命令就可以搞定的事情。
除了开发的个人分支上存在回滚的情况外,我们还会遇到集成分支上需要回滚的情况;对于集成分支的回滚,又可以分为上线前和上线后两种情况;因为紧急程度和上线情况的不同,我们必须采用不同的回滚策略。
我围绕着开发工程师在代码管理中最常遇到的6个问题分别为你介绍了代码回滚的概念梳理了需要回滚及不需要回滚的情况分析了回滚的类别及其不同的回滚策略提炼了回滚原则希望能对你的实际工作有所帮助保持正确的回滚姿势。
思考题
那么,接下来就是留给你的思考题了。
集成分支上线前如果发现新提交的5个commit有3个需要回滚请问除了点击合并请求中的revert按钮这种方法外还可以怎么做
采用特性分支开发的一个项目,每个特性分支合入到 master 时都会产生一个合并的commit而且该项目是禁止直接向master做push操作的。可是该项目的master分支却存在多个非合并产生的commit请问这些commit很可能是怎么产生的
持续交付平台如果要提供一键代码回滚的功能每次回滚都要生成一个新的commit吗即使以前已经产生过同内容的commit了也要重建新的commit么
欢迎你给我留言。

View File

@ -0,0 +1,126 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
07  “两个披萨”团队的代码管理实际案例
07 “两个披萨”团队的代码管理实际案例
在亚马逊内部有所谓的“两个披萨”团队,指的是团队的人数不能多到两个披萨饼还不够吃的地步。也就是说,团队要小到让每个成员都能做出显著贡献,并且相互依赖,有共同目标,以及统一的成功标准,这样团队的工作效率才会高。
现在有很多互联网公司喜欢采用“两个匹萨”团队的模式,你可能很好奇,这些团队通常是如何实施代码管理的?
当前国内互联网公司通常采用特性分支开发的模式,我在第四篇文章《一切的源头,代码分支策略的选择》中,为你详细介绍了这种模式,下面我就以这种模式为例,为你解开困惑。
以迭代周期为一周的项目为例,我将按照从周一到周五的时间顺序,通过整个团队在每天的工作内容,跟你分享项目任务分配,分支创建、集成与分支合并、上线,包括分支删除的关系。你可以从中了解互联网公司研发团队日常代码管理的真实情况,体会团队为了提高研发效率,在代码管理上做出的创新与改进。
背景
周一上午 1130“复仇者” 团队的周会结束会议室里陆续走出了6名工程师
“钢铁侠”5年一线开发经验现任“复仇者”项目经理及产品负责人
“美国队长”6年开发经验负责“复仇者”项目的技术架构兼开发工作
“绿巨人”3年开发经验全栈开发
“雷神”3年开发经验全栈开发
“蜘蛛侠”1年开发经验负责几个成熟模块的维护
“黑寡妇”:资深测试工程师,负责系统集成与测试。
其他同事泡咖啡喝茶的时候“钢铁侠”在公司的GitLab 上已经把 issue 分配给了团队成员,预示着忙碌又充实的一周要开始了。
周一下午
“美国队长”“绿巨人”“雷神”“蜘蛛侠”这4名开发人员早已熟悉团队的工作流程午休之后他们纷纷打开 GitLab 界面,在待办事项上找到自己的 issue查看无误后直接根据 issue 建好了新的特性分支。
每个新分支代表了一个具体的任务待四人建好新分支后“钢铁侠”不由得微微一笑心想哈哈任务都被大伙儿认领了看样子他们下午就要开工啦。这4名开发人员新建的4个分支如图1所示。
图1 开发人员新建的4个分支
这时资深测试工程师“黑寡妇”也没闲着开始查看起本周计划完成的issue整理出功能点、性能要求和粗粒度的接口列表基本明确了测试范围。随后她在公司GitLab平台上为本周迭代设置好了“Smart Merge”如图2所示。
图2 配置Smart Merge
要是有新人加入团队“黑寡妇”肯定会向他推荐这个“Smart Merge”Smart Merge是我和几个好友一同开发的GitLab插件其作用是高效地解决分支合并的一系列问题。高效的工作习惯使得“黑寡妇”早已整理好了说明文档以便随时分享给其他同事。
有了Smart Merge任何一个分支的变更会自动触发合并一旦出现冲突开发人员就会立刻收到邮件通知。
周一下班前4位开发人员分别把各自的本地分支push到了 GitLab 平台。集成后没有冲突,大家开开心心回家了。
周二
“美国队长”起了个大早9点半就到公司了昨天他已经实现了核心功能今天要完善这些功能并升级 API。他忙了个把小时本地开发自测完成并把本地 feature/captain 分支push 到了 GitLab 服务器。
一分钟不到,“美国队长”的邮箱收到了 GitLab 发来的通知,告诉他刚提交的某两个文件和 feature/hulk 分支发生了冲突。
“美国队长”知道肯定是黑寡妇创建的 Smart Merge 帮助自己快速发现了冲突,他直接用 GitLab 的 compare 功能对比了 feature/captain 和 feature/hulk 这两个分支,找到了冲突所在的行。
通过分析“美国队长”判断出feature/hulk的变更是合适的这个冲突应该由他解决掉。
“美国队长”选择在本地对自己的分支执行 git rebase -i 把引入冲突的commit进行了变更自测通过后再次把 feature/captain 分支push到了 GitLab 。为了确保冲突的问题已经被解决他打开了Smart Merge发现状态是“已合并”Merged ,这才端起杯子泡咖啡去了。
上午10:00前后“绿巨人”等人也陆陆续续到公司了。团队已经约好了协作节奏每周四下班前完成一个迭代的上线。
通常周二下午开发人员要把每个 issue 的基本功能开发好,“黑寡妇”周二下午会给 Smart Merge 配置好持续交付的环境,一旦某个分支 push 后,自动完成分支合并,然后自动编译、打包,并部署到测试环境。
在测试环境上,除了跑自动化测试外,“黑寡妇”也会手工做一些集成测试和性能测试。
周二下午,“美国队长”开始 review 大家的代码他把本周开发的4个分支在GitLab上分别创建了4个merge request目标分支都是 master 。
“美国队长”觉得 GitLab 的 review 功能很完善交互也很便捷。这时其他3名开发人员忙着写代码和自测。“黑寡妇”除了搭建测试环境外还补充了自动化测试的用例。
周三
经过周一和周二的努力,本周的基本功能均已实现,“黑寡妇”开始对系统实施集成测试,并做一些压力测试。
上午测试时,“黑寡妇”发现在某些场景下系统存在较大的延迟,这个问题在上周的版本中并不存在。她判断是本周新引入的功能导致了这个问题,但一下子又很难确定是怎么引起的。
于是,“黑寡妇”决定修改 Smart Merge 的配置,把嫌疑最大的分支剔除掉后再打包测试。通过这样的方式,最后查出是 feature/thor 这个分支引入的问题,她把测试情况详尽地告诉了“雷神”。
大半个下午雷神都在查问题,到下午四点钟时,问题终于被“雷神”修复了,他把 feature/thor 分支做了 push然后向“黑寡妇”求助请她合入自己的分支后再帮忙做测试。
“黑寡妇”把“雷神”的分支重新加回到 Smart Merge 中,并把编译包重新部署到了测试环境。经过测试验证:延时大的问题真的不见了。
下班前,“黑寡妇”召集项目组开了个简短的质量会议,大家商量后认为本周计划内的四个开发任务集成后没有大的质量问题,周四可以一起上线。
会后,“黑寡妇”看了看本周的四个合并请求,“美国队长”对四个请求意见都是赞成合入 masterSonar 检查也都合格,加上自己测下来质量也过关,于是,她果断地接受了四个合并申请。
在回家前master 对应的最新 commit 已经顺利地编译、打包后被发到用户验收测试环境,“黑寡妇”对这个环境启动了自动化测试服务。
至此测试加修复Bug忙碌了一整天大家终于可以回家休息了。
周四
“黑寡妇”一早上班时,首先查看了自动化测试的结果,显示 master 分支构建出的包符合质量要求。于是,她又对没有设计自动化测试用例的部分,进行了手工测试,发现几个界面上存在文字描述的问题,随后通知开发做修复。
开发在本地分支上修复问题后 push 到 GitLab再次发起合并请求“黑寡妇”逐个接受了这几个 Fix 的请求。
到中午时分,用于上线的产品包终于生成了。
等到发布窗口开启时,“黑寡妇”通过公司的发布系统把合格的产品包发布到了线上。观察一段时间,线上运行都正常。
对应本次上线,“黑寡妇”及时给 master 打了tag然后把本周成功发布的消息通知到项目组并向“钢铁侠”做了汇报。
“钢铁侠”看大伙儿忙碌了这么多天,豪爽地请大家喝果汁,并告诉大家他又有几个紧急的用户需求,嘱咐大伙下周继续努力。
周五
通常在这一天,项目组会一起清理过期的分支,删除本周已合并到 master 的分支。而对于下周开发的新分支,项目组约定统一从 master 上拉取。另外,利用这一天,项目组也会召开回顾和改进会议,以讨论解决目前的一些已有问题的方案,这些讨论即包含工作流程问题,也包含代码和系统等问题。
总结
我介绍了由6人组成的“两个披萨”团队代码管理的实践通过周一到周五的具体活动你可以看到采用特性分支开发的团队是如何创建分支、集成分支和删除分支的希望能对你的日常工作也有所帮助。
思考题
假设有A、B、C三个功能依次被合并到master并准备上线此时发现A功能有问题不能上线而B和C则必须上线此时你会采取什么办法来解决
欢迎你给我留言。

View File

@ -0,0 +1,158 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
08 测试环境要多少?从现实需求说起
在整个持续交付生命周期中,测试环境的易用程度会直接影响软件的交付速度,但因为以下两点,它又是最被容易忽略的一环。
我们总是把环境理想化,忽略了其管理的难度;
我们也很少设立专职的环境管理员,导致环境长期处于混乱状态。
通常,我们在项目初期并不会关注测试环境的问题,然而在回顾时却发现在环境问题上浪费的时间非常惊人:硬件资源申请困难,测试环境配置繁琐,测试应用更新困难,基础设施稳定性差,服务调用异常,多项目并行造成互相干扰等等问题。
而不管你是开发人员还是测试人员,相信你都或多或少地碰到过这些问题。
在接下来的《环境管理》系列文章中,我会和你聊聊构建一整套好的测试环境的关键点以及具体实施方案。今天,我就先跟你说说和测试环境相关的两个问题:
测试环境的结构一般是怎样的?
什么才是好的测试环境?
互联网公司测试环境的结构
当公司规模较小时,测试环境的维护相对容易。开发和测试共用一套数据库缓存等基础设施,因为应用数量不多,开发环境可以是单机的,无论是手动或半自动化的更新测试环境的应用,花费的时间都还在可接受范围内。
这时,公司环境的结构很简单,分为开发环境,测试环境,生产环境即可。
但实际上我看到的大多数公司的研发过程及配套环境并没有这么简单一般都会存在5套以上的大环境以及更多的子环境每个环境的机器数量可能有数十台甚至更多。
那么为什么会需要这么多套环境呢?我把主要原因概括为了以下两个方面。
纵向上看,人员的增多提高了项目的并行度,如果这时还使用一套环境的话,就会发生以下问题:
开发同学在debug一个困难问题时发现下游的应用突然就不可用了
测试同学在跑了10多分钟测试脚本后发现应用已经被开发更新掉了。-
这样的体验是让人崩溃的。
横向上看,公司的应用架构逐渐转为微服务化,完整的应用数量很容易就达到了几百甚至几千个的量级,建立一套独立而完整的环境变得越来越复杂,往往是研发团队想要构建一套新的环境却构建不出来。
所以,目前互联网公司常见的环境模型一般分为开发环境,功能测试环境,验收测试环境,预发布环境,生产环境这五个大套环境。
第一,开发环境
微服务架构下,单机已经无法完整地运行业务应用,这就需要开发环境内包含一套完整的业务应用依赖以及相关的基础设施,以保证业务开发同学能在本地完成开发测试。
第二,功能测试环境
在开发环境下每个下游依赖应用都只有一个可用的stable版本。而在实际的开发过程中由于项目的并行开发往往会同时存在多个可依赖的版本。而每个项目组的同学在测试时都希望测试过程中的关键依赖应用是可以被独占的版本是固定的不会被其他项目组干扰。
所以,一套独立的功能测试环境就很有必要了。通常,互联网企业会通过中间件的方式分割出一块隔离区域,在功能测试环境中创建多个子环境来解决这个问题。
第三,验收测试环境
验收测试环境和功能测试环境是完全隔离的。当功能测试通过后,你可以在验收测试环境进行最终的验收。
它除了可以用作测试之外,还可以用作产品展示。所以,除了测试和开发人员,产品经理也是验收测试环境的主要使用者。
第四,预发布环境
到了预发布阶段应用已经进入了生产网络和真实的生产应用共享同一套数据库等基础设施。预发布是正式发布前的最后一次测试在这个环境中往往可以发现线下环境中发现不了的Bug。这个环境的运维标准等同于生产环境一般不允许开发人员直接登录机器。
根据不同的业务需求和部署策略,不同公司对预发布环境的实现也有所不同:
一种比较常见的方式是,将金丝雀发布作为预发布,从接入真实流量的集群中挑选一台或一小组机器先进行版本更新,通过手工测试以及自动化测试和监控系统验证,降低新版本发布的风险。
另一种做法是,独立出一组始终不接入真实流量的机器,调用在预发布环境中形成闭环。
相对于第一种方式,第二种方式对生产环境的影响更小,但需要额外的资源和维护成本。
第五,生产环境
生产环境是用户真实使用的环境,对安全性和稳定性的要求最高。
什么是好的测试环境?
在和你分享什么是好的测试环境前,建议你先思考一下开发环境、功能测试环境、验收测试环境、预发布环境这四种测试环境形成的原因是什么,这样有利于你更好的理解好的测试环境的含义。
首先,搭建测试环境的目的是保证最终交付的软件质量,但每套测试环境的用户并不完全一样:
开发环境的用户是开发同学;
功能测试环境的主要用户是测试同学;
验收测试环境的用户是产品经理和测试同学;
预发布环境的使用者是测试同学,但收益者却是运维同学。
而每种角色对于产品研发流程中的需求也是不同的:
开发同学关注研发效率;
测试同学关注测试的可靠性;
产品经理更关注的是真实的用户体验和产品的完整性;
预发布环境的需求其实来自于运维同学,他们需要保证生产环境的稳定性,减少生产环境的变更,所以需要将预发布环境与线下环境完全隔离。
如果你是一位测试环境治理工程师,在规划测试环境以及开发和实施工具的时候,最关键的就是要考虑到不同环境的主要用户是谁,环境要做成什么样才能满足用户在研发流程中的需求。当用户不用发愁环境问题时,研发效率也就自然而然地上去了。
当然,不论一套环境用户是测试同学还是开发同学,以下几个需求都是必须被做到的。
可得性,即在开发一个新项目时,能快速获取构建一个环境需要的机器,基础设施。最好的情况是,能随时可得,随时归还。
快速部署,即在搭建新环境时,能以最快的速度构建出一整套完整的环境。测试环境的部署很频繁,在代码提交后,能在很短的时间内构建代码,在环境上更新,就能更早开始测试。
独立性,即一个环境在使用过程中,可以不受其他项目测试人员的干扰。
稳定性,即不会因为下游服务,基础设施的异常,造成测试中断、等待。
高仿真,主要分为两个方面:“测试数据真实”,即能在测试环境构建出真实的测试用例;“环境真实”,即基础服务的架构和行为与线上环境保持一致,避免因为环境不一致造成测试结果不一致。
但是,毕竟各个环境的用户和使用场景不同,它们的需求也是有差别的。 比如,相对于开发环境,验收测试环境对测试数据的仿真性要求会更高,而开发环境的灵活性,决定了不会过于严格的维护测试数据的真实性。
所以,如何评价一个好的测试环境,就是看它是否最终满足了核心使用者的需求。
总结
通常互联网公司的环境会包括开发环境、功能测试环境、验收测试环境、预发布环境和生产环境这5套。
测试环境的目的是要保证最终将交付的软件产品的质量,所以好用的测试环境,不能从规模、性能和作用的角度来评判,而应该是从它能否满足用户需求去保证软件质量的角度进行定义,于是得出:
当一个环境可以满足其真正核心用户的需求时, 就是一个好用的测试环境。
除此之外,你还需要理解,环境是昂贵的,不仅涉及单一的机器资源成本,环境副本数的增加也意味着更难管理,更复杂的流程,所以仅仅考虑单套使用者的体验是不够的。
那么,在我的下一篇文章中,将会分享多环境带来的成本问题,以及如何在成本、效率、可管理之间权衡取舍。
思考题
请你思考一下测试环境中最让你痛苦的一点是什么?
如果让你来优化测试环境, 你会如何去改善这最让你痛苦的一点?
欢迎你给我留言。

View File

@ -0,0 +1,123 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
09 测试环境要多少?从成本与效率说起
在上一篇文章中,我分享了互联网公司测试环境的常见结构,以及对用户来说什么样的测试环境才是好用的。然而对测试环境来说,只是高效好用还不够,还要考虑到成本问题。
效率和成本永远是一对矛盾体。今天,我就从成本和效率出发,和你聊聊构建测试环境时,还需要考虑的其他维度。
测试环境的成本
谈到环境成本你很自然地就会想到云计算《持续交付发布可靠软件的系统方法》一书出版时云计算还是一个时髦的概念而8年后的今天云技术已经非常成熟了。
今天部分传统企业和互联网企业都在选择混合云架构而创业公司选择公有云已经有了点模式化的意味。公有云非常好地满足了DevOps的“基础设施即代码”的理念哪怕你完全不使用公有云那在环境中整合开源的私有云技术依然能为你带来不少便利。
然而,云计算并非“银弹”,我们上云后,在成本上,还是有很多值得去思考和做的事情。
尤其是当环境数量增加时,你很容易就可以想到成本会增加,但是你可能并不明白要增加哪些方面的成本,以及会增加多少的问题。那么,接下来,我就跟你聊聊当环境数量增加时,你需要考虑的成本有哪些呢?
首先是机器资源成本
保证环境的独立性,是你构建更多套环境的一个主要原因。但是,一套独立的、拥有完整链路的环境成本是非常高的。
那么以阿里云的价格为例我来跟你一起算算这笔账。假设一个只有100个应用的微服务架构环境选取单应用单机2核4G内存的低配置实例方式进行部署单实例的年价格在2000元左右100个实例的话一年的花费就是20万元左右。
而这只是最保守的计算,随着服务规模的增加,以及更多环境的需要,整体花费上涨两个数量级也是很正常的。
这样的问题在开发环境和集成环境的表现是最明显的。为了保证这两套环境的独立性,你必然需要有很高的环境副本数。但无论如何,你都不可能让每一个开发和测试人员都拥有一套完整环境的硬件资源。
可见,每一套环境的机器资源成本都很大,而且随着需求的增加成比例增长。
其次是管理成本
管理成本,包括维护环境的可用性,配置的管理成本,和测试数据的维护成本三个维度。
维护多套环境的第一要点是,维护环境的可用性。-
与云时代之前相比,容器技术已经解决了很多问题。比如,服务器操作系统级别的依赖的标准化更容易了;当出现硬件故障时,迁移和恢复服务也更加方便了。-
但是,容器技术并没有解决故障定位的问题。微服务架构下集群的节点数量多, 调用链复杂你不再能确定到底是环境问题还是程序本身的Bug也就导致定位故障更加困难了。-
所以,更多套环境就意味着更大的集群规模,出现故障的几率会随之增加,而解决故障也会占用你更长的工作时间。
维护多套环境的另一大成本是,配置的管理成本。-
配置是环境管理中最核心的内容,创建一套环境时,为了保证它真正的独立可用,不仅要保证应用可以成功运行,还要保证应用在基础设施的配置是正确的。比如集成测试环境下部署了一个应用的多个平行项目,就需要有办法保证测试人员能访问到正确的应用。-
如果是Web应用你就要考虑把应用绑定到不同的域名这样就会增加域名管理的成本如果是一个service应用你就要考虑到这些service不会被其他项目的、无关环境中的应用调用到同时也不会调用到其他错误的服务。-
每多一套环境,就会多一套这样的配置,而且这些配置都需要在各类基础设施中生效。
维护多套环境的第三大成本是,测试数据的维护成本。-
测试数据也是环境中极为重要的一个组成部分。当并行环境的数量变多后,数据的维护同样是让人头疼的问题。-
为了保证环境的高仿真,哪些环境共用一套数据库,以及测试数据的更新在多套环境中怎么执行等等,都需要非常高的管理成本。
最后是流程成本
流程成本主要包括沟通成本和测试成本两个方面。
沟通成本-
每增加一套环境,你都需要考虑团队成员如何在新环境上沟通协作。谁在占用,何时退出这些信息,你都需要第一时间告知团队。当环境的数量变得非常多以后,做好这些事的难度就很大了。
测试成本-
在开发环境,集成测试环境,验收测试环境,预发布环境,生产环境这样的结构下,核心功能的测试流程就至少会执行五次。每引入一套新的环境,测试流程都会变得更加复杂。
如何调解效率和成本的矛盾?
现在你应该已经意识到,因为增加一套环境带来的成本竟然有那么多。但是为了提高持续交付的效率,隔离的多套环境又是必不可少的。
那么,你究竟应该怎样去规划和设计环境呢?
第一,公共与泳道的
第一个关键点是抽象公共环境,而其中的公共服务基本都属于底层服务,相对比较稳定,这是解耦环境的重中之重。 比如我们经常会将中间件,框架类服务,底层业务公共(账户,登陆,基本信息)服务部署在这套公共环境下。
在公共环境的基础上可以通过泳道的方式隔离相关测试应用利用LB和SOA中间件对路由功能的支持在一个大的公共集成测试环境中隔离出一个个独立的功能测试环境那么增加的机器成本就仅与被并行的项目多少有关系了。
为了帮助你理解,我跟你分享一个具体的案例。
比如你有一个新的下单流程需要测试。你可以将“下单web 2.0”和“下单service 2.0”抽离出来如图中的“功能环境1”所示。并保证被剥离出的“下单service 2.0”只能被当前环境内的web服务器调用。而“下单service 2.0”所依赖调用的“支付service 1.0”则放在公共环境中。
于此同时如图中所示的“功能环境2”可以同时支持“下单service 3.0”这个并行版本与一个新版本的“支付service 2.0”进行联调此环境是不会调用公共环境中的“支付service 1.0”的。
通过这种方式,你就可以解决并行开发和并行测试的问题了。
第二,避免产生多套公共环境
从原则上讲,集成环境中只要有一套公共环境就足够了。但有时候,你会发现项目的范围特别广,依赖应用或者影响应用特别多,特别是一些底层服务的改动或者重构等。在这种情况下,如果把依赖它的所有服务都部署起来,就很有可能变成第二、第三套公共环境了。
这种情况下,你可以通过 mock service 来模拟大多数服务这样就可以达到测试环境的最小集合了。关于mock service 如何做,我会在后续的讲解中,为你详细解释。
第三,减轻配置的复杂度
而对于减轻环境配置的复杂度来说,你可以从以下两个方面着手:
制定一套统一配置的解决方案;
要让环境自己说话,有效减少配置项。
比如,对于数据库,对于不同的测试需求,我们可能会在公共的数据库和独立的数据库之间进行频繁切换;又比如,我们可以在启动应用时自动配置正确的数据库。那么无论有多少套环境,配置也不是一件难事。
关于减轻配置的复杂度,我也会在专栏后续的讲解中,给你详细分析如何实现。
总结
我们究竟需要多少套环境,这个问题的答案应该是这样的:在大环境(开发、集成、验收)的数量上,你要考虑环境的核心用户是谁,环境的核心价值是什么。在环境的核心价值没有冲突时,尽量减少大环境的数量。
有些公司就通过功能分支直接上线的分支策略,对每个分支, 都创建一整套的功能测试环境,并在分支上线后快速释放。以这样的方式,精简了验收测试环境,即大环境的产生。但其代价是发布过程分支之间必须是串行的,即一个分支的上线会阻塞其他分支的上线。
而在每个大环境的子环境上(也就是按照测试需求被剥离出来的功能环境),你必须保证它的副本数可以满足用户测试的隔离需求。比如,在集成测试环境,只产生一套公共环境,并通过工具,支持隔离的功能测试环境的快速建立和销毁,让环境可以按需分配。
思考题
当你需要一套性能测试环境时,是独立出一套大环境还是作为一个子环境依附于某个大环境比较好?
欢迎你给我留言。

View File

@ -0,0 +1,180 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
10 让环境自己说话,论环境自描述的重要性
在前两篇文章中,我从现实需求、成本与效率的角度,分析了对环境管理者来说最重要的一个问题,即到底需要多少套环境来支撑持续交付。如果你已经从中能掌握了一些环境管理的窍门,那么你基本就可以搞定对环境管理的宏观把控了。
但是,除了宏观的把控和管理外,即使只有一套环境,你还是有可能陷入无穷无尽的细节工作中。因为在日常的环境管理过程中,环境配置才是工作的重头和难点。那么今天,我就来跟你详细说说有关环境配置的问题。
从我的实践经验看,要想把环境配置这件事做好,就是要做到让环境自己能说话。
要做到这点,首先需要定义配置的范围。
从面向的目标来看,环境配置大体上可以分为两大部分:
以环境中每台服务器为对象的运行时配置;
以一个环境为整体目标的独立环境配置。
服务器运行时配置
以一个 Java Web 应用为例,需要哪些运行时配置呢?
安装 war 包运行依赖的基础环境,比如 JDKTomcat 等。
修改 Tomcat 的配置文件关注点主要包括应用的日志目录日志的输出格式war 包的存放位置。Tomcat的server.xml配置包括连接数、 端口、线程池等参数。
配置Java 参数包括JVM堆内存的xmx、xmn等参数GC方式、参数JMX 监控开启等。
考虑操作系统参数,比较常见的一个配置是 Linux 的文件句柄数如果应用对网络环境有一些特殊要求的话还需要调整系统的TCP参数等配置。
经过上面这4步一个简单的运行时环境的配置就算是完成了 可以开始运行一个程序了。是不是感觉有点复杂呢?
而这,对正常的运行时配置管理来说,只不过是冰山一角而已。
我们不光要考虑单个实例初始化配置还要考虑每次JDK、Tomcat等基础软件的版本升级引起的运行时配置的变更而且这些变更都需要被清晰地记录下来从而保证扩容出新的服务器时能取到正确的、最新的配置。
另外,对于一个集群的服务器组来说,还需要强制保证它们的运行时配置是一致的。
独立环境配置
独立环境配置的主要目的是,保证一个环境能够完整运作的同时,又保证足够的隔离性,使其成为一个内聚的整体。
所以,要让一个环境能够符合需求的正常运作,你需要考虑的内容包括:
这个环境所依赖的数据库该如何配置,缓存服务器又该如何配置。
如果是分布式系统或者SOA架构的话就需要考虑服务中心、配置中心等一系列中间件的配置问题。
其中,最为重要的是配置中心的配置。只有先访问到正确的配置中心,才能获取到其他相关的环境配置或者应用配置信息。也就是说,如果配置中心的配置错了,那么环境就会陷入混乱状态。
要考虑访问入口问题。 这套环境的入口在哪里?是一个站点还是一个服务入口?-
如果是一个站点的话,那这个站点的访问域名就需要被特殊配置。如果这是一个内部环境的话,那么这个内部域名的 DNS 解析也需要被配置。如果这套环境中有多个 Web 应用那么你就要考虑7层路由的配置问题了。
还要配置环境对应的基础服务,比如监控,短信,搜索等。
读到这里,如此多的与环境有关的配置,有没有让你觉得太复杂了。
再想象一下,如果你的环境要承载多种语言栈,各类应用依赖的基础软件也不同,环境和环境之间有各种关联设置,数据库的连接分配,环境中负载均衡的设置,等等。是不是让你感觉有些焦虑?
如果每天都要和这样的工作做斗争,那简直就是一场噩梦。更别提在这样的环境下,完成持续交付了,那简直就是难如登天。
虽然环境配置有这么多糟心的待处理事项,但是环境本身也是一个非常强大的工具,本身包含非常多的信息,如果这些糟心的事情环境能和你一起来解决,那就简单了,也就是我所说的让环境自己来说话,那么接下来就看看怎么做到吧。
环境一定要标准化
解决复杂问题的办法,无非是先将其分解,再将其简单化,对环境配置这个难题来说也是同样的道理。想要解决它,首先得要想办法分解、简化它。
最好的简化方法,莫过于标准化了。所谓标准化,就是为了在一定范围内获得最佳秩序,对实际的或潜在的问题制定共同、可重复使用的规则。
标准化也就是让环境学会了一门统一的语言,是自己说话的前提。
按照这个思路,我们首先可以实现对语言栈的使用、运行时配置模板、独立环境配置的方法等的标准化:
规定公司的主流语言栈;
统一服务器安装镜像;
提供默认的运行时配置模板;
统一基础软件的版本,以及更新方式;
在架构层面统一解决环境路由问题;
自动化环境产生过程。
看到这里,你可能感觉需要标准化的内容也是多种多样的,而且每个公司的具体情况也不同,那么标准化实施起来也必定困难重重。
从我的实践经验来看,建议你在实施持续交付的同时,去推动形成以下几个方面的规范:
代码及依赖规范;
命名规范;
开发规范;
配置规范;
部署规范;
安全规范;
测试规范。
其实,不管是持续交付还是架构改造,标准先行都是技术实施的前提条件。
约定大于配置
讲到这里,你可能也会疑惑了,和环境有关的内容实在是太多了,即使有了标准化,怎么可能都通过配置实现呢?
举个例子,代码的部署路径,标准化后所有服务器的路径都应该遵循这个标准,但是不可能在每台服务器上都去定义一个配置文件或环境变量来标示它,也没有这个必要。
实际上,你也从来都没有疑惑过部署路径的问题,因为从你来到公司起,它就已经是约定俗成了。而且,每家公司都是这样的,难道不是吗?
像代码的部署路径这种情况,我们就把它叫作“约定大于配置”,在实际工作中,还有很多类似的场景,你完全可以利用这套方法,简化环境配置。
比如每个环境的域名定义可以遵循以环境名作为区分的泛域名实现又比如可以用FATUAT这样的关键词来表示环境的作用又比如可以约定单机单应用再比如可以约定所有服务的端口都是8080。
“约定大于配置”的好处是,除了简化配置工作外,还可以提高沟通效率。 团队成员一旦对某项内容形成认知,他们的沟通将不再容易产生歧义。
“约定大于配置”相当于赋予了环境天生的本能,进一步加强了环境的自我描述能力。
让环境自己能开口说话
有了环境标准化,以及约定大于配置的基础,你就可以顺利地让环境自己开口说话了。
也就是说,通过环境的自描述文件,让环境能讲清楚自己的作用、依赖,以及状态,而不是由外部配置来解释这些内容。
以一台服务器为例,一旦生成,除了不能控制自己的生死外,其他运行过程中的配置,都应该根据它自身的描述来决定。
那么,如何让服务器自己说话呢?
首先需要定义Server Spec。
这是重中之重在服务器生成时写入它自己的描述文件。我们通常把这个文件命名为“Server Spec”。在这个文件里记录了这台服务器的所有身份信息包括IDC型号归属环境作用所属应用服务类型访问路径等。
其次,解决配置中心寻址。
中间件根据Server Spec的描述寻找到它所在环境对应的配置中心从而进一步获取其他配置如数据库连接字符串短信服务地址等等。
最后,完成服务自发现。
其实这就是一个服务自发现的过程。根据服务类型,访问路径等,还可以自动生成对应的路由配置,负载均衡配置等。
总结来说,我们是在尝试把环境配置的方向调个个儿:由原来外部通过配置告知环境应该干什么,转变成环境根据自身的能力和属性,决定自己应该去干什么。
这种尝试,标志着环境配置能力的质的飞跃。一台服务器可以实现自描述,你同样就可以把这个方法推广到所有服务器中。同理,一个环境可以实现自描述,你就可以把自描述的方式扩展到所有环境中。
从此,环境配置将变得不再艰难。
总结
我主要围绕环境配置的问题,讲了它的内容和一些特性,以及简化和优化的一些方案。
一定要意识到,环境配置是非常复杂的,直接影响你的环境治理能力,而环境治理能力又直接影响着持续交付的能力。但是我们还是可以通过:标准化、约定、自描述等方式去简化和优化环境配置工作。
我们的目标是,让环境自己能说话。
思考题
在你的公司,这些环境配置相关的工作由谁来完成?又由谁来为他们制造工具和提高工作效率?
欢迎你给我留言。

View File

@ -0,0 +1,201 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
11 “配置”是把双刃剑,带你了解各种配置方法
很多人分不清配置和配置管理,但其实它们是完全不同的概念。
配置管理: 是通过技术或行政手段对软件产品及其开发过程和生命周期进行控制、规范的一系列措施。 它的目标是记录软件产品的演化过程,确保软件开发者在软件生命周期的各个阶段都能得到精确的产品配置信息。
配置: 是指独立于程序之外,但又对程序产生作用的可配变量。也就是说,同一份代码在不同的配置下,会产生不同的运行结果。
从上面的定义中,你可以看到配置和配置管理有着本质上的不同:配置管理服务于软件研发过程,而配置则服务于程序本身。
作为一名程序员,开发时经常要面对不同的运行环境:开发环境、测试环境、生产环境、内网环境、外网环境等等。不同的环境,相关的配置一般不一样,比如数据源配置、日志文件配置,以及一些软件运行过程中的基本配置等。
另外,你也会遇到一些业务上的,以及逻辑上的配置。比如,针对不同地域采取不同的计费逻辑,计费逻辑又要根据这些地域的需要随时调整。
如果我们把这些信息都硬编码在代码里,结果就是:每次发布因为环境不同,或者业务逻辑的调整,都要修改代码。而代码一旦被修改,就需要完整的测试,那么变更的代价将是巨大的。
因此,我们往往会通过“配置”来解决这些问题。
但是,“配置”本身也很讲究。在什么阶段进行配置,采用什么手段进行配置,都将直接影响持续交付的效果。
那么,接下来我就跟你详细聊聊各种配置方法。
构建时配置
以 Maven 为例,实现多环境的构建可移植性需要使用 profile。profile 是一组可选的配置可以用来设置或者覆盖配置默认值。通过不同的环境激活不同的profile可以实现构建的可移植性。 我们可以看一个简单使用示例:
这段代码定义了dev和prod两个 profile没有定义任何其他配置实际使用中可按需定义任何配置并且使用了 echo 插件验证 profile 是否生效,通过运行。
maven initialize Pdev
maven initialize Pprod
然后,可以看到输出:
[INFO] profiles.active = dev
[INFO] profiles.active = prod
其中, dev 是默认激活的,也就是说如果不填写任何 P 参数,或者 P 参数不为 dev 或者 prod都会使用 dev 作为默认的 profile。
这样在代码构建时你就可以根据具体需要选择对应的profile了。
这个方案看起来很简单, 但也有两个缺点:
它依赖于某个特定的构建工具,而且使用方法不统一。-
什么意思呢如果你不使用Maven作为构建工具这个配置功能就失效了而且对于跨平台、跨语言栈的支持也不友好。
每次都要重新编译,浪费计算资源。-
即使你只是替换一些配置文件,并没有改动任何代码,但为了让配置生效,还是需要完成代码的整个构建过程,这就会在编译上花费大量的计算资源。
因此,为了解决这两个问题,通常会把“打包”这个过程拆解出来,并将它插入构建之后,接下来我就介绍一下“打包时配置”。
打包时配置
“打包”,是我在多年持续交付实践中总结出的一个非常重要的概念。我把打包过程与构建过程脱离,也就是说构建成功后,并不立即打包。而是把打包安排在发布之前,打包完成之后立即发布,打包就与发布过程形成了一个整体。
为什么要独立分离出打包这个步骤呢你可能会问Maven 在构建过程中不是已经完成了 package 步骤吗?
正因为构建时配置,需要针对多个 profile 编译多次,而持续交付有一个核心概念,即:一次构建多次部署。打包就是为了解决这个问题而被发明的。
打包时配置的基本思想是:构建时完全不清楚程序所要部署的环境,因此只完成最基本的默认配置;而发布时清晰地知晓环境信息,因此可根据环境信息,进行相关配置的替换。
在携程,我们开发了一个叫作 ConfigGen 的工具,用以替换配置文件。 这样,你就不需要每次更改配置时,都重新编译整个代码,大幅缩短了整个发布流程的时间, 而且 ConfigGen 完全基于XML适用于任何语言。
ConfigGen 的使用也很简单,只要一个 ConfigProfile.xml 文件即可dev和prd指两个入参根据这两个入参分别定义了currentENV的具体值如下图所示。
其中currentENV 节点便是该环境下的变量然后在项目下面创建一些TPL文件该文件就是最终生成的配置文件的模板其中的占位符将根据之前xml中配置的值进行替换。比如Web.config.tpl如下图所示。
运行 ConfigGen后会在当前项目下生成一个 __ConfigTemp 目录,该目录下包含 dev 和 prd 两个目录,如下图所示。
所得到的 dev/Web.config 文件就是 Web.config.tpl 生成的最终配置文件。
从图中可以看出Web.config 已经正确替换了 currentENV 变量。 __ConfigTemp 里面的配置文件目录结构与项目中TPL文件的目录结构是一致的。
利用类似于 ConfigGen 这样的工具,可以在打包阶段很好地解决不同环境的配置问题。但还是会有解决不了的痛点:
打包时配置,需要借助发布的力量使配置生效。而实际场景中,只是修改了配置就要发布代码往往是不被接受的。特别是,如果你还不具备很成熟的持续部署能力,那将会是很头痛的事情。
因此,为了更好地解决配置问题,绝大多数的互联网企业会推荐使用“配置中心”。如果你所在的公司还没有成熟的配置中心,那么我推荐尽快使用开源系统来搭建配置中心。下面,我就分享一下,配置中心是如何工作的。
运行时配置
随着程序功能的日益复杂,程序的配置日益增多,各种功能的开关,参数的配置,服务器的地址,等等不断增加到系统中。而且应用对程序配置的期望值也越来越高,需要配置系统能够:
修改后实时生效;
支持灰度发布;
能分环境、分集群管理配置;
有完善的权限、审核机制。
在这样的大环境下,传统的配置文件、数据库等方式已经越来越无法满足开发人员对配置的管理需求;另外,对于数据库连接串,各个服务之间的 API Key 等机密配置,如果放在代码里也会引起安全的问题。
针对以上的种种需求和问题,我们采用系统化、服务化的思想,引入了配置中心,尝试彻底解决配置问题。
以携程为例,我们自研了 Apollo 配置中心目前该项目已经在GitHub开源用以满足上述需求。
如下图所示即是Apollo的基础模型
用户在配置中心对配置进行修改并发布;
配置中心通知Apollo客户端有配置更新
Apollo 客户端从配置中心拉取最新的配置,更新到本地配置并通知应用重新载入配置。
Apollo系统有几个特别突出的能力能够很好地解决配置的问题
统一管理不同环境、不同集群的配置,通过一个管理平台可以达到管理多个环境或集群配置的效果,省时省力;
配置修改实时生效(热发布),不再需要重启服务,与应用运行生命周期完全解耦;
版本发布管理,方便配置变更后的比对和追溯,配置变更有迹可循;
支持配置的灰度生效,减少配置错误所带来的故障影响;
客户端配置信息监控,这样有利于管理员进行集中式管理;
提供 Java 和 .Net 原生客户端。
使用配置中心的运行时配置应该说是现在绝大多数企业选择的解决方案。而且面对微服务的技术趋势它也有一定的技术优势。比如Apollo就是Spring Cloud推荐使用的开源配置中心解决方案。
Apollo有详尽的文档其功能基本可以覆盖绝大多数业务对配置的需求因此我建议你也可以基于这套开源系统来搭建一套自己的配置中心解决配置难题。
回滚是配置永远的痛
虽然配置中心已经很强大了,但是你也要清晰地认识到,配置中心不是万金油,比如对于版本回滚的情况:
当你使用构建配置和打包配置时,配置是随着代码的一起发布的。这样的话,如果代码回滚了,配置自然而然的也会跟着一起回滚,旧版本的代码和旧版本的配置在绝大多数情况下是兼容的。但如果你用了配置中心,配置就不会随着代码回滚,就可能引发意想不到的问题。
此时,先回滚配置还是先回滚代码就成了一个死循环的问题。最好的办法是保证配置与代码的兼容性,这有点类似于数据库的 schema 变更。 比如,只增加配置不删减配置、不改变配置的数据类型而是新增一个配置等方法。同时,也要做好代码版本与配置版本的对应管理。
那你可能会问,是不是只要做到代码和配置一起回滚就行了呢?其实不是,配置是一个很复杂的问题,像之前所说,绝大多数情况下,回滚配置能够兼容,但也有不行的时候。
比如,修改了数据库连接串的配置,代码回滚后还是要用最新的配置,如果配置也一起回滚了,反而会出现错误。
所以,对于配置回滚这个复杂问题,没有一劳永逸的办法, 只能根据实际情况选择最适合自己的方案。
但是我有一个推荐做法就是每次回滚时将可能发生变化的配置进行diff操作由负责回滚的具体人根据结果去做最后的判断。
总结
在这篇文章中,我和你讨论了三种配置方案:
构建时配置:会增加构建成本;
打包时配置:依赖发布生效;
运行时配置:配置中心,便于管理和维护。
我的建议是:业务相关的配置尽量放在运行时的配置中心服务里。
同时,一定要注意配置的回滚问题。因为,无论是回滚还是不回滚,它没有标准答案,这个复杂问题必须按当时情况作出相对应的处理。
思考题
在日常开发或者维护的系统中,你还遇到过哪些配置需要管理?你又是如何管理这些配置的呢?
欢迎你给我留言。

View File

@ -0,0 +1,124 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
12 极限挑战,如何做到分钟级搭建环境?
在上两篇文章中,我介绍了环境管理中最关键的几个概念,环境的标准化,让环境自己说话以及环境配置的几种方法。
今天,我分享的主题就是,如何从零出发, 实现一套完整的环境创建。并且尝试挑战一下,如何做到分钟级交付。毕竟,天下武功,无坚不摧,唯快不破。
环境构建流水线
当开发人员向你申请一套新环境时,作为测试环境的维护者,你首先需要明确打造环境构建流水线需要关注的三大内容:
虚拟机环境准备,根据环境的应用数、每个应用需要的硬件配置,准备好环境的硬件资源。
应用部署流水线,在标准化的虚拟机上进行应用部署,当出现问题时如何容错。
环境变更在SOA或微服务的架构体系下常常会因为测试的需求将几套环境合并或拆分创建环境时你需要考虑如何高效地完成这些操作。
ol>
接下来,我会针对这三大内容进行展开,带你快速搭建一套环境。
虚拟机环境准备
在部署应用之前我们首先需要创建应用部署的虚拟机环境。目前在携程我们使用OpenStack做物理机和虚拟机的初始化的工作。
当物理机接到机架上以后打开交换机端口等待机器被发现后调用Nova进行物理机基本的硬件配置。
物理机环境准备完毕后从OpenStack获取虚机所需的镜像、网络等信息调用OpenStack API进行虚拟机部署。虚拟机配置的一个关键点是如何对网络进行配置。-
携程的测试环境使用的是大二层的网络架构,配置简单。但如果你对测试环境的网络规划是,需要做每个测试环境的独立的网段切分的话,配置会更复杂。
虚拟机初始化后需要在虚拟机上进行一些基础软件比如JDKTomcat的安装和配置。业界一般采用的方式是通过自动化的配置管理工具来进行操作。-
目前市场上主流的开源配置管理工具有Puppet、Chef、Ansible、SaltStack等。这几款工具都能帮助你很好地处理配置问题当然它们也有自己独特的设计思想实现语言也不同你可以根据自己的技术背景和要管理的环境情况挑选适合自己的工具。
讲到这里,你肯定会有疑问。虚拟机的初始化流程已经这么复杂了,这个过程已经远远不是分钟级了,那我在文章开始部分说的分钟级是如何实现的呢?
我的建议是,采用资源池的方案。你可以根据用户平时使用虚拟机的情况,统计每天虚拟机申请和销毁的具体数量,预先初始化一定量的虚拟机。 这样用户从上层的PaaS平台创建环境时就不用等待初始化了可以直接从资源池中获取虚拟机这部分的时间就被节省下来了。
但是,采用资源池的方式也有一定的复杂性,比如机型多、资源使用率难以预先估计等问题,当然这些问题对云计算来说,可以被轻松搞定。
应用部署流水线
由于不同公司的中间件和运维标准不同,部署流水线的差异也会很大,所以这里我只会从单应用部署标准化、应用部署的并行度,以及流水线的容错机制,这三个关键的角度,分享如何提速环境的搭建。
单应用部署标准化,这是整个环境部署的基础。对一套测试环境而言,每个应用就像是环境上的一个零件,如果单个应用无法自动发布或者发布失败率很高,那么整个环境就更难以构建起来。而如何实现一个好的发布系统,提升单应用部署速度,我会在后面的文章中详细介绍。
应用部署的并行度,为了提高环境的部署速度,需要尽可能得最大化应用部署的并行度。理想的情况下,环境中的所有应用都可以一次性地并行部署。-
然而,做到一次性并行部署并不容易,需要保证:应用都是无状态的,并且可以不依赖别的应用进行启动,或者仅仅依赖于基础环境中的应用就可以启动,且可以随时通过中间件进行调用链的切换。-
在携程,我们力求做到所有应用都可以一次性并行部署,但这条运维标准并不通用。-
当我们需要更复杂的应用部署调度规则时一个原则是将应用部署的次序、并行方式的描述交给开发人员去实现并基于DevOps的理念即调度策略和规则可以通过工具代码化保证同一套环境反复创建的流水线是一致的。
流水线的容错机制。对于环境构建工具,通常的做法是力求做到全面的标准化、代码化。但是因为环境的创建本身是一个非常复杂的工作流,在创建过程总会有一些异常中断整个流程。比如,某个应用启动失败了。-
而对于这些工作流中的异常,我们应该如何处理呢?
第一种方法是,错误中断法。 创建环境过程中,各种资源申请、应用部署出现问题时,我们将工作流快照下来,然后收集所有的异常信息,返回给用户。由用户判断当前的情况,等用户确认问题已经得到解决后,可以触发一次快照重试,继续被中断的流程。
第二种方法是,优先完成法。 创建环境过程中发生错误时,先进行几次重试。如果重试依然发生错误的话,就忽略当前错误,先走完剩余的流程,等所有的流程都走完了,再一次性将错误返回给用户。-
从整体速度上来看,第二种优先完成的处理方式是更优的,而且也会更少地打断用户。只是方式二需要保证的关键原则是:所有的部署脚本的操作都是幂等的,即两次操作达成的效果是一致的,并不会带来更多的问题。
环境变更
实现了应用部署流水线后创建环境的主流程即虚机准备和应用部署已经完成环境已经可以工作了。但还是不能忽略了后续环境变更的需求和工作。一般情况下研发人员变更环境主要有以下4种场景。
已经有一套新环境,当有新项目时,开发人员会挑选部分应用,组成一个独立的子环境。这里的重点是,要保证子环境和完整环境的调用是互相隔离的。
当存在多个子环境时,可能在某个时间点需要做多个项目的集成,这时开发人员需要合并多个环境。
和合并的情况相反,有些情况下,开发人员需要将一个子环境中的应用切分开来,分为两个或者多个环境分别进行隔离测试。
已经存在一个子环境,当多个并行项目时,开发人员会克隆一套完整的子环境做测试。
对于这4个场景我们需要关注的是在多并行环境的情况下应用拓扑图包括用户访问应用的入口、应用之间调用链的管理以及应用对数据库之类的基础设施的访问。
用户访问应用的入口管理。 以最常用的访问入口域名为例我推荐的做法是根据约定大于配置的原则当环境管理平台识别到这是一个Web应用时通过应用在生产环境中的域名、路由环境名等参数自动生产一个域名并在域名服务上注册。-
这里需要注意的是域名的维护尽量是在SLB负载均衡Server Load Balancer类似的软负载中间件上实现而不要在DNS上实现。因为域名变更时通过泛域名的指向SLB二次解析可以做到域名访问的实时切换。而如果配置在DNS上域名的变更就无法做到瞬时生效了。
应用之间调用链的管理。 对于service的调用关系我在《“配置”是把双刃剑带你了解各种配置方法》这篇文章中提到了携程开源的配置中心Apollo的实现策略所有的服务调用的路由都是通过环境描述文件server.spec自发现的你只要保证文件的环境号、IDC等属性是正确的整个调用链就不会被混淆。-
同时,服务调用中间件需要可以做到自动判断,被隔离的环境内是否有需要被调用的服务,并在当前环境以及基础环境中间进行自动选择,以保证服务被正确调用到。
对数据库的访问。 一是数据库连接串的维护问题与SOA调用链即服务之间的调用关系的维护类似完全可以借鉴二是数据库的快速创建策略。-
对于数据库中的表结构和数据,我们采取的方式是根据生产中实际的数据库结构,产生一个基准库,由用户自己来维护这个基准库的数据,保证数据的有效性。并在环境创建时,提供数据库脚本变更的接口,根据之前的基准库创建一个新的实例,由此保证环境中的数据符合预期。
对于环境的创建和拆分,最主要的问题就是如何复制和重新配置环境中的各个零件。环境创建,就是不断提高虚拟机准备和应用部署两个流水线的速度和稳定性;环境拆分,则需要关注以上所说的三个最重要的配置内容。
而环境的合并需要注意的问题是,合并后的环境冲突。 比如,两套环境中都存在同一个服务应用,而两者的版本是不一致的;又或者,两个环境各自配置了一套数据库。此时该如何处理呢。
因为环境的描述已经被代码化了,所以我们解决这些问题的方式类似于解决代码合并的冲突问题。在环境合并前,先进行一次环境的冲突检测,如果环境中存在不可自动解决的冲突,就将这些冲突罗列出来,由用户选择合适的服务版本。
如何高效、自动化地实现环境变更的关键点还是在于,我在前面几篇文章中提到的如何管理和实现应用配置和环境配置,以及如何配合环境管理在速度上的需求。
总结
对于如何快速搭建一套环境,我从虚拟机环境准备、应用部署流水线和环境变更,这三个方面给你总结了一些常见问题和原则:
可以使用虚拟机资源池,提升获取机器资源的速度;
合理打造并行的应用部署流水线,是进一步提升环境创建速度的方法;
利用配置等方式快速达到环境变更需求,可以再次有效地提升整个环境部署的效率。
思考题
你所在的公司,新环境应用部署的流水线是怎样的?如果要进一步提速的话,还有哪些优化空间呢?
欢迎你给我留言。

View File

@ -0,0 +1,178 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
13 容器技术真的是环境管理的救星吗?
在上一篇文章中,我分享了基于虚拟机打造自动化流水线中的一些常见问题和原则。随着计算机技术的发展,交付方式也在不断地演进和变更。而基于虚拟机的交付方式,正在被基于容器的交付方式所替代。
今天,如果你在一个较大的科技公司,你必定会遇到的如下的场景:
多个技术栈;
多个不同类型的应用;
不同的开发环境和运行环境。
因此,你所面对的交付场景也会变得越来越复杂,带来的挑战也会越来越大。
此外,敏捷研发的流行,使得低成本、高效率的解决研发问题的方式成为主流,因此复杂的交付的场景,显然就会成为拖油瓶。
加之,传统交付方法,已经很难满足这样快速迭代的交付需求,服务交付方式、快速部署、环境隔离、环境一致性等诸多问题亟待解决。
因此,在过去很长一段时间内,持续交付本身也陷入一个发展瓶颈。各规模的团队、企业都承认持续交付是一个好方案,但却都不敢试。其实,主要原因还是,持续交付在技术上没有得到突破性的发展。
但是容器的出现和兴起为微服务、CI/CD、DevOps 带来了新的可能性,使得持续交付又有了向前发展的动力,同时也带来了新的挑战。
那么,容器的出现到底为持续交付带来了哪些契机和挑战呢?我在这篇文章中,将和你讨论:为什么说容器是持续交付最重要的利器之一,是环境管理的将来式,这个问题,助你借助容器构建自己的持续交付体系。
什么是容器
在传统模式下的开发到部署流程是这样的:
在本地电脑上安装开发应用所需要的库文件、扩展包、开发工具和开发框架,完成开发工作;
本地开发完成后,将开发好的应用部署到测试环境进行测试;
一切就绪后,再把应用部署到生产环境。
但问题是,你该如何保证开发、测试和生产这三套环境,甚至更多套环境是完全一致的呢?再有就是,环境的变更问题,虽说“百分之九十九的故障是由变更导致的”是一句废话,但也是一句实话,你又该如何确保每套环境的变更是一致的呢?
而容器的出现,似乎解决了这些问题。
正如 Docker 官网解释的:
容器镜像是软件的一个轻量的、独立的、可执行的包,包括了执行它所需要的所有内容:代码、运行环境、系统工具、系统库、设置。
这代表着,一旦一个应用被封装成容器,那么它所依赖的下层环境就不再重要了。
那么,容器和虚拟机到底有什么区别呢?
引自https://www.docker.com/what-container
容器是一个在App层的抽象整合了运行的应用软件代码以及它的依赖、环境。许多个这样的容器可以运行在同一台宿主机上并与其他容器共享这台宿主机的系统内核。而且每一个容器实例都运行在自己独立的进程中与其他实例隔离。
虚拟机是一种将一台服务器转变成多台服务器的物理硬件设备的抽象。Hypervisor软件是虚拟机的主要部分它使得一台物理设备上可以运行多个虚拟机。
每个虚拟机都是一个完整操作系统的拷贝,再搭建一层 runtime最后供应用程序运行。通常一个虚拟机大小都要超过10 GB。
容器和虚拟机的主要差异,包括三个方面:
首先,多个容器可以共享同一个宿主机的内核,所以容器的体积要比虚拟机小很多,这就使得容器在分发和存储上比较有优势;
其次,启动容器不需要启动整个操作系统,所以容器部署和启动速度更快、开销更小,也更容易迁移,这使得容器拥有更强的恢复能力;
最后,容器连带代码和环境一起部署的方式,保证了它所包含的程序的运行依赖不会被变更,这就使得容器有效解决了不同环境不同结果的问题。
了解了容器的基本概念,我接下来跟你说说,容器可以对持续交付产生什么影响。
重新定义交付标准
没有容器之前,交付标准包括软件环境(也就所谓的机器)和软件代码两部分。交付系统更关注的是软件代码,环境一旦产生后,我们就不再关心或者很难再干预用户后期是如何对其做变更的了。
也就是说,环境的变更没有版本,没有记录,甚至当事人也会忘记当时变更了什么, 不言而喻,这会带来很多未知的安全隐患。
而,容器技术统一了软件环境和软件代码,交付产物中既包括了软件环境,又包括了软件代码。也就是说,容器帮我们重新定义了交付标准。
那么,容器技术到底是如何做到的呢?被重新定义后的交付,又有哪些特点呢?
第一,交付结果一致
容器镜像可以把软件的运行环境以及代码打包在一起,因此可以基于同一个镜像,在不同的地方生成一模一样的运行环境,也就是说单个镜像的交付结果不可变。
当然单个容器只能提供一个服务而实际场景下应用都是跑在SOA或微服务的框架下的。所以还需要利用如 Mesos 或 Kubernetes 这样的编排系统,将多个容器组织起来,并固化编排过程。
基于这两个特性,一旦形成了固定的容器镜像和对应的编排(也成为应用模板),那在不同的环境下,一定可以重复部署,且部署结果保持一致。
第二,交付自动化
容器镜像及容器编排技术很好地解决了CI和CD问题
CI方面与传统方式的不同只在于原先交付的是安装包或软件包而容器交付的则是镜像
CD方面与传统方式相比则有了长足的进步。-
对传统方式而言,部署和安装方式与软件类型、开发方式有直接关系,存在多种多样的可能。-
而容器技术则没有这样的问题唯一的方式就是拉起容器镜像。这就大大简化了部署的复杂度而且在编排系统的支持下完成CD越来越容易了。
第三,交付个性化
传统的交付模式,往往因为环境的初始化问题,只能完成有限种类的交付。运维部门很难为所有的应用做出统一的环境模板,比如需要哪些软件依赖、需要哪些系统配置、部署的步骤是怎样的等等,要统一这些模板,就需要协调多个部门共同完成,难度可想而知。
对于一些受众比较少的程序语言,或者一个仅仅想部署一套开源软件的需求是很难满足的,大多数情况下,需要用户自己去申请虚拟机,然后按照官方提供的文档一步一步安装环境。这样操作,非常麻烦,更别提后续的更新了。
但是,有了容器之后,我们可以使用统一的接口完成任何应用的部署,几乎可以很好地满足所有的个性化需求。
第四,交付版本控制
对于容器来说遵循的是不可变基础设施Immutable Infrastructure的理念也就是说任何变化包括代码、环境、配置的变更都需要重新制作镜像产生一个新的版本。这与版本往往只和代码变更有关的传统方式有所不同。
那么,这样的变化到底是好是坏呢?
变还是不变,这是个问题
不可变基础设施Immutable Infrastructure是 Chad Fowler 在2013年提出的一个很有前瞻性的构想
在这种模式中,任何基础设施的实例(包括服务器、容器等各种软硬件)一旦创建之后便成为一种只读状态,不可对其进行任何更改。如果需要修改或升级某些实例,唯一的方式就是创建一批新的实例来替换它。
这种思想与不可变对象的概念完全相同。
而容器相比于虚拟机体积小和启动快的优势,正好符合了不可变基础设施这一模式的核心思想。
不可变基础设施模式的好处显而易见,主要包括以下三个方面:
很多与runtime相关的配置工作都可以被简化这让持续集成与持续部署过程变得更流畅。
它也更易于应对部署环境间的差异及版本,进行更有效、全面的管理。
对回滚来说,更是得到了充分的保证,只要原先版本的镜像存在,它就一定能被恢复。
虽然不可变基础设施模式能够带来非常多的好处,但是其实现的难度也很高,你需要一套完全不同的版本管理系统,纳入所有的变更,重新定义版本、变更和发布。如何做到这些,我会在后续的文章中为你详细介绍。
但是,这种模式在我看来也是略微违反人性的(人们往往是想怎么简单怎么来)。试想如果你仅有一台机器,只是想升级一下 cURL 的版本,你觉得是直接在容器里更新方便,还是更改 Dockerfile 重打镜像走完一整套发布流程更方便呢?
容器不是银弹
正如上面所说,不可变基础设施模式对运维人员来说绝对是福音,为企业实现持续交付保驾护航。但是,对普通用户来说,这种模式有时候却是一种折磨,不可能有完美的标准化容纳所有的个性化,我们必须为个性化需求做准备。
目前,很多业务开发人员的观念还停留在使用虚拟机的阶段,从虚拟机迁移到容器时,我们也是拼了命地把容器的使用体验向虚拟机靠近,尽量让用户感觉就是在用虚拟机。
初衷是好的,但是这种做法却不能让用户真正认识并理解容器。
在迁移前期,我们经常会遇到这样的案例:由于个别应用对环境的个性化需求,用户需要登录虚拟机安装一些软件,或者更新一些配置。迁到容器后,他们依然这么做,但是结果让他们失望,因为每次应用部署后,之前的环境变更就都消失了。这无疑让他们非常沮丧,就好比写了几个小时的代码忘记了保存。
我们虔诚地遵循了不可变基础设施模式,但是又没有很好地告知用户这一原则。因此,我们不得不提供各种各样的方式让用户完成 “不可变中的可变” 与 “标准化中的个性化”,甚至我们必须在不同的环境使用不同的镜像。
而这,与我们认为的容器交付的理想状态是有差距的。虽然如此,但如何达成这样的目的,我也会在之后的文章中为你具体介绍。
总结
在这篇文章中,我介绍了容器如何代替虚拟机帮助我们应对持续交付的新挑战,但也阐述了使用容器技术实施持续交付的一些不足。
首先,容器是一种轻量级、可移植、自包含的软件打包技术,使应用程序几乎可以在任何地方以相同的方式运行。
然后我分别从交付结果一致、交付自动化、交付个性化和交付版本控制这4个方面重新定义了基于容器的交付标准。
最后,我又从变和不变两个方向,阐述了容器能解决一些已有的问题,但它并不是银弹,它同样会带来问题,而这些问题,则需要改造和重新设计既有的持续交付模式来解决。
思考题
你所在的公司是否已经容器化了?如果已经容器化了,是如何平衡应用标准化与个性化的?对于有状态应用,又该如何使用容器进行交付呢?
欢迎你给我留言。

View File

@ -0,0 +1,137 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
14 如何做到构建的提速,再提速!
在前面几篇文章中,我分享了很多关于构建的观点,然而天下武功唯为快不破,构建的速度对于用户持续交付的体验来说至关重要。
在实施持续交付的过程中,我们经常会遇到这样的情况:只是改了几行代码,却需要花费几分钟甚至几十分钟来构建。而这种情况,对于追求高效率的你我来说,是难以容忍的。
那么,今天我就带你一起看看,还有哪些手段可以帮助构建提速。
升级硬件资源
构建是一个非常耗时的操作,常常会成为影响持续交付速度的瓶颈。原因是,构建过程,会直接消耗计算资源,而且很多构建对硬件的要求也非常高。那么,升级硬件资源就是构建过程提速的最为直接有效的方式。
需要注意的是,这里的硬件资源包括 CPU、内存、磁盘、网络等等具体升级哪一部分需要具体情况具体分析。
比如,你要构建一个 C 语言程序,那么 CPU 就是关键点。你可以增加 CPU 的个数或者提升 CPU 主频以实现更快的编译速度。
再比如你要用Maven构建一个 Java 应用,除了 CPU 之外Maven 还会从中央仓库下载依赖写在本地磁盘。这时,网络和磁盘的 I/O 就可能成为瓶颈,你可以通过增加网络带宽提升网络吞吐,使用 SSD 代替机械硬盘增加磁盘 I/O ,从而到达提升整个构建过程速度的目的。
总之,当你使用成熟的构建工具进行构建时,如果无法通过一些软件技术手段提升软件本身的构建速度,那么根据构建特点,有针对性地升级硬件资源,是最简单粗暴的方法。
搭建私有仓库
构建很多时候是需要下载外部依赖的,而网络 I/O 通常会成为整个构建的瓶颈。尤其在当前网络环境下,从外网下载一些代码或者依赖的速度往往是瓶颈,所以在内网搭建各种各样的私有仓库就非常重要了。
目前,我们需要的依赖基本上都可以搭建一套私有仓库,比如:
使用 createrepo 搭建CentOS 的 yum 仓库;
使用 Nexus 搭建 Java 的 Maven 仓库;
使用cnpm搭建 NodeJS 的 npm 仓库;
使用 pypiserver 搭建 Python 的 pip 仓库;
使用 GitLab 搭建代码仓库;
使用 Harbor 搭建 Docker 镜像仓库
……
除了提升构建时的下载速度外,更重要的是,你还可以用这些工具存储辛勤工作的成果,保护知识产权。
总之,搭建私有仓库一定物超所值。当然,维护和管理这一大批工具需要投入不少人力和经济成本,在公司/团队没有成一定规模的前提下,会有一定的负担。
所以,如果你的团队暂时没有条件自己搭建私有仓库的话,可以使用国内已有的一些私有仓库,来提升下载速度。当然,在选择私有仓库时,你要尽量挑选那些被广泛使用的仓库,避免安全隐患。
使用本地缓存
虽然搭建私有仓库可以解决代码或者依赖下载的问题,但是私有仓库不能滥用,还是要结合构建机器本地的磁盘缓存才能达到利益最大化。
如果每次依赖拉取都走一次网络下载,一方面网络下载的速度通常会比本地磁盘慢很多,另一方面在构建量很大时,并发请求会导致私有仓库出现网卡打爆或者出现莫名其妙的异常,从而导致所有的构建过程变得不稳定,甚至影响其他工具的使用。
所以,妥善地用好本地缓存十分重要。这里说的“妥善”,主要包括以下两个方面:
对于变化的内容,增量下载;
对于不变的内容,不重复下载。
目前,很多工具都已经支持这两点了。
对于第一点,项目的源码是经常变化的内容,下载源码时,如果你使用 Git 进行增量下载那么就不需要在每次构建时都重复拉取所有的代码。Jenkins 的 Git 插件,也默认使用这种方式。
对于第二点Maven 每次下载依赖后都会在本地磁盘创建一份依赖的拷贝,在构建下载之前会先检查本地是否已经有依赖的拷贝,从而达到复用效果。并且,这个依赖的拷贝是公共的,也就是说每个项目都可以使用这个缓存,极大地提升了构建效率。
如果你使用 Docker那么你可以在宿主机上 mount 同一个依赖拷贝目录到多个 Slave 容器上,这样多个容器就可以共享同一个依赖拷贝目录。你可以最大程度地利用这一优势,但要注意不要让宿主机的磁盘 I/O 达到瓶颈。
规范构建流程
程序员的祖训说Less is MoreSimple is Better这与大道至简的含义不谋而合。
程序的追求是简约而不简单,但随着业务越来越复杂,构建过程中各种各样的需求也随之出现,虽然工具已经封装了很多实用的功能,但是很多情况下,你都需要加入一些自定义的个性化功能,才能满足业务需求。
在携程Java 构建过程中就有大量的额外逻辑,比如 Enforcer 检查、框架依赖检查、Sonar 检查、单元测试、集成测试等等,可以说是无所不用其极地去保证构建产物的质量。
因此,当前复杂的构建过程再也回不到仅仅一条 mvn 或者 gcc 命令就能搞定的年代。而这一套复杂的流程下来必定会花费不少时间,让程序员们有更多喝茶和去厕所的时间。
追求高效的同时,又不舍弃这些功能,是一个现实而又矛盾的命题,我们能否做到二者兼顾呢?答案,当然肯定的。
以Java 构建为例Enforcer 检查、框架依赖检查、Sonar 检查、单元测试、集成测试这些步骤,并没有放在同一个构建过程中同步执行,而是通过异步的方式穿插在 CI/CD 当中,甚至可以在构建过程之外执行。
比如, Sonar 扫描在代码集成阶段执行,用户在 GitLab 上发起一个合并请求Merge Request这时只对变更的代码进行对比 Sonar 扫描,只要变更代码检查没有问题,那么就可以保证合并之后主干分支的代码也是没问题的。
所以,用户发布时就无需再重复检查了,只要发布后更新远端 Sonar Qube 的数据即可,同时,这个过程完全不会影响用户的构建体验。
通过以上一些规范构建流程的做法,可以进一步提高构建速度。
善用构建工具
正如我前面所说的,目前很多构建工具已经具备了非常多的功能来帮助我们更好地进行构建,因此,充分理解并用好这些功能就成了我们必须要掌握的武林绝学。
以 Maven 为例我来带你看看有哪些提速方式当然其他的构建工具如Gradle等也都可以采用类似的方法
设置合适的堆内存参数。 过小的堆内存参数会使Maven增加 GC 次数,影响构建性能;过大的堆内存参数,不但浪费资源,而且同样会影响性能。因此,构建时,你需要反复试验,得到最优的参数。
使用 -Dmaven.test.skip = true 跳过单元测试。 Maven默认的编译命令是 mvn package这个命令会自动执行单元测试但是通常我们的构建机器无法为用户提供一套完整的单元测试环境特别是在分布式架构下。因此如果单元测试需要服务依赖则可以去掉它。
在发布阶段,不使用 Snapshot 版本的依赖。 这就可以在 Maven 构建时不填写 -U 参数来强制更新依赖的检查,省下因为每次检查版本是否更新而浪费的时间。
使用 -T 2C 命令进行并行构建。 在该模式下 Maven 能够智能分析项目模块之间的依赖关系,然后并行地构建那些相互间没有依赖关系的模块,从而充分利用计算机的多核 CPU 资源。
局部构建。 如果你的项目里面有多个没有依赖关系的模块,那么你可以使用 -pl 命令指定某一个或几个模块去编译,而无需构建整个项目,加快构建速度。
正确使用clean参数。 通常情况下,我们建议用户在构建时使用 clean 参数保证构建的正确性。clean 可以删除旧的构建产物,但其实我们大多数时间可能不需要这个参数,只有在某些情况下(比如,更改了类名,或者删除了一些类)才必须使用这个参数,所以,如果某次变更只是修改了一些方法,或者增加了一些类,那么就不需要强制执行 clean 了。
总之,如果你能熟练运用各种构建工具,那么你的效率一定会比其他人高,你的构建速度一定比其他人快。
总结
我介绍了五种常见的构建提速的方式,分别是:
升级硬件资源,最直接和粗暴的提速方式;
搭建私有仓库,避免从外网下载依赖;
使用本地缓存,减少每次构建时依赖下载的消耗;
规范构建流程,通过异步方式解决旁支流程的执行;
善用构建工具,根据实际情况合理发挥的工具特性。
然而,每个公司持续交付的构建流程不太一样,面临的问题与挑战也都不太一样,所以在优化前,一定要先了解问题原因,再对症下药。
思考题
你所在公司的构建流程是什么样的?是否也面临性能的问题?你又是是如何解决这些问题的?
欢迎你给我留言。

View File

@ -0,0 +1,155 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
15 构建检测,无规矩不成方圆
在这个专栏的第5篇文章《手把手教你依赖管理》中我介绍了构建 Java 项目的一些最佳实践,同时也给你抛出了一个问题:如果用户偷懒不遵循这些规范该怎么办?
所谓没有规矩不成方圆,构建是持续交付过程中非常重要的一步,而好的构建检测则可以直接提升交付产物的质量,使持续交付的流水线又快又稳。所以,也就有了 Maven 构建中的大杀器Maven Enforcer 插件。
什么是 Maven Enforcer 插件?
Maven Enforcer 插件提供了非常多的通用检查规则,比如检查 JDK 版本、检查 Maven 版本、检查依赖版本,等等。下图所示就是一个简单的使用示例。
上述的配置会在构建时(准确的说是在 validate 时)完成三项检查:
requireMavenVersion检查 Maven 版本必须大于 3.3.9
requireJavaVersion检查 JDK 版本必须大于等于 1.9
requireOS检查 OS 必须是 Windows 系统。
如果你使用 Java 1.8 Maven 3.3.3 在 Linux 上构建, 便会出现如下的错误:
Rule 0: org.apache.maven.plugins.enforcer.RequireMavenVersion failed with message: Detected Maven Version: 3.3.3 is not in the allowed range 3.3.9.
Rule 1: org.apache.maven.plugins.enforcer.RequireJavaVersion failed with message: Detected JDK Version: 1.8.0-77 is not in the allowed range 1.9.
Rule 2: org.apache.maven.plugins.enforcer.RequireOS failed with message: OS Arch: amd64 Family: unix Name: linux Version: 3.16.0-43-generic is not allowed by Family=windows
从而导致构建失败。
那么是否有办法在所有应用的构建前都执行Enforcer的检查呢。
我在专栏的第5篇文章《手把手教你依赖管理》中也已经介绍了在携程内部一般 Java 应用的继承树关系,每个项目都必须继承来自技术委员会或公司层面提供的 super-pom。携程在 super-pom 之上又定义了一层 super-rule 的 pom这个pom 中定义了一系列的 Enforcer 规则。 这样,只要是集成了 super-pom 的项目,就会在构建时自动运行我们所定义的检查。
也许你会问了, 如果用户不继承 super-pom 是不是就可以跳过这些规则检查了?是的, 继承 super-pom 是规则检查的前提。
但是,我们不会给用户这样的机会, 因为上线走的都是统一的构建系统。
构建系统在构建之前会先检查项目的继承树,继承树中必须包含 super-pom 否则构建失败。并且,构建系统虽然允许用户自定义 Maven 的构建命令,但是会将 Enforcer 相关的参数过滤掉用户填写的任何关于Enforcer的参数都被视为无效。Enforcer会被强制按照统一标准执行这样就保证了所有应用编译时都要经过检查。
因为携程的构建系统只提供几个版本的 Java 和 Maven并且操作系统是统一的 Linux CentOS版本所以就不需要使用之前例子中提到的三个检查一定程度的缩小标准化范围也是有效的质量保证手段。
了解了Maven Enforcer插件我再从Maven Enforcer内置的规则、自定义的Enforcer检查规则以及构建依赖检查服务这三个方面带你一起看看构建监测的“豪华套餐”增强你对交付产物的信心。
丰富的内置的 Enforcer 规则
Maven Enforcer 提供了非常丰富的内置检查规则在这里我给你重点介绍一下bannedDependencies 规则、dependencyConvergence 规则和banDuplicateClasses 规则。
第一bannedDependencies 规则
该规则表示禁止使用某些依赖,或者某些依赖的版本,使用示例:
该代码检查的逻辑是,只允许使用版本大于等于 1.8.0 的 org.slf4j:slf4j-api 依赖,否则将会出现如下错误:
bannedDependencies 规则的常见应用场景包括:
当我们知道某个 jar 包的某个版本有严重漏洞时,可以用这种方法禁止用户使用,从而避免被攻击;
某个公共组件的依赖必须要大于某个版本时,你也可以使用这个方法禁止用户直接引用不兼容的依赖版本,避免公共组件运行错误。
第二dependencyConvergence 规则
在《手把手教你依赖管理》一文中我介绍了Maven 的依赖仲裁的两个原则:最短路径优先原则和第一声明优先原则。
但是Maven 基于这两个原则处理依赖的方式过于简单粗暴。毕竟在一个成熟的系统中,依赖的关系错综复杂,用户很难一个一个地排查所有依赖的关系和冲突,稍不留神便会掉进依赖的陷阱里,这时 dependencyConvergence 就可以粉墨登场了。
dependencyConvergence规则的作用是 当项目中的 A 和 B 分别引用了不同版本的C时 Enforce 检查失败。 下面这个实例,可以帮你理解这个规则的作用。
org.slf4j:slf4j-jdk14:1.6.1依赖了 org.slf4j:slf4j-api:1.6.1 而 org.slf4j:slf4j-nop:1.6.0依赖了 org.slf4j:slf4j-api:1.6.0,当我们在构建项目时, 便会有如下错误:
这时就需要开发人员介入了,使用 dependecy 的 exclusions 元素排除掉一个不合适的版本。 虽然这会给编程带来一些麻烦, 但是非常必要。因为,我始终认为你应该清楚地知道系统依赖了哪些组件, 尤其是在某些组价发生冲突时,这就更加重要了。
第三banDuplicateClasses 规则
该规则是 Extra Enforcer Rules 提供的主要目的是检查多个jar 包中是否存在同样命名的 class如果存在编译便会报错。 同名 class 若内容不一致,可能会导致 java.lang.NoSuchFieldErrorjava.lang.NoSuchMethodException 等异常,而且排查起来非常困难,因为人的直觉思维很难定位到重复类这个非显性错误上,例如下面这种情况:
org.jboss.netty包与io.netty包中都包含一个名为NettyBundleActivator的类另外还有2个重复类spring/NettyLoggerConfigurator 和 microcontainer/NettyLoggerConfigurator。
当激活了 banDuplicateClasses 规则之后Enforcer检查便会有如下的报错
通常情况下,用户需要排除一个多余的 jar 包来解决这个问题,但有些情况下两个 jar 包都不能被排除,如果只是个别类名冲突了,那么可以通过 ignoreClasses 去忽略冲突的类,类名可以使用通配符(*),如: org.jboss.netty.container.*。
但是用户不能随意更改这个配置因为它必须得到一定的授权否则随意忽略会产生其他不确定的问题。因此我们将这个插件做了一些改动通过API来获取 ignoreClasses 的内容。当用户有类似的需求时,可以提交 ignoreClasses ,但必须申请,经过 Java 专家审批之后才可忽略掉。
自定义的 Enforcer 检查规则
除了上述的官方规则,实际上携程还做了若干个扩展的规则,如:
CheckVersion用于检查模块的版本号必须是数字三段式或者带有 SNAPSHOT 的数字三段式;
CheckGroupId用于检查 GroupId 是否符合规范我们为每个部门都分别指定了GroupId
CheckDistributionManagementRepository用于检查项目的 distributionManagement 中的 repository 节点,并为每个部门都指定了他们在 Nexus 上面的 repositroy
CheckSubModuleSaveVersion用于检查子模块版本号是否与父模块版本号一致。
以上,便是携程基于 Maven Enforcer 在构建检查上的一些实践,你可以借鉴使用。
但是,有时候 Maven Enforcer 也无法满足我们所有的需求,比如,它无法完成非 Java 项目的检查。因此,我们还有一个通用的依赖检查服务。
构建依赖检查服务
其他语言, 比如 C#NodeJS 等,没有 Maven Enforcer 这样成熟的工具来做构建时的依赖检查。对于这类语言我们的做法是:构建后,收集该项目所有的依赖及其版本号,将这些数据发送给依赖检查服务 TalosTalos 根据内置的规则进行依赖检查。Talos是一套携程自研的独立的组件依赖检查系统其中包含的检查逻辑完全可以自由定义。
而且Talos依赖检查的逻辑更新非常灵活可以直接在平台内使用 Java 代码在线编写检查逻辑,提交后便可实时生效。
以下是一段 .NET 项目检查逻辑的示例代码:
该逻辑的含义是: 当项目的依赖存在 foo.dll 和 bar.dll 时bar.dll 的版本号必须大于 1.0.0.0。看, 是不是非常方便快捷通用!
这样一套组合拳下来,构建检测以及项目依赖的问题已不再那么让人望而生畏了。因此,工欲善其事必先利其器, 好的工具可以解放大量的生产力,最重要的是构建检测后的交付让你我更有信心了。有条不紊的流程与规范,就像一列高速列车下的枕木,时刻保证着整个系统稳定而可靠地推进。
总结与实践
我围绕着构建检测,和你一起学习并介绍了:
Maven Enforcer 插件可以帮我们更好地完成编译检测;
可以使用内置的 Maven Enforcer 规则,覆盖常规检测;
可以使用自定义 Maven Enforcer 检查规则的方式,增加版本号规则等的检查;
Maven Enforcer 之外,你还可以自己丰富一些例如依赖版本检测这样的服务,以提高检测效果。
Maven Enforcer 提供了非常丰富的内置检查规则,感兴趣的话,你可以通过 https://maven.apache.org/enforcer/enforcer-rules/index.html 以及 http://www.mojohaus.org/extra-enforcer-rules/ 逐个尝试这些规则,并说说哪些规则是你工作总最最需要的。
欢迎你给我留言。

View File

@ -0,0 +1,167 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
16 构建资源的弹性伸缩
在前面的文章中,我已经介绍了构建在整个持续交付过程中扮演的重要角色,并且详细讨论了依赖管理和构建检测等方面的内容。在这篇文章中,我将带你搭建一套高可用、高性能的构建系统。
持续集成工具
目前市面上已经有很多持续集成工具了,它们已经替我们解决了很多实际问题,所以我们也就没有必要去再重复造轮子了。这些持续集成工具,最流行的应属 Travis CI、Circle CI、Jenkins CI这三种。
第一Travis CI
Travis CI 是基于GitHub的CI托管解决方案之一由于和GitHub的紧密集成在开源项目中被广泛使用。
Travis CI 的构建,主要通过 .travis.yml 文件进行配置。这个 .travis.yml 文件描述了构建时所要执行的所有步骤。
另外Travis CI 可以支持市面上绝大多数的编程语言。但是因为Travis只支持GitHub而不支持其他代码托管服务所以官方建议在使用前需要先具备以下几个条件
能登录到GitHub
对托管在GitHub上的项目有管理员权限
项目中有可运行的代码;
有可以工作的编译和测试脚本。
Travis CI的收费策略是对公共仓库免费对私有仓库收费。
第二CircleCI
CircleCI 是一款很有特色也是比较流行的云端持续集成管理工具。CircleCI 目前也仅支持 GitHub 和Bitbucket管理。
CircleCI 与其他持续集成工具的区别在于它们提供服务的方式不同。CircleCI 需要付费的资源主要是它的容器。
你可以免费使用一个容器,但是当你发现资源不够需要使用更多的容器时,你必须为此付费。你也可以选择你所需要的并行化级别来加速你的持续集成,它有 5 个并行化级别1x、4x、8x,、12x和16x可供选择分别代表利用几个容器同时进行一个项目的构建如何选择就取决于你了。
第三Jenkins CI
Jenkins 是一款自包含、开源的用于自动化驱动编译、测试、交付或部署等一系列任务的自动化服务它的核心是Jenkins Pipline 。Jenkins Pipline可以实现对持续交付插件的灵活组合以流水线的方式接入到Jenkins服务。
Jenkins 还提供了一整套可扩展的工具集程序员可以通过代码的方式定义任何流水线的行为。另外经过多年的发展Jenkins已经包含了很多实用的第三方插件覆盖了持续交付的整个生命周期。
目前,绝大多数组织都选择了 Jenkins 作为内部的持续集成工具,主要原因是:
代码开源, 插件完善,系统稳定;
社区活跃,成功实践与网上资源比较丰富;
Jenkins Pipeline 非常灵活好用。
大致了解了集成工具之后携程和绝大部分企业一样选择了最开放、最易于扩展的Jenkins作为集成构建的引擎而且分别从实现横向的Master高可用和纵向的Slave弹性伸缩两方面使构建系统更为强大和高效。
Jenkins Master 高可用架构的
目前普遍的 Jenkins 搭建方案是:一个 Jenkins Master 搭配多个 Jenkins Slave。大多数情况下这种方案可以很好地工作并且随着构建任务的增加无脑扩容 Jenkins Slave 也不是一件难事。另外,不管是 Linux Slave 还是 Windows Slave Jenkins 都可以很好地支持,并且非常稳定。
但是,随着业务的增长,微服务架构的流行,持续交付理念的深入人心,构建会变得越来越多,越来越频繁,单个 Jenkins Master 终究会成为系统中的瓶颈。
遗憾的是,开源的 Jenkins 并没有给我们提供一个很好的 Master 高可用方案CloudBees 公司倒是提供了一个高可用的插件,但是价格不菲。
所以,为了鱼与熊掌兼得,最终携程决定自己干。下面是我们构建系统的基本架构:
图1 携程Jenkins Master 高可用架构
携程的解决思路是在 Jenkins 上面再封装两层: Build Service 暴露构建的 HTTP 接口,接收请求后将任务丢给异步队列 Build WorkerBuild Worker 根据不同的策略将任务分发给符合条件的 Jenkins Master。
这里的分发条件,可以是编译任务的平台或语言,比如可以将基于 Windows 和 Linux 的任务分别放在不同的 Jenkins Master 上,也可以将 Java 构建和 NodeJS 构建任务放在不同的 Jenkins Master 上。
除此之外,携程的这个构建系统还可以满足的一种需求是:一些比较复杂且重要的业务线,有时也会提出独立构建资源的需求,以达到独占编译资源的目的。
总而言之,构建任务分发的策略可以是非常灵活的:构建 Worker 和 Jenkins Master 之间有“心跳监测”,可以时刻检查 Jenkins Master 是否还健康,如果有问题就将任务分发到其他等价的 Jenkins Master 上,并给相关人员发送告警通知。
这种拆解 Jenkins Master 主要有以下几个好处:
每个 Job 都可运行在至少两个 Jenkins Master 之上, 保证高可用;
根据不同的策略将 Job 做 Sharding 避免积压在同一个 Master 上;
Jenkins Master 按需配置,按需安装不同的插件,便于管理。
利用这套方案携程就可以做到Master层面的伸缩了。这套方案的实现成本并不是很大简单易懂小团队也完全可以掌握和实施。
Jenkins Slave 弹性伸缩方案
解决了Jenkins Master的高可用问题接着就要去思考如何才能解决Slave资源管理和利用率的问题了。因为你会发现一个组织的集成和构建往往是周期性的高峰和低谷都比较明显而且随着组织扩大幅度也有所扩大。所以如果按照高峰的要求来配备Slave实例数那么在低谷时就很浪费资源了。反之又会影响速度造成排队。
因此我们需要整个Slave集群具有更优的弹性既要好管理又要好扩展。在携程我们尝试过多种虚拟机方案比如全Windows类型、金映象方案等等。最后根据容器的特性选择了容器作为解决方案。期间也经历了Mesos 到 K8s的两套方案。
第一,最初的虚拟机
在最初构建种类不多并且场景不复杂的情况下,我们的 Slave 全部使用 Windows 虚拟机。
我们把所需的构建软件像大杂烩一样一股脑地安装在虚拟机上,比如,编译 .NET 所需的 MSBuild ,编译 Java 所需的 Maven。我们维护着一份啰嗦冗长的安装手册并小心翼翼地这些文档保存在服务器上。
这时,最怕的就是构建环境的变更,比如某个软件要升级,要添加对某个新软件的支持。这些变更需要我们对所有机器的操作都重来一遍,甚至还需要关机重启,十分费时折腾。
后来,我们尝试了将虚拟机维护成镜像,并使用 SaltStack 做自动化变更。虽然日子好过了点,但升级一次环境还是需要投入不少人工成本。另外,文档的更新始终一件苦差事,从来不敢怠慢就怕某次变更没有记录在案。
你我都清楚,写文档从来都不像写代码那么舒服。程序员最讨厌的两件事也都和文档相关:一是给自己的软件写文档,二是别人的软件没有文档。
这让我们非常困扰,我们做的是 DevOps 与持续交付,但是自己的工具管理却如此混乱无章,这也使我们感觉十分蒙羞与窘迫。
第二,容器化的甜头
随着容器越来越流行,我们发现:使用容器镜像保存构建环境是一个非常不错的选择。相对于虚拟机,容器技术主要有以下几个优势:
使用 Dockerfile 描述环境信息相对于之前的文档更加直观,并且可以很自然地跟 Git 结合做到版本化控制,先更新 Dockerfile 再更新镜像是很自然的事。
镜像更容易继承,你可以配置一个 Base 镜像,然后根据不同的需求叠加软件。比如,你的所有构建都需要安装 Git 等软件,那么就可以把它写到 Base 镜像里面。
Docker 镜像可以自由控制,开发人员可以自己推送镜像,快速迭代。重建容器的代价比重建虚拟机小得多,容器更加轻量,更容易在本地做测试。
目前,携程的构建系统已经支持了包括: Java NodeJSGolangErlangPython 等多种语言的构建,并且维护起来非常轻松,完全没有负担。
在尝到了 Linux 容器带来的甜头之后,我们毅然决然地开始研究 Windows 容器技术。经过不断地尝试与探索,终于把它应用到了生产环境,并且取得了非常不错的效果,目前为止运行也十分稳定。
第三,让资源弹起来
容器化在很大程度上解决了运维成本的问题,虽然通过 Docker 管理容器比虚拟机要方便一些,但是管理大量的容器却也没那么得心应手。
此外,我们之前使用容器的方式几乎和使用虚拟机一样,也就是所谓的 “胖容器”,一旦创建,不管用不用,它都在那里。而构建是一个周期性的行为,一般跟着程序员的工作时间走:工作日比周末多,白天比晚上多,甚至还有明显的午饭和晚饭空闲期。
后来 Mesos 与 Kubernetes等主流的容器集群管理工具渐渐浮出水面出现在我们的视野中。
基于 Borg 成熟经验打造的 Kubernetes为容器编排管理提供了完整的开源方案并且社区活跃生态完善积累了大量分布式、服务化系统架构的最佳实践。在2017 年, 携程尝试将 Jenkins 和Kubernetes集成在了一起 。
目前Jenkins 社区已经提供了一个Kubernetes插件而且是免费的使得 Jenkins 与 K8s 的集成变得非常简单轻松。
图2 构建数量与容器数量对比
上图就是我们某台宿主机 24 小时的构建数量与所创建容器的对比图,可以看出两条曲线呈现的趋势基本是一致的。我们在夜晚闲置的资源上,调度了一些其他的离线 Job大幅提升了资源利用率。
所以携程利用容器技术也顺利实现了Slave节点的弹性伸缩。对于中小型企业初期完全可以利用Jenkins及其Kubernetes插件做到Slave节点的资源弹性伸缩。至于与离线Job混部因为要考虑的因素较多可以在应用容器化之后再考虑。
总结与实践
我主要介绍了几种流行的持续集成工具,以及基于 Jenkins 的高可用构建系统的一些基本设计理念和我们系统的演变过程。
通常建议使用成熟的CI产品比如Travis CI、Circle CI、Jenkins CI来作为平台的基础
虽然这些CI工具是成熟产品但面对日新月异的技术需求高可用和伸缩问题还是要自己解决
通过请求分发等设计可以实现Master节点的横向伸缩及高可用问题
利用容器技术可以解决Salve节点的弹性伸缩和资源利用率问题。
最后,你可以尝试搭建一套 Jenkins 与Kubernetes 服务,让你的任务跑在动态创建出来的容器上,并思考一下这个方案有没有什么缺点和不足。
感谢你的收听,欢迎你给我留言。

View File

@ -0,0 +1,181 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
17 容器镜像构建的那些事儿
随着容器发布越来越流行,持续交付最后一公里的产物,逐渐由之前的代码包变成了容器镜像。然而,容器镜像构建与传统的代码构建有很多不同之处,也增加了很多新鲜的技术领域和内容需要我们去学习。
所以,今天我们就一起来聊聊容器镜像构建的那些事儿,打通容器镜像构建的各个环节。
什么是容器镜像?
在虚拟机时代就有镜像的说法当我们创建一个虚拟机时通常会去网上下载一个ISO格式的虚拟机镜像然后经过 VirtualBox 或者 VMware 加载,最终形成一个包含完整操作系统的虚拟机实例。
而容器镜像也是类似的意思,只不过它不像虚拟机镜像那么庞大和完整,它是一个只读的模板,一个独立的文件系统,包含了容器运行初始化时所需要的数据和软件,可以重复创建出多个一模一样的容器。
容器镜像可以是一个完整的 Ubuntu 系统,也可以是一个仅仅能运行一个 sleep 进程的独立环境,大到几 G 小到几 M。而且Docker 的镜像是分层的,它由一层一层的文件系统组成,这种层级的文件系统被称为 UnionFS。下图就是一个 Ubuntu 15.04 的镜像结构。
引用自 docker docsimages and layers
图中的镜像部分画了一个锁的标记它表示镜像中的每一层都是只读的只有创建容器时才会在最上层添加一个叫作Container layer的可写层。容器运行后的所有修改都是在这个可写层进行而不会影响容器镜像本身。
因为这一特性,创建容器非常节省空间,因为一台宿主机上基于同一镜像创建的容器只有这一份镜像文件系统,每次创建多出来的只是每个容器与镜像 diff 的磁盘空间。而虚拟机每增加一个实例,都会在宿主机上占用一个完整的镜像磁盘空间。
什么是 Dockerfile
了解了什么是容器的镜像,以及与虚拟机镜像的区别后,可以清楚地看到:容器都是基于镜像产生的,没有镜像就没有容器。那么,我们应该怎么创建一个镜像呢?
Docker Hub上提供了非常多的常用镜像比如 Ubuntu 镜像CentOS 镜像,或者仅仅是一个包含 Java 程序的镜像,你可以通过 docker pull 命令把它们下载到本地使用。当然你也可以自己在本地通过docker build 制作镜像。
如果你想要修改或者加工这些镜像,可以找到文件系统中对应的 layer 目录,然后进行修改。按照这种方式操作的话,如果我要添加一个文件还好说,但如果要安装一个软件,那就要拷贝一堆文件到各个目录中,相当麻烦。
如果真要这样操作的话容器镜像也就不会有今天如此庞大的用户群体了。Docker帮我们解决这个问题的方式就是提供了Dockerfile。
简单来说Dockerfile 第一个好处就是,可以通过文本格式的配置文件描述镜像,这个配置文件里面可以运行功能丰富的指令,你可以通过运行 docker build 将这些指令转化为镜像。
比如,我要更改 Ubuntu 镜像安装一个 Vim 编辑器,那么我的 Dockerfile 可以这样写:
FROM ubuntu
RUN apt-get install vim -y
其中FROM 指令说明我们这个镜像需要继承 Ubuntu 镜像RUN 指令是需要在镜像内运行的命令。
因为 Ubuntu 镜像内包含了 apt-get 包管理器,所以相当于启动了一个 Ubuntu 镜像的容器,然后在这个容器内部安装 Vim。这期间会产生一个新的 layer这个新的 layer 包含安装 Vim 所需的所有文件。
运行docker build 后会产生一个新镜像,我们可以通过 docker tag 给这个新镜像起一个名字,然后 docker push 到仓库,就可以从仓库下载这个镜像了,后续的其他镜像也可以继承这个镜像进行其他改动。
镜像就是这样通过 Dockerfile 一层一层的继承,不断增加新的内容,直到变成你想要的样子。
Dockerfile 的另外一个好处就是可以描述镜像的变化,通过一行命令就可以直观描述出环境变更的过程,如果再通过 git 进行版本控制,就可以让环境的管理更加可靠与简单。
了解了Dockerfile之后你就可以利用它进行代码更新了最主要的步骤就以下三步
将代码包下载到构建服务器;
通过Dockerfile 的 ADD 命令将代码包加载到容器里;
Docker build 完成新的镜像。
镜像构建优化
原则上,我们总是希望能够让镜像保持小巧、精致,这样可以让镜像环境更加清晰,不用占用过多空间,下载也会更快。
那么如何做好镜像的优化呢你可以从3个方面入手
选择合适的 Base 镜像;
减少不必要的镜像层的产生;
充分利用指令的缓存。
为什么第一条说要选择合适的Base镜像呢因为这是最直接和有效的方式。
举个例子就更好理解了。比如,我只想运行一个 Java 进程,那么镜像里就只有这个 Java 进程所需的环境就可以了,而没必要使用一个完整 Ubuntu 或者 CentOS 镜像。
关于第二点,减少不必要的镜像层,是因为使用 Dockerfile 时,每一条指令都会创建一个镜像层,继而会增加整体镜像的大小。
比如,下面这个 Dockerfile
FROM ubuntu
RUN apt-get install vim -y
RUN apt-get remove vim -y
虽然这个操作创建的镜像中没有安装 Vim但是镜像的大小和有 Vim是一样的。原因就是每条指令都会新加一个镜像层执行install vim 后添加了一层执行remove vim 后也会添加一层,而这一删除命令并不会减少整个镜像的大小。
因此,当我们编写 Dockerfile 时,可以合并多个 RUN 指令,减少不必要的镜像层的产生,并且在之后将多余的命令清理干净,只保留运行时需要的依赖。就好比我买了两斤橘子,只需要把橘子肉保留下来就好,橘子皮可以直接丢掉,不用保留在房间里。
Dockerfile 构建的另外一个重要特性是指令可以缓存,可以极大地缩短构建时间。 因为之前也说了每一个RUN都会产生一个镜像而Docker在默认构建时会优先选择这些缓存的镜像而非重新构建一层镜像。比如一开始我的 Dockerfile 如下:
FROM ubuntu
RUN apt-get install vim -y
使用一段时间之后我发现需要添加新的特性Dockerfile 变成了如下的样子:
FROM ubuntu
RUN apt-get install vim -y
ADD java /usr/local/java
重新 build 时,前面安装 Vim 那步可以使用缓存,而不需要重新运行。当我们需要构建一个新镜像时,这个特性非常有用,可以快速跳过前面构建通过的步骤,而不需要每次都重新构建,尤其适用于在 Docker 里面编译一些大型软件的情况,可以帮你节省大量时间。
镜像构建环境
当我们学会了使用 Dockerfile 构建镜像之后,下一步就是如何搭建构建环境了。搭建构建环境最简单的方式就是在虚拟机上安装 Docker Daemon然后根据你所使用的语言提供的 Docker 客户端与 Docker Daemon 进行交互,完成构建。
但是,我们推崇构建环境容器化,因为我们的构建环境可能除了 Docker 外还会有一些其他的依赖比如编程语言、Git 等等。
上面我也分析了Docker 镜像的各种好处,那如果环境还没有实现容器化,是不是就有点说不过去了?
接下来,我们就看看构建环境如何实现容器化。一般情况下,用容器来构建容器镜像有两种方式:
Docker Out Of DockerDooD
Docker In DockerDinD
第一Docker Out Of DockerDooD
这种方式比较简单,首先在虚拟机上安装 Docker Daemon然后将你的构建环境镜像下载下来启动一个容器。
在默认情况下Docker 客户端都是通过 /var/run/docker.sock 与 Docker Daemon 进行通信。我们在创建 Docker 实例时,把外部的 /var/run/docker.sock mount 到容器内部,这样容器内的 Docker 客户端就可以与外部的 Docker Daemon 进行通信了。
另外,你还需要注意权限问题,容器内部的构建进程必须拥有读取 /var/run/docker.sock 的权限,才可以完成通信过程。
这种方式的好处很明显,我们可以将镜像构建环境打包复用,对宿主机来说,只要安装 Docker Daemon 就可以了。但是这种方式的缺点是,内部的环境必须要与外部保持一致,不然就会报错,比如缺少库文件。此外,如果构建容器时不小心把 Docker Daemon 搞挂了,那么就会影响该宿主机上的其他容器。
为了解决这个问题,我们是否可以在容器内部使用 Docker Daemon 呢?
第二Docker In DockerDinD
Docker In Docker ,就是在容器内部启动一个完整的 Docker Daemon 进程,然后构建工具只需要和该进程交互,而不影响外部的 Docker 进程。
默认情况下,容器内部不允许开启 Docker Daemon 进程,必须在运行容器的时候加上 privileged 参数这个参数的作用是真正取得root的权限。另外Docker 社区官方提供了一个 docker:dind 镜像可以直接拿来使用。
这样一来,容器内部 Docker Daemon 就和容器外部的 Docker Daemon 彻底分开了,容器内部就是一个完整的镜像构建环境,是不是很神奇。
然而 DinD 也不是百分之百的完美和健壮,它也有一些关于安全和文件系统的问题。此外,因为每个容器都有独立的 /var/lib/docker 用来保存镜像文件,一旦容器被重启了,这些镜像缓存就消失了,这可能会影响我们构建镜像的性能。
通过以上两个方法,你就可以做到用容器来构建容器镜像了。
总结
今天,我针对容器镜像构建的那些事儿,和你进行了讨论。
首先容器镜像是一个独立的文件系统它包含了容器运行初始化时所需要的数据或软件。Docker容器的文件系统是分层的、只读的每次创建容器时只要在最上层添加一个叫作Container layer的可写层就可以了。这种创建方式不同于虚拟机可以极大的减少对磁盘空间的占用。
其次Docker提供了Dockerfile这个可以描述镜像的文本格式的配置文件。你可以在Dockerfile中运行功能丰富的指令并可以通过docker build 将这些指令转化为镜像。
再次基于Dockerfile的特性我分享了Dockerfile 镜像构建优化的三个建议包括选择合适的Base镜像、减少不必要的镜像层产生以及善用构建缓存。
最后用容器来构建容器镜像主要有DooD 和 DinD 两种方案。这两种方案,各有优劣,你可以根据自身情况去选择。
思考题
除了上述的 DooD 和 DinD 之外你还知道哪些其他的Docker构建方案吗它们分别有什么特点
Docker构建的缓存机制的基本规则是怎样的如果ADD或COPY命令后是不同的文件缓存机制会怎么处理
欢迎你给我留言。-

View File

@ -0,0 +1,176 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
18 如何做好容器镜像的个性化及合规检查?
你好,我是王潇俊。我今天分享的主题是:如何做好容器镜像的个性化及合规检查。
你是否还记得我在第13 讲篇文章《容器技术真的是环境管理的救星吗?》中说到:容器不是银弹,镜像发布无法很好地满足用户的个性化需求?
在携程的发布标准化中,容器内的环境也是由发布系统定义的,用户即使登录到容器上去做变更,下一次发布之后还是会被回滚回来。但是,对 Dockerfile 的编写和控制需要一定的学习成本,因此我们又不可能将镜像的内容与构建流程完全交给用户来自定义。
于是,就有了我今天的分享,即如何做好容器镜像的个性化及合规检查?根据我在持续交付道路上摸爬滚打的实践经验,总结了以下三种方法来满足用户对容器镜像个性化需求:
自定义环境脚本;
平台化环境选项与服务集市;
自定义镜像发布。
接下来的内容,我将根据这三种方法展开,并将介绍如何通过合规检查来规避个性化带来的风险。
用户自定义环境脚本
我们允许用户在编译后的代码包内放入包含自定义环境脚本的 .paas 目录(这是一个自定义的隐藏目录),来满足用户对环境的个性化需求。
这个.paas目录中可能会存在build-env.sh和image-env.sh两个文件分别运行于构建代码和构建镜像的过程中。
其中build-env.sh是在构建代码之前运行image-env.sh是在构建镜像的时候插入到我们规范的 Dockerfile 中,从而被打到容器内部。
这样就不仅可以满足用户对发布的镜像的个性化需求,同时还能满足对构建代码镜像的个性化需求。
比如某个Python应用依赖一些动态链接库那么这个依赖在构建代码和构建镜像环节都是必须的。这时用户就需要在 build-env.sh和 image-env.sh这两个文件中都写入安装依赖的步骤构建系统会在不同阶段判断是否有这两个文件如果有就运行。
通常情况下,自定义环境脚本的方式,可以满足大部分用户的普通需求。但是,这个方式有两个缺点:
构建镜像需要用完就删,因为我们无法感知用户在构建中修改了什么内容,是否会对下一次构建产生影响。这就要求每次构建都要生成新的容器,会在一定程度上降低构建性能。
如果多个项目有同样的需求,那么这些项目就都要引用这个脚本文件,不但啰嗦,而且后面也不好维护,如果脚本内容变化,还需要通知所有引用的项目都改一遍。
好的工具就是要解决用户的一切痛点,因此针对第二个问题,我们在系统上通过平台化环境选项和服务集市的方式做了统一处理。
平台化环境选项与服务集市
环境选项, 是携程在持续交付平台为用户提供的一些环境变更的常用功能,表现为构建镜像时的一些附加选项。
在上一篇文章《容器镜像构建的那些事儿》中,我介绍了构建镜像一个很重要的原则是:镜像要尽可能得小巧精简,因此我们没有在镜像中为用户安装太多的软件。但是,很多时候用户可能需要这些软件,于是我们就在平台上提供了环境选项的功能。
比如很多用户需要用到Wget软件于是我们就在交付平台上提供了一个 “安装 Wget ” 的环境选项。其实,这个环境选项对应的就是一条 shell 命令:
yum install wget -y
如果某次发布时,用户需要这个工具,可以勾选这个选项,那么就可以在构建镜像时作为参数传给构建系统。如果搭建系统判断出有这个参数,就将会其插入到规范的 Dockerfile 中,从而这个参数就可以被打到容器内部。
环境选项虽然好用,但是只适合一些简单的需求,比如安装一些软件、更改一些配置等。而对一些复杂的需求,则需要创建一个叫作服务集市的功能。举个例子:
携程的服务集市中有一个 JaCoCo 服务,它的作用就是在 Tomcat 启动时更改 JVM 参数收集应用的覆盖率并发送给外部系统。同时外部系统可以控制这个JaCoCo服务的启停并将收集结果处理成可视化的页面。
服务集市功能的使用,会涉及到以下两个关键步骤:
勾选 JaCoCO 服务之后,会在容器中注入 jacocoagent.jar 和启停脚本;
通过对外暴露的API控制在容器中运行启停脚本。
像JaCoCo这样的复杂功能我们会抽象成服务供用户使用。他们只要在构建镜像时选择对应的服务和该服务起效的环境就可以了。
而实际系统要完成的任务则复杂得多首先要通过改写Dockerfile完成以上所说的“勾选JaCoCo服务”同时还要改写镜像中JVM的启动参数等并完成对JaCoCo服务中心的注册。具体的操作各个服务有所不同根据实际需求而定原则就是把这些服务内容增加到对应的环境镜像中去。
通过这种方式构建的镜像不同环境就拥有了不同的服务。比如用户在构建镜像时选择了JaCoCo服务起效的环境是测试环境那么JaCoCo就只在测试环境的镜像中起效而不会在生产环境中起效。
除了JaCoCo以外携程还提供了许多其他与环境有关的服务组成了一个服务集市用户可以按照具体需求组合使用。
携程的服务集市
自定义镜像发布
用户自定义环境脚本、平台化环境选项与服务集市,这两种方式有一个共同的缺点:自定义的部分都需要插到 Dockerfile中因此每次打镜像时都需要运行一次。 这对一些比较快的操作,没有问题,但如果需要安装很多软件,甚至需要编译一些软件时,每次发布都重复运行一次的效率就会非常低下。
为此我们提供了用户自定义镜像的功能该功能分为自定义Base镜像和完全自定义镜像发布两种。
自定义 Base 镜像
自定义 Base 镜像,就是如果基础镜像无法满足用户需求,并且自定义的部分非常重,运行比较久,我们就会建议用户使用自定义的 Base 镜像。但是,这个自定义的 Base 镜像,必须基于官方提供的 Base 镜像因为很多工具和功能都是基于官方Base 镜像的。
虽然 Base 镜像是自定义的,但是应用还是标准的应用,因此发布方式和普通的发布方式没有区别。只是解决了自定义环境脚本与平台化环境选项的运行速度问题,反映到实际的 Dockerfile 上,就只是 FROM指令的指向改变了变成了用户自定义的 Base 镜像地址。
完全自定义镜像发布
但是用户的需求是永无止境的。比如特殊启动方式的应用自定义Base镜像就无法解决。
原则上来说,我不建议使用一些非标准的应用,因为这是不可控的,对生产环境非常危险。但是 Docker 的镜像是如此方便,用户如果只是想在测试环境中使用一些测试工具,虽然这个工具来自于社区,也不是标准的应用,但我们也没有理由全部拒绝。否则,用户很可能会以虚拟机上可以安装任何工具为由,要求退回到虚拟机时代。
但是,这样的退化怎么能被允许呢!
因此,一定要支持完全自定义镜像发布,也就是说用户可以发布任何镜像,只要这个镜像能够跑起来。对私有云来说,这应该是能接受的最大化的自由了。
对于完全自定义发布我们使用 Docker 多阶段构建multi-stage build也就是说用户可以将构建代码和构建镜像合并成一个步骤在同一个 Dockerfile 中完成。
镜像安全合规检查
满足了用户对镜像的个性化需求,也就意味着会引入不可控因素,因此对镜像的安全合规检查也就变得尤为重要了。我们必须通过合规检查,来确认用户是否在容器里做了危险的事情。
只有这样,用户个性化的自由,才不会损害整个环境。毕竟,有克制的自由才是真正的自由。
对自定义镜像首先必须保证它是基于公司官方Base 镜像的,这是携程最不可动摇的底线。 在其他情况下,就算真的不继承公司官方 Base 镜像,建议也必须要满足 Base 镜像的一些强制性规定,比如应用进程不能是 root 等类似的安全规范。
关于自定义镜像是否继承了公司官方镜像,我们采取的方法是对比镜像 Layer即自定义镜像的 Layer 中必须包含官方Base镜像的 Layer。
但是,对比 Layer 也不是最靠谱的方式,因为用户虽然继承了 Base 镜像,但还是有可能在用户创建的上层 Layer中破坏镜像结构。目前Docker 的部署流程中,还有许多潜在漏洞,有可能让一些有企图的人有机可乘,发起攻击。
因此,我们需要一些强制手段来确保镜像的安全,好的安全实践意味着要对可能出现的事故未雨绸缪 。
目前,市面上有很多工具可以为 Docker 提供安全合规检查,如 CoreOS ClairDocker Security ScanningDrydock 等等。
在安全合规检查方面,携程的方案是 Harbor 与 CoreOS Clair 结合使用:当构建系统 Push 一个新的镜像或者用户 Push 一个自定义镜像之后Harbor 会自动触发 CoreOS Clair 进行镜像安全扫描。Clair 会对每个容器 Layer 进行扫描,并且对那些可能成为威胁的漏洞发出预警。
漏洞分严重级别,对于一些非破坏性的漏洞,我们是允许发布的。检查的依据是 Common Vulnerabilities and Exposures 数据库(常见的漏洞和风险数据库,简称 CVE)以及Red Hat、Ubuntu 、Debian 类似的数据库。
这些数据库中,包含了一些常见的软件漏洞检查。比如, libcurl 7.29.0-25.el7.centos 存在如下漏洞:
The curl packages provide the libcurl library and the curl utility for downloading files from servers using various protocols, including HTTP, FTP, and LDAP. Security Fix(es): * Multiple integer overflow flaws leading to heap-based buffer overflows were found in the way curl handled escaping and unescaping of data. An attacker could potentially use these flaws to crash an application using libcurl by sending a specially crafted input to the affected libcurl functions. (CVE-2016-7167) Additional Changes: For detailed information on changes in this release, see the Red Hat Enterprise Linux 7.4 Release Notes linked from the References section.
攻击者可以利用libcurl缓冲区溢出的漏洞在应用的上下文中执行任意代码。
Clair 是一种静态检查,但对于动态的情况就显得无能为力了。所以,对于镜像的安全规则我还总结了如下的一些基本建议:
基础镜像来自于 Docker 官方认证的,并做好签名检查;
不使用 root 启动应用进程;
不在镜像保存密码Token 之类的敏感信息;
不使用 privileged参数标记使用特权容器
安全的 Linux 内核、内核补丁。如 SELinuxAppArmorGRSEC等。
这样能使你的镜像更加安全。
总结与实践
在这篇文章中,我分享了携程满足用户对镜像个性化需求的三种方式:
用户自定义环境脚本通过build-env.sh和image-env.sh两个文件可以在构建的两个阶段改变镜像的内容
平台环境选项与服务集市,利用这两个自建系统,可以将个性化的内容进行抽象,以达到快速复用,和高度封装的作用;
自定义镜像,是彻底解决镜像个性化的方法,但也要注意符合安全和合规的基本原则。
关于对镜像的安全合规检查,携程采用的方案是 Harbor 与 CoreOS Clair 结合使用。除此之外我还给出了在实践过程中总结的5条合规检查的基本建议希望这些实践可以帮到你。
除了 Clair 进行 CVE 扫描之外,还有其他一些关于镜像安全的工具也可以从其他方面进行检查,你也可以去尝试一下。
感谢你的收听,欢迎你给我留言。

View File

@ -0,0 +1,153 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
19 发布是持续交付的最后一公里
19 发布是持续交付的最后一公里
你好,我是王潇俊。我今天分享的主题是:发布是持续交付的最后一公里。
在开始我今天的分享之前,我们先来搞清楚一个问题:部署和发布是不是一回事儿?
有一些观点认为部署和发布是有区别的前者是一个技术范畴而后者则是一种业务决策。这样的理解应该说是正确的。应用被部署并不代表就是发布了比如旁路运行dark launch方式对于客户端产品更是如此的。
但对互联网端的产品来说这个概念就比较模糊了所以从英文上来看我们通常既不用deploy这个词也不用 release 这个词,而是使用 rollout这个词。所以从用词的选择上我们就可以知道发布是一个慢慢滚动向前、逐步生效的过程。
因此,我在《发布及监控》系列文章中提到的“发布”,均泛指 rollout 这样的过程。
发布,头疼的最后一步
无论是为新需求添加的代码,还是静态配置的变更,线上应用的任何变动都要经过发布这道工序才能最终落地,完成交付。通常,发布意味着应用重启、服务中断,这显然不符合如今系统高可用的需求。
同时软件工程和经验也告诉我们世界上不存在没有Bug的代码即便经过详尽细致地测试线下也很难百分之一百地复制线上的环境、依赖、流量更难穷举千变万化的用户行为组合。
于是,发布变更,在许多时候是一件被标记为“高风险系数”的工作,工程师和测试人员经常在深夜搞得筋疲力尽,甚至焦头烂额。
进入持续交付的时代后,这个痛点只会更加突显,因为持续交付意味着持续发布。例如,在测试环境小时级的持续集成场景中,如果没有办法将发布过程流程化、自动化,显然会频繁打断最终的交付过程,大幅降低开发测试效率。
好在上帝创造了一个问题,一定会留下一套解决方案,更多的时候是许多套,我们的目标就是找到它,然后实现最佳实践。
发布的需求
你不妨先问自己一个问题,作为开发人员,或者其他研发角色,你理想中的发布是什么样的呢?
答案当然是:够快够傻瓜。最好点一下鼠标,就立刻能看到线上的变更,整个体验跟本地开发环境调试毫无区别。
更加贪心的同学甚至希望连点击鼠标都不用而是每小时、每天、甚至每commit自动发布希望系统神奇地将自己从SSH和乱七八糟的线上环境中解放出来。
另一方面,从运维的角度来讲,线上系统的稳定性和可用性则是第一考量。运维上线变更前,首先会思考如果这中间出了什么岔子该如何应对,找不到问题时能否快速回滚到之前的状态,整个过程如何最小限度地减少服务的宕机时间。对他们而言,完美的方案就像是能够稳如泰山地给飞行中的飞机更换引擎。
因此,我们想要的应该是:一个易用、快速、稳定、容错力强,必要时有能力迅速回滚的发布系统。
什么是好的发布流程?
好的系统依赖好的设计,而好的工作流方案可以显著减少需要考虑的问题集,有助于创造出高健壮性的系统。对于发布系统,单机部署方案和集群工作流同样重要。
第一,把大象放进冰箱分几步?
单机部署这件事说复杂很复杂,说简单也很简单。
复杂在于不同技术栈的部署方式千差万别脚本语言PHP和需要编译的Golang的上线步骤差很多同样是Java使用Tomcat和Netty的命令也完全不一样。
简单在于,发布过程高度抽象后其实就三个步骤:
在目标机器上执行命令停掉运行中的服务;
把提前准备好的变更产物传上机器覆盖原来的目录;
运行命令把服务再跑起来。
但只是按照这三步走你很容易就能设想到一些反例场景服务虽然停止但新的请求还在进入这些请求全部返回503错误或者假如有Bug或者预料之外的问题服务根本起不来停服时间就不可预知了。
更糟糕的是,假如此时情况紧急,我们想回滚到之前的状态,回滚时就会发现,由于之前的目录被覆盖了,基本回不去了。
第二,靠谱的单机部署
那么,比较完善的发布变更流程应该是怎样的呢?在我看来,可以抽象成五步:
下载新的版本,不执行覆盖;
通知上游调用方,自己现在为暂停服务状态;
运行命令load变更重启服务
验证服务的健康状况;
通知上游调用方,自己服务恢复正常。
假设我们实现了一个程序,简单地顺序执行上面的算法,让我们一起来检验一下这套程序是否能满足发布的需求:快速、易用、稳定、容错、回滚顺滑。
易用:执行脚本就好,填入参数,一键执行。
快速:自动化肯定比手工快,并且有提升空间。比如,因为有版本的概念,我们可以跳过相同版本的部署,或是某些步骤。
稳定:因为这个程序逻辑比较简单,而且执行步骤并不多,没有交叉和并行,所以稳定性也没什么大的挑战。
容错性强:表现一般,脚本碰到异常状况只能停下来,但因为版本间是隔离的,不至于弄坏老的服务,通过人工介入仍能恢复。
回滚顺滑:因为每个版本都是完整的可执行产物,所以回滚可以视作使用旧版本重新做一次发布。甚至我们可以在目标机器上缓存旧版本产物,实现超快速回滚。
通过这个程序的简单执行过程,我们可以看到这套流程的简单实现,基本满足了我们对发布的需求。而且,可以通过添加更复杂的控制流,获得更大的提升空间。
我在这里提到的三个重要概念:版本、通知调用方、验证健康(又被称之为点火),可以说是实现目标的基石。我会在后续章节,详细介绍版本、通知调用方、验证健康这三方面的实现方式和取舍。
第三,扩展到集群
如今应用架构基本告别了单点世界,面向集群的发布带来了更高维度的问题。当发布的目标是一组机器而不是一台机器时,主要问题就变成了如何协调整个过程。
比如,追踪、同步一组机器目前发布进行到了哪一步,编排集群的发布命令就成为了更核心功能。好消息是,集群提供了新的、更易行的方法提高系统的发布时稳定性,其中最有用的一项被称为灰度发布。
灰度发布是指渐进式地更新每台机器运行的版本一段时期内集群内运行着多个不同的版本同一个API在不同机器上返回的结果很可能不同。 虽然灰度发布涉及到复杂的异步控制流,但这种模式相比简单粗暴的“一波流”显然要安全得多。
不仅如此当对灰度发布的进度有很高的控制能力时事实上这种方式可以提供A/B测试可能性。 比如你可以说将100台机器分成4批每天25台发布至新的版本并逐步观察新版本的效果。
其实,集群层面的设计,某种程度上是对单机部署理念的重复,只不过是在更高的维度上又实现了一遍。 例如,单机部署里重启服务线程堆逐批停止实现,与集群层面的分批发布理念,有异曲同工之妙。
几种常见的灰度方式
灰度发布中最头疼的是如何保持服务的向后兼容性发现苗头不对后如何快速切回老的服务。这在微服务场景中大量服务相互依赖A回滚需要B也回滚或是A的新API测试需要B的新API时十分头疼。为了解决这些问题业界基于不同的服务治理状况提出了不同的灰度理念。
接下来,我将分别介绍蓝绿发布、滚动发布和金丝雀发布,以及携程在发布系统上的实践。
蓝绿发布,是先增加一套新的集群,发布新版本到这批新机器,并进行验证,新版本服务器并不接入外部流量。此时旧版本集群保持原有状态,发布和验证过程中老版本所在的服务器仍照常服务。验证通过后,流控处理把流量引入新服务器,待全部流量切换完成,等待一段时间没有异常的话,老版本服务器下线。
这种发布方法需要额外的服务器集群支持,对于负载高的核心应用机器需求可观,实现难度巨大且成本较高。
蓝绿发布的好处是所有服务都使用这种方式时,实际上创造了蓝绿两套环境,隔离性最好、最可控,回滚切换几乎没有成本。
滚动发布,是不添加新机器,从同样的集群服务器中挑选一批,停止上面的服务,并更新为新版本,进行验证,验证完毕后接入流量。重复此步骤,一批一批地更新集群内的所有机器,直到遍历完所有机器。-
这种滚动更新的方法比蓝绿发布节省资源,但发布过程中同时会有两个版本对外提供服务,无论是对自身或是调用者都有较高的兼容性要求,需要团队间的合作妥协。但这类问题相对容易解决,实际中往往会通过功能开关等方式来解决。
金丝雀发布,从集群中挑选特定服务器或一小批符合要求的特征用户,对其进行版本更新及验证,随后逐步更新剩余服务器。这种方式,比较符合携程对灰度发布的预期,但可能需要精细的流控和数据的支持,同样有版本兼容的需求。
结合实际情况,携程最终选择的方式是:综合使用滚动发布和金丝雀发布。 首先允许对一个较大的应用集群特别是跨IDC的应用集群按自定义规则进行切分形成较固定的发布单元。基于这种设计我们开发了携程开源灰度发布系统并命名为Tars 。其开源地址为https://github.com/ctripcorp/tars
关于携程灰度发布的设计和实施以及如何把灰度发布的理念贯穿到你的持续交付体系中我会在后面的第22篇文章《发布系统架构功能设计实例》中详细介绍。
其他考量
处于持续交付最后一环的发布实际上是非常个性化的与实际实现相关甚至是case by case的。因为每个上游系统的少许变更和设计瑕疵层层下压最终都会影响到发布系统。这不但要求发布系统了解链条上绝大多数环节知道发生了什么以便debug甚至时常还需要为其“兜底”。
除此以外,软件工程中没有“银弹”,适用于所有场景的系统设计是不存在的。上面的设计有许多值得探讨的地方,比如发布时到底是使用增量、还是全量,单机切断流量使用哪种手段,集群发布的控制流设计,都是值得探讨的主题。这些内容,我将会在后面的文章中详细展开。
总结
作为《发布与监控》系列文章的开篇,我介绍了发布在持续交付中的位置和需求,并提出了一个可靠的单机部署流程的概念,即我们想要的应该是:一个易用、快速、稳定、容错力强,必要时有能力迅速回滚的发布系统。
明确了发布的需求后,我推演了集群发布中灰度发布的概念和常用方式,包括蓝绿发布、滚动发布和金丝雀发布,并分析了这三种发布方式,给出了携程选择的方案,希望可以帮你选择适合自己团队的发布策略。
思考题
你能详细地整理和描述出你的应用的单机部署过程吗?
欢迎你给我留言。

View File

@ -0,0 +1,159 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
20 Immutable任何变更都需要发布
在专栏的第13讲《容器技术真的是环境管理的救星吗》中我们初步结识了不可变基础设施Immutable Infrastructure这里我们再一起回顾一下
在这种模式中,任何基础设施的实例(包括服务器、容器等各种软硬件)一旦创建之后便成为一种只读状态,不可对其进行任何更改。如果需要修改或升级某些实例,唯一的方式就是创建一批新的实例来替换它。
这种思想与不可变对象的概念完全相同。
为什么我会说,不可变基础设施的思想对持续交付的影响非常深远呢?因为不可变的思想正是解决了持续交付一直没有解决的一个难题,即环境、顺序、配置这些基础设施在测试环节和生产环节的不一致性所带来的问题。
那么,今天我就来详细分析一下不可变基础设施的由来、影响,以及如何实现的相关内容。
从持续交付中来
如果你是一个程序员其实很容易理解不可变基础设施的概念以及其实现的方式。因为它就和Java中的不可变类完全相同类实例一旦创建就无法变更而可以变更的是指向实例的引用。
其实早在2011年出版的《持续交付发布可靠软件的系统方法》一书中就曾提到“蓝绿发布”的概念你需要更新一组实例但并不是直接在原有实例上进行变更而是重新启动一批对等的实例在新实例上更新然后再用新实例替换老实例。此时老实例仍旧存在以便回滚。
其实,这完全就是对不可变类的物理实现,也就是一个典型的不可变模型。
这里,我抛出了“不可变模型”的概念,那么我们再垂直一些,再来看看由不可变模型转化到不可变基础设施,又会有哪些具体的要求。
综合起来一句话,就是:
对任何的包、配置文件、软件应用和数据都不做CRUD创建、替换、更新、删除操作。
也就是说,对于已经存在的基础设施,不再在其上创造任何新的事物。根据不可变模型,推导得出取而代之的方法则是:
构建一个新的基础设施;
测试新的基础设施是否符合需求;
将引用指向这个新的基础设施;
保留原有基础设施以备回滚。
虽然不可变模型的设想很好但其中也会有一些特殊情况存在。比如涉及数据的部分特别是数据库你不可能每次都重建一个数据库实例来达到“不可变”的目的。为什么呢其根本原因是数据库是有状态的。所以从这里可以清楚地看到不可变Immutable的前提是无状态。
不可变基础设施的神话
说到为什么会需要“不可变基础设施”这种方法论,还是挺有意思的。
首先是一个假设:如何保证两个实例的行为完全一致?最有效的方式是,在两个实例上以同样的顺序执行同样的变更。
然后在2002年Steve Traugott 写了一篇名为《为什么顺序很重要》Why Order Matters的论文虽然论文本身涉及了很多数学推理有些难懂但它却很好地解释了为什么大规模基础设施是不可变的并证明了不可变的价值所在。
第一,一致是最终的目标
在论文中,作者讨论了发散、收敛和一致三种模型。
发散, 是我们通常会碰到的基础设施的管理模型。在这个模型中,基础设施随着我们的想法而变化,也就是我们想更新什么就更新什么,最终就会形成一种发散的形态。
《Why Order Matters》论文中的发散模型
收敛, 是Puppet和Chef遵循的设计原则。随着时间推移目标和实际需求汇聚达到一致。通过这个模型我们有了可扩展的基础设施的基础和实现。
《Why Order Matters》论文中的收敛模型
一致, 指的是整个基础设施始终把每一天当成是与第一天相同的模型。根据我们之前的假设,达到这一目的的关键点就在于,有序地正确执行从真正的第一天开始的所有变更。
《Why Order Matters》论文中的一致模型
那么你就会有疑问,为什么会有一致模型?通过发散和收敛这两个模型,没办法解决实例完全一致的问题吗?答案是,确实不行。
即使我们知道发散是一种不良状态,我们可以通过定期的收敛,将基础设施不断地趋向所期望的目标。但其中也会碰到很多问题。
顺序问题:你只有完全保证顺序的正确性,结果才会正确。但是,怎么保证顺序呢?特别是执行结果与你的预期不一致时,比如发生错误时,就会多出一些其他的处理步骤,直接影响原有的既定顺序。这也是为什么顺序那么重要的原因。
频率问题:假设你可以通过一些方法保证顺序,在面对大型基础设施时,应该如何制定收敛频率呢?最简单的回答,自然是越频繁越好。-
那么你就会陷入巨大的陷阱中,你会发现完全无法支撑并发的收敛工作。而且收敛工作与设施的规模成正比,直接否定了系统的可扩展性。
蝴蝶效应:你始终无法确定一个绝对的基准点,认为是系统的初始或者当前应该有的状态。因为你始终还在收敛中,只是无限趋近。因此任何小偏差,都会引起将来重大的、不可预知的问题。这就是蝴蝶效应。
但是,容器却通过分层镜像与镜像发布技术,解决了上面的顺序问题、频率问题和蝴蝶效应。所以说,容器是一个惊人的发明,它使得每一次变更都成为了一次发布,而每一次发布都成为了系统的重新构建, 从而使得“一致”模型的目标能够达成。
第二Immutable的衍生
当然除了容器之外Immutable 理念还有许多不同的衍生。比如黄金映像、VDI虚拟桌面、Phoenix Server和基础设施即代码。
黄金映像,指的是将绝大部分不变的基础设施(包括操作系统、大多数软件、基本配置等),包含在映像内,只留很少一部分变更通过脚本执行解决;
VDI虚拟桌面指的是操作系统运行在后端的服务器上用户只使用属于他自己的虚拟桌面无法改变后端的系统内容
Phoenix Server指的是完全被破坏的服务器能够从灰烬中自动进行恢复
基础设施即代码,指的是把基础设施的构建以代码的方式组织起来,从而通过运行代码可以完全构建出你想要的全部基础设施。
这些衍生技术都遵循Immutable的理念曾在不同的场景下比如快速灾备、快速恢复系统、增强系统健壮性等方面发挥了巨大的作用从而收益。
但是,随着技术的发展,这些衍生技术不再能够适应我们对速度和扩展性的要求,加之容器技术的蓬勃发展,使得这一系列的难题都可以通过容器技术解决,因此我们已经完全可以把注意力放到容器上了。
回到持续交付中去
虽然我们一起从持续交付中走了出来,了解了不可变基础设施的种种,但最终我们还是要回持续交付中去,去解决构建持续交付平台的问题。那么,接下来我们就一起看看面对容器时代的新形式,持续交付要做哪些变化吧。
“不可变”模型的好处,已经显而易见。而对于容器时代的持续交付,也显然已经从原来单纯交付可运行软件的范畴,扩展为连带基础环境一起交付了,所以我们需要为此做好准备。
上文中,我已经总结了一句话,每一次变更都是一次发布,而每一次发布都是系统重新构建,更形象点说,每一次发布都是一个独立镜像的启动。所有持续交付的变化也都可以表现为这样一句话,那具体怎么理解呢。
首先任何的变更包括代码的、配置的、环境的甚至是CPU、内存、磁盘的大小变化都需要制作成独立版本的镜像。
其次,变更的镜像可以提前制作,但必须通过发布才能生效。 这有2个好处
重新生成新的实例进行生效,完全遵循不可变模型的做法;
发布内容既包含代码也包含基础设施更有利于DevOps的实施。
再次,一组运行中的同一个镜像的实例,因为“不可变”的原因,其表现和实质都是完全一样的,所以不再需要关心顺序的问题。因为任何一个都等价,所以也就没有发布或替换的先后问题了。
最后,根据“一致”模型的要求,我们需要记录系统从第一天发展到今天的所有有序变更。 对Docker而言不仅要能向上追溯层层Base镜像的情况更建议将系统和软件的配置以Dockerfile的方式进行处理以明确整个过程的顺序。
这些理念,不仅传统的持续交付中没有涉及,甚至有些还与我们日常的理解和习惯有所不同。比如,你通常认为一个集群中的不同服务器的配置是可以不一样的,但在“不可变”模型中,它是不被允许的。
当然我在之前的《容器技术真的是环境管理的救星吗》一文中也提到过Immutable对持续交付的环境管理来说确实有点违反人性。所以容器对持续交付的影响可以说是利弊都有吧。
也因此,持续交付中遇到“不可变”,更应该去理解它的概念和用意,合理发挥其优势。
总结
首先,我分享了“不可变”模型的概念,以及它的由来,介绍了三个非常有价值的模型:发散模型、收敛模型和一致模型。
其次,我解释了为什么“不可变”如此重要的原因,也就是重复发散到收敛过程无法解决的三个问题:顺序问题、频率问题和蝴蝶效应。
最后,我针对“不可变”及容器,提出了持续交付面对的新问题,即:每一次变更都是一次发布,每一次发布都是一个独立的镜像的启动。
思考题
你所在的公司有没有什么地方可以体现出“不可变”思想?如果没有,是什么原因呢?
感谢你的收听,欢迎你给我留言。

View File

@ -0,0 +1,177 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
21 发布系统一定要注意用户体验
你好,我是王潇俊。我今天分享的主题是:发布系统一定要注意用户体验。
我在第19篇文章《发布是持续交付的最后一公里》中介绍了蓝绿发布、滚动发布和金丝雀发布这三种灰度发布方式也分享了携程根据自身情况综合使用滚动发布和金丝雀发布的方式构建了自己的灰度发布系统Tars。
但是,了解了灰度发布的知识,甚至是看过了别家的灰度发布系统,但并不一定能解决如何将这些灰度发布的理念贯彻到你自己的持续交付体系的问题。
其实,解决这个问题最好的方式,就是构建一套发布系统来落地灰度发布。这也是我今天这篇文章,以及后续两篇文章(《发布系统的核心架构和功能设计》《业务系统架构对发布的影响》)要重点解决的问题。
如果有一款发布系统,既能完成持续交付的目标,又能提升研发同学的工作效率,岂不美哉。那么,为了我们美好的目标开始努力吧。今天我就先从用户体验的角度,以携程发布系统为例,来和你聊聊如何落地发布系统。
1 张页面展示发布信息
如果要说什么样的设计才能让用户体验达到完美,那肯定是众说纷纭。从不同的视角去看,都会得到不同的答案。那么,对发布系统来说,我们应该怎么看待这个问题呢?
我们不妨做个类比,应用的发布和火箭发射其实有点相像。
平时我们看火箭发射时,往往会看到一个巨大的屏幕,这个巨大的屏幕汇集了火箭发射当时的各种信息,比如实时视频、各种数据图表、周围的情况,等等。
所有相关人员的注意力都会优先集中在这个大屏幕上,只有发生异常时,才会由具体的负责人在自己的岗位上进行处理。
这也就说明一个很重要的问题对于发布这件事儿来说首先应该有1张页面且仅有1张页面能够展示发布当时的绝大多数信息、数据和内容这个页面既要全面又要精准。 全面指的是内容清晰完整,精准指的是数据要实时、可靠。
除了以上的要求外对于实际的需求还要考虑2个时态即发布中和未发布时展示的内容应该有所区别。
发布中:自然应该展示发布中的内容,包括处理的过程、结果、耗时、当前情况等等。
未发布时:应该显示这个应用历史发布的一个过程,也就是整个版本演进的路线图,以及当前各集群、各服务器上具体版本的情况。
所以基于以上考虑携程的发布系统整体设计就只有一张核心页面如图1所示。因为涉及到安全问题所以图片处理的有些模糊。
图1 携程发布系统-未发布时
2 个操作按钮简化使用
发布系统是一个逻辑比较复杂的系统。而且用户在使用时,往往会伴随一定的心理压力,毕竟每次发布都有造成故障的可能。所以,我见过有些公司,要求发布系统的使用者要经过严格的培训:他们需要熟练地掌握整个系统的使用,知晓每个按钮按下去的后果。
但,这显然不是持续交付想要达到的目标。
与DevOps的理念一样我们更希望看到的场景是谁开发谁运行。也就是说我们的目标是每个开发者都能通过这套系统去发布和运行自己的代码。
这也就决定了,如果发布系统的用户体验做得很复杂、功能按钮非常多的话,就会增加系统的使用难度,背离我们实施持续交付的初衷。
所以携程在思考发布系统的用户体验设计时就提出了这样一个目标页面上除了“回滚”按钮常在外最多同时展示2个操作按钮。这样用户的选择就会变得非常容易要么左要么右总能找到适合自己的。
最终,用户在页面上可能会看到的同时出现的按钮组合有以下四种情况:
开始发布1个按钮
中断发布1个按钮
中断或重试发布2个按钮发生在有局部错误的情况下
中断或继续发布2个按钮发生在发布被刹车时。
关于如何实现这样的需求,我会在下一次分享《发布系统的核心架构和功能设计》时,着重介绍。
3 种发布结果
对发布系统的用户来说,他们最关心的无疑是发布结果。所以,发布结果的显示,也要力求简单,降低使用难度。
因为,结果的数量将直接决定用户操作和状态流转的复杂度。结果越多,程序要处理的逻辑也就越多,操作人员需要处理的状况自然也就越多。所以,如果能够对发布状态做足够的抽象和简化,那么整个系统的复杂度就将会得到指数级的简化。
系统不复杂了,用户体验自然也就简单了。这是一个相辅相成的过程。
从最抽象的角度来说发布系统只需要3种结果成功、失败和中断。
成功状态:很好理解,即整个发布过程,所有的实例发布都成功;
失败状态:只要发布过程中有一个步骤、一个实例失败,则认为整个发布事务失败;
中断状态:发布过程中任何时间点都可以允许中断此次发布,中断后事务结束。
特别需要说明的是,部分失败和全部失败在发布系统的设计上没有分别,这也是出于事务完整性的考虑,即优先完成整个事务的发布。
4 类操作选择
将发布结果高度概括为成功、失败和中断后配合这三种状态我们可以进一步地定义出最精简的4种用户操作行为即开始发布、停止发布、发布回退和发布重试。
开始发布,指的是用户操作开始发布时,需要选择版本、发布集群、发布参数,配置提交后,即可立即开始发布。
停止发布,指的是发布过程中如果遇到了异常情况,用户可以随时停止发布,发布状态也将停留在操作“停止发布”的那一刻。
发布回退,指的是如果需要回退版本,用户可以在任意时刻操作“发布回退”,回退到历史上最近一次发布成功的版本。
发布重试,指的是在发布的过程中,因为种种原因导致一些机器发布失败后,用户可以在整个事务发布结束后,尝试重新发布失败的机器,直到发布完成。
5 个发布步骤
在讲解了一个发布事务相关的操作和步骤,包括开始发布,停止发布,发布回退和发布重试之后,接下来我和你聊聊单个实例具体的发布过程。
就像我在第19讲《发布是持续交付的最后一公里》中总结的“靠谱的单机部署”流程一样单个实例的发布过程也可以分为5个步骤
markdown为了减少应用发布时对用户的影响所以在一个实例发布前都会做拉出集群的操作这样新的流量就不会再继续进入了。
download这就是根据版本号下载代码包的过程
install在这个过程中会完成停止服务、替换代码、重启服务这些操作
verify除了必要的启动预检外这一步还包括了预热过程
markup把实例拉回集群重新接收流量和请求。
在这5个步骤中第四步verify比较特殊。因为包含了预热这个耗时通常比较长的过程有时甚至需要几十分钟。所以这个步骤的处理必须是异步的。同时还需要用户在发布配置时设置一个超时时间以便防呆处理即在异步处理长时间无返回时能够继续处理。
集群中的每个实例都会分批次逐个按顺序去完成这5个步骤。这5个步骤本身是串行的任何一步出错该实例的操作都会立即停止。
6 大页面主要内容
最后,再回过头来看一下,在分享的一开始,我提出了一个设想,要做到出色的用户体验,需要将发布的主要信息,呈现在唯一的一张页面上。那么,这张页面涉及到的主要内容到底有什么呢?
根据携程的实践我提炼了这一张页面要展示的最主要的6部分内容。
第一集群。集群是发布的标准单元。如图2所示用户可以选择左侧的集群在界面右侧查看当前运行的版本、历史发布情况、操作发布。
第二实例。实例是集群的成员通常情况下一个集群会有多个实例承载流量。在界面上用户可以查看实例的基本信息了解实例的IP、部署状态、运行状态等。用户能够看到发布时的状态与进度这些信息可以帮助用户更好地控制发布。
第三,发布日志。在发布中和发布完成后,用户都可以通过查询发布日志了解发布时系统运行的日志,包括带有时间戳的执行日志和各种提示与报错信息,方便后续排查问题。
第四,发布历史。发布历史对发布系统来说尤为重要。用户可以通过发布历史了解集群过去所做的变更,并且可以清晰地了解集群回退时将会回退到哪一天发布的哪个版本。
第五,发布批次。由于集群中有很多实例,如何有序地执行发布,就是比较重要的事情。设定发布批次,可以让集群的发布分批次进行,避免问题版本上线后一下子影响所有的流量。每个批次中的实例采用并行处理的方式,而多个批次间则采用串行处理的方式。
第六,发布操作。所有的发布操作按钮都会集中在这个区域,以便用户快速定位。
图2 携程发布系统-发布时
上面这6大部分内容就是我在设计携程的发布系统时抽象出的最主要的内容。当然在最初设计时我们也考虑过将其他内容也加进来比如监控内容。但最终我们放弃了因为这些都不是发布系统最关注的信息而且当发布速度达到分钟级时肉眼也已经无法解决监控或预警的问题了。
所以,在考虑灰度发布系统的用户体验时,我建议你可以参考以下三个原则:
信息要全面直观,并且聚合,而不要分散;
操作要简单直接,不要让用户做过多思考;
步骤与状态要清晰,减少模糊的描述。
总结
一路看下来不知道你是否已经发现整篇文章的6个章节恰好能用1~6这六个数字串接提炼我也正是希望这种形式能够加深你对发布系统产品设计的概念理解。这里我们再一起简单回顾一下吧。
1张页面展示发布信息且仅有1张页面展示发布当时的绝大多数信息、数据和内容这个页面既要全面又要精准。
2 个操作按钮简化使用即页面上除了“回滚”按钮常在外最多同时展示2个操作按钮。目的是要降低发布系统的使用难度做到“谁开发谁运行”。
3 种发布结果,即成功、失败和中断状态,目的是简单、明了地显示用户最关心的发布结果。
4类操作选择包括开始发布、停止发布、发布回退、发布重试目的是使状态机清晰明了。
5个发布步骤即markdown、download、install、verify和markup。这里需要注意到的是verify这步包含了预热由于耗时往往比较长一般采用异步的处理方式。
6大页面主要内容包括集群、实例、发布日志、发布历史、发布批次、发布操作来统一、简洁而又详细呈现发布中和未发布时的各种信息。
思考题
如果你是一个灰度发布系统的用户,你最关心的信息都有哪些?是否有我在这篇文章中没有提到的内容,你又将如何处理这些内容呢?
感谢你的收听,欢迎你给我留言。

View File

@ -0,0 +1,151 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
22 发布系统的核心架构和功能设计
你好,我是王潇俊。我今天分享的主题是:发布系统的核心架构和功能设计。
我在分享《发布系统一定要注意用户体验》这个主题时,介绍了从用户体验的角度出发,设计一套发布系统的设计理念,以及具体实现。但是,用户体验设计得再好,后端系统无法支持,也就如同巧妇难为无米之炊。
截止到目前携程一共有7000多个应用平均每周生产发布8000多次而测试环境的发布平均每周要40000多次如果发布系统没有一个清晰的架构设计完成这样艰巨的任务是难以想象的。
所以,今天我就从核心架构和功能设计的角度,和你聊聊如何设计一套发布系统。
发布系统架构
作为整个持续交付体系中极为重要的一个环节,应用的发布是提升交付效率的关键。高效的发布系统架构应该是清晰的、健壮的、低耦合的,从而达到在最糟糕的情况下也能运作的目的。
携程在发布系统这件事上也不是一蹴而就在经历了各种尝试和努力后最终设计出了一套分布式、高可用、易扩展的发布系统其系统架构如图1所示。
图1 携程发布系统的系统架构
Roll Engine即发布引擎主要负责创建发布批次按批次粒度实施部署策略通过异步方式调用Salt Master服务再分发到各个Agent节点上执行部署任务。真正的单机部署脚本会根据不同的应用或机型进行分发和定制。而 Controller ,则作为接收外部指令和读写数据的控制器。当然,对于一些对外的通知事务,发布系统会采用消息机制向外广播。
发布系统需要将发布相关的元数据信息主要包括App应用、Group集群、Server服务器等从外部CMDB资产数据库落地到本地数据库作为一层本地缓存。
数据更新的方式,主要有两种,一种是在每次发布前更新,另一种是通过消费通知消息进行更新,以保证发布元数据的准确性。
根据携程发布系统结构设计的经验,我总结了几个需要注意的点:
每台服务实例上的发布脚本一旦产生则不再修改,以达到不可变模型的要求;
发布引擎和Salt Master之间采用异步通信但为增强系统健壮性要有同步轮询的备案
面对频繁的信息获取,要善用缓存,但同时也一定要慎用缓存,注意发布信息的新鲜度。
发布系统核心模型
发布系统的核心模型主要包括Group、DeploymentConfig、Deployment、DeploymentBatch和DeploymentTarget这5项。
Group即集群一组相同应用的实例集合是发布的最小单元其概念如图2所示。
图2 Group概念示意图
同时Group的属性非常重要包括Site站点、Path虚拟路径、docBase物理路径、Port应用端口、HealthCheckUrl健康检测地址等这些属性都与部署逻辑息息相关。携程之所以这样设计是因为group这个对象直接表示一个应用的一组实例这样既可以支持单机单应用的部署架构也可以支持单机多应用的情况。
DeploymentConfig即发布配置提供给用户的可修改配置项要通俗易懂包括单个批次可拉出上限比、批次间等待时间、应用启动超时时间、是否忽略点火。
Deployment即一个发布实体主要包括Group集群、DeploymentConfig发布配置、Package发布包、发布时间、批次、状态等等信息。
DeploymentBatch即发布批次通常发布系统选取一台服务器作为堡垒批次集群里的其他服务器会按照用户设置的单个批次可拉出上限比划分成多个批次必须先完成堡垒批次的发布和验证才能继续其他批次的发布。
DeploymentTarget即发布目标服务器或实例它与该应用的Server列表中的对象为一对一的关系包括主机名、IP地址、发布状态信息。
这里一定要注意,发布系统的对象模型和你所采用的部署架构有很大关系。 比如携程发布系统的设计中并没有pool这个对象而很多其他企业却采用pool实现对实例的管理。又比如在针对Kubernetes时我们也需要根据它的特性针对性地处理Set对象等等。
发布流程及状态流转
发布系统的主流程大致是:
同一发布批次中,目标服务器并行发布;不同发布批次间则串行处理。每台目标服务器在发布流程中的五个阶段包括 Markdown、Download、Install、Verify、Markup。
如图3描绘了具体的发布流程。
图3 携程发布系统的流程
发布过程从技术实现上来说,就是通过状态机控制状态流转,而每个状态都对应一些具体的操作。
那么,我们就一起来看看整个状态流转如何通过状态机进行控制:
首先借助于Celery分布式任务队列的Chain函数发布系统将上述的Markdown、Download、Install、Verify、Markup五个阶段定义为一个完整的链式任务工作流保证一个Chain函数里的子任务会依次执行。
其次每个子任务执行成功或失败都会将DeploymentTarget设置为对应的发布状态。 例如堡垒批次中的DeploymentTarget执行到Verify点火这个任务时如果点火验证成功那么DeploymentTarget会被置为VERIFY_SUCCESS点火成功的状态否则置为VERIFY_FAILURE点火失败的状态。
发布过程中如果有任意一台DeploymentTarget发布失败都会被认为是发布局部失败允许用户重试发布。因此重试发布只针对于有失败的服务器批次进行重试对于该批次中已经发布成功的服务器发布系统会对比当前运行版本与发布目标版本是否一致如果一致且点火验证通过的话则直接跳过。
这里需要注意的是, 堡垒批次不同于其他批次堡垒批次中DeploymentTarget的Chain的最后一个子任务是Verify点火而不是Markup。
再次点火验证成功DeploymentTarget的状态流转到VERIFY_SUCCESS后需要用户在发布系统页面上触发Baking操作即堡垒批次中DeploymentTarget的Markup此时执行的是一个独立的任务事务会将堡垒批次中的服务器拉入集群接入生产流量。也就是说这部分是由用户触发而非自动拉入。BAKE_SUCCESS堡垒拉入成功之后就是其他批次的RollingOut事务了这也是一个独立的任务需要由用户触发其他批次开始发布的操作。
最后,设置发布批次。
除堡垒批次外其他的机器会按照用户自主设置的最大拉出比来分批每个批次间允许用户设置等待时间或者由用户手动执行启动下个批次发布的操作。从第3个批次起允许用户设置较短的或者不设置等待批次的间隔时间以提高最后几个批次的速度即尾单加速这样可以提高整个发布过程的效率。
携程的发布系统利用了一个分布式异步任务框架来处理整个发布过程的事务然后通过状态机来控制这些任务的开始和停止。当然由于我们使用Python语言所以选择了Celery框架其他语言也有很多成熟的类似框架也建议你在实施过程中充分利用这些框架的优势。
刹车机制
为了保证用户体验的顺畅和发布系统的高容错性除堡垒批次外的每个发布批次均采用了Quick and Dirty的发布方式即不管成功或失败优先尝试把版本发布完继续执行下个发布批次后续再解决个别目标服务器发布失败的问题。
试想在这种发布方式下,我们应该如何避免大面积的发布失败影响业务呢?
于是我们需要为发布系统提供刹车机制即在每个批次开始发布任务前系统会根据用户设置的单个批次可拉出上限比进行失败率的计算与控制。发布时一旦达到这个失败率立即中断当前发布从而保护Quick and Dirty发布方式。
一旦刹车后,发布系统允许用户先执行重试发布操作,如果因为重试批次中的服务器失联或者其他外因导致重试无果,则用户需要终止当前发布,重新设置单个批次可拉出上限比,或者临时将服务器从各个负载均衡设备或访问入口中拉出(与发布拉出为独立的标志位),由此发布系统的分组策略会剔除被拉出的服务器,不再做发布。与此同时,用户可以同步进行失败服务器的下线或者更换操作,而不会阻塞发布。
提升发布速度
从上面的发布过程中,你不难发现影响发布速度的步骤通常是下载和点火。
为了提高下载速度携程在各个机房搭建了发布包专用的存储系统实现了类似CDN的功能编译和打包系统在任何一个写入点写入发布包都会尽快同步到各个IDC及各个独立存储中这样真正发布时服务器只需从本IDC或本网段做下载。
对于点火,携程在框架层面统一提供了点火入口和常规的点火验证方法,收口了所有应用的点火验证规范和标准,容错性和速度都得到大幅提升。
而回滚方面不再设置堡垒批次发布系统默认提供了单个批次可拉出上限比为50%的配置,即分两个批次执行回滚发布。这样可以追求最快的回滚速度。当然在日常发布过程中,比如扩容发布,也可以不设置堡垒批次,但前提是待发布的版本已经被证明可以正确工作。
在单机部署逻辑上,发布系统在服务器本地保留了多个版本,在回滚发布时,可快速进行目录切换,进而直接省略了下载发布包的过程,最大限度地缩短应用的故障时间,提升回滚速度。
降级机制
对外部系统的服务依赖例如LB负载均衡服务的拉出或拉入调用发布系统需要具备降级机制熔断外部系统依赖的能力一旦发现外部服务不可用可以及时处理保证用户的紧急发布需求。
对外部系统的元数据依赖例如从CMDB同步Group信息的场景下发布系统可以使用Redis锁合并重复的请求提高同步数据的吞吐能力以解决重试并发的问题。另外由于发布系统做了数据缓存也就同时具备了一键降级CMDB等其他外部系统依赖的能力。
降级机制能够保证在突发异常情况下,发布系统可以解除所有外部依赖,独立完成任何发布应用的任务。也就是说,降级机制可以保证发布系统做到,只有部署包存在,就能恢复服务。
总结
我今天分享的主题就是,从后端系统设计的角度,来看看一套发布系统的核心架构和功能应该如何设计。我以携程目前使用的发布系统为例,从发布系统的架构、核心模型、发布流程及状态流转三个方面,展开了今天的分享。
首先,高效的发布系统架构应该是清晰的、健壮的、低耦合的,携程在经历各种迭代后,采用了以 Protal、Controller、Roll Engine、Salt Scripts 为核心的分层架构。
其次,在设计核心模型时,考虑到部署架构的个性化设计,即要兼容单机单应用,又要兼容单机多应用的问题,携程的发布系统采用了以 Group 和 Deployment 为核心的主要对象模型设计方案。这里你需要注意的是,发布系统的对象模型和你所采用的部署架构有很大关系,所以还是要量体裁衣。
再次关于发布系统的发布流程可以通过状态流转控制单机发布的5个步骤和发布批次。这部分你需要注意的是堡垒批次不同于其他批次堡垒批次中DeploymentTarget的Chain的最后一个子任务是Verify点火而不是Markup。
最后,一款出色的发布系统,除了要考虑架构、核心模型,以及发布流程的问题外,还必须同时考虑一些附加问题,比如:
为了降低Quick and Dirty方式对业务功能的影响你需要提供发布刹车机制
利用分布式存储、尾单加速、symlink回滚等方式可以提升发布速度
必要的降级机制,可以保证发布系统的高可用。
思考题
如果你对携程的Tars感兴趣可以通过https://github.com/ctripcorp/tars获取。你也可以参考开源版本Tars迅速搭建一套发布系统并结合自身的情况看看还需要增加什么设计
欢迎你给我留言。

View File

@ -0,0 +1,116 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
23 业务及系统架构对发布的影响
在分享《发布系统一定要注意用户体验》和《发布系统的核心架构和功能设计》这两大主题时,我分别从用户体验和架构设计两个方面,和你分享了携程灰度发布系统的一些经验和实践。但是,要做出一个出色的发布系统,仅仅考虑这两方面还不够。
因为发布系统最终要服务的对象是业务应用,而业务应用又和业务、企业的系统架构有紧密的联系,所以要做好一套发布系统,我们还要考虑其要服务的业务及系统架构的需要,并且要善于利用系统架构的优势为发布系统服务。
那么接下来,我们就一起来看看,业务、企业整体的系统架构会给发布系统带来什么影响,发布系统又可以借用它们的哪些架构能力。
单机单应用还是单机多应用?
众所周知,.NET应用采用的基本都是Windows + IIS 的部署模式这是一种典型的单机、单Web容器、多应用的部署模式。
在这种模式下,单机多应用的问题主要体现在两个方面:
一方面应用划分的颗粒度可以做到非常细小往往一个单机上可以部署20~30个应用甚至更多而且应用与应用间的隔离性较差
另一方面由于IIS的设计问题不同虚拟目录之间可能存在共用应用程序池的情况即多个应用运行在同一个进程下导致任何一个应用的发布都可能对其他的关联应用造成影响。
所以,对发布系统而言,.NET应用的这种架构简直就是噩梦发布系统需要重新定义发布单元的含义并维护每个虚拟目录和对应的发布单元与应用之间的关系。
在携程,我们为了解决这个问题采用的方案是,去除根目录的被继承作用,默认每个虚拟目录就是一个应用,并且每个虚拟目录的应用程序池独立。而每个虚拟目录以应用的名称命名,保证单机上不会发生冲突。
这样,应用与应用之间就解耦了,发布系统的设计也会变得简单很多。
除了上面这种.NET的单机多应用情况无法改变外其他所有Linux下的应用都可以做到单机单应用。其实这也正是虚拟化思想最初的设计理念。为什么呢因为与单机多应用相比单机单应用更简单直接更易于理解和维护。
比如单机单应用不需要考虑分配服务端口的问题所有的Web应用都可以使用同一个统一端口比如8080端口对外服务。但是单机多应用的情况下就要考虑端口分配的问题这算不算是徒增烦恼呢
另外,单机单应用在故障排除、配置管理等方面同样具有很多优势。一言以蔽之,简单的才是最好的。
当然,简单直接,也正是发布系统所希望看到的情况。
增量发布还是全量发布?
增量发布还是全量发布,其实是个挺有意思的问题。
在过去网络带宽是瓶颈的年代里,或者面对体量巨大的单体应用时,增量发布可以节省很多计算资源,确实是一个很好的解决方案。甚至现在的移动客户端发布,也还会选择增量发布的技术来快速发布静态资源。
但是,互联网应用的场景下,更多的发布需求来自于发布频率非常高的后台服务。在这样的情况下,增量发布反而会造成不必要的麻烦。
比如,增量发布对回滚非常不友好,你很难确定增量发布前的具体内容是什么。如果你真的要确定这些具体内容的话,就要做全版本的差异记录,获取每个版本和其他版本间的差异,这是一个巨大的笛卡尔积,需要耗费大量的计算资源,简直就是得不偿失。很显然,这是一个不可接受的方案。
反之,全量发布就简单多了,每个代码包只针对一个版本,清晰明了,回滚也非常简单。所以,我的建议是,全量发布是互联网应用发布的最好方式。
如何控制服务的 Markup 和 Markdown
首先你需要明确一件事儿除了发布系统外还有其他角色会对服务进行Markup和Markdown操作。比如运维人员在进行机器检修时人为的Markdown操作。因此我们需要从发布系统上能够清晰地知晓服务的当前状态和最后进行的操作。
另外这里还引入了一个全新的问题当一个服务被执行Markdown操作后什么系统还能继续处理这个服务而什么系统则不能继续处理这个服务
比如发布系统如果发现服务最后进行的操作是Markdown那么还能不能继续发布呢如果发布那发布之后需不需要执行Markup操作呢有些情况下用户希望利用发布来修复服务因此需要在发布之后执行Markup而有些情况下用户发布后不能执行Markup很可能运维人员正在维护网络。
为了解决这个问题携程在设计系统时用不同的标志位来标识发布系统、运维操作、健康检测以及服务负责人对服务的Markup和Markdown状态。4个标志位的任何一个为Markdown都代表暂停服务只有所有标志位都是Markup时服务中心才会向外暴露这个服务实例。
这样做的好处是将4种角色对服务的操作完全解耦他们只需要关心自己的业务逻辑既不会发生冲突也不会影响事务完整性更无需采用其他复杂的锁和Token机制进行排他操作。
检查、预热和点火机制
我在分享从用户体验和核心架构的角度设计发布系统时提到过发布过程中必然会有Verify的过程。
如果这个过程依赖手工操作的话,一来难以保证发现问题的速度,二来也很难保证落实力度。但如果这个过程能够做到自动化的话,则可以大幅减少因发布而引发的生产故障,同时还可以保证一些服务启动依赖检测。
在携程我们借助于VIValidate Internal框架中间件实现了Verify过程的自动化我们把这个过程形象地叫作“点火”。所有使用这个中间件的应用启动后发布系统执行的第一个操作就是这个VI接口所提供的检查方法当然用户完全可以根据业务自定义应用的检查方法。这也就保证了发布过程中一定会执行到Verify过程而不会因为各种原因而被遗漏。
Verify是一个异步过程可能耗时较长但是程序员们很快就发现这个VI组件不但可以做检查还可以在检查时进行一些预热、预加载这样的任务。
携程通过这样一个中间件组件,很高效地解决了发布过程中的两个问题:
如何对各个应用做个性化的自动化检查;
如何在发布过程中解决应用预加载这类的需求。
如何保证堡垒流量?
我在《发布是持续交付的最后一公里》这篇文章中介绍金丝雀发布时,说到了携程选择的是综合使用滚动发布和金丝雀发布的方案,使用堡垒机的方式来预发和测试新的版本。
但是采用这个方案我们需要考虑分布式服务架构带来的影响即如何保证堡垒机的流量一定会分发到对应下游服务的堡垒机上。就好比发布一个包含Web和Service两个应用的新功能我需要保证Web堡垒的流量只发送给Service的堡垒否则就会出问题。
我们解决这个问题的思路是软负载系统通过发布系统获得堡垒机的IP在堡垒机发出的请求的header中附加堡垒标识这样软负载在判断出有堡垒标识时则只会将请求发向下游的堡垒机。当然是否加注这个标识完全由发布系统在发布堡垒时控制。
这样,我们就解决堡垒流量控制的问题。
总结
因为发布系统最终服务的对象是业务应用,所以发布系统的设计除了考虑用户体验、核心架构外,还要注意业务、系统架构对发布系统的影响,并且要合理利用业务、系统架构的能力,去完善发布系统的设计。
业务、系统架构对发布系统的影响,主要体现在是选择单机单应用还是单机多应用、选择增量发布还是全量发布这两大方面。在这里,我的建议是简单的才是最好的,即采用单机单应用的部署架构;对于后台应用,以及发布非常频繁的应用来说,全量发布更直接,更容易达成版本控制,并做到快速回滚。
除此之外,我们还要利用系统架构使发布系统具有更优的发布能力,这主要包括三个方面:
利用软负载或者服务通讯中间件的多个状态位简单直接地解决多角色对服务Markup和Markdown的冲突问题
利用中间件的能力使得Verify过程不会被遗忘同时还可以完成自动化检查和预热达到高效可控的目的
通过软负载和通讯中间件,解决堡垒机与堡垒机间的流量分发问题,保证堡垒机的流量只在上下游服务的堡垒机中流转。
因此,有效运用系统架构的能力,会为你带来意想不到的收益。
思考题
在你的实际工作中,发布系统有没有受到架构设计的影响?是积极影响还是消极影响呢?消极影响的话,你有想过如何改变吗?
感谢收听,欢迎你给我留言。

View File

@ -0,0 +1,181 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
24 如何利用监控保障发布质量?
你好,我是王潇俊,今天我和你分析的主题是:如何利用监控保障发布质量。
在前几次的分享中,我详细介绍了发布在持续交付过程中的重要地位,以及如何去思考和设计一套灰度发布系统。作为发布及监控系列的最后一篇文章,今天我就和你聊聊灰度发布的最后一个过程:监控,以及如何做好发布后的监控。
之所以有今天这次分享,最重要的原因是要告诉你:千万不要认为发布结束,就万事大吉了。特别是生产发布,发布结束时才是最危险的时刻。 因为,故障都是伴随着发布变更而来的。所以,我们需要有一套监控系统,及时发现问题、定位问题,帮助我们减少因故障带来的损失。
同时随着分布式系统的普及以及APMApplication Performance Management系统性能管理概念的兴起针对分布式系统的全链路监控系统也逐步发展起来为持续交付提供了有力的支持。可以说一套性能优良的监控系统可以为持续交付保驾护航。
当然,这个专栏的主要内容是帮你解决持续交付的问题,所以我不会去分享监控系统如何设计这种需要一整个专栏才能解决的问题。
因此,我今天分享的重点是,帮助你去理解监控的常规概念,和你聊一些技术选型方案,并一起讨论一些与持续交付有关的问题。
监控的分类
从一般意义上来讲,我们会把监控分为以下几类:
用户侧监控,关注的是用户真正感受到的访问速度和结果;
网络监控即CDN与核心网络的监控
业务监控,关注的是核心业务指标的波动;
应用监控,即服务调用链的监控;
系统监控,即基础设施、虚拟机及操作系统的监控。
因此我们要做好一个监控系统可以从这五个层面去考虑将这五个层面整合就可以做成一个完整的、端到端的全链路监控系统。当然监控系统的这5个层次的目标和实现方法有所不同接下来我将分别进行介绍。
第一,用户侧监控
随着移动互联网的兴起,用户对 Mobile App 的体验成了衡量一个系统的重要指标,所以对用户侧的监控也就变得尤为重要。因为,它能够第一时间向我们反馈用户使用系统的直观感受。
用户侧的监控通常从以下几个维度进行,这些监控数据既可以通过打点的方式,也可以通过定期回收日志的方式收集。
端到端的监控主要包括包括一些访问量、访问成功率、响应时间、发包回包时间等等监控指标。同时我们可以从不同维度定义这些指标比如地区、运营商、App版本、返回码、网络类型等等。因此通过这些指标我们就可以获得用户全方位的感受。
移动端的日志。我们除了关注系统运行的日志外,还会关注系统崩溃或系统异常类的日志,以求第一时间监控到系统故障。
设备表现监控主要指对CPU、内存、温度等的监控以及一些页面级的卡顿或白屏现象或者是直接的堆栈分析等。
唯一用户ID的监控。除了以上三种全局的监控维度外用户侧的监控一定要具备针对唯一用户ID的监控能力能够获取某一个独立用户的具体情况。
第二,网络监控
网络是整个系统通路的保障。因为大型生产网络配置的复杂度通常比较高,以及系统网络架构的约束,所以网络监控一般比较难做。
一般情况下从持续交付的角度来说网络监控并不需要做到太细致和太深入因为大多数网络问题最终也会表现为其他应用层面的故障问题。但是如果你的诉求是要快速定位root cause那就需要花费比较大的精力去做好网络监控了。
网络监控,大致可以分为两大部分:
公网监控。这部分监控可以利用模拟请求的手段比如CDN节点模拟、用户端模拟获取对CDN、DNS等公网资源以及网络延时等监控的数据。当然你也可以通过采样的方式获取这部分数据。
内网监控。这部分监控,主要是对机房内部核心交换机数据和路由数据的监控。如果你能打造全局的视图,形成直观的路由拓扑,可以大幅提升监控效率。
第三,业务监控
如果你的业务具有连续性,业务量达到一定数量后呈现比较稳定的变化趋势,那么你就可以利用业务指标来进行监控了。一般情况下,单位时间内的订单预测线,是最好的业务监控指标。
任何的系统故障或问题,影响最大的就是业务指标,而一般企业最重要的业务指标就是订单和支付。因此,监控企业的核心业务指标,能够以最快的速度反应系统是否稳定。 反之,如果系统故障或问题并不影响核心业务指标,那么也就不太会造成特别严重的后果,监控的优先级和力度也就没有那么重要。
当然,核心业务指标是需要经验去细心挑选的。不同业务的指标不同,而即使定义了指标,如何准确、高效地收集这些指标也是一个很重要的技术问题。比如,能不能做到实时,能不能做到预测。这些问题都需要获得技术的有力支持。
第四,应用监控
分布式系统下,应用监控除了要解决常规的单个应用本身的监控问题外,还需要解决分布式系统,特别是微服务架构下,服务与服务之间的调用关系、速度和结果等监控问题。因此,应用监控一般也被叫作调用链监控。
调用链监控一般需要收集应用层全量的数据进行分析要分析的内容包括调用量、响应时长、错误量等面向的系统包括应用、中间件、缓存、数据库、存储等同时也支持对JVM等的监控。
调用链监控系统,一般采用在框架层面统一定义的方式,以做到数据采集对业务开发透明,但同时也需要允许开发人员自定义埋点监控某些代码片段。
另外,除了调用链监控,不要忘了最传统的应用日志监控。将应用日志有效地联合,并进行分析,也可以起到同样的应用监控作用,但其粒度和精准度比中间件采集方式要弱得多。
所以,我的建议是利用中间件作为调用链监控的基础,如果不具备中间件的能力,则可以采用日志监控的方式。
第五,系统监控
系统监控指的是对基础设施的监控。我们通常会收集CPU、内存、I/O、磁盘、网络连接等作为监控指标。
对于系统监控的指标我们通常采用定期采样的方式进行采集一般选取1分钟、3分钟或5分钟的时间间隔但一般不会超过5分钟否则监控效果会因为间隔时间过长而大打折扣。
发布监控的常见问题
持续交付,或者发布系统,对监控的诉求又是什么呢?其实简单来说只有一句话,即:快速发现发布带来的系统异常。
对于这样的诉求,优先观察业务监控显然是最直接、有效的方式。但是只观察业务监控并不能完全满足这样的需求,因为有两种情况是业务监控无能为力的:
第一种情况是我们所谓的累积效应,即系统异常需要累积到一定量后才会表现为业务异常;
另外一种情况就是业务的阴跌,这种小幅度的变化也无法在业务监控上得到体现。
因此,我们还需要配合应用监控,关注被发布应用的异常表现。
但是,在分布式系统,或者微服务架构下,有时被发布应用本身并没有异常表现,却影响了与之相关联的其他应用。所以,除了关注被发布应用本身以外,我们还要关注它所在的调用链的整体情况。
在持续交付体系中,还有一些关于监控的其他问题,主要包括测试环境是否也需要监控、发布后要监控多久,以及如何确定异常是不是由你刚刚发布的应用引起的。接下来,我们一起看看如何解决这三个问题。
第一,测试环境也要监控吗?
首先,我们需要认识到一个问题,即:部署一套完整的监控系统的代价非常昂贵。而且,监控作为底层服务,你还要保证它的稳定性和扩展性。
因此,测试环境是否需要监控,确实是一个好问题。
我来说说我建议的做法:
如果你的监控系统只能做到系统监控或日志级别的系统监控,那么对于一些对系统性能压榨比较厉害、对稳定性也没太多要求的测试环境来说,无需配备完整的监控系统。
如果你的监控系统已经做到了调用链甚至全链路的监控那么监控系统无疑就是你的“鹰眼”除了发现异常它还可以在定位异常等方面给你帮助比如对测试环境的Bug定位、性能测试等都有极大帮助。在这样的情况下你就一定要为测试环境配备监控系统。
你可能还会问,测试环境有很多套,是不是每套测试环境都要对应一套监控系统呢?这倒未必。你可以对监控系统做一些改造,通过数据结构等方式去兼容多套测试环境。
第二个问题,发布后需要监控多久?
一般来说,需要延时监控的情况都是针对生产发布来说的。
如果生产发布过程本身就是一个灰度发布过程的话,那么你基本就没有必要进行延时监控了。
但是如果整个灰度过程本身耗时并不长的话我的建议是要进行一定时间的延时监控。我们通常认为发布完成30分钟以后的异常都属于运行时异常。所以我建议的发布后监控时间为30分钟。
第三个问题,如何确定异常是由我的发布引起的?
具备了持续部署能力之后,你最直观的感受就是发布频次变高了。
以携程为例我们每天的生产发布频次超过2000次如果算上测试环境的发布则要达到1万次左右。如此高频率的发布我怎么确定某个异常是由我这次的发布引起的呢而且除了发布还同时进行着各类运维变更操作我怎么确定某个异常是发布造成的而不是变更造成的呢
解决这个问题,你需要建立一套完整的运维事件记录体系,并将发布纳入其中,记录所有的运维事件。当有异常情况时,你可以根据时间线进行相关性分析。
那么,如何构建一套完整的运维事件记录体系呢?很简单,你可以通过消息总线的形式去解决这个问题。
总结
今天,我围绕着灰度发布的最后一个过程:监控,展开了这次的分享。因为我们这个专栏要解决的主要问题是持续交付,所以我并没有过于详细地阐述如何设计一个监控系统,而只是为你介绍了监控体系的一些基本概念,以及一些与持续交付、持续部署相关的问题。
首先,我介绍了监控的几种分类,以及分别可以采用什么方式去采集数据:
用户侧监控,可以通过打点收集,或者定期采集日志的方式进行数据收集;
网络监控,通过模拟手段或定期采样进行收集;
业务监控,需要定义正确的指标以及相匹配的采集技术,务必注意实时性;
应用监控,可以通过中间件打点采集,也可以通过日志联合分析进行数据采集;
系统监控,通常采用定期采样的方式收集数据。
其次,我和你分享了三个对发布来说特别重要的监控问题:
测试环境的监控需要视作用而定,如果不能帮助分析和定位问题,则不需要很全面的监控;
一般发布后我建议继续坚持监控30分钟把这个流程纳入发布流程中
完整的运维事件记录体系,可以帮你定位某次故障是否是由发布引起的。
通过今天的分享,我们可以明白,只有拥有了强大的监控系统,我们才能放手持续交付,即监控可以为持续交付保驾护航。
思考题
你所在的公司是如何构建监控体系的呢,达到持续交付的需求了么?
欢迎你给我留言。

View File

@ -0,0 +1,222 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
25 代码静态检查实践
你好,我是王潇俊,今天我和你分享的主题是:代码静态检查实践。
从这次分享开始,我们要正式开始分享测试管理系列这个主题了。测试管理本身是一个很大的范畴,而且和我们之前聊到过的环境、配置等关系密切。
因为这个专栏我们要解决的最主要的问题是持续交付,所以我在这个测试管理这个系列里面,不会去过多的展开测试本身的内容,而是要把重点放在与持续交付相关的三个重点上:
代码静态检查;
破坏性测试;
Mock与回放。
这三个重点内容,我会分别用一篇文章的篇幅去解释。今天,我们就先从代码静态检查的实践开始吧。
虽然不同编程语言会使用不同的静态检查工具,但这些静态检查工具的工作原理和检查流程很类似。所以,为了更好地聚焦核心内容,我选择互联网公司常用的 Java 语言的静态检查来展开今天的分享。
如果你所在公司采用的是其他编程语言,那也没关系,相信你理解了这篇文章中关于原理、流程的内容后,也可以解决你所用具体语言的代码静态检查。如果在这个过程中,你还遇到了其他问题,欢迎你给我留言,我们一起去解决。
为什么需要代码静态检查?
代码静态检查,即静态代码分析,是指不运行被测代码,仅通过分析或检查源程序的语法、结构、过程、接口等检查程序的正确性,并找出代码中隐藏的错误和缺陷(比如参数不匹配、有歧义的嵌套语句、错误的递归、非法计算、可能出现的空指针引用等等)。
在软件开发的过程中静态代码分析往往在动态测试之前进行同时也可以作为设计动态测试用例的参考。有统计数据证明在整个软件开发生命周期中有70%左右的代码逻辑设计和编码缺陷属于重复性错误,完全可以通过静态代码分析发现和修复。
看到这个统计结果,相信你已经蠢蠢欲动,准备好好执行代码静态检查了,这也是为什么我们要做代码静态检查的原因。
但是,代码静态检查规则的建立往往需要大量的时间沉淀和技术积累,因此对初学者来说,挑选合适的静态代码分析工具,自动化执行代码检查和分析,可以极大地提高代码静态检查的可靠性,节省测试成本。
静态检查工具的优势
总体来说,静态检查工具的优势,主要包括以下三个方面:
帮助软件开发人员自动执行静态代码分析,快速定位代码的隐藏错误和缺陷;
帮助软件设计人员更专注于分析和解决代码设计缺陷;
显著减少在代码逐行检查上花费的时间,提高软件可靠性的同时可以降低软件测试成本。
目前已经有非常多的、成熟的代码静态检查工具了。其中SonarQube 是一款目前比较流行的工具,国内很多互联网公司都选择用它来搭建静态检查的平台。
SonarQube采用的是B/S架构通过插件形式可以支持对Java、C、C++、JavaScript等二十几种编程语言的代码质量管理与检测。
Sonar通过客户端插件的方式分析源代码可以采用IDE插件、Sonar-Scanner插件、Ant插件和Maven插件等并通过不同的分析机制完成对项目源代码的分析和扫描然后把分析扫描的结果上传到Sonar的数据库之后就可以通过Sonar Web界面管理分析结果。
静态代码检查近五年的发展状况
既然静态检查工具的优势如此明显,那么我们就一起看看在实际场景下,这些工具的实施情况又如何呢。
自2013年以来国内的大型互联网公司已开始积极地搭建持续交付环境并如火如荼地开展持续交付的实践。在这个过程中为了获得更高的投入产出比实施团队通常会组织各个业务线的负责人共同确立一套通用的交付流程。
同时,静态代码检查工具发展迅速,加之各大互联网公司全力追求效率的综合作用,于是持续交付流程除了启用代码静态检查工具外,还发生了如下变化:
从某些团队开展静态检查到所有团队都开展静态检查;
持续交付系统从缺少静态检查到强制静态检查;
从借用其他公司的检查规则到形成自己的检查规则。
由此可见,代码静态检查已经从可有可无变得不可或缺了,已经从部分实施进入到了全体实施的阶段。
设定科学的检查流程
既然代码静态检查已经变得不可或缺了,那么你自然需要明白一个问题,即如何才能把它全面实施起来。
在持续交付实践中,我们鼓励尽早地发现代码问题。为了达到这样的效果,静态检查相关的流程可设定如下:
鼓励开发人员在开发环境不管是IDE还是编辑器加命令行下执行静态检查
不管采用的是主干开发还是特性分支开发的分支策略,都尽可能地在代码合入主干之前,通过静态检查;
没有通过静态检查的产品包,不允许发布到线上或用户验证环境。
整个流程可以用下面这张图来表示。
图1 静态检查的流程
其中S2和S3这两个环节我们可以借助持续交付系统进行强制检查来完成。
这三个环节的检查,我需要特别说明两点:
公司或团队通常会有一个公共检查规则的最小集合简称Rules不管哪个步骤的检查至少得保证通过这个最小集合的检查。如果采用SonarQube作为静态检查的管理平台那么可以把这个Rules配置为一个Profile。利用这样一个机制你可以很方便地管理规则配置。
不管是开发环境还是持续交付系统都需要及时、方便地获取到这个统一的Rules。这也正是SonarQube 在努力实现的它推出的IDE插件SonarLint只需简单的几步配置就能同步Sonar服务最新的 Profile。-
虽然目前SonarLint还不能完全替代 FindBugs、PMD和Checkstyle这三个最常用的静态检查工具但是我们可以预见类似SonarLint这样的IDE插件在开发人员群体中是颇受欢迎的。你只需安装一个插件就能涵盖所有的静态检查规则而且可以毫不费力地实时获取公司统一的检查标准。
跳过检查的几类方式
为持续交付体系搭建好静态检查服务并设置好Rules后你千万不要认为事情结束了直接等着看检查结果就行了。因为通常还会有以下问题发生
代码规则可能不适合程序语言的多个版本;
第三方代码生成器自动产生的代码存在问题,该怎么略过静态检查;
静态检查受客观情况的限制,存在误报的情况;
某些规则对部分情况检查得过于苛刻;
其他尚未归类的不适合做静态检查的问题。
其实,这些问题都有一个共同特点:静态检查时不该报错的地方却报错了,不该报严重问题的地方却报了严重问题。
于是,我们针对这个共性问题的处理策略,可以分为三类:
把某些文件设置为完全不做静态检查;
把某些文件内部的某些类或方法设置为不做某些规则的检查;
调整规则的严重级别,让规则适应语言的多个版本。
这样就可以提高静态检查的准确度了,接下来我们需要考虑的问题就是提高静态检查的效率了。
如何提高静态检查的效率?
提高静态检查的效率的重要性,可以概括为以下两个方面:
其一,能够缩短代码扫描所消耗的时间,从而提升整个持续交付过程的效率;
其二,我们通常会采用异步的方式进行静态检查,如果这个过程耗时特别长的话,会让用户产生困惑,从而质疑执行静态检查的必要性。
那么,怎么才能提升静态检查的效率呢?
除了提升静态检查平台的处理能力外,在代码合入主干前采用增量形式的静态检查,也可以提升整个静态检查的效率。增量静态检查,是指只对本次合入涉及的文件做检查,而不用对整个工程做全量检查。
当然,为了有效保证整个工程项目的代码质量,持续交付系统通常会在版本发布到用户验证环境或者上线之前,对整个工程进行全量检查。
这样做,既能保证产品上线的质量,又可以提高集成过程中的检查效率。
如何制定规则?
如果你要在实际工作中制定自己的个性化规则,又该如何进行呢?
在实践中,日常的定制规则往往有两种方式:
从已有的规则集合中挑选团队适用的规则,必要情况下调整规则的严重等级和部分参数;
基于某个规则框架,编写全新的规则。这种方式需要自行编码,难度成本较大,所以我一般不推荐你采用,确实找不到现成的规则时再采用这种方案。
Sonar代码静态检查实例
了解了代码静态检查的理论知识我们现在就来具体实践一下。你可以从中体会如何搭建一套Sonar服务并把它与实际流程结合起来。
第一步搭建Sonar服务安装CheckStyle 等插件。
图2 Sonar系统配置
第二步:设置统一的 Java 检查规则。
图3 Java规则设置
第三步在IDE中安装SonarLint插件后就可以使用SonarSource的自带规则了。
图4 IDE插件安装
第四步如果SonarLint的检查规则不能满足开发环境的要求你可以执行相关的mvn命令把检查结果吐到Sonar服务器上再看检查结果命令如下
mvn org.sonarsource.scanner.maven:sonar-maven-plugin:3.2:sonar -f ./pom.xml -Dsonar.host.url=sonar服务器地址 -Dsonar.login=账号名称 -Dsonar.password=账号密码 -Dsonar.profile=检查规则的集合 -Dsonar.global.exclusions=排除哪些文件 -Dsonar.branch=检查的分支
第五步在GitLab 的Merge Request 中增加Sonar静态检查的环节包括检查状态和结果等。
图5 GitLab MR集成Sonar结果
第六步发布到用户验证环境UAT先查看静态检查结果。如果没有通过检查则不允许发布。
图6 Sonar检查结果报告
通过上面这六步,一套代码静态检查机制就基本被构建起来了。
总结
在分享和你分享代码静态检查实践这个主题时,我分享了近五年国内的大型互联网公司在持续交付实践中摸爬滚打的经验。
从这五年的发展实践中,我们可以清楚地看到,越来越多的研发团队把静态检查作为了一个不可或缺的环节,这也确实帮助研发团队提升了代码质量。
当然,机器是死的,人是活的,我们千万不要过分迷信静态检查的结果,还要时刻擦亮眼睛,看看是否存在误报等问题。
思考题
为什么代码静态检查应尽量在开发前期就实施?
在你看来,一款好的静态检查工具或一套好的静态检查系统,应该具备哪些特点?
感谢收听,欢迎你给我留言。

View File

@ -0,0 +1,146 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
26 越来越重要的破坏性测试
你好,我是王潇俊。今天我和你分享的主题是:越来越重要的破坏性测试。
其实,持续交付中涉及到的与测试相关的内容,包括了单元测试、自动化测试、冒烟测试等测试方法和理念,我为什么我把破坏性测试拿出来,和你详细讨论呢?
原因无非包括两个方面:
其一,单元测试等传统测试方法,已经非常成熟了,而且你肯定也非常熟悉了;
其二,破坏性测试,变得越来越重要了。
那么,破坏性测试到底是因为什么原因变得原来越重要呢?
随着SOA、微服务等架构的演进分布式系统对测试的要求越来越高不再像传统的单体应用测试一样可以很容易地无缝嵌入到持续交付体系中。因为分布式系统的测试不仅需要大量的前提准备还存在着非常严重的服务依赖问题。
这就使得分布式系统的测试工作,除了要关注运行的应用本身外,还要考虑测试环境的因素。
很快,我们就发现,破坏性测试可以解决分布式系统测试的这些难题,而且还可以帮助我们解决更多的问题。它可以弥补传统持续交付体系只关注代码或应用本身,而忽略其他外部因素影响运行中代码的问题。而且,破坏性测试还能很好地证明整个分布式系统的健壮性。
所以,与其老生长谈一些传统的测试方法,不如我们一起看看更新鲜、更好用的破坏性测试。
什么是破坏性测试?
顾名思义,破坏性测试就是通过有效的测试手段,使软件应用程序出现奔溃或失败的情况,然后测试在这样的情况下,软件运行会产生什么结果,而这些结果又是否符合预期。
这里需要注意的是,我们需要使用的测试手段必须是有效的。为什么这样说呢,有两点原因。
第一,破坏性测试的手段和过程,并不是无的放矢,它们是被严格设计和执行的。不要把破坏性测试和探索性测试混为一谈。也就是说,破坏性测试不应该出现,“试试这样会不会出问题”的假设,而且检验破坏性测试的结果也都应该是有预期的。
第二,破坏性测试,会产生切实的破坏作用,你需要权衡破坏的量和度。因为破坏不仅仅会破坏软件,还可能会破坏硬件。通常情况下,软件被破坏后的修复成本不会太大,而硬件部分被破坏后,修复成本就不好说了。所以,你必须要事先考虑好破坏的量和度。
破坏性测试的流程与用例设计
说到底,破坏性测试还是一种人为、事先设计的测试方法,所以它的流程与普通的软件测试流程基本一致:都包括设计测试用例、开发测试脚本、执行测试脚本、捕获缺陷、报告缺陷的过程。
破坏性测试与普通测试流程,唯一不同的是,绝大部分普通测试可以在测试失败后,继续进行其他的测试;而破坏性测试,则有可能无法恢复到待测状态,只能停止后续的测试。
所以,在持续交付的哪个步骤和阶段执行破坏性测试,就非常讲究了,你需要经过严密地设计和预判。
所以,在设计破坏性测试的测试用例时,我们通常会考虑两个维度:
第一个维度是,一个破坏点的具体测试,即设计一个或一组操作,能够导致应用或系统奔溃或异常。此时,你需要注意两个问题:
出现问题后的系统或软件是否有能力按预期捕获和处理异常;
确认被破坏的系统是否有能力按照预期设计进行必要的修复,以确保能够继续处理后续内容。
第二个维度是,整个系统的破坏性测试,我们通常会采用压力测试、暴力测试、阻断链路去除外部依赖等方法,试图找到需要进行破坏性测试的具体的点。
这两个维度的测试方法、流程基本一致,区别只是第二维度的测试通常不知道具体要测试的点,所以破坏范围会更大,甚至可能破坏整个系统。
破坏性测试的执行策略
由于具有切实的破坏力这个特点,我们在执行破坏性测试时需要考虑好执行策略,以避免发生不可挽回的局面。
一般情况下在发布前执行破坏性测试相对比较安全。但这也不是绝对的比如你一不小心把UAT等大型联调环境搞坏了其代价还是很可观的。
因此,绝大部分破坏性测试都会在单元测试、功能测试阶段执行。而执行测试的环境也往往是局部的测试子环境。
那么问题又来了,真实环境要比测试子环境更复杂多变,在测试子环境进行的破坏性测试真的有效吗?这真是一个极好的问题。
所以,最近几年,技术圈衍生出一个很流行的理论:混沌工程。
混沌工程
随着分布式系统架构的不断进步,传统的破坏性测试也越发捉襟见肘,最主要的问题有两个:
第一,它被设计得太严格,以至于失真了。而真正有破坏力的故障,都是随机的、并行的、胡乱的。
第二,它覆盖不了生产环境,只能做到类似抽样检验的能力,且很难重复和持续。
所以,混沌工程的理论就应运而生了。
混沌工程是在分布式系统上建立的实验,其目的是建立对系统承受混乱冲击能力的信心。鉴于分布式系统固有的混乱属性,也就是说即使所有的部件都可以正常工作,但把它们结合后,你还是很难预知会发生什么。
所以,我们需要找出分布式系统的这些弱点。我把这些弱点归为了以下几类:
当服务不可用时,不可用或不完整的回退能力;
不合理的设置超时时间引起的重试风暴;
依赖服务接收过多的流量,从而导致中断;
由单个故障点引起的级联故障;
……
我们要避免这些弱点在生产过程中影响客户,所以需要一种方法来探知和管理这些系统固有的混乱,经实践证明,通过一些受控实验,我们能够观察这些弱点在系统中的行为。这种实验方法,就被叫作混沌工程。
说到具体的实验方法需要遵循以下4个步骤即科学实验都必须遵循的4个步骤
将正常系统的一些正常行为的可测量数据定义为“稳定态”;
建立一个对照组,并假设对照组和实验组都保持“稳定态”;
引入真实世界的变量,如服务器崩溃、断网、磁盘损坏等等;
尝试寻找对照组和实验组之间的差异,找出系统弱点。
“稳定态”越难被破坏,则说明系统越稳固;而发现的每一个弱点,则都是一个改进目标。
混沌工程也有几个高级原则:
使用改变现实世界的事件,就是要在真实的场景中进行实验,而不要想象和构造一些假想和假设的场景;
在生产环境运行,为了发现真实场景的弱点,所以更建议在生产环境运行这些实验;
自动化连续实现,人工的手工操作是劳动密集型的、不可持续的,因此要把混沌工程自动化构建到系统中;
最小爆破半径,与第二条配合,要尽量减少对用户的负面影响,即使不可避免,也要尽力减少范围和程度。
这样,就更符合持续交付的需求和胃口了。
Netflix公司的先驱实践
Netflix为了保证其系统在AWS上的健壮性创造了Chaos Monkey可以说是混沌工程真正的先驱者。
Chaos Monkey会在工作日期间随机地杀死一些服务以制造混乱从而检验系统的稳定性。而工程师们不得不停下手头工作去解决这些问题并且保证它们不会再现。久而久之系统的健壮性就可以不断地被提高。
Netflix公司有一句名言叫作“避免失败的最好办法就是经常失败”。所以Chaos Monkey会在日常反复持续执行真正地持续融合在系统中。这也为其持续交付中的测试提供了很好的借鉴。
总结
破坏性测试能够很好地测试分布式系统的健壮性,但也因为其破坏特点,使得它在持续交付中无法显示真正的威力;而混沌工程的提出,很好地解决了这个问题,使破坏性测试的威力能够在持续交付过程中被真正发挥出来。
混沌工程的一个典型实践是Netflix公司的Chaos Monkey系统。这个系统已经证明了混沌工程的价值和重要性值得我们借鉴。
思考题
你是否考虑过要在自己的公司引入Chaos Monkey如果要引入的话你又需要做些什么准备呢
感谢你的收听,欢迎给我留言。

View File

@ -0,0 +1,190 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
27 利用Mock与回放技术助力自动化回归
你好我是王潇俊。今天我和你分享的主题是利用Mock与回放技术助力自动化回归。
在《代码静态检查实践》和《越来越重要的破坏性测试 》这次的分享中,我介绍了对持续交付有重大影响的两个测试类型,即静态代码检查和破坏性测试。
你可能已经发现,这两种测试正好适用于持续集成和测试管理的一头、一尾:
静态代码检查,适合在所有其他测试类型开始之前优先进行,把住第一关;
破坏性测试,则适用于集成或验收测试之后,甚至是对系统进行持续长久的测试。
那么,我们现在再一起来看看,持续交付过程中还有哪些测试方法,以及还有哪些问题和难点吧。
持续交付中的测试难点
其实,对于持续交付中的测试来说,自动化回归测试是不可或缺的,占了很大的测试比重。而进行自动化回归测试,就始终会有“三座大山”横在你面前。
“第一座大山”:测试数据的准备和清理。
通常情况下,回归测试的用例是可以复用的,所以比较固定,结果校验也比较确定。而如果要实现回归测试的自动化,就需要保证每次测试时的初始数据尽量一致,以确保测试脚本可复用。
如果每次的数据都不同,那么每次的测试结果也会受到影响。为了做到测试结果的可信赖,就有两种方法:
一种是,每次全新的测试都使用全新初始化数据;
另一种是,在测试完成后,清除变更数据,将数据还原。
但是,这两种方法的实现,都比较麻烦,而且很容易出错。
“第二座大山”:分布式系统的依赖。
分布式系统因为有服务依赖的问题,所以进行一些回归测试时,也会存在依赖的问题。这个问题,在持续交付中比较难解决:
单元测试时要面对两难选择,测依赖还是不测依赖;
集成测试时,如何保证依赖服务的稳定性,或者说排除由稳定性带来的干扰,所以到底是依赖服务的问题,还是被测服务的问题很难确定;
真实的业务系统中,往往还存在多层依赖的问题,你还要想办法解决被测应用依赖的服务的依赖服务。
我的天呢,“这座大山”简直难以翻越。
“第三座大山”:测试用例的高度仿真。
如何才能模拟出和用户一样的场景,一直困扰着我们。
如果我们的回归测试不是自己设计的假想用例,而是真实用户在生产环境中曾经发生过的实际用例的话,那么肯定可以取得更好的回归测试效果。那么,有没有什么办法或技术能够帮助我们做到这一点呢?
如何翻越这“三座大山”我在这里给你准备了Mock和回放技术这个两大利器也就是我接下来要和你重点分享的内容。
两大利器之一Mock
我先来说说什么是Mock
如果某个对象在测试过程中依赖于另一个复杂对象而这个复杂对象又很难被从测试过程中剥离出来那么就可以利用Mock去模拟并代替这个复杂对象。
听起来是不是有点抽象下面这张图就是Mock定义的一个具象化展示我们一起来看看吧。
图1 测试过程中,被测对象的外部依赖情况展示
在测试过程中,你可能会遇到这样的情况。你要测试某个方法和对象,而这个被测方法和对象依赖了外部的一些对象或者操作,比如:读写数据库、依赖另外一个对象的实体;依赖另一个外部服务的数据返回。
而实际的测试过程很难实现这三种情况,比如:单元测试环境与数据库的网络不通;依赖的对象接口还没有升级到兼容版本;依赖的外部服务属于其他团队,你没有办法部署等等。
那么这时你就可以利用Mock技术去模拟这些外部依赖完成自己的测试工作。
Mock因为这样的模拟能力为测试和持续交付带来的价值可以总结为以下三点
使测试用例更独立、更解耦。利用Mock技术无论是单体应用还是分布式架构都可以保证测试用例完全独立运行而且还能保证测试用例的可迁移性和高稳定性。为什么呢-
因为足够独立,测试用例无论在哪里运行,都可以保证预期结果;而由于不再依赖于外部的任何条件,使得测试用例也不再受到外部的干扰,稳定性也必然得到提升。
提升测试用例的执行速度。由于Mock技术只是对实际操作或对象的模拟所以运行返回非常快。特别是对于一些数据库操作或者复杂事务的处理可以明显缩短整个测试用来的执行时间。-
这样做最直接的好处就是,可以加快测试用例的执行,从而快速得到测试结果,提升整个持续交付流程的效率。
提高测试用例准备的效率。因为Mock技术可以实现对外部依赖的完全可控所以测试人员在编写测试用例时无需再去特别考虑依赖端的情况了只要按照既定方式设计用例就可以了。
那么如何在测试中使用Mock技术呢
目前市场上有很多不同的Mock框架你可以根据自己的情况进行选择。主要的应用场景可以分为两类基于对象和类的Mock基于微服务的Mock。
第一基于对象和类的Mock
基于对象和类的Mock我比较推荐使用的框架是Mockito或者EasyMock。
Mockito或者EasyMock这两个框架的实现原理都是在运行时为每一个被Mock的对象或类动态生成一个代理对象由这个代理对象返回预先设计的结果。
这类框架非常适合模拟DAO层的数据操作和复杂逻辑所以它们往往只能用于单元测试阶段。而到了集成测试阶段你需要模拟一个外部依赖服务时就需要基于微服务的Mock粉墨登场了。
第二基于微服务的Mock
基于微服务的Mock我个人比较推荐的框架是Weir Mock 和 Mock Server。这两个框架都可以很好地模拟API、http形式的对象。
从编写测试代码的角度看Weir Mock 和 Mock Server这两种测试框架实现Mock的方式基本一致
标记被代理的类或对象,或声明被代理的服务;
通过Mock框架定制代理的行为
调用代理,从而获得预期的结果。
可见这两种Mock框架都很容易被上手使用。
第三携程的Mock Service实践
在携程我们一次集成测试可能依赖的外部服务和数据服务会有几百个而这几百个服务中很多都属于基础服务都有被Mock的价值。
所以携程借鉴了Mock Server的想法在整个测试环境中构建了一套Mock Service所有服务的请求都会优先通过这套系统同时所有服务的返回也会被拦截。这套Mock Service看起来就像是一个巨大的代理代理了所有请求。
那么测试人员只要去配置自己的哪些请求需要被Mock Service代理就可以了如果请求的入参相同且Mock Service中存在该请求曾经的返回则直接被代理。反之则透传到真正的服务。
虽然这会增加性能开销,但是对于整体的回归测试来说,价值巨大,而且方便好用、无需编码。
Mock技术通过模拟绕过了实际的数据调用和服务调用问题横在我们面前的“三座大山”中的其中两座测试数据的准备和清理、分布式系统的依赖算是铲平了。但是如何解决“第三座大山”呢即如何做到模拟用户真正的操作行为呢
两大利器之二“回放”技术
要做到和实际用户操作一致,最好的方法就是记录实际用户在生产环境的操作,然后在测试环境中回放。
当然,我们要记录的并不是用户在客户端的操作过程,而是用户产生的最终请求。这样做,我们就能规避掉客户端产生的干扰,直接对功能进行测试了。
首先,我们一起来看一下如何把用户的请求记录下来。
这里我们需要明确一个前提原则,即:我们并不需要记录所有用户的请求,只要抽样即可,这样既可以保持用例的新鲜度,又可以减少成本。
我们在携程有两种方案来拦截记录用户操作:
第一种方案是在统一的SLB上做统一的拦截和复制转发处理。这个方案的好处是管理统一实现难度也不算太大。但问题是SLB毕竟是生产主路径上的处理服务一不小心就可能影响本身的路由服务形成故障。所以我们有了第二种替换方案。
第二种方案是,在集群中扩容一台服务器,在该服务器上启动一个软交换,由该软交换负责复制和转发用户请求,而真正的用户请求,仍旧由该服务器进行处理。-
这个方案比第一种方案稍微复杂了一些,但在云计算的支持下,却显得更经济。你可以按需扩容服务器来获取抽样结果,记录结束后释放该服务器资源。这个过程中,你也不需要进行过多的配置操作,就和正常的扩容配置一样,减少了风险。
这样,我们就完成了用户行为的拦截记录。而用户行为记录的保存格式,你也可以根据要使用的的回放工具来决定。
然后,我们再一起看看回放的多样性。
因为回放过程完全由我们来控制,所以除了正常的原样回放外,我们还可以利用回放过程达到更多的目的。
我们既可以按照正常的时间间隔,按照记录进行顺序回放;也可以压缩回放时间,形成一定的压力,进行回放,达到压力测试的目的。
而且,如果可以对记录的请求数据做到更精细的管理,我们还可以对回放进一步抽样和删选,比如只回放符合条件的某些请求等等,找出边界用例,利用这些用例完成系统的容错性和兼容性测试。
当然,你如果希望做到回放的精细管理,那我的建议是根据你的实际业务特性自研回放工具。
自研回放工具的整体思路其实非常简单,就是读取拦截的访问记录、模拟实际协议、进行再次访问。当然,你还可以给它加上更多额外的功能,比如数据筛选、异常处理、循环重复等等。
现在,利用“回放”技术,我们也顺利翻越了最后“一座山”,实现了用户行为的高度仿真。
总结
我以提出问题-分析问题-解决问题的思路,和你展开了今天的分享内容。
首先,我和你分享了自动化回归测试会遇到的三个难题:测试数据的准备和清理、分布式系统的依赖,以及测试用例的高度仿真。
我们可以利用Mock技术即通过代理的方式模拟被依赖的对象、方法或服务的技术通过不同的框架解决自动化回归测试的前两个问题
基于对象和类的Mock解决一个应用内部依赖的问题
基于微服务的Mock解决应用与应用之间外部依赖的问题。
然后,我和你分享了携程的“回放技术”,即先通过虚拟交换机,复制和记录生产用户的实际请求,在测试时“回放”这些真实操作,以达到更逼真地模拟用户行为的目的,从而解决了自动化回归测试遇到的第三个问题。
所以利用Mock和“回放”技术我们能够提高自动化回归测试的效率和准确度从而使整个持续交付过程更顺滑自动化程度更高。
思考题
你所在的公司,有没有合理的回归测试过程?如果没有,是为什么呢,遇到了什么困难?通过我今天分享的内容,你将如何去优化这个回归测试的过程呢?
感谢你的收听,欢迎你给我留言。

View File

@ -0,0 +1,189 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
28 持续交付为什么要平台化设计?
你好,我是王潇俊。今天我和你分享的主题是:持续交付为什么要平台化设计?
专栏内容已经更新一大半了,我和你也基本上已经逐个聊透了持续交付最核心的五大部分内容,包括:配置管理、环境管理、构建集成、发布及监控、测试管理。理解了这五大部分基本内容,你也就已经基本掌握了持续交付的核心内容,以及整个闭环流程了。
我猜想你可能已经开始尝试在团队内部署一套持续交付体系了,在部署的过程中又碰到了一些问题:比如,是否要为不同的语言栈建立不同的构建和发布通道;又比如,我还滞留在手工准备环境的阶段,无法有效自动化,应该怎么办。
要解决这些问题,你就需要达到一个更高的高度了,即以平台化的思维来看待持续交付。
那么从今天开始,我们就一起来聊聊持续交付平台化的话题吧。
什么是平台化
“平台化”这个词,你应该已经听到过很多次了吧。特别是互联网领域,我们都爱谈论平台化。那么,“平台化”到底是什么意思呢?
其实早在20世纪70年代欧洲的军工企业就开始利用平台化的思维设计产品了。当时的设计人员发现如果分别研制装甲车、坦克和迫击炮的底盘时间和金钱成本的消耗巨大。
因为这些武器的底盘型号不同,所以它们所需要的模具、零件也就不同,除了要分别设计、制造、测试、生产外,还要花费巨额成本建设不同的生产流水线,而且各底盘的保养和使用方式不同,需要进行不同的人员培训。可想而知,这样分别设计的成本是巨大的。
所以,这些军工企业们就决定要采用一个通用的底盘设计,然后在通用底盘上安装不同的炮管和武器,达到个性化的需求。
之后,这种平台化的设计和制造方法,在航空制造业和汽车制造业得到了广泛运用,获得了极大的成功,并一直被沿用至今。
而,互联网又再次给“平台化”插上了新的翅膀。互联网厂商平台化的玩法,往往是指自己搭台子,让其他人唱戏。也就是说,由互联网厂商自己提供一些基础保障能力,建立必要的标准,从而形成底层支撑平台;而由其他供应商或用户利用这个底层平台提供的服务,自己完成具体业务、功能流程设计,从而达到千人千面的个性化服务能力。
互联网厂商的这种做法,就使得企业的服务能力被放大到了极致。
持续交付为什么要实现平台化?
持续交付要做到平台化的原因,主要可以归结为以下三方面。
随着软件技术的发展,任何企业最终都将面临多技术栈的现实。不同的技术栈,就意味着不同的标准、不同的工具、不同的方式,所以我们就必须要通过合理的持续交付平台,去解决不同技术栈的适配工作。
随着持续交付业务的发展,团队会越来越庞大,分工也会越来越明细。这就要求持续交付体系能够支持更大规模的并发处理操作,同时还要不断地提升效率。更重要的是,当持续交付成为企业研发的生命线时,它必须做到高可用,否则一旦停产,整个研发就停产了。
随着持续交付技术本身的发展,还会不断引入新的工具,或新的流程方法。如果我们的持续交付体系不能做到快速适应、局部改造、高可扩展的话,那它自身的发展与优化将会面临严峻的挑战。
以上三个方面的原因,决定了我们需要打造一套高可用、可扩展的持续交付平台。
持续交付平台的设计
在前面的几个系列中,我分享了很多与持续交付的选型、实践与做法相关的内容。那么,在持续交付平台化的系列中,我会和你一起去整合前面看似零散的内容。
为此我总结了实现持续交付平台化的7个步骤也可以说是7个方法论通过对这7个步骤的思考你将清楚要构建一套持续交付平台
具体需要做哪些工作;
资源有限时,如何取舍;
最重要的任务是什么;
外部对你的限制和帮助有哪些。
希望通过我的总结,结合之前的分享,你能把持续交付的各个阶段串联起来,形成自己的平台化思路。
第一步,确定模块及其范围
交付流水线的概念,我已经在专栏第一篇文章《持续交付到底有什么价值》中介绍过了。如果你记不太清楚了,可以再回顾一下这篇文章的内容。
持续交付平台的工作流程基本就是根据这个流水线确定的,即:由编码开始,经过集成编译,再部署到相应环境,进行测试,最后发布到生产环境的过程。
持续交付平台最终将完成这个端到端的过程,那么流水线的每一步都可以认为是一个模块。由此,整个平台的核心模块就是:代码管理、集成编译、环境管理、发布部署。
这四个模块是持续交付平台中最核心,最容易做到内聚和解耦的模块。每个核心模块的周围,又围绕着各种子模块,比如:
代码管理模块,往往会和代码审核、静态扫描和分支管理等模块相联系;
集成编译模块,也会与依赖管理、单元测试、加密打包等模块相生相随的;
环境管理模块,离不开配置管理、路由管理等模块;
发布部署模块,还需要监控模块和流控模块的支持。
图1 持续交付平台核心模块
这样,如上图所示,根据交付流程划分完模块后,整个持续交付平台所要涵盖的大致范围也就确定了。
第二步,学会做减法
好的产品,都需要不断地做减法,持续交付平台也是如此。
我们已经在第一步罗列了需要完成的模块但很显然不可能一下子完成所有的这些模块设计和开发a。所以持续交付平台设计的第二步就如何抓住最核心的内容。
正如我在第一篇文章《持续交付到底有什么价值》中所说,并不是只有完整的端到端自动化才叫“持续交付”,代码管理,集成编译,环境管理、发布部署这四大核心模块,其实就是一个交付的闭环,只是交付的内容不同,但这些交付都是可测的、可评定的,所以并不是半成品。
因此,我们就可以考虑挑选最为重要或最为急迫的模块,优先加以实施。甚至,你可以优先实现这四个模块中的一个,先解决一部分问题。这样做减法的方式,我们称为横向缩小范围。
另外一种做减法的方式是减少纵向的深度。也就是优先支持单一的技术栈,或特定的、比较简单的场景,比如先搞定组织内的单体应用。
通过做减法先完成这个平台最核心模块的方式,可以控制平台的初建成本,而且效果也比较容易预期。比如,携程就是优先完成了发布部署模块,再逐步向持续交付的上游拓展。
而对于后续要做加法的事情,可以以后或者由其他团队慢慢补上,这才是平台的意义。
第三步,制定标准
研发任何系统,首先要记住一句话:“标准先行”。
我们谈到标准时,往往会涉及很多方面,比如:对外衔接的标准、对内沟通的标准;质量的标准,速度的标准等等。而对持续交付平台的设计来说,最重要的标准是定义各个模块交付产物的标准。
比如代码管理模块最终的交付产物到底是什么形式又是什么是一个代码包还是git仓库地址
又比如,发布部署模块,到底执行的是怎样的过程:重启应用是使用线程回收机制,还是进程重启机制;
只有制定了标准,其他团队或者其他系统才能有据可依地逐步加载到这个平台之上。
不同的组织和企业,标准和规范的内容要求不一样。所以,我无法一一列举这些标准和规范,但是你一定要清楚,这是重中之重的一个步骤。
第四步,选择合适的驱动器
所谓驱动器,就是用来驱动整个持续交付流水线的引擎。
不同规模的团队,适合的驱动器不同:
中小规模的团队我推荐使用开源的系统做驱动器比如使用Jenkins作为驱动器当然Jenkins还有资源调度和编排能力
较大规模的团队,或者业务比较复杂的情况下,我建议自行研发驱动器,以适应自身组织的特殊需求。-
当然我并不是说自行研发驱动器肯定就比Jenkins这样的系统要好。但是后者更注重普适性而前者则可以根据自身业务情况进行取舍甚至不需要考虑流水线的可配置性直接使用状态机写死流程。这样的好处是掌控力强修改简单且不易出错。
如果是更大规模的团队我的建议是把驱动器与功能模块同等看待将流水线驱动看做是平台的一个抽象功能既可以驱动CI或CD功能也可以驱动其他的任务其他模块提供的服务都是这个驱动服务可以执行的具体实现而已。-
在复杂情况下,“人”才是最好的驱动器,可以做出最正确的判断。有些特殊的复杂场景,机械的驱动器程序已经无法解决,需要人工介入。所以通过驱动服务,既可以驱动自动化任务,同时又可以驱动“人”,才能保证最优的结果。
第五步,抽象公共能力
既然我们要设计一个平台,自然就要把很多公共功能抽象到平台层处理。需要抽象的公共功能,主要包括:
账户与权限,包括单点登录,以及鉴权、授权体系等等;
用户行为日志,即任何行动都要能够被追溯;
消息通知,即提供统一的通知能力;
安全与故障处理,即系统级的防护能力和故障降级。
持续交付平台的设计除了要抽象这些公共功能外还需要考虑打通上下游系统的问题比如需要从CMDB获取服务器信息从应用中心获取应用信息等等。
第六步,考虑用户入口
完成了持续交付平台内部功能的设计后,就要考虑用户入口的问题了。
用户入口是提供一个统一的站点、使用命令行格式、使用IDE插件还是直接使用Jenkins系统作为与用户交互的界面可以根据团队的资源、能力等实际情况进行选择。
通常情况下,我会比较建议为持续交付独立形成一个 Portal这样不会受到其他系统的限制你可以根据自己的设计更好地完成用户引导和使用体验的设想。
第七步,聚力而成
通过上面这六步,我们已经初步完成了持续交付平台的设计,之后就是如何实现的问题了。
其实,如何实现持续交付的平台化,主要看你的决心和实践。但一定要记住,如果你决定要实施一个持续交付的平台,那就一定要学会运用团队的力量。
比如,架构同学,一定能够在制定规范和架构方面给你建议和帮助;而运维同学,肯定在环境治理和部署方面比你更有经验。
所以,你要做的是搭好平台,利用团队优势共同建设持续交付体系。
以上的内容,就是搭建一套持续交付平台最关键的七个步骤了。这里,我们可以用一张图片,表示这个持续交付平台的大致架构。
图2 持续交付平台的大致架构
总结
因为技术和业务的复杂性不断增加,持续交付需要解决的问题也变得越来越复杂。所以,我们需要利用平台化的思想解决持续交付体系日益复杂的问题。
持续交付是一个需要所有研发团队共同参与的活动,持续交付平台的建设也同样需要借助各个团队、各个职能的力量。
如果你正在负责持续交付这件事情,你应该充分考虑如何搭台,让大家来唱戏这样件事情。
思考题
你所在的组织内,持续交付中的哪些内容需要其他团队协助呢?
感谢收听,欢迎你给我留言。

View File

@ -0,0 +1,148 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
29 计算资源也是交付的内容
你好,我是王潇俊。今天我和你分享的主题是:计算资源也是交付的内容。
在传统的持续交付中,我们虽然一直深受环境和计算资源的困扰,但却很少去考虑如何去彻底解决这个问题。归根结底,原因有两方面:
这个问题解决起来难度较大;
这个问题也不太算是持续交付的范畴。
但是随着DevOps深入人心以及云计算的发展我们已经有了解决这些问题的思路和方法。所以今天我要和你聊聊如何解决这方面的问题从而获得更优的环境管理能力。
计算资源包括什么内容?
通常情况下我们所说的计算资源包括CPU、内存、本地磁盘、外部存储、网络等。
为了提高计算资源的利用率,传统做法往往是按需申请和分配资源。但是计划往往赶不上变化,整个申请和分配过程冗长,缺乏快速弹性的能力,最终影响了持续交付的效率。
计算资源是导致持续交付的反模式的原因
从实际情况来看,计算资源是导致持续交付反模式的主要原因。
在《持续交付:发布可靠软件的系统方法》一书中,作者给我们列举了几个反模式,比如:
手工部署软件,即由详尽的文档描述一个部署过程,部署需要手工操作及验证;
开发完之后才向类生产环境部署,即开发完成后才第一次向类生产环境部署,如果是全新的应用,第一次部署往往会失败;
生产环境需要手工配置管理,即有专门的团队负责生产环境的配置变更,修改配置时,需要这个专门的团队手工登录到服务器进行操作。
你可以按照我在发布及监控这个系列分享的内容,通过合理打造一套发布系统,解决“手工部署软件”这个反模式的问题。
但是,“开发完之后才向类生产环境部署”和“生产环境需要手工配置管理”这两个反模式,我们总是难以克服。究其原因,我们是在计算资源的交付上出了问题。为什么呢?
产生“开发完之后才向类生产环境部署”反模式的原因是,开发测试环境和生产环境的差异。导致这个差异产生的原因,绝大多数是因为,生产环境的模拟成本太大,线下难以模拟,归根结底是计算资源的问题。
产生“生产环境需要手工配置管理”反模式的原因是,环境需要长期使用,而且需要不断更新,不能即取即用。说到底,这还是计算资源问题。
这时,你可能会说,计算资源的问题也可以通过一些方法解决。比如,准备一个计算资源缓冲池,从这个缓冲池中获取计算资源就很方便、快捷了。
这确实是一个解决方案,但是这个解决方案带来的成本浪费是巨大的。
然后,云计算出现了,正好可以彻底解决这些问题。现在,我就和你聊聊云计算是如何解决这些问题的。
云计算带来的划时代变革
其实,云计算带来的变革,主要体现在以下三个方面。
云计算的弹性能力,使得计算资源的提取成为持续交付过程的一个自然步骤。-
使用云计算之前,获取计算资源的过程往往耗时很长、结果不可预知,因此往往不能作为持续交付流水线的一部分,而是要独立出来,或者采用异步方式进行处理。-
然而,云计算的弹性能力,使得我们获取计算资源的速度和数量有了质的飞跃,而且保证了结果的可控性。所以,计算资源的提取,也就可以与其他环节一样作为持续交付中的一个自然步骤了。
云计算的Immutable可以保证计算资源的生命周期与持续交付过程一致。-
以前,为了降低计算资源回收交替的成本,我们往往会采用复用计算资源的方式。但是,如果遇到不同的应用,而这些应用又需要不同的配置,或安装一些不同的软件的话,我们就需要对这些被复用的机器进行人工维护。-
所以,这个复用方式,就使得一个计算资源的生命周期变得不再清晰,我们也再次掉进了“生产环境需要手工配置管理”的陷阱。-
但是,在云计算中,计算资源的生命周期可以被严格定义。所以,计算资源就可以做到专事专用,进而保证与持续交付过程的一致性。
云计算本身不区分环境,这样可以获取到与生产环境几乎一样配置的资源。-
以前,测试所用的计算资源和生产环境所用的计算资源,往往不一致。最核心的原因是,我们没有制订计算资源的交付标准,或者制订的标准在资源交付后,由于脱离了控制而被轻易打破。-
而云计算则会通过系统对计算资源的抽象、标准化,以及管控,可以很容易地获取到与生产环境更相近的测试环境。
目前,各公有云都提供了非常完备的持续交付平台,越来越多的企业正在或正将把生产环境搬迁到公有云上。如果你的应用符合云原生的特性,那恭喜你,你可以尽情地享受云计算带来的红利了。
但以目前的情况来看想要做到完全依托于公有云实现持续交付你还需要经过大量云原生的改造。比如需要所有的应用都无状态应用启动过程没有特殊的额外步骤使用公有云提供的路由方案等等。所以绝大多数组织仍旧选择以依赖私有云或私有IaaS平台的形式来解决问题。
重塑持续交付平台的相关部分
有了云计算或者说私有IaaS平台这个强大的底层支持我们下一步要解决的就是充分发挥它的能力。所以现在我就和你分享一下持续交付平台的哪些部分可以利用云计算的强大能力。
首先,弹性的集成编译环境。
不同技术栈的应用需要不同的编译环境,而且要保证编译环境和运行时环境一致,否则会发生意料之外的问题。这样一来,如果组织内部同时有多个技术栈存在,或应用对环境有多种要求时,就需要有多个独立的编译环境了。
因此,如果没有云计算的话,持续交付通常要准备一个由多台不同服务器组成的编译集群,用以覆盖所有的编译需求。另外,为了达到持续交付的效果,我们还可能需要再横向地为每个独立编译环境多准备几个实例,以便达到多个编译任务并行的目的,而不是要一直排队。
然而,这样的做法对资源的控制和利用非常不利,很有可能在编译高峰时仍旧资源不够用,而扩大资源池后,大多资源又处于空闲状态。
现在,利用云计算的弹性,你可以按需生成一个个特定的编译环境,及其所需的计算资源。这就使得你无需再提前准备一个巨大的资源池,也自然不必为如何合理配比资源池中的资源而烦恼了。
要达到这种效果,你只需要修改集成编译模块的编译调度逻辑就可以:
在编译调度前,生成所需要的编译环境实例;
在编译完成之后,保存编译日志和相关交付产物后,销毁编译实例、释放计算资源。
因此,有了云计算的支持,编译模块的效率和资源利用率都可以上一个台阶。
其次,重新定义环境管理。
云计算的弹性能力,可以帮助我们改善环境管理模块的能力,使得环境管理的方式更加灵活。
比如,原先一个测试子环境的生命周期往往与某个功能研发或是项目研发不一致,会提前准备,或是多次复用;又或者由于资源紧缺的原因,测试环境只能模拟部分实际环境;另外还会有一些环境被作为公共的资源一直保留,从不释放。这些问题都增加了环境管理的复杂度。
现在,有了云计算平台的强大能力,我们完全可以打破这些限制,将环境的生命周期设计得与项目生命周期一致,每个项目或每个功能都可以拥有自己独立的测试环境;另外,你还可以动态定义所需的任何环境,或者利用模板技术,快速复制一个已存在的环境。总之,环境管理变得越来越灵活了。
除了计算资源之外,云计算也同时提供了非常强大的网络定义能力,为环境管理插上了翅膀。
我们可以通过VPC专有网络对任何环境定义网段划分、路由策略和安全策略等。这样环境与环境之间就拥有了快速处理网络隔离和相通的能力。借此我们也可以很容易地创造沙箱环境、专用测试环境等。
有了云计算的支持,环境管理真的可以飞起来。
最后,充分利用存储。
云计算除了可以提供计算资源和网络资源的便利外,同时也可以解决资源存储的问题。分布式存储的能力,同样能给持续交付提供有利的帮助。
比如利用共享存储你可以在多个编译实例之间共享Workspace虽然性能会稍微下降一点但可以很方便地保证不同实例之间使用相同的本地缓存而无需再去考虑如何同步本地缓存使其一致的问题。
又比如,利用分布式存储,你就无需再担心部署包的备份问题了。
再比如如果你还使用公有云的存储服务的话比如Amazon的S3云计算可以帮你很方便地把交付产物同步到全球各地简化你异地部署的工作。
总结
今天我和你讨论的主题是,云计算这样的新兴技术会对持续交付会产生哪些影响。我们可以把这些影响概括为四大方面:
利用云计算,能够很容易地打破持续交付反模式;
利用云计算,可以使持续交付的编译模块变得更为灵活,提升利用率;
利用云计算,可以自由地发挥想象力,简化环境管理的工作;
利用云计算,可以使用存储服务,使持续交付工作更便捷。
总之,你会发现,计算资源是持续交付非常重要的依赖,有了云计算的支持之后,我们完全可以把计算资源的交付也纳入到持续交付过程中,更好地做到端到端的交付。
思考题
你将如何利用云计算的能力,优化现有的持续交付体系呢?
感谢你的收听,欢迎给我留言。

View File

@ -0,0 +1,159 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
30 持续交付中有哪些宝贵数据?
你好,我是王潇俊。今天我和你分享的主题是:持续交付中有哪些宝贵数据。
通过我前面和你分享的内容,相信你已经掌握了持续交付流水线所包含的五个主要动作:代码管理、环境管理、集成和编译管理、发布管理,以及测试管理。而且,你也应该已经初步掌握了建设持续交付体系的基本方法。
那么,如何使这个初步建立的持续交付体系更上一层楼呢?现在我们都选择用数据说话,所以优化一套系统的最好办法,就是从数据角度进行分析,然后找出优化方向,再进行具体的改进。
所以,我今天就分享一下,我在携程建设持续交付系统时,遇到的几个与数据密切相关的案例。通过对这些数据的分析,我们可以明确优化系统本身处理能力的方向,也可以快速发现日常工作中与持续交付相违背的行为,从而再次展现我们搭建的持续交付系统的作用。
案例1要用好的数据来衡量系统
让我记忆犹新的第一个案例,是我们持续交付平台刚上线时,就遇到了一个很大的问题。这个问题就是,这套系统的稳定性怎么样?
这个问题不仅老板会问你,用户会问你,其实你自己都会问自己。如果没有相关的数字指标,那我怎么证明这套系统的稳定性好呢?如果我无法证明这套系统的稳定性,又怎么说服整个公司,把它当做研发的核心流水线呢?
期间,我想过很多方案,比如用宕机时间来计算稳定性。但是实际使用起来,你就会发现,这个衡量指标很不靠谱。
第一,对于平台系统来说,有很多相关联的子系统,有些子系统属于旁支系统,对实际业务影响不大,而有些则影响非常大。那么,我怎么合理计算这些系统间的权重呢?
第二毕竟是一套对内的系统有的时候即使宕机了特别是在夜间因为没有用户使用其实际影响几乎为0而反之有的时候比如处在发布高峰的下午系统宕机则会产生较大的影响。所以用宕机时间这个指标衡量的话就会把这些影响摊平不能正确地反映真实的问题。
第三,宕机时间这个单一指标,不能全面地评价系统的稳定性。比如,有些子系统的运维会在宕机时进行降级(比如,增加排队时间等手段等等),使系统处于将死而不死状态。这种处理,虽然从业务的角度可以理解为降级,但却对系统的真实评价却起到了反作用。
其实上面的这三个问题,也会在真实的业务系统上碰到。所以,我们借鉴了携程业务系统的稳定性评估方案,最后决定采用如下的实施方案:
首先,我们通过监控、保障、人为记录等手段,统计所有的故障时间。需要统计的指标包括:开始时间、结束时间和故障时长。
然后计算过去三个月内这个时间段产生的持续交付平均业务量。所谓业务量就是这个时间段内处理的代码提交、code review进行的编译、代码扫描、打包测试部署环境处理测试执行和生产发布的数量。
最后,计算这个时间段内的业务量与月平均量相比的损失率。这个损失率,就代表了系统的不稳定性率,反之就是稳定性率了。
这样计算得到的不稳定性率指标,就要比简单粗暴地用宕机时间要精确得多,也不再会遇到前面提到的三种问题。
由此得到的计算模型一旦固定下来,你只要做好业务量的数据统计,其计算难度也会不大。
因此,我推荐你也可以使用这个数据指标来衡量自己系统的稳定性。
案例2数据既要抓大势也要看细节
第二个让我映像深刻的案例,是一个与长尾数据有关的案例。
自从我们的持续交付平台在携程上线以后,一直颇受好评。当然,在系统上线后,我们也进行过几次优化,编译和打包速度被提升了非常多。而这些优化的方法,我也已经在专栏前面的文中进行了分享,如果有哪些不太清楚的地方,你可以去回顾一下这些文章,也可以给我留言我们一起讨论我在搭建系统时遇到的具体问题。
而且,我们运维团队也一直谨记,要通过数据分析不断优化系统。所以,我们一直非常关注总体的集成编译速度,因此除了追踪平均速度外,还会定期进行全量个例分析。
从整体的平均速度这个指标来看系统一直表现良好。但在99线以上我们却发现存在一些长尾特例。正常的Java代码平均编译时间大概在1分钟之内而这些特例却在7分钟以上。在几次的全量个例分析中我们虽然发现了这个问题但并没有特别关注。而且在查看了编译过程的几个主要计时点后我们也确实没有发现任何问题。
所以,这时我们都认为,这些长尾数据可能真的只是一些特殊个案。毕竟相对于每天几万次的集成编译来说,这个数量实在太小。
但就是这个小小的数据疏忽,我们差点忽略一个非常重要的故障点。随着时间的推移,我们发现这个长尾在慢慢变大。最终,我们还是尝试去一探究竟,发现原因其实是:
持续交付系统在打包之后会通过网络专线向另外一个IDC分发部署包和容器镜像。但由于历史原因两个IDC之间的防火墙对流量进行了不恰当的限制。随着持续交付在全公司的开展这个分发量也越来越大使网络流量达到了瓶颈从而形成了之前的长尾现象。
当然,这个问题的解决方案十分简单。但是,从中我们看到:大的故障和影响,往往都是出于一些非常愚蠢的失误。
所以,这个案例也一直在提醒着我,看数据不仅要抓大势,也要关注细节,特别是异常细节。
案例3数据可以推动持续交付
第三个案例是,关于怎么利用数据来改善业务开发团队的持续交付过程。
任何一个团队,都会有它自己的研发习惯、迭代速度以及交付频率。自从携程上线了持续交付平台之后,我们从数据上就能很明显地发现,每个团队乃至整个公司,每周的发布数据是一个固定的趋势。
从周跨度上看周三、周四为发布高峰从日的维度看中午12点开始发布数量逐步增加下午4点达到发布数据的高峰晚上7点之后发布数据逐步回落。
几乎每个团队的发布数据趋势都是这样只有少数几个团队呈现了不同的趋势它们的周趋势与其他团队基本一致但日趋势则非常不同高峰出现在下午5点然后立即回落且高峰值远远高于其他团队的平均值。
这是怎么回事呢很明显这是一种集中式发布的形态。但是我在携程也没听说过有这样的流程。之后我们经过了解发现这种集中发布的情况是这几个团队一直在沿用以前的发布模式即将所有发布会汇总到一个发布负责人处由他专门负责发布。所以为了方便工作这些发布负责人员选择在下午5点集中开始发布。
虽然这种做法和流程没什么问题,但却有违于我们推崇的“谁开发,谁运行”理念,并且也因此增加了一个实际不是必须的工作角色。在这之后,我们改造了这几个团队的流程,相当于是推动了整个公司的持续交付。
这个案例第一次让我们认识到,我们可以用手上的数据去推动、去优化持续交付体系。
这三个案例,都充分说明了数据对持续交付、持续交付平台的重要性,所以我们也要善用这些宝贵的数据。接下来,我再和你分享一下,持续交付体系中还有哪些数据值得我们关注。
常规系统指标数据列举
在日常工作中,我把需要关注的系统指标数据做了分类处理。这样,我可以通过这些数据指标去了解每一个持续交付子系统的当前状况,并确定需要优化的指标。
第一类指标,稳定性相关指标
作为基础服务,稳定性是我们的生命线。所以,对于所有的子系统,包括:代码管理平台、集成编译系统、环境管理系统、测试管理系统和发布系统,我们都会设立必要的稳定性指标,并进行数据监控。这些稳定性相关的数据指标,代表整个系统的可用度。
各系统的稳定性计算则可以参考第一个例子中的算法。
第二类指标,性能相关指标
与系统性能相关的指标,通常可以直接反应系统的处理能力,以及计算资源的使用情况。更重要的是,速度是我们对用户服务能力的直观体现。很多时候,系统的处理速度上去了,一些问题也就不再是问题了。比如,如果回滚速度这个指标非常优秀,那么业务发布时就会更有信心。
与性能相关的指标,我比较关注的有:
push和fetch代码的速度
环境创建和销毁的速度;
产生仿真数据的速度;
平均编译速度及排队时长;
静态检查的速度;
自动化测试的耗时;
发布和回滚的速度。
第三类指标,持续交付能力成熟度指标
与持续交付能力成熟度相关的指标,可以帮助我们度量组织在持续交付能力上的缺陷,并加以改善。
不同的子系统,我关注的指标也不同。
与代码管理子系统相关的指标包括commit的数量code review的拒绝率并行开发的分支数量。-
这里需要注意的是,并行开发的分支数量并不是越多越好,而是要以每个团队都保持一个稳定状态为优。
与环境管理子系统相关的指标包括:计算资源的使用率,环境的平均大小。-
这里需要注意的是,我一直都很关注环境的平均大小这个数据。因为我们鼓励团队使用技术手段来避免产生巨型测试环境,从而达到提高利用率、降低成本的目的。而且,这个指标也可以从侧面反映一个团队利用技术解决问题的能力。
与集成编译子系统相关的指标包括:每日编译数量,编译检查的数据。-
我们并不会强制要求编译检查出的不良数据要下降,因为它会受各类外部因素的影响,比如历史代码问题等等。但,我们必须保证它不会增长。这也是我们的团队在坚守质量关的体现。
与测试管理子系统相关的指标包括:单元测试的覆盖率,自动化测试的覆盖率。-
这两个覆盖率代表了组织通过技术手段保证质量的能力,也是测试团队最常采用的数据指标。
与发布管理子系统相关的指标包括:周发布数量,回滚比率。
发布数量的增加,可以最直观地表现交付能力的提升;回滚比率,则代表了发布的质量。综合使用周发布数量和回滚比例这两个指标,就可以衡量整个团队的研发能力是否得到了提升。
以上这些数据指标,就是我们在携程要关注的了。希望通过我对这些数据指标的介绍,可以帮助你了解如何衡量自己的持续交付体系,并通过分析这些数据找到优化当前体系的方向。
总结
今天我通过三个实际工作中的例子,和你分享了应该如何利用持续交付中产生的数据。
首先,你可以利用与业务量相关的数据模型来衡量持续交付系统的稳定性;
然后,在日常的数据分析中,除了要抓住主要数据的大趋势外,你还要关注那些异常的个性数据,它能帮你及早地发现问题;
最后,通过日常的数据分析,你也能发现持续交付流程上的一些问题,并协助团队一起改进。
当然,这只是三个比较突出的例子而已。在实践中,实施持续交付的过程中还有很多数据需要我们关注。我也一并把这些数据分成了三类,包括:
稳定性相关指标;
性能相关指标;
持续交付能力成熟度指标。
希望这些案例,以及这些数据指标可以对你日常的分析工作有所帮助。
思考题
你有没有在推进持续交付过程中遇到一些阻力呢?你有没有尝试通过数据分析去解释和解决这些问题呢?你又有哪些案例,想要和我分享呢?
感谢你的收听,欢迎你给我留言。

View File

@ -0,0 +1,155 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
31 了解移动App的持续交付生命周期
你好我是王潇俊。今天我和你分享的主题是了解移动App的持续交付生命周期。
我已经和你分享完的前30个主题里介绍的都是偏向后端持续交付体系的内容。在服务端持续交付的基础上我会再用两篇文章和你聊聊移动App的持续交付。
与后端服务相比移动App的出现和工程方面的发展时间都较短所以大部分持续交付的流程和方法都借鉴了后端服务的持续交付。但是移动App因为其自身的一些特点比如版本更新要依赖用户更新客户端的行为等所以移动App的持续交付也呈现了一些独有的特点。
同时移动App的持续交付也存在一些痛点。比如没有主流的分支模型甚至产生了Android开发团队使用Gerrit这样一个独特代码管理平台和分支模型的特例又比如移动App的编译速度也随着应用越来越大变得越来越慢再比如Apple Store审核慢、热修复困难等问题。
这样总体来看移动App的持续交付体系的搭建完全可以借鉴服务端的持续交付的经验。然后再针对移动App固有的特点进行改进和优化。
因此在这个系列我只会通过三篇文章和你分享移动App持续交付体系的特色内容而对于共性的内容部分你可以再次回顾一下我在前面所分享的内容。如果你感觉哪里还不太清楚的话就给我留言我们一起讨论解决吧。
作为移动App的持续交付系列的第一篇文章我会和你一起聊聊移动App交付涉及的问题、其中哪些部分与后端服务不太一样以及如何解决这些问题打造出一套持续交付体系。
代码及依赖管理
首先和后端服务一样移动App的持续交付也需要先解决代码管理的问题。
我在专栏的第四篇文章《一切的源头代码分支策略的选择》中和你分享了各种代码分支策略比如Git Flow、GitHub Flow 和 GitLab Flow这三种最常用的特性分支开发模型的思路、形式以及作用。
对于移动App来说业界流行的做法是采用“分支开发主干发布”的方式并且采用交付快车的方式进行持续的版本发布。
关于这种代码管理方式我会在下一篇文章《细谈移动App的持续交付流水线pipline》中进行详细介绍。
其次移动App的开发已经走向了组件化所以也需要处理好依赖管理的问题。
移动端的技术栈往往要比统一技术栈的后端服务更复杂,所以在考虑依赖管理时,我们需要多方位地为多种技术栈做好准备。比如:
针对 Android系统业界通常使用Gradle处理依赖管理的问题。Gradle是一个与Maven类似的项目构建工具。与Maven相比它最大的优点在于使用了以Groovy为基础的DSL代替了Maven基于XML实现的配置脚本使得构建脚本更简洁和直观。
针对iOS系统我们则会使用CocoaPods进行依赖管理。它可以将原先庞大的iOS项目拆分成多个子项目并以二进制文件的形式进行库管理从而实现对iOS的依赖管理。另外这种管理依赖的方式还可以提高iOS的构建速度。
除了以上两个技术栈外移动App还会涉及到H5、Hybrid等静态资源的构建、发布和管理。那么同样的我们也就需要Nexus、npm等构建和依赖管理工具的辅助。
可以说移动App的技术仍旧在快速发展中与后端服务比较成熟和统一的状态相比我们还要花费更多的精力去适配和学习新的构建和依赖管理工具。
项目信息管理
项目信息管理主要包括版本信息管理和功能信息管理这两大方面。
对于移动App的持续交付来说我们特别需要维护版本的相关信息并对每个版本进行管理。
对后端服务来说,它只要做到向前兼容,就可以一直以最新版本的形式进行发布;而且,它的发布相对自主,控制权比较大。
但对移动App来说情况则完全不同了一方面它很难保证面面俱到的向前兼容性另一方面它的发布控制权也没那么自主要受到应用商店、渠道市场和用户自主更新等多方面因素的影响。
所以在移动App的持续交付中我们需要管理好每个版本的相关信息。
另外为了提高移动App的构建和研发效率我们会把整个项目拆分多个子项目而主要的拆分依据就是功能模块。也就是说除了从技术角度来看移动App的持续交付会存在依赖管理的内容外从项目角度来看也常常会存在功能依赖和功能集成的需要。所以为了项目的协调和沟通我们需要重点管理每个功能的信息。
可见做好项目信息管理在移动App的持续交付中尤为重要而在后端服务的持续交付中却没那么受重视了这也是移动App的持续交付体系与服务端的一大不同点。以携程或美团点评为例它们都各自研发了MCD或MCI平台以求更好地管理项目信息。
静态代码检查
静态代码检查的内容,就和后端服务比较相似了。为了提高移动端代码的质量,业界也陆续提供了不少的静态代码检查方案。比如:
Clang Static Analyzer被Xcode集成但其缺乏代码风格的检查可配置性也比较差
OCLint其检查规则更多也更易于被定制
Infer是Facebook提供的一款静态检查工具具有大规模代码扫描效率高、支持增量检查等特点。
我们也可以很方便地把这些静态检查工具集成到移动App的持续交付当中去。基本做法你可以参考我在第25篇文章《代码静态检查实践》最后分享的Sonar代码静态检查的实例的内容。
构建管理
移动App构建管理的大体流程我们可以借鉴后端服务的做法通过代码变更触发自动的持续集成。集成过程基本遵循拉取代码、静态检查、编译构建、自动化测试以及打包分发的标准过程。
移动App和后端服务的持续交付体系在构建管理上的不同点主要体现在以下三个方面
你需要准备 Android 和 iOS 两套构建环境而且iOS的构建环境还需要一套独立的管理方案。因为iOS的构建环境你不能直接使用 Linux 虚拟机处理而是要采用Apple公司的专用设备。
在整个构建过程中,你还要考虑证书的管理,不同的版本或使用场景需要使用不同的证书。如果证书比较多的话,还会涉及到管理的逻辑问题,很多组织都会选择自行开发证书管理服务。
为了解决组件依赖的问题,你需要特别准备独立的中央组件仓库,并用缓存等机制加快依赖组件下载的速度。其实,这一点会和后端服务比较相像。
发布管理
移动App的发布管理和后端服务相比相差就比较大了。
首先移动App无法做到强制更新决定权在终端用户。移动App的发布你所能控制的只是将新版本发布到市场而已而最终是否更新新版本使得新版本的功能起效则完全取决于用户。这与后端服务强制更新的做法完全不同。
其次移动App在正式发布到市场前会进行时间比较长的内测或公测。这些测试会使用类似Fabric Beta 或者 TestFlight 这样的 Beta 测试平台,使部分用户优先使用,完成灰度测试;或者在公司内部搭建一个虚拟市场,利用内部资源优先完成内测。而且,这个测试周期往往都比较长,其中也会迭代多个版本。
最后移动App的分发渠道比较多样。还可能会利用一些特殊的渠道进行发布。为了应对不同的渠道的需求比如标准渠道版本控制部分内容一些字样的显示等等。在完成基本的构建和打包之后还需要做一些额外的配置替换、增删改查的动作。比如更新渠道配置和说明等。
以上这些因素就决定了移动App与后端服务的发布管理完全不同。关于移动App的发布我会在下一篇文章《细谈移动App的持续交付流水线pipeline》中进行详细介绍。
运营管理
移动App发布之后还有一件比较重要的事项那就是对每个版本的运营管理。
这里讲的运营主要是指,追踪、分析和调优这个版本发布的表现和反馈。我们运营时,主要关注的内容包括:奔溃报告、区域分析、用户分析、系统资源消耗、流量消耗、响应时长、包体大小、系统监控,以及预警等。
通常,我们也会对比版本之间的这些运营指标,以判断应用是变好了,还是变坏了。
热修复
后端服务修复Bug的方式一般是发现Bug后可以立刻再开发一个新版本然后通过正常的完整发布进行修复。
而移动App的发布需要用户安装才能起效这就决定了它不能采用后端服务修复Bug的方式。因为这会要求用户在很短的时间内重新安装客户端这样的用户体验相当糟糕。
但是我们也无法避免Bug。所以对移动App来说我们就要通过特定的热修复技术做到在用户不重新安装客户端的前提下就可以修复Bug。这也就是我所说的热修复。
关于热修复比如Android系统主要的方式就是以下两步
下发补丁内含修复好的class到用户手机
App通过类加载器调用补丁中的类。
其实现原理主要是利用了Android的类加载机制即从DexPathList对象的Element数组中获取对应的类进行加载而获取的方式则是遍历。也就是说我们只需要把修复的类放置在这个Element数组的第一位就可以保证加载到新的类了而此时有Bug的类其实还是存在的只是不会被加载到而已。
当然技术发展到今天我们已经无需重复造轮子了完全可以利用一些大厂开放的方案和平台完成热修复。比如百川的hHotFix、美团Robust、手机QQ空间、微信Tinker都是很好的方案。
iOS系统方面Apple公司一直对热修复抓的比较严。但是从iOS7之后iOS系统引入了JavaScriptCore这样就可以在Objective-C和JavaScript之间传递值或对象了从而使得创建混合对象成为了可能。因此业界产生了一些成熟的热修复方案。比如
Rollout.io、JSPatch、DynamicCocoa-
这三个方案只针对iOS的热更新。目前Rollout.io和JSPatch已经实现了平台化脚本语言用的都是JavaScript。Rollout.io除了支持OC的热更新外还支持Swift。-
DynamiCocoa源自滴滴目前还没开源所以我也没怎么体验过。但是它号称可以通过OC编码自动转换成JavaScript脚本这对编码来说好处多多。
React Native、Weex-
这两个方案都是跨平台的热更新方案。其中React Native是由Facebook开发的 Weex是由阿里开发的。就我个人的体验来说Weex从语法上更贴近编程思路而且还实现了平台化使用起来更加便捷。
Wax 、Hybrid-
这两个方案比较特殊。其中Wax采用的脚本语言是Lua而不是JavaScript所以比较适用于游戏而Hybrid主要面向H5Hybrid App已经被证明不是好的方案所以用户越来越少了。
总结
今天我主要和你分享了移动App的持续交付生命周期的几个主要部分包括代码及依赖管理、项目信息管理、静态代码检查、构建管理、发布管理、运营管理以及热修复。
然后我分享了相比于后端服务移动App的持续交付体系有哪些不同的地方。比如项目信息管理、运营管理和热修复在移动App的交付过程中被提到了更重要的位置而其他几个主要过程代码、构建、发布这三部分都因为移动App开发的特性与后端服务相比有所区别这些区别也是我要在下一篇文章中和你重点分享的内容。
思考题
对于移动App的交付来说版本和信息管理非常重要。你所在的公司是如何管理这些信息的有哪些可以优化的可能吗
感谢你的收听,欢迎你给我留言。

View File

@ -0,0 +1,198 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
32 细谈移动APP的交付流水线pipeline
你好我是王潇俊。今天我和你分享的主题是细谈移动APP的交付流水线pipeline
在上一篇文章《了解移动App的持续交付生命周期》中我和你分享了移动App的整个交付生命周期并把移动客户端的交付与后端服务的交付方式进行了对比。从中我们发现移动App自身的特点使得其持续交付流程与后端服务存在一定的差异。
所以今天我会在上一篇文章的基础上和你分享移动App持续交付中的个性化内容。这些个性化的内容主要表现在流水线的三个重要环节上
采用与发布快车Release Train模式匹配的代码分支管理策略
支持多项目、多组件并行的全新构建通道;
自动化发布,完全托管的打包、发布、分发流程。
接下来我就从这三个角度和你详细聊聊移动App的持续交付吧。
发布快车模式
首先,我先和你说说什么是发布快车。
顾名思义,发布快车,就像一列由多节车厢组成的火车,每一节车厢代表一个发布版本,整个火车以一节节车厢或者说一个个版本的节奏,定期向前发车。而工程师们,则会把自己开发完成的功能集成到一节节的车厢上,这样集成在一节车厢的功能代码,就形成了一个新的版本。
如图1所示就很好地展示了发布快车的含义。
图1 发布快车详解图
从这张图上,我们可以看到,每个版本(也就是每节车厢)都由多个功能组成。
关于发布快车还有三个关键点,容易被误解或者疏忽。
第一个关键点是并不是说所有开发的功能都一定要集成到最近的那节车厢、最近的那个版本中。任何功能都应该按照既定计划规划纳入到适合的那节车厢、那个版本中。这也是为什么移动端App的持续交付需要良好的信息管理的原因。
第二个关键点是,我们必须要保证固定间隔的发车时间,每周、每两周都可以,但必须保证每个车厢到点即发。只有这样,我们才能保证持续交付流水线的持续运行,以及不间断地产出。这里需要注意的是,对于一些特殊的、不规则的发布,我们要把它们归类到热修复的流程,而不是在发布快车中处理。
第三个关键点是,这个过程的最终产物是可以发布到市场的版本,而不是发布到用户侧的版本。虽然我们把这个发布模式叫作发布快车,但其实它的最终产物是可以发布的待发布版本。所以这个流程完成后的版本没有被正式发布,或出现了部分缺陷无法发布的情况是很正常的,可以被接受。我们并不需要保证每个版本都一定能发布到用户手上。
发布快车的发布模式特别是以上说的三个特性非常符合移动App对持续交付的需求分散开发定期集成控制发布。所以绝大部分的移动App团队都选择采用发布快车的发布方式。
那么,如何才能实现这个发布快车模式的真实落地呢?
选择与发布快车模式匹配的代码分支策略;
改造出与发布快车模式匹配的构建通道;
实现发布流程的全自动化。
选择与发布快车模式匹配的代码分支策略
首先选择一套与之匹配的代码分支管理策略否则整个发布快车的实施会非常别扭。我们先一起回顾一下专栏的第4篇文章《一切的源头代码分支策略的选择》。
我在这篇文章中介绍的代码分支策略中Gitlab Flow与 发布快车模式的思想看上去非常接近。那我们不妨推演一下,这个分支策略是否符合我们的需要。
首先项目仓库的初始状态如图2所示。这里有一个版本V1代码仓库中有2个分支Master是集成分支Production是发布分支。
图2 项目仓库的初始状态
然后以V1的commit为基准建立功能分支1并进行开发如图3所示。
图3 引入功能分支1
如图4所示功能分支1开发完成后合并入Master。测试通过之后形成版本V2V2就可以作为待发布的产物了。另外在形成V2之前我们可以看到另外一个功能分支2也被建立了但这个功能分支并没有被合并到Master所以不会出现在版本V2中。
图4 形成版本2并引入功能分支2
从图5中我们可以看到V2版本后又出现了一个新的功能分支3它与功能分支2并行开发。这两个功能分支合并入Master之后被同时附加到版本V3中。
图5 引入功能分支3并形成版本3
正如以上的几个步骤,如果每个版本都是定时进行构建和打包,那么这样的代码分支管理模型就是一个典型的符合发布快车的物理实现了。
全新的构建通道
当然,为了发布快车模式的落地,我们只是建立与之配套的代码分支管理策略还远远不够,还需要有配套的构建通道。
你可能会问,发布快车模式的落地,为什么还要选择特定的构建通道呢?
我先和你说说发布快车,以及与之配套的代码分支策略的弱点都有哪些吧。
如果功能分支合入Master分支的过程缺乏校验以及必要的构建检查的话那么Production分支在进行自动定期构建时就很容易产生问题而一旦产生问题就会错过这个要定期发布的版本。
如果这只会影响到一个或少数几个功能的话,还好;但设想一下,如果你要发布一个大版本,由于某个小功能而影响了所有的其他功能,是不是就得不偿失了呢?
所以,为了高效的持续交付,我们就必须对构建通道进行一定的改造。
图6 被改造后的构建通道
如图6所示我们会在功能分支合并入Master分支前增加一次构建Merge CI Service这次构建的作用是保证功能分支的集成是成功的否则不允许合并同时对于一个代码仓库来说增加的这次构建过程要保证是串行的即如果这个仓库正有一个合并构建在进行则后续的合并构建需要等待。
这个合并构建过程保证了Master分支上的任何commit随时都可以成功构建。之后再根据发布快车的要求定期启动版本构建Auto CI Service就能顺利地得到可测试版本了。
构建测试版本之后流水线还可以继续处理在production分支上打上对应的tag。
自动化的发布
构建通道建立之后就是发布了。我在上一篇文章中提到移动App的发布与后端服务有所区别。移动App的发布需要特别注意这两点需求
通常在发布到市场之前,会先发布内部,进行针对新功能的内测;
通常为了节省调试信息带来的额外开销内部发布会采用debug包而正式发布则采用release包。
但是从另一方面看相比于后端服务的发布移动App的发布步骤固定且逻辑相对简单。
iOS系统的发布步骤为构建导出ipa包记录符号表备份上传至iTC
Android系统的发布步骤为构建打包更新渠道标识签名保存mapping文件备份上传至发布点。
理解了iOS和Android系统各自的发布步骤我们就可以很容易地做到发布自动化了。
比如针对iOS的版本发布来说在构建和打包之后我们可以获取到对应的ipa包关联对应的版本信息元数据后就可以上传到内部的发布站点供QA下载测试了或者上传到Apple TestFlight进行公测当然也可以部署到App Store了。
接下来,我就和你详细说说如何做到发布的自动化。
你可以使用Fastlane等类似的工具完成整个发布过程还可以根据不同发布的渠道定义各自的lane。当然Fastlane也可以提供打包等一系列Action帮助你完成自动化。
lane :release do #发布到AppStore
increment_build_number #自增版本号的方法
cocoapods #更新pod
gym #打包
deliver(force: true) #发布到AppStore
end
这是一段最简单的Fastfile脚本。它的功能是利用Fastlane提供的Action完成了打包并发布到AppStore。另外你还可以在AppfileFastlane用来描述App基本信息的专用描述文件中定义关于App的信息。
当然你还可以按照发布流程的需求定义自己的lane和Action完成不同的操作。
private_lane :build do |options|
project = options[:project] #获取项目对象
build_number = project.build_number #获取项目定义的版本号
gym(
workspace: project.workspace, #编译工作空间
configuration: project.config, #编译配置
include_symbols: true, #是否包含符号
scheme: project.scheme, #编译计划
xcargs: "BUILD_NUMBER=#{build_number}", #版本号
build_path: project.package_path, #编译路径
output_directory: project.package_path, #ipa包输出地址
output_name: project.ipa_name, #ipa包的名字
silent: false) # 编译Action
end
这段代码展示的就是用gym action构建一个自定义的、带参数的完整的构建过程了。我们可以看到这里的参数是具体的一个project对象。当然这里还有一个叫作output_directory的参数你可以利用这个参数把构建的ipa包放到内部的下载地址。
这样看移动App的自动化发布是不是很简单https://github.com/fastlane/examples这里还有更多相关的例子你可以参考它们完成更加复杂的自动化发布。
总结
今天,我和你一起分享了移动客户端持续交付流水线的几个详细点:
利用发布快车的发布模式,可以有效地管理客户端的版本,保证研发工作按节奏持续向前进展;
采用带发布分支的GitLab Flow配合发布快车的模型可以使其做到物理落地
发布快车本身也有一些弊端比如对Master分支的合并检查不够严格的话会拖累项目进度因此我们采用改造构建通道的方式避免了这个问题的产生
移动App的发布有其独特的流程通常是先内测后正式发布但其流程相对固定且容易自动化。所以我的建议是实现发布的完全自动化以提高研发效率。
另外我还介绍了Fastlane这样一个工具能够帮助你快速完成自动化的实现。
当然我今天所分享的只是移App持续交付流水线的一种方式。在工程实践中不同的产品和组织往往会存在不同的流水线。
所以关于移动App的流水线并没有对错、优劣之分合适的才是最好的。
思考题
你所在的团队移动App的持续交付流水线有哪些点与我今天分享的内容有所不同你可以分析出是什么原因导致了这些不同吗又是否可以进行优化呢
感谢你的收听,欢迎你给我留言。

View File

@ -0,0 +1,174 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
33 进阶如何进一步提升移动APP的交付效率
你好我是王潇俊。今天我和你分享的主题是进阶如何进一步提升移动App的交付效率
通过我在前面分享的《了解移动App的持续交付生命周期》和《细谈移动App的交付流水线pipeline》两个主题你应该已经比较全面和细致地理解了移动客户端持续交付的整个过程。
当然搭建持续交付体系的最终目的是提升研发效率。所以仅仅能把整个流水线跑起来肯定满足不了你的胃口。那么今天我就再和你聊聊如何进一步提升移动App的交付效率。
提升交付效率的基本思路
同其他很多问题的解决方式一样提升移动App持续交付的效率也是要先有一个整体思路再具体落实。
理解了移动App的交付流水线后你很容易就能发现它其实与后端服务的交付流水线十分相似。
后端持续交付流水线包括了代码管理、环境管理、集成和编译管理、测试管理以及发布管理这五个核心过程。而与之相比移动App的运行形势决定了其在环境管理方面没有特别多的要求。
所以我们可以从代码管理、集成和编译管理、测试管理以及发布管理这四个方面来考虑问题。而将这四个方面直接对应到研发流程的话就是标准的开发、构建、测试、发布过程。因此移动App持续交付流水线的优化我们只要从这四个过程中寻找优化点即可。
我们优化移动App持续交付体系的整体思路就是首先找到这四个核心过程中存在的问题或瓶颈再进行针对性的优化从而达到提升效率的目的。
接下来,我们就逐一击破这四个核心过程中的难题吧。
如何提升开发效率?
从开发人员的角度看提升效率最好的方法就是2个字解耦。落到技术实现上来说就是通过组件化形成合理的开发框架。
组件化是指解耦复杂系统时将多个功能模块拆分、重组的过程。对于移动App来说可以横向地按功能模块进行组件化也可以纵向地按照架构层次进行组件化。当然目前移动App的架构往往都已经比较复杂了所以通常都是两者混合的模式。
组件化带来的好处包括:
方便拆分代码仓库,降低分支管理难度,提高开发并行度。-
在上一篇文章《细谈移动App的交付流水线pipeline》中我给出了一种适应发布快车模式的代码分支管理模型。试想一下如果一个巨大的App的所有代码都集中在同一个代码仓库中而所有的并行开发功能又都会形成一个个的功能分支的话它们之间相互的影响将是难以想象的。-
其实任何一个代码仓库当需要管理的并行分支超过10个时都会让人头痛。所以组件化的好处就是对整个项目进行解耦把不相干的功能组件从代码仓库这个层面进行隔离以免互相影响。
组件可以多版本存在,通过依赖快速选取所需版本。-
所有的组件都可以同时发布多个版本,发布的形式可以是代码包、二进制组件等等。这样做的好处是,对于组件的提供方和依赖方来说,只需要通过版本控制就能管理或者选取自己需要的组件功能,这种方式更符合编程习惯,也降低了减少沟通成本。
专业分工,形成更优的组织结构。-
一旦实施组件化,各种更专业的通用组建会慢慢形成(比如网络处理、图片处理、语音处理等等),而这些更专业的组件,也会渐渐地由更专业的人或团队进行开发和维护,专业的分工使得研发效率得到进一步提升。-
所以,组件化其实就是通过专业分工,提升了整个组织的开发效率。
当然,组件化并非完美无瑕,它同时也会引起一些问题,比如:
组件间的依赖问题。由于多组件、多版本的存在,加之它们之间的传递依赖,所以组件化之后的依赖管理问题不容小觑。
组件间的兼容问题。兼容性问题,是由组件间的依赖问题引发的,由此组件的发布管理也会成为瓶颈:组件间到底要不要兼容?出现了不兼容的情况,应该怎么办?
其实,组件化带来的这些负面影响,在开发人员的维度是看不到的,往往会发生在构建阶段。还好,这些问题并不是无解的。接下来,我们就一起看看构建阶段如何解决这些问题,并提高效率吧。
如何提升构建效率?
从目前业界流行的处理方法来看,提升构建阶段的效率,可以从扁平化依赖管理和二进制交付两个维度解决。
第一,扁平化依赖管理
组件的依赖问题到底有多让人头痛。我们一起来看看图1中的组件依赖示例吧。
一个App中的两个组件B和C都依赖了组件G但依赖的却是组件G的不同版本。所以这个组件G的2个版本间就发生了冲突。
由此可见,由传递依赖带来的不确定性,是我们经常会遇见并非常讨厌的组件依赖形式。因为发现和处理的成本都很高。
图1 组件依赖冲突
通常情况下一个移动App可以拆分出十几到几十个组件。大型的移动App如淘宝、美团甚至可以拆分出几百个组件。要保证这么多组件间依赖传递的准确性其难度非常大。所以为了解决这个问题业界现在普遍直接采用扁平化的依赖管理方式减少甚至去除传递依赖以此避免组件、版本冲突的问题。
图2 扁平化依赖
而且这样的扁平化管理方式对于一个App版本来说更清晰、直观。
那么,实现这种扁平化的管理方式,需要具备什么前提吗。
答案,当然是需要。这个前提就是:不同组件之间,以及不同版本之间要保证可兼容。但是,你我都清楚,要想保证全部版本的完全可兼容性,其成本是巨大的。所以,在实践中,我们不会去保证所有版本的绝对兼容,而是去实现所有版本、组件间的相对兼容性。
相对兼容性是指每个组件在发布新版本时对于其所依赖的其他组件都选择组件仓库中的最新版本。这样就保证了这个组件在发布之后的兼容性。如果所有组件都可以这么做就能保证其各自都兼容。但是这个方法不是绝对的比如我们会遇到并发发布或者多个组件间引起的功能逻辑的冲突等问题所以还是需要对移动App进行集成测试。
第二,二进制交付
解决了组件的依赖问题之后,我们需要再考虑的问题是,如何才能提高编译速度。
传统的移动App组件集成及编译的方式如图3所示。组件先以源码的方式集成到目标项目然后对整个项目进行编译。如果组件比较多的话采用这种方式的编译时间会非常长。有时甚至要编译1个多小时。显然我们不会接受这种低效的集成与编译方式。
图3 组件源码集成
所以为了加快编译速度业界通常会采用二进制交付和集成的方案。如图4所示二进制交付会优先把组件编译成二进制包再形成版本并通过组件仓库进行版本管理正如图中的组件A Lib包。在真正编译时我们只要直接链接二进制包就可以了无需再进行一次编译。
图4 二进制交付及集成
使用二进制包的方式可以帮我们大幅提升移动App的编译速度。而且因为有了中间交付物我们可以采用与后端服务一样的方式在本地缓存需要依赖的组件进一步加速编译过程。
通过对开发、构建过程的优化我们已经将原来的交付效率至少提高了1倍。
接下来我们再一起看看如何优化测试和发布流程以求移动App的持续交付体系更高效。
如何提升测试效率?
提高移动App测试效率的方法主要的思路有三个
代码静态扫描工具。-
移动App的测试同样可以使用与后端服务一样的代码静态扫描工具。但相比之下后端服务通常使用的那些工具虽然普适性强但太重且定制的门槛也很高所以针对移动App的代码静态扫描目前多数大厂都采用自研的方式定制静态代码扫描工具。另外针对移动App开源的静态代码扫描工具如Lint等已经可以满足小团队的使用了。
UI自动化测试。-
这部分的关注点是成本和收益比你我都清楚UI自动化测试的脚本维护成本高导致其难以被大规模使用。所以针对重要的模块和组件有计划地使用UI自动化测试是重中之重。
自动Monkey测试。-
Monkey是一款非常好用的探索性测试工具可以大幅提升测试效率有效解决手工测试的盲点。iOS系统的测试由于系统限制比较多所以可以在模拟器上执行Monkey的方式。
合理地利用这些测试工具和方法,就可以有效提升客户端的测试效率。
当然在测试过程中,合理地搭配监控工具,如性能监控、白屏检测等,可以起到更好的作用。
如何提升发布效率?
在前面两篇文章中我提到过移动App的发布流程与后端服务相比差别较大根本原因在于移动App天生具备的分批发布特性。
所以提升移动App的发布效率我们也要采用与后端服务不一样的方式。在这里我总结了提升移动App发布效率需要注意的两个问题
要注意分发的精准性。精准性指的是,分发的目标、数量、时长,以及渠道一定要合理、有效,否则就会消耗无谓的分发成本。-
这里,我和你分享一个关于分发精准性的技巧。其实,说是技巧,更不如说是大家在发布过程中容易疏忽的内容。为了进行小批量的测试,通常我们都会准备一个针对性的测试用户名单。但是,你有没有想过这份名单的更新周期呢?我看到很多组织都极少更新这份名单,其实这样既对用户体验不好,也会影响测试结果。小白鼠也要时常替换的,否则就会失去实验价值。-
关于这份名单的更新周期,我的建议是:结合业务实际情况,尽量避免一个用户连续多次成为小白鼠。
要注意分发的稳定性。稳定性指的是,在分发的过程中,一定要做好监控数据的收集和分析,并且要考虑好风险的处理以及必要的回滚和热修复手段。-
这里我也和你分享一个关于稳定性的技巧。提高分发稳定性的一个方法就是减少分发时更新的内容并同时减少更新的时间。而对于移动App来说静态资源包的差分发布就是一个优化方案。-
比如,携程在选择静态资源包的差分发布时,就经历了这样一个优化过程:从全量包发布,到文件二进制差分,再到预差分。前两个方案都是在更新时,进行差分;而预差分则是在版本发布时,就已经做好了差分计算。与前两种方案相比,预差分的目的就是减少更新时间。但预差分的缺点是,可能要对所有要发布的版本进行差分处理,这将是一个巨大的笛卡尔积。-
所以,携程在经历几次尝试后,最终选择的方案是:结合全量包发布、文件二进制差分,以及预差分三种方案的特点,形成了按需差分的方案。即,先收集用户正在使用的版本,然后只做这些版本与最新版本的差分,从而减少差分处理的成本。
在我看来确保每次分发的有效性以及每次分发都能达到预期就是提高移动App发布效率的一种最有效的手段。
总结
在了解了移动App持续交付体系的内容后你就可以自己去动手搭建一套持续交付体系了。持续交付体系搭建起来后我们需要考虑的问题就成了如何优化这个体系的流程提升这个体系的效率。为此我从开发、构建、测试和发布这四个核心流程的角度和你分享了一些实践经验
利用组件化的思想提升开发效率,但同时也会带来组件依赖及发布的问题;
利用扁平化依赖管理的方法解决组件依赖和发布的问题,同时采用二进制交付的方式,进一步提高构建效率;
合理利用静态代码扫描、UI自动化、自动Monkey等测试工具和方法进一步提升测试效率
确保分发的精准性和稳定性,是提升发布效率的有效手段。
至此通过持续交付移动App的三篇文章再结合着以前我分享的后端服务的持续交付体系的内容你完全可以自己厘清构建移动App持续交付体系的流程了也知道了如何去优化这个流程。
希望这些内容,可以开拓你的思路,能够帮助你解决实际项目中遇到的问题。如果你还有哪些不清楚的内容,欢迎你留言和我一起讨论。
思考题
在今天的分享中我介绍了一种扁平化依赖管理的方法。在实际工作中你是如何管理依赖和bundle的呢
感谢你的收听,欢迎你给我留言。

View File

@ -0,0 +1,154 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
34 快速构建持续交付系统(一):需求分析
从今天这一篇文章开始我们就进入这个专栏的最后一个系列实践案例系列了。在这个系列里我将通过4篇文章以实际操作为主带你快速构建一套持续交付系统。
当然,首先我们要做的是,一起整理一下思路,看看我们的系统具体要满足哪些实际的需求,需要具备什么功能。然后,建立需求的锚点,根据这些锚点,展开具体的搭建工作。
因此,在这篇文章中,我会以先介绍模拟团队和项目,再提出具体持续交付需求的思路,罗列一些要模拟的背景,并为你解说这些场景。这样做,可以帮助你在后面的三篇实践文章中找到对应的需求点,也可以让你与现在团队的持续交付体系作一番比较,找到相通之处,从而加深你对持续交付体系的理解。
模拟团队介绍
我在第7篇文章《“两个披萨”团队的代码管理实际案例》中和你分享了“两个披萨”团队的代码管理实践。基本上我们可以把一个这样的团队看作是一个微型研发团队。虽然这样规模的一个团队也可以很好地运用我们即将搭建的持续交付系统但是因为过于理想化而缺乏了典型性。
所以为了更全面地介绍持续交付系统的搭建过程我将要模拟的团队规模扩大至3个“两个披萨”团队的大小。即整个产品的研发需要由这3个团队合作完成。这3个团队的分工如下图所示
由这样3个团队组成的中小型研发组织架构也是目前互联网公司比较流行的。
模拟系统介绍
介绍完模拟团队的情况,接下来,我们需要再了解一下需要模拟的系统。对于持续交付体系来说,系统的业务逻辑并不是要解决的最重要的问题。因为不管业务逻辑如何,持续交付的过程大致都是相通的,都包括了代码管理、环境管理、集成编译管理、测试管理和发布管理这五大步骤。
反而,系统之间如何集成运作,以及依赖关系、交付形式,关系着这持续交付系统应该如何实现,才是更重要的内容。
在这里我们要模拟的这个系统最终表现为移动App持续交付体系的形式需要中间件、业务后台以及业务客户端这3个团队交付产物的协作才算是完整
首先用户通过团队3交付的移动App进行系统操作
其次移动App需要调用团队2提供的业务后台服务War获取数据和处理业务逻辑
最后后台服务War需要依赖团队1提供的业务中间件Jar完成底层操作如配置读取、缓存处理等。
这三个团队的依赖关系和交付产物,也决定了他们要采用不同的交付方式:
团队1有两类交付方式
第一类是,中间件服务的交付,使用传统的虚机部署,提供可部署的代码包;
第二类是中间件组件的交付使用Jar包发布发布到组件仓库。
团队2的交付方式是后台服务使用Docker交付部署在k8s集群上。
团队3的交付方式是标准的 iOS App交付。
这也是目前比较流行的移动互联网系统的架构形式,当然其中也覆盖了目前流行的容器交付。如果你现在要在一个微型研发团队搭建这样的持续交付系统,那你也可以根据这样的架构形式做适当裁剪,去除一些不需要的功能,顺利达成持续交付的目的。
主体流水线的需求
模拟团队对整个持续交付流水线的需求如下图所示:
整个过程可以大致描述为代码合并到master后能够自动触发对应的集成编译如编译通过则部署到对应的测试环境下部署成功后驱动自动化测试测试通过则分批部署到生产环境。
主体流水线发生的状态变更都需要通过E-mail通知发起人。这里的发起人就是代码提交者和合并审核人。
这条主体流水线,看上去很简单、功能明确。但是,麻雀虽小五脏俱全。因此,各个步骤还都有一些细节实现上的要求。接下来,我们就一起看一下吧。
代码与配置管理相关的需求
3个模拟团队的代码分支策略均采用标准的 GitLab Flow 模型要求是代码通过code review后才能合并入master分支合并入master分支后能够触发对应的集成编译。
同时,我们需要代码静态扫描服务,帮助我们更好地把控代码质量。这个服务的具体工作形式是:
因为代码扫描是异步处理的,所以扫描过程将在代码编译通过之后开始。而扫描结果,则作为是否可继续流水线的依据。
这里需要注意的是,整个代码扫描过程是异步进行的,所以在没有得到扫描结果前,主体流水线将继续进行。
如果主体流水线已经执行完,而代码扫描还没结束,也就是还没有得到扫描结果的话,整条流水线需要停下来等待;而如果在执行主体流水线的过程中,代码静态扫描的结果是不通过的话,那么就需要直接中断主体流水线的执行,此次交付宣告失败。
构建与集成相关的需求
我们对编译与集成的要求,具体可以概括为以下几点:
首先能够同时支持传统的部署包、Docker镜像以及移动App的编译和集成。而且能够在触发编译时自动进行适配支持这样才能保证各个团队有新项目时无须再进行额外配置。
其次,所有构建产物及构建历史,都能被有效、永久地记录和存储。因为单从传统的编译驱动管理角度看,它以编译任务为基准,需要清除过久、过大的编译任务,从而释放更多的资源用于集成编译。但是,从持续交付的角度看,我们需要完全保留这些内容,用于版本追溯。
再次各构建产物有自己独立的版本体系并与代码commit ID相关联。这是非常重要的交付产物的版本就是它的唯一标识任何交付物都可以通过版本进行辨识和追溯。
最后,构建通道必须能够支持足够的并发量。这也就要求集成构建服务要做到高可用和可扩展,最好能做到资源弹性利用。
打包与发布相关的需求
要清楚打包与发布的需求,就需要先了解各个团队的部署标准和环境状况。
从这3个团队交付产物的角度来看他们需要的环境可以描述如下
团队1提供中间件服务。其测试服务器需要1个集群2台虚拟机生产环境需要2个集群各7台虚拟机。
团队2 提供业务后台服务。其测试服务器需要1个集群2个Docker 实例生产环境需要2个集群各7个Docker 实例。
团队3交付移动App。其需要的环境就是内部测试市场。
整个发布体系除了要考虑标准的War包和Docke镜像发布外我们还要考虑Jar包组件的发布。因为团队1的Jar包对应有两类交付方式所以对Jar包的发布我们需要做一些特殊考虑
测试环境可以使用Snapshot版本但是生产环境则不允许
即使测试通过也不一定需要发布Jar包的每个版本到生产环境
Jar包是发布到对应的组件仓库发布形式与其他几类差别比如War包、Docker镜像等较大。
基于以上的考虑我们需要对Jar包的发布做特殊的系统处理。
另外,为了发布过程更加可控,我们需要对代码目录、进程管理、日志格式等进行统一的标准化。这部分标准化的具体内容,我将穿插在具体实现时再做详细说明。
自动化测试的需求
在这里我们的自动化测试平台选择的是TestNG这也是业界最为流行的自动化测试平台之一。
对于测试系统需要注意的是不要有一个测试任务失败就中断交付最好是跑完所有测试任务并收集结果。当然我们可以通过TestNG平台很容易做到这一点。
相反,另外一点倒是我们要注意的,就是“停不下来”。比如测试脚本出现死循环。
除此之外,自动化测试过程中还会发生许多意想不到的事情,特别是造成了一些破坏,使得测试过程无法正常继续等情况。所以,我们需要能够处理这样的异常,比如加上超时机制,使持续交付系统能够继续正常运作。
总结
今天,我通过对要模拟的团队和系统的介绍,引出了我们即将实战搭建的这套持续交付系统的需求锚点。这里,我再概括一下整个持续交付体系的需求:
要模拟的团队有3个分别为中间件团队、后端业务团队和移动App团队3个团队最终产出一个可工作的移动App。
而模拟团队在持续交付主体流水线的需求下,对各个主要模块还有一些具体的需求:
代码与配置需要code review以及静态代码扫描
构建与集成能同时支持Jar、War、Docker以及App版本管理可追溯支持高并发
打包与发布同时支持Jar、War、Docker、App的发布以及统一的部署标准
自动化测试通过TestNG驱动实现全自动测试。
从下一篇文章开始,我会通过开源工具和你一起解决这些需求,最终完成成这套系统的搭建。
思考题
在这一篇文章中,我们模拟的是一个比较完整的团队,而在实际项目中你的团队是不是更小?如果是的话,在建设持续交付体系的过程中,你会裁剪掉哪些需求呢?
感谢你的收听,欢迎你给我留言。

View File

@ -0,0 +1,393 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
35 快速构建持续交付系统GitLab 解决代码管理问题
在上一篇文章中我和你一起理清了我们即将构建的持续交付系统的需求以及要具备的具体功能。那么从这一篇文章开始我们就要正式进入实战阶段了。我会和你详细介绍基于开源工具从0开始搭建一套持续交付平台的详细过程以及整合各个持续交付工具的一些技术细节。
按照我在前面分享的内容搭建一套持续交付系统的第一步就是搭建一套代码管理平台。这里我选择的开源工具是GitLab它是一套高仿GitHub的开源代码共享管理平台也是目前最好的开源解决方案之一。
接下来我们就从使用GitLab搭建代码管理平台开始吧一起看看搭建GitLab平台的过程中可能遇到的问题以及如何解决这些问题。
利用GitLab搭建代码管理平台
GitLab早期的设计目标是做一个私有化的类似GitHub的Git代码托管平台。
我第一次接触GitLab是2013年, 当时它的架构很简单SSH权限控制还是通过和Gitolite交互实现的而且也只有源码安装标准Ruby on Rails的安装方式的方式。
这时GitLab给我最深的印象是迭代速度快每个月至少会有1个独立的release版本这个传统也一直被保留至今。但是随着GitLab的功能越来越丰富架构和模块越来越多也越来越复杂。
所以,现在基于代码进行部署的方式就过于复杂了, 初学者基本无从下手。
因此我建议使用官方的Docker镜像或一键安装包Omnibus安装GitLab。
接下来我就以Centos 7虚拟机为例描述一下整个Omnibus GitLab的安装过程以及注意事项。
在安装前你需要注意的是如果使用虚拟机进行安装测试建议虚拟机的“最大内存”配置在4 G及以上如果小于2 GGitLab可能会无法正常启动。
安装GitLab
安装SSH等依赖配置防火墙。
sudo yum install -y curl policycoreutils-python openssh-server
sudo systemctl enable sshd
sudo systemctl start sshd
sudo firewall-cmd permanent add-service=http
sudo systemctl reload firewalld
安装Postfix支持电子邮件的发送。
sudo yum install postfix
sudo systemctl enable postfix
sudo systemctl start postfix
从rpm源安装并配置GitLab的访问域名测试时可以将其配置为虚拟机的IP比如192.168.0.101)。
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh | sudo bash
sudo EXTERNAL_URL=”http://192.168.0.101” yum install -y gitlab-ee
整个安装过程大概需要10分钟左右。如果一切顺利我们已经可以通过 “http://192.168.0.101” 这个地址访问GitLab了。
如果你在安装过程中遇到了一些问题相信你可以在GitLab的官方文档中找到答案。
配置GitLab
安装完成之后还要进行一些系统配置。对于Omnibus GitLab的配置我们只需要重点关注两方面的内容
使用命令行工具gitlab-ctl管理Omnibus GitLab的一些常用命令。-
比如你想排查GitLab的运行异常可以执行 gitlab-ctl tail 查看日志。
配置文件/etc/gitlab/gitlab.rb包含所有GitLab的相关配置。邮件服务器、LDAP账号验证以及数据库缓存等配置统一在这个配置文件中进行修改。-
比如你想要修改GitLab的外部域名时, 可以通过一条指令修改gitlab.rb文件
external_url http://newhost.com
然后,执行 gitlab-ctl reconfigure重启配置GitLab即可。
关于GitLab更详细的配置你可以参考官方文档。
GitLab的二次开发
在上一篇文章中我们一起分析出需要为Jar包提供一个特殊的发布方式因此我们决定利用GitLab的二次开发功能来满足这个需求。
对GitLab进行二次开发时我们可以使用其官方开发环境 gdk https://gitlab.com/gitlab-org/gitlab-development-kit。但如果你是第一次进行GitLab二次开发的话我还是建议你按照 https://docs.gitlab.com/ee/install/installation.html 进行一次基于源码的安装这将有助于你更好地理解GitLab的整个架构。
为了后面更高效地解决二次开发的问题我先和你介绍一下GitLab的几个主要模块
Unicorn是一个Web Server用于支持 GitLab的主体Web应用
Sidekiq队列服务需要Redis支持用以支持GitLab的异步任务
GitLab ShellGit SSH的权限管理模块
GitalyGit RPC服务用于处理GitLab发出的git操作
GitLab Workhorse基于Go语言用于接替Unicorn处理比较大的http请求。
图1 GitLab架构图引自GitLab官网
对GitLab应用层的修改我们主要关注的是GitLab Rails和GitLab Shell这两个子系统。
接下来,我们一起看一个二次开发的具体实例吧。
二次开发的例子
二次开发最常见的是对GitLab添加一个外部服务调用这部分需要在app/models/project_services下面添加相关的代码。
我们可以参考GitLab对Microsoft Teams的支持方式
在app/models/project_services/microsoft_teams_service.rb下添加一些可配置内容及其属性这样我们就可以在GitLab的service模块页面下看到相应的配置项了。
frozen_string_literal: true
class MicrosoftTeamsService < ChatNotificationService
def title
'Microsoft Teams Notification'
end
def description
'Receive event notifications in Microsoft Teams'
end
def self.to_param
'microsoft_teams'
end
def help
'This service sends notifications about projects events to Microsoft Teams channels.<br />
To set up this service:
<ol>
<li><a href="https://msdn.microsoft.com/en-us/microsoft-teams/connectors">Getting started with 365 Office Connectors For Microsoft Teams</a>.</li>
<li>Paste the <strong>Webhook URL</strong> into the field below.</li>
<li>Select events below to enable notifications.</li>
</ol>'
end
def webhook_placeholder
'https://outlook.office.com/webhook/…'
end
def event_field(event)
end
def default_channel_placeholder
end
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{ type: 'checkbox', name: 'notify_only_default_branch' }
]
end
private
def notify(message, opts)
MicrosoftTeams::Notifier.new(webhook).ping(
title: message.project_name,
summary: message.summary,
activity: message.activity,
attachments: message.attachments
)
end
def custom_data(data)
super(data).merge(markdown: true)
end
end
在lib/microsoft_teams/notifier.rb 内实现服务的具体调用逻辑。
module MicrosoftTeams
class Notifier
def initialize(webhook)
@webhook = webhook
@header = { 'Content-type' => 'application/json' }
end
def ping(options = {})
result = false
begin
response = Gitlab::HTTP.post(
@webhook.to_str,
headers: @header,
allow_local_requests: true,
body: body(options)
)
result = true if response
rescue Gitlab::HTTP::Error, StandardError => error
Rails.logger.info("#{self.class.name}: Error while connecting to #{@webhook}: #{error.message}")
end
result
end
private
def body(options = {})
result = { 'sections' => [] }
result['title'] = options[:title]
result['summary'] = options[:summary]
result['sections'] << MicrosoftTeams::Activity.new(options[:activity]).prepare
attachments = options[:attachments]
unless attachments.blank?
result['sections'] << {
'title' => 'Details',
'facts' => [{ 'name' => 'Attachments', 'value' => attachments }]
}
end
result.to_json
end
end
end
以上就是一个最简单的Service二次开发的例子。熟悉了Rails和GitLab源码后你完全可以以此类推写出更复杂的Service。
GitLab的HA方案
对于研发人员数量小于1000的团队我不建议你考虑GitLab 服务多机水平扩展的方案。GitLab官方给出了一个内存对应用户数量的参照如下
16 GB RAM supports up to 2000 users-
128 GB RAM supports up to 16000 users
从这个配置参照数据中我们可以看到一台高配的虚拟机或者容器可以支持2000名研发人员的操作而单台物理机128 GB配置足以供上万研发人员使用。
在携程除了要支持开发人数外还要考虑到高可用的需求所以我们经过二次开发后做了GitLab的水平扩展。但是即使在每天的GitLab使用高峰期整机负载也非常低。因此对于大部分的研发团队而言做多机水平扩展方案的意义并不太大。
同时实现GitLab的完整水平扩展方案也并不是一件易事。
图2 GitLab官方HA方案引自GitLab官网
我们先看一下社区版的GitLab官方提供的HA方案的整体架构图可参考图2。从整体架构上看PostgreSQL、Redis这两个模块的高可用都有通用的解决方案。而GitLab在架构上最大的问题是需要通过文件系统在本地访问仓库文件。于是水平扩展时如何把本地的仓库文件当做数据资源在服务器之间进行读写就变成了一个难题。
官方推荐的方案是通过NFS进行多机Git仓库共享。但这个方案在实际使用中并不可行git本身是IO密集型应用对于真正在性能上有水平扩展诉求的用户来说NFS的性能很快就会成为整个系统的瓶颈。我早期在美团点评搭建持续交付体系时曾尝试过这个方案当达到几百个仓库的规模时NFS就撑不住了。
对于水平扩展这部分内容,有一个非常不错的分享:阿里的《我们如何为三万人的公司横向伸缩 GitLab》。但是实施这个方案你需要吃透Git的底层所以并不容易实施。
而携程的解决方案就比较简单了:
我们在应用层处理这个问题根据Git仓库的group名字做了一个简单切分并使用ssh2对于Git访问做一次代理保证对于不同项目的http访问能够分配到确定的机器上。
这个方案的优点是实施起来相对简单缺点是无法向上兼容升级GitLab会比较麻烦。
当然你还可以参考GitLab的官方建议并结合我分享的经验完成自己的HA方案。
如何应对代码管理的需求?
我们先一起回忆一下上一篇文章中我们对代码管理平台的需求即要求能够支持3个团队的开发工作且具备code review和静态代码检查的功能。
要实现这些需求我需要先和你介绍一下GitLab提供的几个比较重要的功能。
了解GitLab提供的功能
Gitlab作为开源的代码管理平台其原生也提供了不少优秀的功能可以直接帮助我们解决上一篇文章中的一些需求。这些功能主要包括
Merge Requests-
分支代码审核合并功能关于Merge Request和分支策略。你可以回顾一下第四篇文章《 一切的源头,代码分支策略的选择》和 第七篇文章《“两个披萨”团队的代码管理实际案例》的内容。-
之后就是,我们根据不同的团队性质,选择不同的分支管理策略了。-
比如在我们的这个系统中中间件团队只有6个开发人员且都是资深的开发人员他们在项目的向下兼容方面也做得很好所以整个团队选择了主干开发的分支策略以保证最高的开发效率。-
同时后台团队和iOS团队各有20个开发人员其中iOS团队一般是每周三下午进行发布所以这两个团队都选择了GitLab Flow的分支策略。
issues-
可以通过列表和看板两种视图管理开发任务和Bug。在携程我们也有一些团队是通过列表视图管理Bug通过看板视图维护需求和开发任务。
CI/CD-
GitLab和GitLab-ci集成的一些功能支持pipline和一些CI结果的展示。携程在打造持续交付系统时GitLab-ci的功能还并不完善所以也没有对此相关的功能进行调研直接自研了CI/CD的驱动。-
不过由于GitLab-ci和GitLab天生的集成特性目前也有不少公司使用它作为持续集成工作流。你也可尝试使用这种方法它的配置很简单可以直接参考官方文档。而在专栏中我会以最流行的Jenkins Pipeline来讲解这部分功能。
Integrations-
Integrations包括两部分
GitLab service是在GitLab内部实现的与一些缺陷管理、团队协作等工具的集成服务。
Webhook支持在GitLab触发代码push、Merge Request等事件时进行http消息推送。
我在下一篇文章中介绍的代码管理与Jenkins集成就是通过Webhook以及Jenkins的GitLab plugin实现的。
理解了GitLab的几个重要功能后便可以初步应对上一篇文章中的几个需求了。之后搭建好的GitLab平台满足代码管理的需求我们可以通过三步实现
创建对应的代码仓库;
配置Sonar静态检查
解决其他设置。
接下来,我和你分享一下,每一步中的关键点,以及具体如何满足相应的代码需求。
第一步,创建对应的代码仓库
了解了GitLab的功能之后我们就可以开始建立与需求相对应的Projects了。
因为整个项目包括了中间件服务、业务后台服务以及业务客户端服务这三个职责所以相应的我们就需要在GitLab上创建3个group并分别提交3个团队的项目。
对于中间件团队我们创建了一个名为framework/config的项目。这个项目最终会提供一个配置中心的服务并且生成一个config-client.jar的客户端供后台团队使用。
后台服务团队的项目名为waimai/waimai-service产物是一个war包。
移动团队创建一个React Native项目mobile/waimai-app。
第二步配置Sonar静态检查
创建了三个代码仓库之后为了后续在构建时进行代码静态检查所以现在我们还需要做的就是配置代码静态扫描工具。而在这里我依旧以Sonar为例进行下面详解。
我们在使用SonarQube服务进行静态检查时需要注意的问题包括
Sonar的搭建比较简单从 https://www.sonarqube.org/downloads/ 下载Sonar的压缩包以后在 conf/sonar.properties 中配置好数据库的连接串然后执行bin/linux-x86-64/sonar.sh start命令。之后我们可以再查看一下日志logs/sonar.log当日志提示“SonarQube is up”时就可以通过http://localhost:9000访问sonar了。如果你有不明白的问题可以参考 https://docs.sonarqube.org/display/SONAR/Installing+the+Server
和GitLab的扩展一般只能通过二次开发不同Sonar通过plugin的方式就可以完成扩展。在extensions/plugins目录下面已经预置了包含 Java、Python、PHP 等语言支持以及LDAP认证等插件。你可以通过直接安装插件的方式进行扩展。
插件安装完成后我们就可以尝试在本地使用Maven命令对中间件和后台团队的Java项目进行静态检查了React Native项目则是通过sonar-scanner配合ESLint完成静态检查的。
GitLab的Merge Request需要通过触发Jenkins构建 Sonar 来驱动代码的持续静态检查,至于如何集成我会在下一篇文章中和你详细介绍。
关于静态检查的更多知识点,你可以再回顾一下第二十五篇文章《代码静态检查实践》
第三步,解决其他设置
经过创建对应的代码仓库、配置Sonar静态检查这两步再配合使用GitLab提供的Merge Request、Issues、CI/CD和Integration功能代码管理平台基本上就算顺利搭建完毕了。
之后剩余的事情包括:
为项目添加开发者及对应的角色;
根据分支策略设定保护分支仅允许Merge Request提交
创建功能分支。
至此,我们需要的代码管理平台就真的搭建好了,开发人员可以安心写代码了。
总结及实践
在上一篇文章中我们已经清楚了整个持续交付体系中代码管理平台要具备的功能所以今天我就在此基础上和你一起使用GitLab完成了这个代码管理平台的搭建。
首先我介绍了GitLab的安装及配置过程并通过Microsoft Teams这个具体案例介绍了如何完成GitLab的二次开发以应对实际业务的需求。同时我还介绍了GitLab的高可用方案。
然后我针对代码管理平台要支持3个团队的code reivew和代码静态扫描的需求和你分享了如何使用三步实现这些需求
第一步,创建对应的代码仓库;
第二步配置Sonar静态检查
第三步,解决其他设置。
完成以上工作后,我们的代码管理平台就可以正式运作了,也为我们下一篇文章要搭建的编译构建平台做好了准备。
最后希望你可以按照这篇文章的内容自己动手实际搭建一套GitLab以及配套的Sonar服务。
千里之行始于足下,如果搭建过程中,遇到了什么问题,欢迎给我留言一起讨论。

View File

@ -0,0 +1,311 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
36 快速构建持续交付系统Jenkins 解决集成打包问题
在上一篇文章中, 我和你一起利用开源代码平台GitLab和代码静态检查平台SonarQube实现了代码管理平台的需求。那么我今天这篇文章的目的就是和你一起动手基于Jenkins搭建集成与编译相关的系统。
Jenkins的安装与配置
Jenkins这个开源项目提供的是一种易于使用的持续集成系统将开发者从繁杂的集成工作中解脱了出来使得他们可以专注于更重要的业务逻辑实现。同时Jenkins还能实时监控集成环境中存在的错误提供详细的日志文件和提醒功能并以图表的形式形象地展示项目构建的趋势和稳定性。
因此在携程我们选择Jenkins作为了代码构建平台。而为了用户体验的一致性以及交付的标准化携程针对Java、.net等用到的主要语言为开发人员封装了对于Jenkins的所有操作并在自研的持续交付平台中实现了整个持续交付的工作流。
而如果是第一次搭建持续交付系统我建议你不用像携程这样进行二次开发因为Jenkins本身就可以在持续交付的构建、测试、发布流程中发挥很大的作用完全可以满足你的搭建需求。而且它提供的Pipeline功能也可以很好地驱动整个交付过程。
所以在这篇文章中我就以Jenkins为载体和你分享如何搭建集成与编译系统。
第一步安装Jenkins
为了整个持续交付体系的各个子系统之间的环境的一致性我在这里依然以Centos 7虚拟机为例和你分享Jenkins 2.138最新版的安装过程。假设Jenkins主机的IP地址是10.1.77.79。
安装Java环境
yum install java-1.8.0-openjdk-devel
更新rpm源并安装Jenkins 2.138
rpm import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
yum install jenkins
然后,我们就可以通过 “http://10.1.77.79” 访问Jenkins了整个安装过程很简单。
当然Jenkins还有其他的安装方式你可以参考 https://jenkins.io/doc/book/installing/。
第二步配置Jenkins对GitLab的访问权限
Jenkins安装完成之后我们还需要初始化安装Jenkins的一些基础配置同时配置Jenkins对GitLab的访问权限。
在新版的Jenkins中第一次启动时会有一个初始化向导引导你设置用户名、密码并安装一些插件。
在这里我推荐你勾选“安装默认插件”用这种方式安装Pipline、 LDAP等插件。如果这个时候没能选择安装对应的插件你也可以在安装完成后在系统管理->插件管理页面中安装需要的插件。
那么如何才能使编译和GitLab、SonarQube整合在一起呢这里我以一个后台Java项目为例对Jenkins做进一步的配置以完成Jenkins和GitLab、SonarQube的整合。这些配置内容主要包括
配置Maven
配置 Jenkins钥匙
配置GitLab公钥
配置Jenkins GitLab插件。
接下来,我就逐一和你介绍这些配置内容吧。
配置Maven
进入系统管理->全局工具配置页面安装Maven并把名字设置为M3。如图1所示。
图1 Maven配置
这样配置好Maven后Jenkins就会在第一次使用GitLab时自动安装Maven了。
配置 Jenkins钥匙
配置Jenkins钥匙的路径是凭据->系统->全局凭据->添加凭据。
然后,将你的私钥贴入并保存。 如图2所示。
图2 Jenkins钥匙配置
配置GitLab公钥
在GitLab端, 进入 http://{Gitlab Domain}/profile/keys贴入你的公钥并保存如图3所示。
图3 GitLab公钥配置
通过配置Jenkins钥匙以及配置GitLab公钥两步你就已经完成了Jenkins对GitLab仓库的访问权限配置。
配置Jenkins GitLab插件
Jenkins的GitLab-plugin 插件的作用是在代码提交和Merge Request时触发编译。安装这个插件的方法是进入Jenkins的系统管理->插件管理页面,选择 GitLab Plugin 安装。
Jenkins重启后选择凭据->系统->全局凭据->添加凭据再选择GitLab API Token。然后将 http://10.1.77.79/profile/personal_access_tokens中新生成的access token贴入GitLab API Token并保存。
关于GitLab-plugin插件的更详细介绍你可以参考它的官方文档。
完成了这四步的必要配置之后你就可以开始使用Jenkins Pipline构建集成与编译系统的工作流了。
使用Jenkins Pipeline构建工作流
在使用Jenkins搭建集成和编译系统前我们先一起回忆一下我在《快速构建持续交付系统需求分析》中提到的关于集成与编译系统的需求
我们需要在代码push之后自动触发编译和集成。如果编译成功这套系统还要能继续处理自动化测试和部署。并且在整个过程中这个系统要能自动地适配三种不同的代码平台和交付产物。
那么如何才能驱动整个事务的顺利完成呢这里我们就需要用到大名鼎鼎的Jenkins Pipeline了。
Jenkins Pipeline介绍
Jenkins Pipeline是运行在Jenkins上的一个工作流框架支持将原先运行在一个或多个节点的任务通过一个Groovy脚本串联起来以实现之前单个任务难以完成的复杂工作流。并且Jenkins Pipline支持从代码库读取脚本践行了Pipeline as Code的理念。
Jenkins Pipeline大大简化了基于Jenkins的开发工作。之前很多必须基于Jenkins插件的二次开发工作你都可以通过Jenkins Pipeline实现。
另外Jenkins Pipeline大大提升了执行脚本的可视化能力。
接下来我就和你分享一下如何编写Jenkins Pipeline以及从代码编译到静态检查的完整过程。这个从代码编译到静态检查的整个过程主要包括三大步骤
第一步创建Jenkins Pipeline任务
第二步配置Merge Request的Pipeline验证
第三部编写具体的Jenkins Pipeline脚本。
第一步,创建 Jenkins Pipeline任务
首先在Jenkins中创建一个流水线任务并配置任务触发器。详细的配置如图4所示。
图4 触发器创建
然后在GitLab端配置Webhook。配置方法为在GitLab项目下的settings->Integrations下配置并勾选 “Merge request events”选项。
经过这些配置后, 每次有新的Merge Request被创建或更新都会触发Jenkins的Pipeline而再由自定义的Pipeline脚本完成具体任务比如代码扫描任务。
第二步配置Merge Request 的 Pipeline 验证
在驱动代码静态扫描之后我们还要做一些工作以保证扫描结果可以控制Merge Request的行为。
进入settings->Merge Request页面, 勾选“Only allow Merge Requests to be merged if the pipeline succeeds”。这个配置可以保证在静态检查任务中不能合并Merge Request。
第三步编写具体的Pipeline脚本
然后我们再一起看一下为了实现我们之前的需求,即获取代码-编译打包-执行Sonar静态代码检查和单元测试等过程。Jenkins端的Pipeline脚本如下同时我们需要将该脚本配置在Jenkins中。
node {
def mvnHome
#修改Merge Request的状态并checkout代码
stage('Preparation') { // for display purposes
mvnHome = tool 'M3'
updateGitlabCommitStatus name: 'build', state: 'running'
checkout scm
}
#执行Maven命令对项目编译和打包
stage('Build') {
echo 'Build Start'
// Run the maven build
sh "'${mvnHome}/bin/mvn' -Dmaven.test.skip=true clean package"
}
#启动sonar检查允许junit单元测试获取编译产物并更新Merge request的状态
stage('Results') {
// Run sonar
sh “'${mvnHome}/bin/mvn' org.sonarsource.scanner.maven:sonar-maven-plugin:3.2:sonar”
junit '**/target/surefire-reports/TEST-*.xml'
archive 'target/*.war'
updateGitlabCommitStatus name: 'build', state: 'success'
}
}
在这个脚本中一共包括了3个stage。
第一个stage
从GitLab中获取当前Merge Request源分支的代码同时通Jenkins GitLab插件将Merge Request所在的分支的当前commit状态置为running。这个时候我们可以在GitLab的页面上看到Merge Request的合并选项已经被限制了如图5所示。
图5 GitLab Merge Request
第二个stage
比较好理解就是执行Maven命令对项目编译和打包。
第三个stage
通过Maven调用Sonar的静态代码扫描并在结束后更新Merge Request的commit状态使得Merge Request允许被合并。同时将单元测试结果展现在GitLab上。
通过以上这三步我们已经完整地实现了这个集成和编译系统的需求在GitLab端创建Merge Request时预先进行一次代码扫描并保证在代码扫描期间代码无法被合并入主干分支只有扫描通过后代码才能被合并。
当然这个示例的Pipline的脚本还比较简单。但掌握了基本的思路之后在这个基础上我们还可以添加更多的改进代码达到更多的功能。
比如我们在Sonar检测之后可以调用Sonar的API获取静态检查的详细信息然后调用GitLab的API将静态检查结果通过comment的方式展现在GitLab的Merge Request页面上从而使整个持续集成的流程更加丰满和完整。
多语言平台构建问题
上面的内容我以Java后台项目为例详细介绍了Jenkins Pipeline的创建。 但是,在实际的工作中,整个编译平台需要支持的是多种语言。所以,我要再和你分享下多语言情况下,集成和编译系统可能会碰到的问题。
在这里,我将多语言栈情况下,集成与编译系统常会遇到的问题,归结为两类:
多语言CI流水线的管理
Jenkins Pipeline的管理。
接下来,我们就一起看看,如何解决这两个问题吧。
多语言CI流水线管理
关于如何进行Docker编译和移动端编译的问题你可以先回顾一下第17篇文章《容器镜像构建的那些事儿》以及第32篇文章《细谈移动APP的交付流水线》的内容并将相关的逻辑Pipeline化。
当然对于Docker镜像和iOS App这两种不同的交付流水线你还需要特别关注的几个点我再带你回顾一下。
第一Docker镜像
对于构建docker镜像我们需要在静态检查之后增加一个stage把Dockerfile放入代码仓库。Dockerfile包括两个部分
base镜像的定义包括Centos系统软件的安装和Tomcat环境的创建
war包部分将Jenkins当前工作目录下的war包复制到Docker镜像中保证每次Docker镜像的增量就只有war包这一个构建产物从而提高Docker镜像的编译速度。
第二iOS App
而对于iOS应用需要在修改Build stage的逻辑中 增加fastlane shell命令。详细步骤可以参考第32篇文章《细谈移动APP的交付流水线》的内容我就不再此赘述了。
特别需要注意的是因为iOS机器只能在OS X环境下编译所以我们需要在Pipeline脚本的node上指定使用Jenkins的Mac Slave。
Jenkins Pipeline的管理
原则上对于每个项目你都可以配置一个Jenkins Pipeline任务。但当我们需要维护的平台越来越多或者项目处于多分支开发的状态时这种做法显然就不合适了比如
每个项目组的开发人员都需要调整Jenkins的脚本很容易造成代码被错误更改
当需要回滚代码时,无法追述构建脚本的历史版本。
在专栏的第20篇文章《Immutable任何变更都需要发布》中我曾提到环境中的任何变更都需要被记录、被版本化。
所以在Jenkins Pipeline的过程中更好的实践是将Pipeline的脚本文件Jenkinsfile放入Git的版本控制中。每次执行Jenkins Job前先从Git中获取到当前仓库的Pipeline脚本。
这样不仅降低了单个项目维护Jenkins job的成本而且还标准化了不同语言平台的构建从而使得一套Jenkins模板就可以支持各个语言栈的编译过程。
多平台构建产物管理
除了多语言栈的问题外,我们还会碰到的另一个问题是,构建产物的管理问题。
当开发语言只是Java的时候我们管理的构建产物主要是jar包和war包而管理方式一般就是把Nexus和Artifactory作为代码管理仓库。
而引入一种新的部署工具后我们就需要额外的管理方式。比如引入Docker镜像后我们需要引入用于存储和分发Docker镜像的企业级Registry服务器Harbor。
所以,为了保证整个系统工具链的一致性,我们需要做到:
产物的统一版本化即无论是Java的war包或是.net程序的压缩包都需要支持与上游的编译系统和下游的部署系统对接。
对于同一个版本的多个构建产物需要将它们和代码的commit ID实现有效的关联。比如对于同一份Java代码生成的war包和Docker镜像我们可以通过一个版本号把它们关联起来。
但是,这两种做法会使得整个持续交付系统的研发复杂度更高。
所以,携程最终选择的方案是:标准化先行。也就是说,保证不同语言的发布有且只有一套统一的流水线,并通过在编译系统的上层再封装一层自研系统,以达到不同的物理构建产物,可以使用同一个逻辑版本号进行串联管理的目的。
而针对这个问题业界普遍采用的解决方案是用Artifactory或者Nexus对构建产物进行统一管理。Artifactory和Nexus都包括了开源OSS版和付费专业版。
另外,你可能在选择构建产物仓库的时候会有这样的疑惑:我到底应该选择哪个仓库呢。那么,我就再和你分享一下我之前调研得到的一些结论吧。
如果你需要管理的产物只是Java相关的Maven或者Gradle那么Nexus或者Artifactory都能工作得很好你可以随意选择。
如果你有管理多语言构建产物的需求而又没有付费意愿的话我建议你使用Nexus 3的OSS版本。Nexus 3的OSS版本支持10多种主流编程语言。而Artifactory的OSS版本能支持的编译工具就非常有限只有Gradle、Ivy、Maven、SBT这四种。
如果你有管理多语言构建产物的需求而且也接受付费的话我推荐你使用Artifactory的付费版本。Artifactory的付费版本中包含了很多头部互联网公司的背书方案功能相当丰富。而且如果你所在公司的开发人员比较多的话Artifactory按实例付费的方式也更划算。
好了,到此为止,我们的集成构建系统也搭建完成了。加上我们上一篇文章中一起搭建的代码管理平台,我们已经可以跑完三分之二的持续交付过程了。
所以,在接下来的最后一篇文章中,我将会为你介绍关于自动化测试和发布的一些实践,这样就能完整地实现我们对持续交付系统的需求了。
总结与实践
通过今天这篇文章我和你分享了如何快速安装和配置一套有效的Jenkins系统以及如何打通Jenkins与GitLab之间的访问。这样就可以使这套基于Jenkins的集成与编译系统与我们在上一篇文章中基于GitLab搭建的代码管理平台相呼应从而满足了在代码平台push代码时驱动集成编译系统工作的需求。
当然在今天这篇文章中我还详细分析了Jenkins Pipeline的创建以及与Merge Request的联动合作配置同时提供了一个Pipeline脚本的例子帮助你理解整个Pipeline的工作原理。这样你就可以根据自己的具体需求搭建起适合自己的持续交付流水线了。
除此之外,我还提到了关于多语言平台和多平台构建产物的问题。对于这种复杂的问题,我也给出了解决问题的一些行之有效的办法。比如,使用统一逻辑版本进行产物管理等。
这样通过搭建Jenkins系统构建Pipeline流水线以及处理好构建产物这三部曲相信你已经可以顺利构建起一套适合自己的集成与编译系统了。
那么,接下来,建议你按照我今天的分析自己动手试一下,看看整个搭建过程是否顺利。如果你在这个过程中碰到了任何问题,欢迎你给我留言一起讨论。

View File

@ -0,0 +1,286 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
37 快速构建持续交付系统Ansible 解决自动部署问题
今天这篇文章已经是实践案例系列的最后一篇了。在《快速构建持续交付系统GitLab 解决配置管理问题》和《快速构建持续交付系统Jenkins 解决集成打包问题》这两篇文章中我们已经分别基于GitLab搭建了代码管理平台、基于Jenkins搭建了集成与编译系统并解决了这两个平台之间的联动、配合问题从而满足了在代码平台 push 代码时,驱动集成编译系统工作的需求。
算下来,我们已经通过前面这两篇文章,跑完了整个持续交付体系三分之二的路程,剩下的就是解决如何利用开源工具搭建发布平台完成代码发布,跑完持续交付最后一公里的问题了。
利用Ansible完成部署
Ansible 是一个自动化运维管理工具支持Linux/Windows跨平台的配置管理任务分发等操作可以帮我们大大减少在变更环境时所花费的时间。
与其他三大主流的配置管理工具Chef、Puppet、Salt相比Ansible最大的特点在于“agentless”即无需在目标机器装安装agent进程即可通过SSH或者PowerShell对一个环境中的集群进行中心化的管理。
所以这个“agentless”特性可以大大减少我们配置管理平台的学习成本尤其适合于第一次尝试使用此类配置管理工具。
另外利用Ansible我们可以完成虚拟机的初始化以及Tomcat Java程序的发布更新。
现在我们就先看看如何在我们的机器上安装Ansible以及如何用它来搭建我们的代码发布平台。这里我们再一起回顾下我在第34篇文章《快速构建持续交付系统需求分析》中提到的对发布系统的需求
同时支持 Jar、War、Docker的生产发布以及统一的部署标准。
对于移动App我们只要发布到内部测试集市即可所以只需要在编译打包之后上传至指定地址这个操作在Jenkins Pipeline里执行就可以了所以本篇就不累述了。
Ansible安装
对于Ansible环境的准备我推荐使用pip的方式安装。
sudo pip install Ansible
安装完之后, 我们可以简单测试一下:
提交一个Ansible的Inventory文件 hosts该文件代表要管理的目标对象
$ cat hosts
[Jenkinsservers]
10.1.77.79
打通本机和测试机的SSH访问
$ ssh-copy-id deployer@localhost
尝试远程访问主机 10.1.77.79
$ Ansible -i hosts all -u deployer -a “cat /etc/hosts”
10.1.77.79 | SUCCESS | rc=0 >>
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
如果返回SUCCESS则表示我们已经可以通过Ansible管理该主机了。
接下来我们再看一下如何使用Ansible达到我们的发布目标吧。
Ansible使用
现在我先简单介绍下在初次接触Ansible时你应该掌握的两个最关键的概念Inventory和PlayBook。
Inventory
对于被Ansible管理的机器清单我们可以通过Inventory文件分组管理其中一些集群的机器列表分组并为其设置不同变量。
比如我们可以通过Ansible_user 指定不同机器的Ansible用户。
[Jenkinsservers]
10.1.77.79 Ansible_user=root
10.1.77.80 Ansible_user=deployer
[Gitlabservers]
10.1.77.77
PlayBook
PlayBook是Ansible的脚本文件使用YAML语言编写包含需要远程执行的核心命令、定义任务具体内容等等。
我们一起看一个Ansible官方提供的一个例子吧。
---
- hosts: webservers
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum:
name: httpd
state: latest
- name: write the apache config file
template:
src: /srv/httpd.j2
dest: /etc/httpd.conf
- hosts: databases
remote_user: root
tasks:
- name: ensure postgresql is at the latest version
yum:
name: postgresql
state: latest
- name: ensure that postgresql is started
service:
name: postgresql
state: started
这段代码的最主要功能是使用yum完成了Apache服务器和PostgreSQL的安装。其中包含了编写Ansible PlayBook的三个常用模块。
yum 调用目标机器上的包管理工具完成软件安装 。Ansible对于不同的Linux操作系统包管理进行了封装在CentOS上相当于yum 在Ubuntu上相当于APT。
Template 远程文件渲染,可以把本地机器的文件模板渲染后放到远程主机上。
Service 服务管理同样封装了不同Linux操作系统实际执行的Service命令。
通常情况下我们用脚本的方式使用Ansible只要使用好Inventory和PlayBook这两个组件就可以了使用PlayBook编写Ansible脚本然后用Inventory维护好需要管理的机器列表。这样就能解决90%以上使用Ansible的需求。
但如果你有一些更复杂的需求比如通过代码调用Ansible可能还要用到API组件。感兴趣的话你可以参考Ansible的官方文档。
使用Ansible进行Java应用部署
我先来整理下针对Java后端服务部署的需求
完成Ansible的PlayBook后在Jenkins Pipeline中调用相关的脚本从而完成Java Tomcat应用的发布。
首先在目标机器上安装Tomcat并初始化。
我们可以通过编写Ansible PlayBook完成这个操作。一个最简单的Tomcat初始化脚本只要十几行代码但是如果我们要对Tomcat进行更复杂的配置比如修改Tomcat的CATALINA_OPTS参数工作量就相当大了而且还容易出错。
在这种情况下一个更简单的做法是使用开源第三方的PlayBook的复用文件roles。你可以访问https://galaxy.Ansible.com 这里有数千个第三方的roles可供使用。
在GitHub上搜索一下Ansible-Tomcat并下载就可以很方便地使用了。
这里我和你一起看一个具体roles的例子
---
- hosts: Tomcat_server
roles:
- { role: Ansible-Tomcat }
你只需要这简单的三行代码就可以完成Tomcat的安装以及服务注册。与此同时你只要添加Tomcat_default_catalina_opts参数就可以修改CATALINA_OPTS了。
这样一来Java应用所需要的Web容器就部署好了。
然后,部署具体的业务代码。
这个过程就是指把编译完后的War包推送到目标机器上的指定目录下供Tomcat加载。
完成这个需求我们只需要通过Ansible的SCP模块把War包从Jenkins推送到目标机器上即可。
具体的命令如下:
- name: Copy a war file to the remote machine
copy:
src: /tmp/waimai-service.war
dest: /opt/Tomcat/webapps/waimai-service.war
但是这样编写Ansible的方式会有一个问题就是把Ansible的发布过程和Jenkins的编译耦合了起来。
而在上一篇文章《快速构建持续交付系统Jenkins 解决集成打包问题》中我提到要在编译之后把构建产物统一上传到Nexus或者Artifactory之类的构建产物仓库中。
所以此时更好的做法是直接在部署本地从仓库下载War包。这样之后我们有独立部署或者回滚的需求时也可以通过在Ansible的脚本中选择版本实现。当然此处你仍旧可以使用Ansible的SCP模块复制War包只不过是换成了在部署机上执行而已。
最后,重启 Tomcat 服务,整个应用的部署过程就完成了。
Ansible Tower 简介
通过上面的几个步骤我们已经使用Ansible脚本简单实现了Tomcat War包分发的过程。
这样的持续交付工作流,虽然可以工作,但依然存在两个问题。
用户体验问题。-
我们一起回顾下第21篇文章《发布系统一定要注意用户体验》中的相关内容用户体验对发布系统来说是相当重要的。-
在上面使用Ansible进行部署Java应用的方案中我们采用的Jenkins Pipeline和Ansible命令行直接集成的方式就所有的信息都集中到了Jenkins的console log下面从而缺少了对发布状态、异常日志的直观展示整个发布体验很糟糕。
统一管理问题。-
Ansible缺乏集中式管理需要在每个Jenkins节点上进行Ansible的初始化增加了管理成本。
而这两个问题我们都可以通过Ansible Tower解决。
图1 Ansible Dashboard 来源 Ansible 官网)
Ansible Tower是Ansible的中心化管理节点既提供了Web页面以达到可视化能力也提供了Rest API以达到调用Ansible的PlayBook的目的。
如图1所示为Ansible Tower的Dashboard页面。我们可以看到这个页面提供了整个Ansible集群发布的趋势图以及每次发布在每台被部署机器上的详细结果。
灰度发布的处理
通过上面的内容我们已经可以通过合理使用Ansible顺利地部署一个Java应用了而且还可以通过Ansible Tower监控整个发布过程。而对于灰度发布过程的处理你只需要在Jenkins Pipeline中编写相应的脚本控制具体的发布过程就可以了。
比如通过Inventory定义灰度分批策略再利用Pipeline驱动PlayBook就是一个典型的灰度发布的处理过程。其实这只是将原子化的单机操作批量化了而已。
当然这个过程中我们还需要考虑其他一些问题。而对于这些问题如何解决你就可以参考发布及监控系列的六篇文章第19篇至第24篇了。
至此标准的Java应用的发布就已经大功告成了。接下来我再和你说说其他产物Jar包、Docker镜像的发布方式。
Jar包的发布
Jar包的发布本身就比较简单执行一条Maven命令mvn deploy就可以完成。但Jar包发布的关键在于如何通过工具提升Jar包发布的质量。
在不引入任何工具和流程辅助时我们在携程尝试过让开发人员自行通过“mvn deploy”进行发布。但结果可想而知造成了很多问题。诸如大量低质量的代码进入仓库release版本的Jar包被多次覆盖版本管理混乱Bug难以排查等等。
后来我们初次收紧了发布权限也就是只把“mvn deploy”的权限下放给每个团队的技术经理tech leader。这种方案虽然在一定程度上解决了Jar包质量的问题但同时也降低了发布效率。这里发布效率的降低主要体现在两个方面
一方面,每次发布都需要经过技术经理,增加了他的工作负担;
另一方面“mvn deploy”权限需要由SCM人员手工完成增加了发布的沟通成本降低了整个团队的开发效率。
再后来为了解决这些问题我们在GitLab上进行了二次开发允许开发人员自主选择某个pom module的Jar包进行发布并记录下每次的Jar包发布的记录。
在Jar包发布的第一步我们使用Maven Enforcer插件进行构建检测以保证所有进入仓库的Jar包是合规的。这部分内容你可以参考第15篇文章《构建检测无规矩不成方圆》。
如果你不想通过在GitLab上进行二次开发控制Jar包发布的话简单的做法是通过Jenkins任务参数化创建一个Jar包发布的job。让用户在每次发布前填入所需的代码仓库和module名并在job的逻辑中保证Jar包编译时已经通过了Enforcer检查。
这样我们就可以顺利解决掉Jar包发布的问题了。
使用Spinnaker处理Docker
现在我们再来看一下如何选择开源的Docker交付平台。
在携程我们第一版的Docker发布流程是基于自研发布工具Tars和mesos framework集成实现的。这个方案成型于2016年底那时容器编排平台的局面还是Mesos、Swarm,以及Kubernetes的三方大战三方各有优势和支持者。
时至今日Kubernetes基本已经一统容器编排平台。为了更多地获取开源红利携程也在向Kubernetes的全面迁移中。
目前携程对接Kubernetes的方案是使用StatefulSet管理Pod并且保持实例的IP不会因为发布而产生变化而负载均衡器依然使用之前的SLB中间件并未使用Kubernetes天然支持的Ingress。这和我在第23篇文章《业务及系统机构对发布的影响》中提到的markdown、markup机制有关系你可以再回顾一下这篇文章的内容。
如果今天让我再重新实现一次的话我更推荐使用Kubernetes原生方案作为Docker编排平台的第一方案这样更简单有效。如果你还没有在持续交付平台中支持Kubernetes的话我的建议是直接考虑搭建持续交付平台Spinnaker。
Spinnaker 是 Netflix 的开源项目,致力于解除持续交付平台和云平台之间的耦合。这个持续交付平台的优点,主要包括:
发布支持多个云平台比如AWS EC2、Microsoft Azure、Kubernetes等。如果你未来有在多数据中心使用混合云的打算Spinnaker可以给你提供很多帮助。
支持集成多个持续集成平台包括Jenkins、Travis CI等。
Netflix 是金丝雀发布的早期实践者Spinnaker中已经天然集成了蓝绿发布和金丝雀发布这两种发布策略减少了开发发布系统的工作量。 在此你可以回顾一下我在第19篇文章《发布是持续交付的最后一公里》中和你分享的蓝绿发布和金丝雀发布。
图2 Spinnaker 金丝雀发布配置图(来源 Spinnaker 官网)
虽然我并未在携程的生产环境中使用过Spinnaker但由处于持续交付领域领头羊地位的Netflix出品并且在国内也已经有了小红书的成功案例Spinnaker还是值得信任的。你可以放心大胆的用到自己的持续交付体系中。
好了,现在我们已经一起完成了发布平台的搭建。至此,整个持续交付体系,从代码管理到集成编译再到程序发布上线的完整过程,就算是顺利完成了。
总结与实践
在今天这篇文章中我主要基于Ansible系统的能力和你分享了搭建一套部署系统的过程。在搭建过程中你最需要关注的两部分内容是
利用Inventory做好部署目标的管理
利用PlayBook编写部署过程的具体逻辑。
同时我还介绍了Ansible Tower这样一个可视化工具可以帮助你更好地管理整个部署过程。
另外对于Jar包的发布以及Docker的处理我也结合着携程的经验和你分享了一些方法和建议希望可以帮到你。
至此,我们要搭建的整个持续交付系统,也算是顺利完成了。
同样地,最后我还是建议你动手去搭建一套发布系统,看看是否能够顺利地完成这个过程。如果你在这个过程中碰到了任何问题,欢迎你给我留言一起讨论。-

View File

@ -0,0 +1,169 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
持续交付专栏特别放送 答疑解惑
整个专栏的全部37篇文章已经更新完毕了。在这三个多月的时间里我一直在尽自己的最大努力想要把自己过往的一些经验和遇到的问题分享给你。但是毕竟篇幅、时间有限针对一些比较复杂的案例或者是针对不同层次的读者也很难做到面面俱到。
所以借着专栏即将结束的机会我整理了一下大家的留言总结了一些比较典型的问题并从中挑选了5个问题在这篇文章中给与回答。虽然这些问题我依旧不能做到面面俱到但也想再为你略尽绵薄之力。
因此,今天我就针对下面这五个问题,再详细的展开一下,和你分享一些携程在这些方面的真实方案和实践:
测试环境使用和管理的实例;
怎么处理数据库发布和回滚;
Immutable在携程是如何落地的
携程的破坏性测试DR演练
携程GitLab HA方案。
测试环境使用和管理的实例
在第8篇《测试环境要多少从现实需求说起》和第9篇《测试环境要多少从成本与效率说起》文章中我和你分享了携程的测试环境包括这么三类
FAT环境为每个团队或功能准备的独立功能测试环境
FWS环境部署稳定版本的功能服务以供其他团队联调的环境
UAT环境用户接受测试的环境包括独立部署的DB、缓存和中间件。
这三类环境中UAT环境的使用和管理方法大家都已经比较熟悉了所以这里我再着重和你分享一下FAT和FWS环境相关的内容。
FAT和FWS环境的关系如图1所示。
图1FAT和FWS环境的关系
FAT与FWS环境的关系
FAT环境属于不同部门可以包括多套环境。在管理时既可以按需临时生成也可以作为常备环境持久保留。我们可以在一套FAT环境中部署任意个服务应用。
而FWS环境主要部署的是中间件和公共服务通常情况下它的版本与生产版本一致。
FWS和FAT这两类环境在网络上完全相同并共用一组数据库和缓存。
如何控制服务调用关系?
既然FWS和FAT这两类环境完全相同而且不同的FAT环境中也会存在相同的服务应用那么我们就必然要解决一个问题如何控制服务的调用关系。
因为即使是相同的服务应用部署在不同的FAT环境中的应用版本号也可能不一样。如果按照标准服务治理方式的话那么就需要把所有FAT环境中的同一个服务认为是一个服务集群。而同一应用的不同版本同时服务的话它们提供的功能也不一样这会对测试产生负面影响。因为你无法确定出现Bug的版本到底是哪一个。
那么,携程是如何解决这个问题的呢?
携程的解决方案是由SOA通信中间件指定服务的具体地址即通过配置指定要调用的服务的具体地址。当然如果每个服务都要去指定配置那么就太过繁琐了。所以我们还定义了一条默认规则
如果没有特别指定的服务调用地址则优先调用同一个环境中的相关服务。如果同一个环境中该服务不存在则尝试调用FWS中部署的实例。
在携程如何创建测试环境?
在携程我们有一套完整的测试环境自助管理平台开发人员或QA团队可以按需自助完成对对测试环境的任意操作。这里我也分享一下在携程创建一个测试环境的大致步骤。
第一步选择一个已经存在的FAT环境或者重新创建一个FAT环境。如果是重新创建的话可以选择重新创建一个空的环境或者是复制一个已有的环境。
第二步选择要在这个FAT环境下部署的服务应用先进行关系绑定这个FAT环境下要部署的所有服务应用的描述再部署。如果该服务属于其他团队则可以要求该团队协助部署由平台来处理
在携程一个团队只能部署属于自己的服务应用如果你的FAT环境中包含了其他团队的应用则要由其他团队部署。这样做的好处是各司其职能更好地控制联调版本。
第三步配置这个FAT环境相关的信息。携程的配置中心同样也支持多测试环境的功能可以做到同一个配置key在不同环境有不同的value。
第四步,对于特殊的服务调用,进行单独配置。
经过这样的四步,一个测试环境就被创建起来了。期间测试环境的任何变化,都可以通过环境管理平台完成。比如,增减服务应用、修改配置,或是扩容/缩容服务器等。
如何处理数据库发布和回滚?
这也是一个大家比较关心的问题。我来和你分享一下携程的实践吧。
在携程数据库的变更是和应用发布拆分开的。也就是说我们的数据库有单独的持续交付流程。这个持续交付的过程大致如图2所示。
图2 数据库持续交付
在这个过程中有两处DBA审核
第一处审核,是在提交脚本之后。审核的内容主要是变更内容是否合法、方式是否得当、是否影响业务等等。
第二处审核,是在提交生产变更后。审核的主要的内容是,判断变更是否会对当时的生产系统产生影响。比如,订单表的更新、大表的变化等,就不允许在业务高峰期进行。
整个数据库发布的持续交付流程是以测试通过为驱动的。这个过程要经历开发、功能以及集成测试3个环境。而数据库的发布又与代码发布不同步所以如果有兼容问题的话就容易被发现了。
那么,怎么做到兼容呢?携程对数据库变更的要求是:
第一,与业务相关的,只能新增字段,不能删除字段,也不能修改已有字段的定义,并且新增字段必须有默认值。
第二对于必须要修改原有数据库结构的场景则必须由DBA操作不纳入持续交付流程。
所以,按照这个管理方式处理数据库的持续交付的话,数据库本身基本就没有需要回滚的场景了。
Immutable在携程是如何落地的
在第20篇文章《Immutable任何变更都需要发布》中我提到了“不可变”的概念和价值也讲到了任何系统的变更都要视为一次发布。然而在传统的基于虚拟机的系统架构下要做到这一点代价非常大。
所以携程基于Docker容器和k8s落地了不可变模型。
具体的实现思路,其实也很简单。在落地不可变模型之前,我们只有应用发布,这一个可追溯的版本树;那么,针对不可变的需求,我们在其上增加了一个系统变更版本树。同样地,原来只在代码交付时才会进行镜像和部署;现在在系统变更时,我们也会针对性地生成镜像、标注版本、进行部署。
将应用发布和系统变更这两条版本树合并,就是完整的不可变模型需要的版本树了,也就是落地了不可变模型。
携程的破坏性测试DR演练
其实,携程的破坏性测试也只是刚刚起步,还没有完全具备混沌工程的能力,其原因主要是:很多的老旧系统比较脆弱,不具备在所有的随机破坏后快速恢复的能力。
但是携程在同城多机房DR灾难恢复方面做得还是比较出色的。其实DR也是一种破坏性测试一般采用的方式是局部断电或者流量切换。所以我们也会定时做DR演练以检验系统健壮性是否达标。
其实破坏性测试和DR演练这两种方式的最终结果是一样的都是将所有生产流量从灾难机房迁移至其他正常机房。当然要完成这样的切换同时不影响正常业务我们需要在架构层面多花费一些精力。比如数据库的同步、Redis的同步、SLB路由的快速切换等等。
我们一起看一下DR演练的具体过程吧。假设IDC B的某个服务单元出现了异常如图3所示。
图3 个别服务单元故障
而此时IDC A有这个服务单元的灾备存在那么系统就会被触发流量切换GLB会将所有发给故障服务单元SLB上的流量切换到IDC A的灾备服务单元上如图4所示。
图4 流量切换后
这样,故障的服务单元就暂停了服务,直接由灾备服务顶上了。
当然这种演练不仅仅是整个服务单元异常这一种场景还可用于单元内的个别服务的异常演练这时的流量切换就不再是由GLB这种上层来做了而是利用SLB这一层的能力切换部分服务的流量到灾备服务上。
最后你还要记住的很重要的一点就是要能探测到故障单元是否恢复正常了。如果恢复正常了的话流量还要还原回去。这部分的能力可以利用SLB的健康检测实现。
其实,整个破坏性测试过程中最容易出现问题的是,数据库和缓存的处理。如果没有跨机房数据实时同步的能力,建议最好不要尝试,毕竟不要把演练变成了破坏。
携程的GitLab HA方案
携程的GitLab HA方案主要是基于Sharding思想大致的架构设计如图5所示。
图5 携程GitLab HA方案
这个方案的核心思想是通过Nodejsssh2代理和分发所有SSH请求利用Nginx代理和分发所有http请求。具体的实施包括以下三点
第一每台宿主机上有多个GitLab实例可以是虚拟机形式当然也可以是容器形式。
第二同台宿主机上的GitLab实例共享一个Volume这样就保证了即使某一个GitLab实例故障也可以快速将流量切换到同宿主机的其他实例上继续提供服务。
第三我们对每台宿主机的仓库简单地用rsync做了冷备。此处并没做互备否则就变成NFS方案了因为我们的目的是只要保证存储故障时可恢复所以无需采用NFS方案
这个方案的开发成本和维护成本都比较小、简单实用,你也可以借鉴。

View File

@ -0,0 +1,87 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
持续交付专栏特别放送 高效学习指南
你好,我是王潇俊。
“持续交付36讲”专栏全部的正文部分已经更新完毕了。这里我总结了一份【高效学习指南】希望交付给你的是我在创作这个专栏前后的想法变化以及怎么才能从这个专栏中收获更多。
而不管你是刚刚订阅这个专栏,还是已经学完了全部专栏文章,亦或是只看了其中几篇而未能继续坚持下去,我都相信这篇【高效学习指南】,可以帮你更上一个台阶。
关于持续交付的知识图谱
更新完了37篇正文我也曾想结合自己的经验希望可以梳理出一个关于持续交付的知识图谱帮助你了解持续交付的知识边界。但我思索再三几次提笔又放下最终还是放弃了。因为我真的无法界定这个知识图谱的边界持续交付涉及到的内容实在是太多了。
如果我说Java是一种技术你应该不会反对吧而我说数据结构也是一种技术算法也是架构也是你照样不会反对吧
如果我又说与产品相关的内容,更像是一种管理,是对技术成果的一种管理,这看起来也没什么问题,对吧?
那么,我们口中谈论的“持续交付”到底是什么呢?技术?管理?流程?方案?经验?还是实践?
持续交付好像和这些都沾边,是不是?但,肯定也不是说涉及到了这些内容的方方面面。所以,我决定不再归纳这个专栏的知识图谱了。
但是,我相信上面的这些问题,却给了你也给了我,第一个也是最重要的一个学习持续交付的建议,即:
在学习前,你首先要确定自己的学习目的,是想了解别人的经验,还是要掌握普适的流程,又或是要学习其中的个别技术。
我始终相信,只有确认了目的地,才能找到所谓的“捷径”。否则,“持续交付”在你我的眼中,终是“一锅大杂烩”,而实施起来就是毫无头绪。
关于理论与实践
其实关于“理论与实践”,也是一个需要辩证看待的问题。到底是先掌握理论,还是应该先去实践,这同样是个见仁见智的问题吧。
就像有些读者,在专栏里面留言说“缺少干货和实践”。对这个问题,我不置可否。因为,我始终认为先理解持续交付的基础知识,再动手搭建一套自己的系统,是个水到渠成的事儿。所以,我就把整个持续交付体系的搭建,放在了专栏最后的“实践案例”系列中。
所以,如果你觉得有必要的话,也可以先看最后“实践案例”的系列文章,然后在搭建持续交付体系过程中遇到具体问题时,再去前面的基础文章中寻找答案。或许,这种方式对你来说会更有感觉。
但是我并不太提倡这种做法。就像你在学习Java时一定是先看到“类是对对象的抽象”这种拗口、抽象的定义一样搭建一套持续交付体系也需要先去理解一些技术本质的内容。所以在我看来先看懂理论再结合着实践回顾才是最理想的学习方式才会事半功倍。
另外你可能会纠结于我到底应该先学会使用基础工具还是应该先去掌握解决问题的思路。那么在我看来工具也好实现也罢随着你的成长和技术发展最终都不会成为问题的核心就好像Docker一出现就瞬间解决了以往的很多技术难题一样。
所以,解决问题的思路和方法,才是我最想分享给你的。因为,即使这些思路、方法过时了,你也能从中领悟到它们的来龙去脉,以及为什么会演变出新的思路、方法。这,就是温故而知新。
关于小团队与大团队
或许,你也像大多数人一样,一直在纠结一个事情,小团队和持续交付的关系。
其实在我看来持续交付体系的建立和团队的大小没什么直接关系。但是持续交付ROI却与团队大小有直接关系。
我最近也离开携程创业了目前技术团队只有5个人主要产品是基于微信小程序。微信小程序的发布也有一套审核流程所以我针对这个过程借用了一些Hook的能力做了CD。但是最近也有一家上千人的大厂询问我关于持续交付的问题。他们的问题是团队连续搞了很长时间最后却连基本工具也没顺利搭建起来。
所以说,团队大小并不会限制你的持续交付能力,你也无需再纠结于自己的团队大小而无法实施持续交付的问题了。
但从另一方面来看,任何技术、流程和工具,在大团队中的收益一定会比小团队要好。其实,这是必然的,因为受益的群体大了嘛,而且能切实地解决更多复杂问题,更明显地提高协作效率。而这些,又恰恰是一个大型研发团队最需要解决的问题。
所以,大团队对持续交付的关注度要远远高于小团队。
推荐一些参考资料
有不少的读者,希望我推荐一些持续交付方面的参考资料。因此,我稍作整理,把我觉得最最值得推荐的参考资料列在了这里:
《持续交付:发布可靠软件的系统方法》,是一本必读书籍。
《凤凰项目》以小说的方式介绍了一些持续交付和DevOps的理念非常有趣。
官方文档。这里我有一个建议,就是在学习和运用开源系统和工具时,要先通读官方文档。这些文档都是作者心血和智慧的结晶,从中你定可以收获颇丰。所以,我建议你能把阅读官方文档形成一个习惯。
这里,我再啰嗦一句,持续交付体系中涉及到很多开源软件,如果你想做好持续交付,那就一定要去理解它们,而不只是使用它们。
我讲的不一定都是对的
最后,关于持续交付,我还有一个非常重要的建议,要分享给你:我们通常在网络上看到的与持续交付相关的分享,都是经验与实践。
那么,我们就需要认清这么一个现实,既然是经验与实践,那就很可能要受到当时情况和条件的限制:这个经验与实践,也许在当时是最优的,但未必现在还是最优的;也许在此处能正常工作,但换一处未必能行。
但是,你也不要因此而悲观。虽然我不能告诉你怎样做一定是对的,但是我分享了怎样做一定是错误的。这些一定错的部分,也可以说是我们进行持续交付的一条底线吧。
而且,如果你有机会可以修正和弥补那些不那么正确或不那么适用的部分,将可以使你自己的持续交付过程变得更好。
所以,最后,我希望我的专栏能给你带来不同的思考。思考出你自己的观点和解决方案,并付诸实施,这才是最棒的。

View File

@ -0,0 +1,85 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
结束语 越痛苦的事,越要经常做
专栏终于写完了,痛苦的三个月终于结束了,我也终于可以长舒一口气了。
其实按理来说,写技术专栏,特别是你自己比较熟悉的领域,应该不至于那么辛苦。但就是这么巧,答应极客时间开始写专栏后不到一个月,我就作为技术合伙人加入了一个创业团队。每天要忙的事情真得好多,我再也不能随性地规划自己的时间了。
现在我大概看了下我上传音频稿的时间基本都在夜里23点以后。其中还有20篇是在24点以后。也因此导致我老婆曾经一度以为我创业的内容是做夜间直播。
虽然这个写作和录音的过程很痛苦真的很痛苦现在是凌晨1点45分我在写结束语但我最终坚持下来了并因此收获了快乐与成长。而这些快乐与成长都是源于尚未谋面的“你”。
我一直都在关注你给我的留言,并努力地去解答您提出的问题。当然,对于你对专栏的吐槽与不解,我也都历历在目。这些都时刻鞭策着我,要更多地分享自己的所知、所感和所悟,要能真正帮到你。
在这个专栏里,我和你一起分享了,持续交付的理念、概念、经验和实践,涉及到了持续交付中最重要和最核心的知识点。我还通过一个具体的系统搭建案例,手把手地带你搭建了一套持续交付系统。相信你也能从中获得一些你所期望的知识吧。
说到“结束”,我还想再和你分享一下我在“持续交付”生涯中,经历的那些“痛苦”,让“你”也和我一同感受一下这些“痛”。所谓,有福同享有难同当嘛。
所以,今天我决定多和你透露一些做持续交付的“痛苦”,让你也感同身受一下。
第一痛,要比架构师懂得多
其实这个问题,我在专栏里也反复提到过。你要做持续交付,并且要做好的话,那就得比任何其他的技术人员懂得多。否则,你怎么搭建一套为他们服务的系统呢。
而且,从整个专栏里你也看到了,搭建持续交付系统的过程,充斥着各种与中间件、系统、网络相关的内容;还要迎合业务架构的设计去考虑适配。这,都会倒逼你去学习和了解各种架构知识、业务知识、开发知识等等。
所以,在携程的研发坊间流传着这样一个说法:如果你发现有技术问题解决不了,也不知道找谁解决,那你可以去找系统研发部(在携程,我们负责持续交付系统的工程师,归属于这个部门)那帮人,他们一定能帮到你。
对此,你是不是感觉非常“痛苦”呢?
第二痛,要比开发人员动作快
我一直都很佩服搞互联网开发的这帮工程师们,他们的开发动作实在是太快了。临近中午出需求,下午就上线再平常不过了。
但是呢,他们会用同样的速度标准来要求你。你不是要搞持续交付吗?不是要我遵循规则和流程吗?那么好,请你把速度提到和我们一样,绝不能因为你的持续交付而降速。
是啊,这是多么朴素的要求。但对持续交付来说,又是多么痛的领悟。这不仅要求你要具备过硬的系统设计能力,更对你的开发能力提出了同样的高要求。
第三痛要比QA团队眼睛尖
QA团队是持续交付系统非常重要的用户之一而且他们也往往把持续交付系统看成是自己最重要的生产工具之一。所以QA团队会用超高的质量标准来要求你的持续交付系统。
这也可以理解,毕竟一旦用上了你的持续交付系统,就等于控制了他们的生产线,要是有问题,第一个倒霉的就是他们。
说到眼尖的另一个重要原因是,任何系统不可能一蹴而就。开发一个系统,总要有先有后,总要经历一定的时间。而持续交付,讲究的又是端到端的完整性,所以你一定要眼尖地去发现最重要的问题,并逐一解决掉,才能持续推进。否则,就你那几个人的小团队,非得忙死不可。
第四痛,要比运营人员“心脏大”
说到要心脏大,无非就是两方面:顶得住压力、受得住委屈。
既然是交付系统,自然是和运维团队一样,要对线上生产环境做动作的。常在河边走哪有不湿鞋的,搞出个一两次生产故障是再平常不过的事情了。而且,有时候做持续交付,你连合适的测试环境都没有,只能硬着头皮上生产环境做测试。此时,你一定要顶住压力,要有信心。怕出错,就别干这一行!
另一方面,你一定要能受得住委屈。“你说你一个依赖错误造成的编译不通过,为什么你自己不能解决,反而来问我是不是编译系统有问题呢?”。做持续交付,你真的得天天应付这种问题。
反过来,业务开发团队庆祝绩效突破的时候,却好像从来不会说到持续交付系统的功劳。
So心脏不够强大如何实施持续交付
第五痛,要比产品经理还会“吹”
做持续交付真的要足够会包装自己。因为你要推广你的系统、要找到你的种子用户你要讲道理、讲技术、做演示讲PPT。这些技能真的是缺一不可。
现在是凌晨2点25分我大概花了40分钟已经洋洋洒洒地和你吐了这么多苦水了是不是挺能“吹”的
越痛苦的事,越要经常做
其实,讲了这么多,你都明白,我是在讲反话。要是上面这五点你都“痛”到了,那你一定也很会痛并快乐着。因为,经历过了这些痛,你真的就将自己锤炼为了别人眼里的“牛人”。
那么,最后留一句持续集成的经典名言给你:越痛苦的事,越要经常做。
这句话的出处是,在没有“持续集成”概念之前,每次项目集成都很痛苦,而持续集成就是经常“痛苦”地去做这件事,最后把这个过程变得不那么痛苦了。
同样地,上面那些“痛”,我也建议你多去体验。那么,当真正遇到这些问题了,你也就不会那么痛了,你的持续交付事业也就很顺利了。
最后,还有一点要分享给你,痛苦的事情要经常做,但没必要一直自己做,你完全可以找个“教练”陪伴你。就像我在写作专栏这个“痛苦”的过程中,一直有极客时间编辑的陪伴、鼓励和帮助。而我,也很乐于做你的“教练”。
最后,虽然专栏结束了,但你依旧可以继续留言找到我,我会一直陪伴在你“持续交付”的道路上。
](http://cn.mikecrm.com/myFCXTV)-