桔妹导读:持续稳定并体验良好的测试环境,一直是影响产品迭代效率和稳定性的关键环节,也是DevOps自动化测试环节中最具挑战的一环,滴滴在测试环境上的探索从公司成立之初就从未停止,在这过程中沉淀了很多宝贵的经验和教训。本文细数滴滴在测试环境的发展历程,希望能给大家带来一些启发。
1.前言
伴随着滴滴的不断成长,业务复杂度与日俱增,团队在协作和迭代效率问题上日益严重,"微服务"成了很多公司解决上述问题的必经之路,滴滴也不例外。随着微服务在滴滴的落地,我们确实成功缓解了协作和迭代效率上的问题,但同时也引出了很多新的问题,比如构建测试环境的复杂度,这也是我们今天要聊的主题——滴滴在测试环境上的探索与实践。
2.All in one
在滴滴微服务落地初期,一个业务涉及服务数并不会太多,最多也就十几个,在这种场景下,不管怎么搭建测试环境都是相对容易的,哪怕是手动维护也耗费不了多少成本,所以在这个时期我们通过工具自动构建所有服务,把所有服务都打包在一起基本就够用了,我们管它叫"All in one"环境。这种方式持续了很长时间,直到现在依然支持着部分测试工作,但随着微服务数的增多这种方式越来越难以维护。
3.Fastdev
当我们的微服务数已经达到了四位数的时候,All in one模式早已不能支撑测试需求,所以我们开始探索其他解决方案。我们把目光转移到了UT上,它最大的优势就是下游依赖全部通过mock的方式替换掉,只需部署被测服务即可。常见的类似方案还有契约测试,常用工具是Pact。契约测试认为在微服务架构下E2E测试其实是一个伪需求,应该按照契约分别测试Consumer和Provider。我们也一度认为微服务下的测试可能真的不适合做E2E测试,但当真正应用的时候,我们发现Pact测试太理想化了,如果针对下游依赖很少的服务来说还行(但我们觉得All in one模式可能比Pact还好用),但对于最上游依赖成百上千个服务,场景复杂多变的服务来说,Pact维护成本可能比环境维护成本还大(这里我们不讨论架构设计的合理性,只讨论现状)。但Pact测试还是给了我们一些启发,如果我们能解决自动生成契约的方式,似乎问题就解决了。所以从17年开始我们尝试通过线上录制流量,线下回放流量的方式验证了可行性,随后在滴滴内部逐渐推广开来,我们内部管它叫Fastdev,同时对外分别开源了针对PHP的https://github.com/didi/rdebug和针对Go的https://github.com/didi/sharingan,设计思路如下
随着Fastdev的成功,似乎我们找到了银弹,它可以完全模拟线上环境,测试场景也最真实,就算流量变了,我们只需要手动编辑一下个别流量也能实现mock能力,成本可比Pact编辑成本低多了。但通过实际应用下来,我们还是低估了业务的复杂度和用户的接受度,虽然Fastdev能够模拟线上真实场景,对重构类变更(流量不变的情况)确实效果显著,但一旦这次变更涉及到流量变化,就有可能导致所有流量都跟着变,比如在原来快车的基础上增加了拼车逻辑,所有调用参数里都需要添加拼车相关数据,那流量编辑的成本可能比Pact还高,因为除了要理解业务之外,还要熟悉每个通信协议设计,比如Redis,mysql,http,thrift等等,就算我们针对所有协议设计了统一的DSL,也依然解决不了用户的使用成本。
4.Test in Production
针对流量变化的测试,我们的关注点又回到了环境搭建上,All in one的模式肯定是不可持续了,那如何才能以低成本方式保证每人都有一套稳定的、高仿真度的测试环境呢?我们发现预发环境(类线上环境,除了没有真实流量之外,其他跟线上环境无异)承担了很多自动化测试工作,并且足够稳定和仿真,所以我们又开始了通过预发环境做测试的环境的探索,也就是业界常说的TiP(Test in Production),先别着急喷TiP的各种问题,后面会说。其实从整个环境问题来说,最难的其实就是下游依赖,如果我们把下游的预发环境做为测试环境的依赖,上游只需要部署被测服务即可,然后通过逻辑隔离的方式(测试账号)实现人手一套环境,成本也足够低。按照这个思路,我们在18年开始了TiP-Sim环境(线上仿真环境)的建设,设计方案如下。
在实践线上仿真环境的过程中遇到过很多问题,其中一个就是流量闭环问题,对于A调B,B调C的串行链路比较好支持,但涉及到A->B->C->A这种回调链路或者A->B->C,A只需要跟C单独联调,跳过B的链路就不行了,无法实现流量的闭环。如果每人部署一套全量环境肯定不现实,所以我们借鉴了一些业界常用的染色和分流方案,以很小的成本就实现了流量闭环。我们没有采用在接入层或者路由层去做流量染色和分流,原因有两方面,一是改动成本较大,二是直连没有经过接入层。最终我们选择了类似Service Mesh中Sidecar方案,在每个业务模块上部署一个代理(只部署在基准环境),所有的染色和流量都在代理上做,通过篡改Traceid实现流量标识和分流(sim001xxxxxxxxxxxxxxx),最终实现了流量闭环。目前成了滴滴主要的联调测试环境,设计如下。
5.OSim
但现实总是残酷的,这就说到了上面提到的TiP问题,其中最大的问题就是RD不敢在上面随意调试,因为采用的是逻辑隔离,网络和数据都跟线上共用,稍有不慎就有可能导致线上事故,类似事故已经出现多起。所以线上仿真环境并不敢大面积应用到RD开发测试中,只在开发的差不多情况下才敢部署到TiP测试,当然我们也尝试在这个环境的基础上做了数据层面的物理隔离,但依然解决不了影响线上的风险,最终我们还得是回到起点 —— 创建一套稳定的线下测试环境,从根本上杜绝环境污染,这就是接下来要说的线下仿真环境——OSim(Offline-Simulation)。
得益于滴滴服务全面上云,很多基础能力的建设都可以以IaC的方式进行简单配置实现,比如同一个服务不同集群之间在不同的网段,不同集群之间的不同标识等等,都让我们复用线上能力的同时区分线上线下容易了很多。OSim环境类似上面提到的线上仿真环境,最大的区别就是网络变成了线下,实现了与生产环境的物理隔离,根本上解决了线上环境污染的问题,但这也意味着我们要把所有服务都要在线下搭建一遍,除了业务服务之外,所有的基础服务也需要适配线下环境,比如存储服务,代理服务,配置推送服务,日志采集,APP、小程序等等,这相当于再造一个线下滴滴,挑战可想而知。但刚才说了得益于上云和前人经验,很多东西已经准备好了,比如云上的存储,网络的隔离,app debug包等等,同时根据经验,我们在项目创立之初就确定了三大原则:
所有线下服务必须跟线上服务同时部署——保证高仿真度;
谁的服务谁负责——把整个项目拆解到最适合的人手里;
接入公司统一技术方案,比如RPC、服务发现、链路追踪等等——减少维护成本;
基于以上原则,我们把所有涉及的服务都做了线上线下自适配,根据容器提供的标识可以自动区分线下和线上环境。但总有一些例外,针对一些特殊场景,我们也会选择直接调用线上,比如跟地图服务无业务无关的调用,又或者一些不怎么变动的服务以mock方式提供等等,我们并没有过度追求完美,够用就好。环境搭建好以后,如何持续保障环境的稳定性又是一个巨大的挑战。由于滴滴很早之前就实现了异地双活,所以对新增一个机房(线下环境就相当于模拟创建了一个新的机房)的边际维护成本几乎为零,这样我们沿用线上的标准来维护线下环境,比如报警监控,oncall机制,甚至可用性指标和事故复盘机制(目前还是轻运营的方式)等等,无需单独为线下环境单独搞一套稳定性方案,同时也可以验证我们稳定性技术方案的扩展性。
6.总结
到此滴滴在整个测试环境上探索和实践就讲完了,大体分为All in one、流量录制回放、线上仿真和线下仿真四种模式,在整个演进历程中,我们发现每一种测试环境特定场景下都有存在的必要性,谁都代替不了谁,所以每个环境都值得团队投入更大的精力去建设,毕竟测试环境是所有测试能力的基础。
7.感想
最后想跟大家探讨一个话题:"到底该由谁来进行测试"?
在说观点之前,我想拿OP到SRE角色的转变来举例。记得几年前,我还在某大厂工作,每次上线都要坐到OP旁边,小心翼翼的求着OP给我上线,人多了还得人肉排队,好不容易轮到我,还得陪着OP慢慢灰度验证,一旦发现问题,就得让OP操作回滚,然后被OP一脸嫌弃,甭提多痛苦了。现在看来,应该很少有这种上线方式了吧,由SRE提供部署平台,RD负责所有的部署流程似乎成了最常规的操作,也消除了RD和SRE协作成本,SRE也能专注在工具优化上。
其实测试也是类似道理,RD同样应该负责测试工作,由QA(或者叫SET)提供稳定的高效的测试平台协助RD测试。但有人可能会质疑,RD这样岂不是啥都做了,需求更迭代不过来了,其实根本原因还是在测试成本,如果测试能变成跟部署一样简单,那RD来测试也就没那么多问题了。同时RD开发本身也离不开测试,每一次小的变更可能都需要跑一下测试来验证,更别提复杂变更了。但如果RD只是把简单测试过的代码交给QA测试,后面就等着QA报bug,这种状况会让QA压力越来越大,RD对变更越来越没自信,测试工具的建设也越来越跟不上业务迭代,陷入恶性循环。但如果RD肩负起测试的责任,不管是在需求评审阶段,代码设计阶段,还是在编码阶段都会考虑代码的可测试性,架构设计的合理性等,QA也会有更多的精力投入到工具建设中,RD的测试效率也会更快,最终达到一种良性循环。在DevOps思想中就提过,RD应该承担起需求分析,设计,编码,测试,上线,线上问题oncall等整个流水线工作,这样沟通的成本最低,效率最高,但这中间每一个环节都应该足够便利,所有人都有义务来改进这里的每一个环节,来使整个DevOps流程更顺畅。
这个时候可能又有人说,让RD来测试,很容易陷入自己的思维定式,测试不充分,第三方测试可以跳出思维定式,这里我们更赞成《Google软件测试之道》中提到的测试活动管理者,如果组织PM,运营,内/外部用户,借助灰度发布等手段,全方面的验证可能效果会更好。这里不是说QA就彻底不进行测试了,借助更多测试手段,最终QA的测试工作会变得轻松很多,也会有更多精力投入到更具创新和挑战的工作当中。