@gaoxiaoyunwei2017
2019-06-06T15:22:43.000000Z
字数 6621
阅读 885
豆沙包
作者简介
陈展文
2018 DevOps 金牌讲师谢沁沐
招商银行负责持续交付流水线平台的开发负责人
今天给大家分享的是招行在两年前基于K8S搭建的整个流水线,我会先给大家分享比较宏观的重点方面,然后就具体技术细节再详细进行介绍。
今天主要的内容分为四部分:
第一,建设背景。
第二,容器化技术解决方案。
第三,非容器化解决方案。
第四,未来计划。
其实我们面临很多问题,比如怎么把相关的流程配出来,可能需要花很多时间,我们涉及的工具链很多,构建的环境很复杂,需要支持的流水线比较庞大。
具体来说,首先,我们本身的工具链很多,从项目管理、配置管理、构建、测试等等,用到了很多新的、旧的、历史的工具链。工具多、文档多、步骤多、数据多。
第二,因为是银行系统,有老的系统和新的系统,涉及到的编程语言、编译语言,包括系统开放的、核心的都很多,包括Linux等等。
第三,我们涉及到的流水线非常大,两年前评估时我们想用Jenkins替代原来的商业解决方案,我们发现在评估怎么做的时候,一台Jenkins如果支持1000个job,可能出现一些不稳定,或者类似于泄露的问题。平均如果是一条流水线有6个stage,支持类似今天这种规模,需要60台的jenkins服务器,更不用说这60台怎么管理了,同时涉及隔离性差、运维成本高的问题。
核心系统这么复杂,质量要求肯定很高,一旦算错账,客户不满意,银监会不满意,谁都不满意,我们的压力很大,春节银行放假了,我们也还是要值班的。
我们日常做运维过程中遇到很多困惑,相信大家都遇到过。首先,测试可能会抱怨代码质量低,上来就报错,耽误测试时间。另外,很多人都会遇到一个问题,测试时间太短了,两周一个上线窗口,我们最长的测试时间可能是两周到三周,但我们在做核心项目的时候写了五万条案例,不可能全测完,只能测新功能。新功能测试不全面的情况下,上线怎么保证不出质量问题呢?这是测试要思考的问题。另外,开发同学会遇到很多问题,上线流程太长了,要各种领导签字,比写代码还麻烦。这都是日常工作中遇到的困惑,我们希望通过DevOps解决。
我们的思路是引入Docker容器技术,核心思想是在用户创建流水线时,给它一个专属的Master,当运行完流水线后,把运算资源全部回收,解决隔离性差和计算资源浪费的问题。
容器化技术栈的解决方案主要包含两部分,第一部分是Slave动态化过程,第二部分是Master动态化的过程。
我们的目的是在不同的编译场景启动Jenkins,让它真正构建任务。我们是通过容器的镜像继承机制达到这个目的。定义一个基础镜像,把Linux等常见命令封装进去,用户可以自定义高定制化的编译命令。如果需要新增编译场景,就可以很快速从基础镜像继承一个镜像过来,安装好需要的编译环境就可以了。通过这样的机制,很快就支持了类似于很多跨平台的语言,比如Python、Java之类的。
解决了镜像问题,接下来就要解决第二个问题,就是Slave动态资源回收的问题。我们的思路是介入Job的两个生命周期,分别是pre-run阶段和stop阶段。pre-run阶段的实现方式是编写插件Cloud Plugin,运行阶段Cloud Plugin会形成容器create进行编译动作,之后把容器回收掉,从而达到动态回收计算资源的目的。整个过程是通过JNLP连接方式实现的。
第二个部分是Master动态化的问题。我们原本希望通过和Slave一样的方式解决,就是用户按需的时候,需要用到Master才创建,用完就回收掉。但很不幸,我们没有做到,因为Master的启动速度比较慢,Slave的连接是秒级的,但Master在容器里的启动速度普遍在三四十秒,慢的时候甚至超过一分半钟。如果用户运行时才建立Master,这个用户体验实在太差了,所以我们放弃这种方案,回退到用Master池的机制维护动态化的过程。
具体实践是这样的,有不同的流水线,在运行的时候从Master池建出不同的Master,再通过Master插件动态生成Slave容器进行真正的编译,再把它们两个回收。一段时间内,整个Master的数量相对稳定,所以我们通过一些池的参数,比如maxTotal,用它们来连接。一旦Master池的Master用完了,并不是放回池里下次使用,而是直接抛弃它,这样能提高构架的隔离性,容器的创建和废弃很低,没必要复用。
以上是Master动态化的机制。怎么实现它?主要有两个组件,一个是上图红框里的scheduler服务,主要提供Master的制备和销毁策略。第二个是Jenkins API gateway,可以做创建job、更新job的操作。用户在运行流水线的时候,可以通过scheduler服务借一个红色的Master出来,并借一个新的Master,保证在参数生效的同时,池里有足够用的Master可以供下次构建使用。在运行的时候,可以通过Jenkins API gateway调用API执行真正的job,运行后把Master回收掉。
这套方案做完后会发现在创建流水线的时候,我们本质上不需要动Jenkins,只需要做一些配置,写在数据库里就可以了,不需要消耗容器资源,运行或停止的时候,我们只是通过借和还Master,通过调用网关创建job来实现效果。
我们整体的架构分为几层,最下面的层是用到的基础设施,包括数据库、ES、镜像仓库,在镜像仓库上会有一堆容器的镜像,包括系统本身运行所需要的容器镜像模板,同时还会有一堆编译环境。第三层是在镜像基础上构建的很多相关服务,包括有很多应用级别相关的服务,最终用户可以通过web、手机以及相关的API进行访问。
大家可以看到,刚才这一套其实还是挺复杂的,因为涉及到很多的容器和镜像,包括我们的服务器服务本身,还有编译的镜像,而且我们自己实际在做迭代开发,从两年前开始到现在,我们还持续每两周会发布一次迭代,发布时跟普通的应用系统不一样,我们涉及大量的基础镜像的更新。怎样做到零停机,或者做到尽量少的停机?
我们做了蓝绿部署。不是零停机的蓝绿,我们做了一些取舍,当你做的可用性更好时,对技术的挑战也会更大。现在生产环境是蓝环境的环境在跑,两周一个迭代,周四会发新版本到绿环境,上去之后会做完整相关的回归测试,做一些相关的验证。做好了之后,比如周五,我们会花大概五分钟的时间,就可以把整个环境切到新的绿环境,同时再做一次简单的验证,基本上在15-20分钟内就可以重新给所有的开发者提供服务了。
除了前面讲到的容器化解决方案外,我们还存在大量非容器化的场景。比如.NET占据了半壁江山,我们也想用windows的方案,但目前不是很成熟,同时我们还有很多不适合在容器上编译的事情,比如手机端、mac,还有一些编译化场景。一开始用单台的Jenkins尝试驱动它们,但用单台的Jenkins也会遇到刚才出现的问题。
其实容器化方案是相对动态比较完整的方案,如果要介入难以容器化的场景,单台Jenkins难以满足要求。既然单台无法解决,我们的思路是多台是否可以解决?
我们有一个非容器化的过渡方案。我们会预先制备好固定数量的Master,将不同的Slave连接到不同的Master上。比如Master1,把VS2018、VS2010挂上去了。但这个方案有个特点,Master需要提前制备,需要提前规制好有多少的项目,有多少的构建机器,需要按这个数量规制需要的Master。
另外它还存在多个流水线共用一个Master的场景,这种隔离性比较差,而且Master一旦出现问题,会导致集体失败。它的连接方式还是通过手工连接的方式,要预先把机器启动起来。具体的接入场景分三步走。
第一步,拿到目标构建机器,要把它里面装好客户端,把构建环境装好。
第二步,通过手工方式连回Master1。
第三步,在平台上把构建机制注册号,用户才能真正从平台上进行真正的构建,这个过程还是比较复杂的。
这个方案在线上运行了四五个月左右,也暴露出了一些问题,比如单个Master Job数量还是过多,因为我们嵌入的windows或其他场景的流水线越来越多,很快一个Master上的Job数量超过了2000个,已经超过了性能上限。第二点,极易产生雪崩效果。如果一个Master上的某一个Job出现卡住或异常,很有可能导致同Master的其他Job也产生异常,这个团队的流水线会影响到别的团队的流水线,这个在我们这里是不能被接受的。第三点,Master的性能和稳定性下降。第四点,本身流水线的配置是在存活的Master上有,也在我们的数据库上有,但这中间的一致性很难保证,因为Master不太稳定,数据成功了,Master上也可能也没更新到。第五点,这是开发者比较痛苦的,一套业务方案可能需要做两次实现,因为整个Job的运行逻辑在两种方案里完全不一样。
我们也有一些应对措施,比如每天都会凌晨去定时重启Master,从而解决它的内存泄露和性能下降的问题。我们会定期扩展Master的数量,如果三个不够就扩到十个,十个不够就扩到二十个,再把Job从这个Master上迁移到另一个Master上,这都是一堆很冗余的事情。
总体来讲大家可以看到,这些措施其实是治标不治本的办法,最终我们的开发人员和运维人员还是处于很痛苦的状态,于是我们还是希望能改进,我们尝试做了一些分析。
第一块,尝试一下复用Master,而不是用固定的Master。
第二块,希望各种操作系统最小化环境依赖,同时把容器和非容器方案的实现逻辑统一,因为维护两条逻辑,大家去排错和开发很麻烦。
第三块,不能因为统一的过程让非容器执行的稳定性有问题。
第四块,我们也希望简化执行机接入流程。
首先第一个目标就是要复用Master池。我们思考的第一个问题就是原有的连接方式是不是还可以继续用?原来的JNLP连接方式是先借助Master,Master通过Cloud Plugin生成容器,这个需要Slave先主动连Master,然后再回连的过程。在非容器化构建的场景里,Slave提前存在,Master是后面建出来的。我们怎么实现连接?可能JNLP的方式不能直观来用,所以我们要寻找Master主动连接Slave的机制,答案是肯定的,我们有这样的方式。一种是windows Slave的插件,一个是SSH的插件。只要把目标机器的IP或授权用户名和口令定义给它,Master就可以主动连接并构建Slave。windows的机器走的是WMI协议,SSH走的是SSH协议。
这给我们提供了一个思路,我们有Cloud Plugin,是否能把它们结合起来形成新的Cloud Plugin?可以动态连接windows Slave。
现在理论上讲Linux系的都很好做标准化和系统化,几乎现在行业内用的所有机器都有这种协议,Linux机器和Mac节点都有内置,有了这套方案,我们支持的技术栈就可以很广泛了。
有了插件,接下来就要调整方案。我梳理了一下最关键业务的核心流程,首先是从池里建Master,对于容器化的场景和非容器化的场景,都是从同一个Master池里面建。
第二件事情是调用Master的API创建Job,通过下图灰框里的服务实现。
第三件事情,调用API直接运行Job,这里唯一一点有区别的地方,我们的插件需要按需决定这个Job的实行究竟应该先做容器,还是自己主动构建机器?我们把信息传给插件,它可以自己做这个事情。Job运行之后,它也要决定究竟是回收还是断连,最终会归还Master。
大家可以看到,只有在运行Job的阶段,我们的容器和非容器化方案是有不一样的地方,并且我们把所有的变化点都封装到插件里,理论上讲运行流水线整个核心逻辑就变得非常统一了。JDK运行在容器里,VS2008运行在windows虚拟机上,都是同样的运行逻辑。
有了刚才的机制,我们需要考虑另一个问题,我们存在一些场景,需要在不同的构建环境里共享中间制品。比如构建在windows上执行,开源组件扫描要在容器进行,必须扫描开源后的文件。之前在容器化方案里全靠挂载形式实现,但windows的虚拟机没法挂载,我们引用对象存储解决这个问题。当需要上传中间制品的时候,丢到ECS里,需要中转的时候从ECS里面拿。
解决了这个问题,刚才也说过一个重要的点,保证构建机的稳定性。现在Master的问题全部解决了,所有Job每条流水线每一次的构建用的都是不同的Master,不存在共享Master的问题,但压力会下行到Slave构建机上,因为它是被共享的。
我们对Slave构建机做了监控,下图是三个构建机实例的数据,除了监控CPU、内存、磁盘信息外,还有一些业务数据在这上面做了监控,比如运行流水线数量的时间图,包括运行的流水线,方便我们更快排查错误。
除了监控之外,我们还实现了告警方案,通过企业的软件发送到对应的支持人员,能提前知道机器是不是磁盘不够了,CPU是不是跑的比较高了,可以提前预判或预防一些事故的发生。在实现了刚才的几点之后,我们来整个看一下非容器化的构建方案。
右边这块的Master池一模一样,对于不同的流水线而言,用的Master都是同一种Master。唯一有不同的点就在于连接方式,Slave存在共享情况。理论来讲出现一些隔离性的潜在风险,对于有这种高隔离性要求的团队,我们会单独制备独享构建机,把风险控制在可控范围内。
我们用了这个方案对非容器化的编译环境进行改造,Master的性能问题可以完全规避。第二部分是Job,在同一台windows机器相互影响的问题也不存在了。第三个是避免Job和配置不一样的情况,非常重要的一点是程序员也开心了,不用维护两套不同技术栈的代码。
全部汇总起来看一下,我们通过这套方案,本质上现在能支持超过17000条流水线,这个不是同时运行的。大家都知道开发工作肯定会有不同的时间发起,这17000条去年都是活跃的流水线。第二块是我们支持超过20多种的编译环境构建环境,包括容器化的,主要是能在Linux平台上做的各种编译环境,包括非容器的构建场景。第三块,除了一些比较复杂的自动化测试外,本身流水线整体的运行时间会控制在15分钟之内,包括下代码、编译、扫描、自动化部署、单人测试、接口测试,包括复杂的界面测试,平均可以达到这样的水平。
再看一下资源使用情况。假设一开始不用这套方案,要用原生的Jenkins支持刚才的17000条流水线,需要935台虚拟机来做,而且还有一个问题,它们之间的利用率不一样,同时达到一定程度性能会急剧下降。用了容器化和非容器化的方案结合后,基本能够通过十台物理机支撑整个容器化的编译,77个构建机可以支持非容器化的情况,对于动态回收、弹性伸缩、资源共享、高利用率,都会起到很好的作用。
理论上只要存储资源够,这个扩展就是无限的,而且每一条流水线获得的运行效率性能基本不会下降,都可以维持在这个水平上。
最后看一下未来计划,刚刚讲到容器化和非容器化的技术栈。
我们总结了一下,第一块是整合工具链,一站式体验,今天给大家分享的主要是DevOps工具链非常核心的流水线。
第二块,同时我们正在做很多协同、电子看板,包括端到端的需求开始,写代码、部署、自动化测试等等一系列的工具链,怎么把它更好地整合。
第三块,AIOps很火,这一届DevOps讲了很多关于AIOps的东西,我们这个平台绝大部分都是让开发者自助使用。当然使用过程中会有一些问题,怎样通过智能日志更好运维减轻我们自己的压力?这是下一步需要做的。包括在运行流水线的过程中怎样知道每一个步骤,涉及到的关联是什么,整个过程如何可视化度量?我们后续也在做规划。
最后还是一个容器的支持,大家可以看到windows的方案,其实还是存在一些问题的,3月底微软出了生产级别的windows容器解决方案,后续我们会积极看这个方案,如果这个方案work,对我们来说可以进一步优化刚才的系统方案,从而支持容器的应用构建、测试环境的快速创建。