技术开发 频道

京东数科DevOps落地攻略

  【IT168 技术】本文根据董璐在2018年10月19日第十届中国系统架构师大会(SACC2018)现场演讲内容整理而成。

  讲师介绍:

  董璐,京东数科持续集成平台研发负责人,负责持续集成平台的设计与实现,推动持续集成方案在京东数科的实施落地。近期两年,主要针对京东数科的研发体系现状,深耕研发过程的改进,尽可能地提高研发效率,保证研发过程的平稳有序,结合行业特点,制定符合本公司自身情况的持续集成方案。

  文章摘要:

  本主题首先介绍京东数科的持续集成的进阶之路,从人工的蛮荒时代,到基本的工具集使用,最后到研发自己的研发协同平台。重点介绍分支开发策略模式下,从Dev到Ops的JCI研发协同平台落地实践攻略。

  演讲大纲:

  ·研发Team的历史形态

  ·推进DevOps的目的

  ·演变落地的过程

  ·未来的规划与展望

  演讲正文:

  大家好,我是来自京东数科的董璐,今天和大家分享的主要内容是京东数科DevOps平台的落地过程。

  分享内容大致可以分为四个部分,首先,讲述之前京东数科的研发形态,在做DevOps平台之前,我们当时是什么样的模式,研发、产品、测试和运维人员当时处在何种工作状态,当时的历史包袱以及需要解决的问题有哪些?其次,是讲一下我们推动DevOps平台的目的,针对京东数科暴露出来的问题,我们如何有针对性的、有层次的、有顺序的去搭建DevOps平台,每个步骤的重点关注点哪里?最后,给大家分享我们下一步的规划和展望。

  这张图中有很多大家熟悉的名词,比较陌生的可能就是J-ONE和SURE,这两个平台其实是京东数科之前的发布平台,主要专注于如何将系统发布到生产环境中。另外,GitLab、Jenkins、SVN这些平台的出现是因为团队中每个Team都有自己的代码托管平台,为什么会出现这样的情况呢?当公司发展到一定规模时,通常会通过并购的方式来扩展业务,京东数科也不例外。而被收购公司整个团队往往都会有自己的研发习惯,当他们合并过来之后,因为京东数科没有自己的DevOps平台,只能是团队内部自己搭建代码托管,每个Team都有自己的技术栈,使用的代码托管平台自然也可能不同。

  这就是历史包袱之一,大家的源代码托管在相对比较分散的不同的平台之上,发布上线是由运维部统一发布上线,但我们的发布上线也有两个途径,并不是只有一个入口,J-ONE和SURE都允许将通过各种途径打的包上传发布到生产环境中。

  那么,总结一下我们当时面临的情况是什么样的呢?首先,代码托管的地点很分散,每个Team都有自己搭建的代码托管平台,当然现在的情况也是这样,因为迁移是有迁移成本的,如果没有额外的赋能给到团队,那么他们大概率是不会愿意为此付出工作量。

  其次是发布上线没有管控,刚才提到线上发布是走两个通道,但是包的来源是不清楚的,即无法确定这个包是否是通过哪个平台构建出来的或者上传的包是否为当时已经通过测试验证上线的包。申请上线与提测验证阶段是互不干扰,相互断离的两个操作,上线操作所使用的包并不能保证就是当时测试通过的发布包。

  第三是源码不可追溯,如果这次发布上线的版本从1.0升级到了2.0,那么2.0版本的源码有可能研发本人知道在哪里,但公司层面是不知道的,因为它不知道你有多少个代码托管平台。即便是知道在哪个平台,也没有分支管理,没办法找到对应的源码是什么。

  最后是研发过程不持续,自己搭建的Jenkins可能不会涉及到类似分支策略、验证等问题,无法查证线上版本在历史迭代过程中是基于哪个工具。而正是因为这些问题,导致在京东数科内部没有办法走一套完整的DevOps平台输出到生产环境。

  上文的分享主要是针对研发人员使用代码托管平台完成代码,没有提及到产品经理和测试人员的工作。产品经理可能会采集需求,例如当从其它Team中收集到一些任务,下发给研发人员进行开发,之后从代码托管平台中下载下来并交给测试来进行打包部署测试,在整个过程中产品经理可能根本不知道我的需求处于什么阶段。也许,产品经理可能会使用自己的工具去记录需求,但它是脱离研发过程的,换句话说,你根本无法追溯哪些需求是在哪个版本上线的,当时是由谁来处理的,测试人员是谁,涉及到的测试用例都有哪些。

  相应的,测试人员也是游离在整个研发过程之外的,当产品迭代了很多次之后,你根本不知道当时的测试是谁做的,测试用例是什么,对应的测试报告是什么,基于哪个需求……换言之,它在DevOps上是打不通的。

  基于此,我们想要做的DevOps平台是希望能够把各个TEAM环节都打通,每次迭代链路上的每个环节都是可追溯的。其次,我们希望这个平台首先要能够保证现有工作的顺利进行,然后能够保证每个节点之间是连续的。

  二.推进DevOps的目的

  我们推动DevOps的目的是什么呢?杰克·韦尔奇的名言给了一个很好的回答:“如果外界的变化率超过了内部的变化率,那么末日就不远了。”随着整个互联网的发展,所有企业都在迭代升级,你要做的事情一定要比外部迭代快,在外部需求还没有引爆的时候就切入进去。

  迭代速度很重要,但是不能一味提高速度,而忽略了质量,要在保证质量的基础之上去提高效率,不能每次需求上线的时候,用户在使用时,平台直接崩溃了,那么提速再快也没有意义。

  如前文所述,京东数科之前各个部门是各自为政,他们之间的交流沟通,甚至是上线之后如果要发过来追溯任何一个环节,都需要线下讨论。这样,发现问题、查找源头会非常慢,甚至根本就找不到。

  我们希望能够做到统一管控,当公司发展到一定规模时,找不到源码会是一件灾难性的事情。假设Team中有个人离职了,将代码交接给下一个人,而下一个人也离职了,那么代码很可能就留在了代码托管平台上,没人知道源码放在哪里。京东数科现在就有一些源码已经找不到了,只能拿当时的生产包再继续发布迭代。

  除了统一管控,我们还希望在上线过程中能够去追溯之前的研发过程,而不是任何一个有权限的人上传了一个生产包就发布上线了,这个生产包的来源、测试人员、测试用例、规则验证、测试用例等等都不清楚。

  在整个DevOps平台上,我们希望能够采集到所有过程中的数据去辅助团队Leader做决策,例如,整个研发过程的发布上线频率、迭代速度、每次迭代产生的bug率、每个人的代码效率……通过这些内容我们可以反推到Team中每个研发人员的能力层次。另外,我们还可以去评估测试人员,例如产品发布上线之后,产品的崩溃情况、是否有比较紧急的bug出现、出现的频率。甚至,我们还可以获取到它当时使用的第三方库,从而去反向推理线上的产品存在哪些安全隐患。

  下面详细介绍一下提升质量。在我们看来,DevOps的使用者不等于研发人员和运维人员,它应该囊括整个研发过程中的所有人员,例如测试人员、产品经理,而我们希望所有的角色都可以参与到DevOps平台中,不仅是完成各自的工作,同时还要把自己做的事情交给DevOps平台去采集。

  DevOps平台的意义不在于打通所有开源软件,把所有的事情串联起来,而是根据你做的事情反映出当时的情况,通过采集数据提升价值,能够平台做的事情绝不让员工重复工作,节省员工的工作时间。

  举个例子,假设一行代码上线之后,我能够找到当时是基于哪个任务、需求或bug来做的,什么时间,谁写的……如果有人上线了新的一版之后,我的功能出现了问题,那我能够找到是谁的代码影响到了我的功能,这个操作人是基于什么原因修改了我的代码……而这一系列的操作,我不希望是自己一个一个去找,而是平台能够很直观地将当时的信息提取展示出来。

  提升效率的第一点就是减少沟通成本,把各个环节都打通这是DevOps平台最基本的一点。第二点是增加质量把控,在整个环节中设置一些关卡让平台自动去解决问题。例如,平台提供代码自动扫描的环节,扫描出来的所有问题都根据级别自动转化成不同的问题类型,反馈给研发人员。

  降低研发风险,因为整个环节是打通的,所以是不允许有外界因素直接插进来导致整个链路断掉。例如,我们在上传发版包的时候其实是处于一个不明了的状态,只是当时有个需求说要把这个版本上线,但这中间发生了什么,我们完全不清楚。这存在隐患吗?当然存在,但我们也没有办法,因为我们只有这一个途径来把包发布到生产环节上。

  提高自动化水平,我们不希望整个DevOps平台落地了,还需要人为的去做很多事情配合平台,这相当于给员工增加了额外的工作量,我们希望让有些功能能够平滑地迁移过来,变成DevOps平台能够给员工带来一些额外的功能。

  统一管控,我们会以这几个点来进行切入:

  项目立项,最早京东数科是有多个立项入口的,然后通过同步的形式统一到生产运维,进行IaaS、IP地址、网络等等底层的分配。这就会出现很多问题,首先,你在立项的时候无法确定和别人有何区别,其次,因为没有统一入口,审批人员也是不同的,到了上层之后,你就会发现有很多重复的应用。第三,因为是通过不同的渠道去发版,所以在发版的时候你可能根本找不到对应的应用在哪里。

  基于以上原因,我们要统一立项入口。

  需求审批,研发人员对此可能感触很多,虽然他们只需要执行收到的需求即可,但是有时提需求的人都可能没有考虑清楚这个需求要不要上。滴滴团队中设置了一个需求委员会,在整个研发过程起到一个审批需求作用,产品经理或者其它角色的人员如果有一个需求,需要先提交给需求委员会去判断是否有价值。通常来说,研发人员的需求大多来自直接上级、测试人员或者平台使用方,而像京东现在已有好几十条业务线,一个需求提过来,我们很难确定这个需求是否有共性。所以我们需要有一个统一的需求入口,并包括审批过程。

  代码权限,为什么需要代码权限?首先,是为了防止权限扩大,其次是为了提高需求实现质量。将所有代码都统一到一个平台上,然后明确使用者是具备读还是写的权限,例如在做移动端持续集成时可能会涉及到二进制包的上传、读取和打包,就需要你去做组件化拆分,融合其它Team的二进制包,那么二进制包的下载地址只能是可读的。

  提测发布,我希望让每一个测试人员都参与进来,一版应用发布上线之后,我们能够清楚的知道它经历了多少次提测、提测人员是谁、针对的测试用例是什么,甚至我们还可以追溯到它的历史进程。

  整个流程打通之后,接下来就是要采集到整个过程的数据。首先是基础数据采集,例如任务数据、需求数据等更偏向于项目管理的东西。当然,京东数科DevOps平台只切入了项目管理的一部分,没有深入的去做,需求拆分子需求、规划、排期等等都不是DevOps关注的重点。我们只需要在整个研发过程中能够追溯到代码的源码,并支持对接到其它平台即可。

  其次,是采集到所有代码库的动态变化,代码托管之后的整个过程是什么样的,何人何时提交了代码,在整个过程中代码总共提交了多少了,提交的代码行数有多少,距离最后发版的最近的一次提交,修改的有效代码行数是多少……

  再次,构建过程产出,构建过程规则检查结果、依赖树采集、依赖关系采集。最常见的例子就是依赖二进制包问题,应用包中往往会依赖很多第三方库和包,而这些库和包往往会随着时间的推移而出现一些漏洞。所以,我们希望应用上线之后,一旦发现包有问题,能够反查到哪些应用需要更新。

  研发过程信息,包括提测频率、发布频率、代码扫描结果和单元测试结果。在工作中,研发人员可能需要频繁的去提测,而测试人员可能每次的针对测试用例有20个,这样无形之中就会增加很多工作量。而如果DevOps平台把持续迭代过程中的数据都采集过来,做统计分析,那我们就可以清楚看到Team中的每个研发人员和测试人员的能力。

  三.演变落地的过程

  说完了我们推进DevOps的目的,接下来讲讲整个演变落地的过程。

  京东数科DevOps平台的演变落地大致可分为三个阶段,第一阶段是工具化,因为京东数科当时的情况是各个Team都在使用开源平台或软件搭建自己的环境,所以秒上DevOps平台是不可能实现的,总要有个迭代研发的过程,我们先把各种工具封装在一起,供员工使用。

  第二阶段是平台化,把所有的开源工具整合到一起,需要自研的模块,就用自研工具将开源模块替换掉,并融入到DevOps平台中。通过自研的一些模块,能够让我们的devops平台联系更紧密,也更容易扩展。

  第三阶段是一体化,把每个组件都组合到一起。就像前文提到的,每一行代码都可以追溯到它原来出自哪个任务、经历了几次变更、对应的测试人员是谁,整个研发链路是清晰可查的,无论从哪个节点切入,都能找到对应的链路情况。

  在做一体化时我们要考虑到整个研发过程,所以在开发DevOps平台时需要把所有要接入平台的角色都考虑在内。

  首先是提出产品需求。通常DevOps平台都会从代码出发,有一个代码托管地址来托管代码,然后在其上进行研发、单元测试、打包、发布上线。但我们是基于需求来做的,有产品人员或运营人员提出产品的业务需求,也有架构人员或研发人员提出系统的技术需求,这两方面的需求都需要采集到平台中,然后通过任务下发的形式,将需求转化为任务,下发到研发team中。除此之外,也希望我们的DevOps平台能够对接产品方经常使用的平台,至少将数据打通,不影响产品方的正常工作。

  第二,在做了需求管理之后,接下来就需要有个环节来控制需求下发的频率。京东数科目前面向十几个业务线,每个业务线都会向DevOps提出一些需求安,但并不是每个需求都是共性的,另外,我们也会采集一些外部需求。之后,我们会评审这些需求是否合理,再下发分配到整个研发迭代周期里。

  第三是分配研发任务,分配任务主要发生在产品经理和研发Leader之间,研发Leader确认OK之后,研发经理决定在何时发版上线,并把任务分配下发到所有研发人员。这样每个需求都会拆分成多个任务,落实到每个研发人员那里。

  第四是质量保证,由测试团队对开发完成的功能进行测试,同时使用工具多管齐下,保证项目质量,在测试人员的自动化测试、功能测试等环节进行质量保证的同时,也可以先使用代码扫描工具对代码进行质量扫描,对出现的问题进行修复,进一步提升代码质量。

  最后是运维监控,测试完成发布到生产环境之后,我们能够追溯到当时生产环境的部署情况,例如虚拟机、容器、当时的运行环境等等。

  这是工具化演变落地的第一步,首先是是项目管理,项目管理包括立项和资源分配,例如域名、生产环境机器、服务器以及硬件等资源的分配。其次是需求分析,我们会把使用的一些工具记录再Wiki上。第三是设计实现,代码需要统一管控,所以需要把所有代码都迁移过来。而这个工程会由平台帮你去做,例如会预设一些分支策略。第四是构建部署,最早我们是基于Jenkins来做的,通过Jenkins接口去做调用,通过邮件申请审批的形式去配置,在Jenkins上打包。第五是质量保证,我们搭建了一个代码扫描平台,使用者自己去做扫描,平台统一给出报告。

  之后,我们基于此做了一个改变,可能某些开源工具已经不适合这个平台了,我们就把它替换掉。例如,我们之前是通过API来使用GitLab的,当然GitLab自己也会提供一些API,但是在持续集成的过程中,提供的API已经不能满足我们的需求了。分支策略是一个比较敏感的策略,我们需要它去保证整个研发过程是可持续的。之前,每次发版时,我们都要把多个需求合到集成分支上去,但是现在我们希望这个工作由平台来完成,当需求过来时,自动创建一个研发分支,在分支上做有针对性的需求或任务代码研发,其中的分支策略也由后台自动运行。如果是继续使用GitLab,那么我们也会去做二次开发。

  在做代码托管时,如果代码仓库过大也会出现问题,例如有些静态资源的仓库可能会有几个G,那么很可能GitLab就崩掉了,所以它是没有办法持续给所有Team提供一个共同使用的代码托管平台。

  在做移动端持续集成时,会有二进制包访问权限的问题,而在GitLab上做权限访问也会有问题,例如,我想对一个分支做访问权限,定时打开或者锁定集成分支,之前我们都是人为地去控制权限,而现在我们把这些都交给平台去做。

  另外,我们还对SVN做了一些API研发来支撑上层,让DevOps平台直接控制代码,无需更多关注分支策略的问题。当有新的任务产生,你会看到任务在哪里,分支是什么,切换到分支上去做研发就可以了。测试通过之后,平台会自动做代码合并,合并到集成分支上。如果两个需求存在分支冲突,平台会发邮件通知使用者在线下合并之后再通过集成分支去发布上线。

  总之,我们会让整个代码托管平台开放出更多API,供DevOps平台去调用,持续的控制代码。

  在平台化的过程中,我们把所有工具都打通,让使用者可以在平台中平滑的去做任何想做的事情。

  首先,项目管理还是基于原来的平台来做项目立项,从需求分析开始整个迁移到DevOps平台来完成。需求分析就会涉及到一些需求值、需求分配、派发等问题,而这些我们在DevOps平台中没有深入的去做。

  举个例子,GitLab替换掉了之后,我们就可以直接把任务派发到研发人员,研发人员可依据此看到分支在哪里,直接在上面做研发,而分支策略是全部封装到后台的,使用者是完全无感知的。

  任务看板会辅助你做周会。平时工作时,我们可以通过任务看板去分配工作时间,而当代码上线或者周会Code Review时,登录任务看板就可以直接跳转到对应的源码上。

  之前我们的构建部署是基于Jenkins做的脚本,后来我们把Jenkins后端的所有任务都用shell脚本改写掉,保留了Jenkins的调度功能。这样后端任务也会做一些其他的事情,比如数据采集,当整个工程在构建的过程当中,它会采集整个工程的依赖数是什么、分析源码是什么,在构建发版时所依赖的第三方包是不是在黑名单里……而你要做的就是把代码提交上去,整个校验扫描过程都由平台完成,同时扫描采集到的数据和结果都会自动转化到任务中,这样如果测试不通过,那么也可以很快的找到哪个环节出现了问题。

  质量保证,DevOps平台上做了整个QA的链路提测,提测完成之后,我们能够知道这个事情应该分配给谁,当时的测试用例是什么……当然,bug管理、代码扫描都是为了质量保证,增加这些特性的主要目的还是为了保证DevOps链路的完整性,毕竟它不是一个简单的构建工具。

  最后,我们希望在保证质量的基础之上去提升研发效率, 发布平台使用的还是Source和J-ONE,这样能够保证他们原来的使用习惯不变。

  一体化指的是把整个链路都打通,所有的数据都可以被扫描的。例如,每次迭代发版的人力和时间投入,需求立项之后,需求的规定发布时间是何时,是否按时发布,需求如何拆分到不同的Team,各Team的配合情况如何,任务如何关联到源码,源码是基于哪个任务生成的,然后是研发人员比较关心的设计实践和构建部署,在做质量保证时,所有扫描结果测试的bug都会同步的转发到任务中,甚至可以包含到下一次迭代中去做发布上线。

  总之,一体化要做的事情就是采集整个过程的数据,并结合投入的人员和资源做分析。

  上图就是我们京东数科整个DevOps平台落地过程中经历的各个阶段。

  四.未来的规划和展望

  我们希望我们的DevOps平台不仅能够帮助用户能够快速的发版上线,而且能够帮助用户更快速的解决发生的问题,更重要的是能够追溯当时问题的来源点在哪里。

  微服务是我们下一步要解决的问题,在测试环境中如何集中式部署多独立环境的微服务环境,换句话说就是我对多个服务都有依赖,但是我希望在测试环境中能够有互不干扰的完整的自管理的微服务。测试时可能是多个测试团队同时在做,那么我也不希望大家是在同一套测试环境中做测试。

  我们目前采用的方案是先采集整个服务的调用链,之后去部署一套全新的、完整的服务测试环境。

  首先,要部署一个微服务去测试它是否对其它服务有依赖,我们会基于Docker、DNS去做一个完整的、基于域名的、可在多个微服务内部调用的且与其它环境互不干扰的测试环境。然后基于持续交付平台,采集到所有依赖调用链的数据,基于调度中心去部署服务,用Docker把服务部署到同一个DNS服务下面,这些微服务之间的互相调用会注入到环境依赖的同一个DNS服务器上,这个服务器可调用当前微服务内部的所有服务,但不能跨DNS调用服务,同时我们也会做一些监控,来保证独立环境的稳定。

  在A、B、C各个环境中都会部署一套独立的测试内容。基于此可能还会遇到另外一个问题,那就是一些公共组件无法独立化,例如消息平台、日志采集平台等。这时,我们就需要把公共组件也容器化,而这个操作可能还需要其他部门来配合完成,通过提供镜像模板、配套API以及网络环境支持等,来完成中间件的容器化。

0
相关文章