编者按:持续部署这个词我们经常听到,可是到底怎样才是做到了持续部署?如何才能做到持续部署?本文将为你逐层拆解持续部署的内涵和实施路径。
策划&编辑|雅纯
云研发时代,主流的发布形态变成了服务化的发布形态,这种发布形态让持续发布有了现实的基础。发布的前提是把待发布制品部署到生产环境,所以持续发布的前提是持续部署。
持续部署的4个要求
持续部署要求持续地提供一个稳定可预期的系统服务。有时候发布过程当中会停机,停机更新的这段时间系统不可用,这就是非持续的部署形态。
我们希望的持续部署:
首先应该是准确的——部署结果准确可预期的;
第二,应该是可靠的——整个持续部署过程中线上服务不受影响;
第三,应该是持续的——随着持续部署的发生,有可持续部署的软件增量;
第四,过程成本低——持续部署过程是低成本和高效的。
如何做到这4点呢?
1、准确、可预期的部署结果
准确地部署依赖三个前提:明确的待发布制品及配置、明确的运行环境、明确的发布过程及发布策略。
下面是一个简单的发布示例:
发布首先有明确的image,即上游过来的构建产物。同时包含很多配置,如启动配置、容器的配置等。另一个是环境,我们会在部署工具中配置k8s,这个配置最后会形成一个环境,而这个环境会在DevOps过程中被用到。最后我们把制品和配置发布到这个环境上,就完成了发布。
所以,发布的过程是把制品和配置的集合应用到环境的集合上的过程。首先要有明确的待发布制品和运行环境,其次通过相应的描述,把制品、配置和环境都描述清楚,形成发布的内容,才可以进入下一步。
最简单的发布就是kubectl apply,但这种发布方式存在着一些问题。
第一,结果不确定。kubectl之后pod可能并没有起来,deployment可能是不能用的,服务可能有失败,发布之后可能会遇到pod不够,资源没有,这些都是未知的。所以发布是否成功,发布成功了多少都不确定,这是不可预期的。
第二,状态不可见。发布不是一蹴而就的,是逐步的过程。发了多少,有多少问题,哪些流量已经切过来,这些情况都是未知的。
第三,过程不可控。在这个发布过程中,一条命令下去之后是无法撤回的。
如果版本有问题,有严重的Bug,全部的流量跌零,是无法反悔的,非常危险。所以在真正的发布过程中,我们要有干预手段,比如当我发现流量会导致可用性的大量下降,需要能够马上停止发布。
无论采用何种部署方式,我们都希望尽量减少对线上服务的影响,这种影响降到极致,即部署过程完全不影响线上服务。这是我们的第二个原则。
2、部署过程不影响线上的服务
要做到不影响线上服务,有4个要求:
第一,滚动式部署
采取灰度的方式,将绝大多数服务滚动地部署上去,当确认没有问题再把流量切过去,做到线上的服务不中断。滚动有可能会过快,需要保证每一个批次的间隔足够监控发现问题,有足够时间收集到足够数据做判断。
第二,部署可观测
部署本身可能会产生一些告警,比如部署导致一些服务节点水位下降,而非整个服务的水位下降。所以部署与监控需要打通,首先要避免无意义的告警,其次要让监控及时发现部署产生的问题,比如部署两台节点,流量如何?服务情况如何?延时是否增加?这些都需要去监控。
第三,随时可干预
部署过程中可能会有很多不确定的问题突然出现,这时需要一些干预手段,比如分流的操作,进行相应的切流,避免问题影响到整个系统。
第四,随时可回滚
如果你的干预不能快速解决掉问题,这时就需要回滚了。要做到随时可回滚,是因为部署过程中有一些失败情况相应的修复成本特别高,快速回滚,才能保证服务不会受到影响。
常见发布模式举例
这里介绍几种常见的发布策略。
(一)灰度发布
灰度发布常见的架构如上。首先有一个负载均衡,负载均衡下面的服务版本当前是V1,要发布新的版本是V2,可以从里面摘一个节点,五分之一的流量用V2。
这种情况下,原来所有的Pod都在Deployment1上,但是有一个新的Pod会在Deployment2上,从Loadbalancer到Service路由的时候就会有一部分流量路由到新的Deployment2上。
有时候,为了更精细的控制流量,也会通过ingress或者mesh这样的手段,将特定的流量,比如5%的包含grey的cookie标的流量路由到Deployment2上。
我们期望deployment2逐步替换掉deployment1,deployment1的流量慢慢被替换、被下线。整个的过程当中用户是无感知的,请求是正常的,各类监控,基础监控,应用监控,业务监控都正常,这是我们期望的结果。
灰度发布最常见的做法是生成一个新的deployment,关联新版本的Pod,在一段时间内同时存在两个deployment版本,通过不断调整两边的的Pod数量达到灰度发布的目的。这个是最常见的部署策略,成本也比较低,缺点是无法做很精细的流量控制,但服务量不大可以考虑这种方式。
这种发布形式对服务有要求,首先要求对于某一个具体的service,最多只有一个进行中的发布,因为需要有流量的不断切换做验证的过程。
第二,对某一个service发布完之后只能有一个版本的deployment运行,不允许两个同时存在。
第三,在整个过程当中存在两个版本的deployment,有两个版本的服务在提供,要保证这两个版本服务都能够正确提供,不管上游是什么,下游是什么,都可以正确处理业务需求。
第四,整个发布过程不能造成服务的中断。如果普通的短连接服务,要保证一个session不会因为发布导致前后断开或前后不连续。如果是长连接要保证这个连接能够自动地迁移到新的服务上。
最后,整个发布过程不会造成用户请求的错误,而是会有一个优雅下线机制保证它处理完之后不接受新的请求,在这种情况下才能够保证达到期望的灰度发布的效果。
所以整个灰度发布的过程不仅仅是对发布的工具,发布的策略有一些要求,对应用程序本身也有不少的要求,才能达到非常平滑的灰度发布。
基于此,我们总结了几点针对灰度发布实践的建议供大家参考。
第一,我们建议应用需要保证对前一个(或数个)版本的兼容。这个版本的兼容数量取决于应用的线上情况,有时线上会同时存在几个版本的应用,我们需要保证对这几个版本的兼容性。
第二,创建一个新的deployment,提供同样的service,通过调整pod数或者ingress流量来进行灰度,这种灰度的情况下可以很精细地控制它,所建议通过流量控制。
第三,定义灰度批次以及每一批的比例和观察时间。灰度批次要设计合理,保证每个批次之间的间隔足够我们去发现问题并做处理。如果灰度间隔特别短,有可能监控还没有来得及告警就进入下一个更大的批次,可能带来非常大的风险。
第四,除了关注基础监控和应用监控外,也需要关注业务监控数据。监控是一个很大的范畴,但是从发布的角度讲,我们的最终目的是要避免发布带来的业务损失,发布可能会导致业务不可用,或业务出现错误,更严重的是发布造成业务某一些观测指标产生大的变化,比如说用户转化率或者是用户登录成功次数等数据异常。这些异常的数据应该及时被发现,并且立即暂停。
第五,当发布过程完成之后,应该先做流量切换进行观察,而不要急于清理pod,保证将来做回滚的时候更高效。如果这个pod还在,很快就能把流量切过来,可以缩短线上服务受影响的时间。
第六,记录下发布的版本,方便进行回滚。除了具体的版本我们还要知道在哪里部署过,这样才方便回滚。记录下相应的版本,如果合规检查自动化做得比较好,也可以做到一键回滚。
第七,回滚与重新发布不同。回滚与发布的策略不同,不可能和发布一样每次批次很小,为了解决问题需要做到减小批次、缩短时间、快速回滚。
最后,如果系统支持多租户,建议基于租户做流量隔离和AB测试,尤其是AB测试的时候比较方便。
(二)蓝绿部署
另外一个常见的部署方式是蓝绿部署:
蓝绿部署和灰度相似,只是所需要的资源更多一点。这个取决于软件的部署形态,以及机器资源的数量。蓝绿比灰度对软件的要求会更低,可以保证所有的业务都部署好之后再去切,但是灰度不行,要能够持续部署。但是蓝绿的风险也是比较高的,一旦出问题就是全局性的。
要做到不影响线上的服务,除了部署策略外,也会有其他问题,比如软件只开发了一半,或者服务部署上去希望和别的服务配合在一起才能作为一个完整的系统服务提供给用户,这时需要用到特性开关方式。
特性开关本质上是一类特殊的配置,一般以动态配置的形式下发。平时可以做持续部署,但开关保持关闭,等到客户端或者前端发布上去之后,再将开关打开。所以严格来说特性开关的打开本身也是一次发布,特性开关本身也需要版本管理。
我们希望达到的终极目标,是任何时候任何人都可以放心的发布软件。这意味着,你的服务任何时候都能发,任何人都可以放心地发,发布的操作是非常简单的,不需要特殊的技能,且发布之后不会出现什么大问题,即便出现问题也能很快解决。
因此,我们的愿景是:任何时候,任何应用都可以发布上线。
对阿里巴巴来说可以具象化为:双11不封网,双11的时候想发就发。实际在双11的过程当中,也是有很多紧急发布的,这里需要有非常完整的技术保障,保证发布的安全性和可靠性,因为如果一旦出现问题可能就是舆情故障。而且越是这个时候就越可能会产生一些雪崩效应,可能会带来一系列的故障和问题,最后整个系统会瘫痪掉。
3、可持续部署的软件增量
做持续部署,持续是关键。图中上面的一组,蒙娜丽莎的微笑每次都是一小块,最后做成了第5块,但是1、2、3、4每个都是不完整的,而下面的1、2、3、4、5每一个都是完整的,但是在不断地丰富细节。它想表达的是:
(1)我们的软件增量应该对应一个明确的需求价值点,有一个明确的需求价值点可以交付。
(2)第二,软件的增量应该是完整的,是可以独立发布的单元。
(3)第三,软件的增量要能够被独立验证。
KentBeck说过一句话:
也就是说集成是一件非常重要的事情,因为我们在绝大多数软件开发协作过程当中都是把问题拆分解决再集成的过程。
集成有以下三个步骤:代码提交,打包部署,验证。这3个步骤非常简单。
集成的目的是为了验证完整性,验证合并后的代码能够构建,能够完成相应的功能性测试的验证,帮助我们尽早发现风险。因此要做到尽早集成,每次集成的批量应该很小。
两个单元:一个从部署的角度来说是可部署的单元;另外一个从集成的角度是可集成的单元。
部署的单元是可发布到可测试,是需求的视角。增量是一个需求,一个特性,用户可以看得到的,可以用到的功能。另一个是可集成的单元,即许多可构建的单元,从逻辑上能够构建在一起,完成单元测试,然后再去做代码级别的验证,这是代码的视角。
代码提交后,代码分析,编译构建,都是在做代码的质量检查。编译成功很多时候就是给我们第一手的反馈。编译器在构建的过程帮助我们发现在写代码过程当中的一些问题。构建本身,如果速度比较快,程序员是特别喜欢用的,一旦编不过他就知道有什么问题,然后再单元测试,集成测试,功能测试,最后进入待发布的状态。所以前半部分蓝色的是做持续集成,以达到待发布状态。
4、低成本、高效地部署发布
有了待发布的制品,如何可以低成本高效地部署发布?
首先看一些常见的问题。最常见的就是延迟集成,比如一个月集成一次,一个月批量提交一把。第二种是累积负债,主干上面一直不稳定,有很多的问题,永远测不通过,这就是累积负债。
第三个是无测试自动化,整个测试完全靠手工来保证,或者是有测试自动化但是不稳定,没有办法依赖,这时就完全靠人去判断这个测试是否OK。第四个是返工,经常因为质量的问题或者是缺陷导致发布活动经常反复,带来大量的时间浪费。
还有一个是耗时的活动,比如人工查代码,人工做每一个阶段的审批,人工看每一阶段的质量情况,这些都会耗费大量的时间,从而导致整个发布比较低效。当软件完成某一项工作之后进入一个状态,要进入下一个状态时是通过人工的方式判断状态迁移,这时耗时就很长,因为存在反馈等待,导致在整个发布的时候相对来说很难做的很高效。
上图两个应用的发布统计,上面是A应用,下面是B应用,每个点表示一次发布,绿色的点表示发布成功,黄色的点表示发布被取消了,红色表示发布失败。纵轴是这次发布的耗时。横轴表示的是在哪一天完成了发布,所以纵轴表示的是时长,横轴表示时间点。
其实这两个应用都做的不是很好。第一个应用的问题是发布的频率非常低,很多时候一个月就发一两次,但是发布的成功率还比较高。第二个应用发布频率比较高,可能几天就有一次发布,但是失败率非常高,失败的次数远远大于成功的次数。
所以两个各有问题,并且这两个应用整个发布时间都比较长,经常是要24小时甚至更多。如果发布超过了8小时就意味着在一天中搞不定,需要加班,因为发布是比较高危的事情,很多公司发布的时候都需要盯盘,不可能还没有发完就先离开。这种情况下如果发布需要耗时超过一天,假设两个人轮流也需要12个小时。
举一个例子,有很多企业把他们集成的时间放在周二,发布时间放在周四,因为默认发布要加班,而且周四那天搞不定,周五还要继续发,所以如果放在周五的话,就需要周六加班。很多情况下,就算放到周四,绝大部分情况下发到周五晚上或者周六才能发上去,发到周日的也不少。
从这两张图里面看,我们发现A应用发布频率很低,很长时间才发一次,另外B应用里面很多发布失败,确实有相应的一些风险,比如说时间很长,又很容易出错的话,就很难做到按需发布。
仔细观察一下B应用,如果接近的时间连续有多次红点再加一个绿点,往往意味着连续地发布失败,需要紧急修复再发布。这意味着这个软件很有可能在紧急修复中间出现风险。想要持续快速高质量,放心大胆地发布上去,就要提到集成和发布的能力。
快速集成的手段包括减小批量和保持顺畅。因为集成的粒度和资源利用率以及周期时间是有相关性的,小批量周期时间比较短,大批量周期时间一般来说比较长,资源利用率相对来说比较接近,不会有太大的问题,所以通过减小批量可以让周期时间尽可能地变短,变短之后频率就会提高,频率多反馈又快,对于应用的修复或者相应的问题响应就会变的更快。第二,保持顺畅。把里面的问题解决掉之后,才能让这条道顺畅起来。
第一,减小批量,刚刚我们已经提到从需求的粒度和代码的粒度,需要可发布单元,可测试单元尽可能地少,可构建单元,可单测单元代码粒度尽可能的小。
从保持顺畅的角度来说,我们有很多的实践,最常见的是各种自动化,比如测试能够做到自动化,构建的自动化,部署自动化,整个流程串起来能够自动化,状态迁移能够自动化等。
第二,要管理异常,即发布过程当中避免车辆追尾,应该把异常先修复掉,再让整个过程顺畅起来。当发布的这条流水线里面出现问题,这时应该先停下来,先不要checkin,不要触发,先把问题解决掉。先让主干变好,然后继续剩下的工作,要先保证把主干修复掉,剩下的时间再做剩下的集成。有一些企业会要求谁提交代码谁负责解决掉问题,如果在半小时或者限定时间内解决不掉,系统会自动把代码摘掉,让后面的人可以继续集成。
另外一个是减少依赖,如果集成和发布过程当中,对外部有特别多的依赖,这时也会造成堵塞,因为依赖就会导致等待。
另外一个是质量内建,内建好上游的质量之后才能保证下游更快。如果上游不解决相应的问题,下游肯定会堵塞在那里。我们应该尽早的从上游找问题,尽可能的用一种上游思维思考如何能够保证早一点让下游变的更好。
及时反馈也是一样,有了问题准确及时反馈到具体的人。要避免垃圾式反馈,避免过多无用或不相关的信息打扰开发者,导致开发者对整个反馈机制失去信任。
最后是复用,能复用就尽量复用,避免重复造*。
以上是我们认为的持续部署的4个原则及实践建议。
要想让持续部署在企业规模化落地,需要好的工具支撑。所以下一讲,我们将分享如何借助持续发布流水线将上述实践落地工具中,以便推广到整个团队。
点击下方链接免费体验云效持续交付流水线Flow!
https://www.aliyun.com/product/yunxiao/flow?channel=yy_rccb_36