CI/CD的利器k8s+docker

首先docker它具有以下的优势

  • 标准化:所有环境一个镜像,解决环境差异性。避免因为开发环境和测试环境不一致带来的问题。
  • 快速化:启动时间短,秒级。
  • 隔离性:namesapce,cgroups,联合文件系统提供除内核外完整的隔离性,解决互相影响的问题。
  • 易扩展:使用镜像迅速搭建N套环境,无间隙。 版本管理--镜像的版本管理对应测试环境的版本管理,易操作。
  • 易用性:Dockerfile编写简单,会写shell的人就会制作镜像

而k8s也有很多的优势:

  • 天然的解决方案:玩过k8s的都知道,使用k8s你几乎可以不用太担心负载均衡,弹性伸缩,服务发现,路由转发等等令人头疼的问题。 它的service和deployment自动帮你做负载均衡,HPA自动的弹性伸缩,kubedns为你提供域名解析,实现服务发现。 官方的ingress直接为你实现好了7层路由。
  • 自动化运维:k8s的自动化运维能力也是让人津津乐道的。它的健康检查能全方位的检测容器状态和进程状态。出现问题的时候自动销毁,自动修复。 容器出错重启容器,节点宕机漂移到其他可用节点重新部署。期间负载均衡模块自动导流。
  • 强大的调度能力:在k8s中实现多租户是很方便的。 quota可以隔离资源,namespace可以隔离应用。 node selector和节点亲和性更是可以细粒度的控制应用部署在何处。 强大的调度能力使我们的维护成本降到最低。
  • 简单易用的横向扩展能力:一个命令既可扩容,创建应用的时候调度系统动态分配资源。
  • 强大的社区:google支持,强大的社区阵容,不用担心没有解决方案

那么K8S在实际的场景中能为我们带来什么呢? 我举几个场景吧。

横向扩展提升性能与容量

刚才我们说过面对大规模的持续集成与测试环境时。 面对的每日数以千计甚至数以万计的构建次数。上面还要运行着很多的测试环境。 所以一台机器搞这事铁定是没戏的。如果是以前,我们基本都是在jenkins上加入大量的slave节点并用shell脚本来维护。 但是当节点越来越多的时候,维护这些节点和环境的成本就越来越高了。 所以如果我们能让k8s来搞定这些事情,扩容与调度都交给k8s来做。 jenkins只负责pipline,这就解决了我们自己开发和维护这些节点程序的成本。k8s的扩容是很简单的。并且它给你一个统一的接口,让你对所有节点和容器的操作只有一个入口。 不会像以前shell脚本那样满天飞的节点配置和逻辑判断。 而且k8s能够对自我管理这些节点,自动决定把容器调度到哪个合适的节点上,不必我们操心。

精准的按需调度

在一个复杂的场景中,我们不仅需要集群的调度系统能按照当前节点的资源使用情况进行调度,也就是尽量把任务分配到较为空闲的节点上进行负载均衡。 我们还需要更精准的按需调度。 K8S中有一种策略叫node selector, 我们通过给集群打上不同的label给节点分类,在提交任务的时候可以选择不同类别的节点进行运行。 比如我的数据库是需要存放数据的,而这些数据只存在那一个特定的节点上, 这样我们能通过这样的机制把数据库容器调度到固定的节点上。 当然我们还可以有更强大的调度。比如GPU资源再哪里都是稀缺的,我们并不希望大量的普通任务调度到拥有GPU的节点上,免得它们把资源占满后会导致真正有GPU需求的任务无法调度上来。 k8s有一种策略叫污点,任务除非在特别指定的情况下,否则是无法调度到这个加了这个污点的GPU节点上的。 但这样也有问题,因为这个策略太排他了,实际上如果我的GPU任务没有那么多,站不满资源。 那这些剩下的资源还不能被其他任务调度,这个资源利用率是无法忍受的。 所以k8s也有第三种调度,叫亲和性和反亲和性。 这个有点复杂,但是它能达到一种效果。 就是它可以优先把普通任务调度到其他节点上,其他的节点不够用的时候,才把这些任务调度到GPU节点上。 这样能最大程度的保证其他任务不会影响GPU任务还能最大化增加资源率用率。

资源治理

上一点说按需调度的时候,也讲过资源利用率的问题。 我们面临大规模的测试环境的时候, 资源治理是一个非常重要的问题。 公司不是土豪, 我们的资源永远是有限的, 在有限的资源中支撑更多的环境是我们的目标。 我们总在计算着一个节点上能抗多少任务,什么类型的任务。服务起少了,资源利用率低,服务起多了,可能直接把节点撑爆了。 这是一个挺两难的事。所以k8s中有quota的概念,是一个非常重要的特性。首先启动k8s的时候可以设置为系统预留多少资源,这些资源是k8s不会使用的,专门留给linux系统,以免过多的使用撑爆集群。而且k8s把每一个资源都分为两种申请方式。 request和limit, request代表着预留资源,也就是说如果我们为一个设置为request memory为2G。 那么k8s就会为它预留2个G的资源,即便这个任务只用了1个G,但其他任务也无法使用另外一个G的资源。 这种策略保证了服务绝对拥有启动服务的最小资源,因为只要申请了,系统就会为你预留这些资源使用。 但是这样的策略有个缺点,就是我们一般无法准确预估出一个服务会用到多少资源,也许他只用了申请资源的一半。 所以我们还有一个中申请资源叫limit, 他跟requeset配合着使用,request是系统预留的资源,是给任务独占的资源,即便任务根本是用不了这么多的资源,但他也会预留出这些资源给他使用,给一个任务设置过多的这种预留资源肯定是不利于资源利用率的, 但是预留资源少了,也可能造成其他任务抢占资源导致本任务无法运行。 所以request一般都是设置成需要运行任务的最小资源。 但是最小资源不代表着他永远都使用这么小的资源,在服务的高峰期的时候他会使用更多的资源。 所以我们有limit这种资源申请的方式, 如果说request设置的是资源的下限,是任务申请的最小资源, 而limit就是任务申请资源的上限,代表着不论如何,任务使用的资源都不可以超过此上限,超过了就会被k8s kill掉。确保一个任务不会超出它的预期占用过多的资源。 这样, request和limit相互配合,就会产生更弹性的资源治理方式。 尤其由于他们这两种申请资源的方式存在,我们的系统才会拥有超卖的能力。 超卖是一个在集群管理中常见的词汇。 意思是一个服务本来申请了固定的资源保持平时的开销 (request方式),但是在服务高峰期的时候,准许分配给他更多的资源,也就是超卖给他更多的资源来抗住高峰期的压力(limit方式)。这样就给了我们更加弹性的资源利用方式。 

健康检查

之前说过k8s有自动化运维的能力, 其中一个重要的能力就是健康检查和故障恢复。 一般来说,在我们面对数量庞大的部署实例的时候, 最头疼的就是如果环境不稳定,比如节点故障,或者进程本身故障的时候,我们该如何维护这些部署实例。 在部署实例还比较少的时候这些都不是问题,我们人肉维护都是可以接受的。 但是当我们有微服务的时候,有多套测试环境的时候,数以百计的部署实例会让所有维护人员都疯掉。 尤其是有些服务的故障可能是很临时性的,比如仅仅是进程oom了,或者部署的当前节点发生了什么故障。 并不是进程本身的问题。可能只需要重启或者换个节点重新部署就能解决的问题。那这时候k8s的调度能力就起到了很大的作用。 首先k8s能够自动监控每个节点的健康状况, 如果A节点发生了故障,k8s会监控到A节点是不健康的状态, 那么原来启动在A节点上的服务,都会自动的漂移到其他的可用节点上重新启动,来保证我们环境的可用性。 这是节点上的健康检查 。 同时我们也有用服务的健康检查, 在k8s中,没启动一个服务都可以为它配置相应的健康检查策略,包括资源的和进程的本身。当任务服务出现异常退出的时候,k8s都会自动的检测到并在合适的节点上重启该服务,这其中不会有任何的人工操作。这种故障恢复能力,是在我们面对大规模测试环境中要拥有的重要能力。

k8s还有很多有助于维护我们测试环境的特性,比如驱逐策略,负载均衡,弹性伸缩,init container等等。这里就不多做介绍了。

下面介绍一下k8s最简单的玩法。

  • 在k8s中,我们使用1个或多个master节点来控制集群,之后启动多个node节点接入集群。 集群中所有的节点都共享公共的镜像仓库。这样我们把所有需要部署的服务都制作成镜像,就可以为团队提供稳定的测试环境和测试服务的基础PAAS平台。 重要的是我们从此拥有了相应的自动化运维和横向扩展的能力。 通过统一的入口可以维护多套测试环境,资源不够了就加节点。 说白了,我们就是在拿钱砸效率。我们在推行微服务之前,在高峰时期曾支撑过70套左右的测试环境。 其中包括了各种版本以及对接各个团队的需求。 当然了为了能够维护这种量级的测试环境。 我们需要一些机制上的支撑。

  • 跨主机通信:weave或flannel, 玩过docker的人都知道网络是第一个要克服的难题。 每个docker进程都会自己维护一个私有网络,那么在集群中不同节点上的docker 容器如何互相通信是一个问题。 所幸目前已经有一些插件支持k8s组件一个overlay网络。 比如我们曾经使用的weave和现在正在使用的flannel。 它会在集群的每一个节点上都安装一个路由容器,帮助我们转发网络请求。 通过这样构建一个overlay网络的形式达到跨节点通信的目的。 而且这些组建都已经有成熟的容器化部署方案,我们直接拿来用就可以

  • 服务发现:kube-dns,docker 容器的另一个特点是容器的ip也是随机分配。我们在启动前无法获取ip,并且在容器重启后ip也会发生变化。 所以个成熟的服务发现的机制就比较重要了。 k8s官方提供的kubedns是满足我们的需求的。 在k8s中有service的概念,我们只要创建一个service,k8s集群就会在kubedns上创建一个域名指定到这个service 上。 而每个service,最后都会转发到我们的容器里面。

  • 7层路由:ingress, 我们可以通过dns和flannel这种网络组建解决服务发现和跨主机通信的问题。 但是从集群外部访问集群内部还没有解决。 因为即便是k8s的overlay网络也只是一个私有的虚拟网络。 如果我们想要从外部访问集群的服务,还需要做一些处理。 最简单的方式是nodeport,这是一种端口映射规则, 利用iptables建立转发规则,把集群节点的端口和容器端口绑定, 这样用户使用ip+端口的方式就可以访问集群服务了。 但是这种方式有一个缺点是需要维护大量的端口映射列表。用户使用起来也很麻烦,起码要记住每一个服务端口号是什么。 所以在这个前提下我们引入了7层路由的概念。 其实这个7层路由就是一个nignx, 只不过不同的是它是使用k8s中的hostnetwork模式启动的,它的特点是直接使用宿主机的网络,而不是使用集群的overlay网络,所以这个nignx容器是可以和集群外部网络通信的。 同时由于我们安装的weave或者flannel会在每一个集群节点上安装一个路由容器,它会帮助这个nginx容器做转发,所以它也能够访问集群网络。 所以这个使用hostnetwork的容器就变成了一个可以同时和集群网络以及外部网络通信的容器。 它也就变成了一个7层的路由。 这时候我们在公司内部的DNS上配置一个泛域名解析, 凡是以固定域名结尾的请求都解析到这个容器的ip上。通过nignx识别不同的service转发到不同的容器中。就实现了这个7层路由的功能。 比如,我们在nignx中创建的规则是解析一个域名的service部分,然后讲请求转发到对应的k8s的service上。 一个名字叫test01.n1.com的域名和test02.n1.com的service部分是test01和test02。 nignx会解析出来然后转发到不同的k8s service上。 这块我就不具体详细说明怎么实现的了,大家在网上搜一下ingress就可以了。 这是官方的7曾路由的解决方案,除了这个nginx外还要创建单独的ingress规则。 而我们使用的是自研的,省略了这些步骤。

监控:官方的解决方案是Heapster+grafana+InfluxDB。 可以在github上找到现成的镜像使用。我们一开始也是使用这种方式。后来我们慢慢演进到了使用filebeat来收集容器日志,灌入es中做更精细的日志监控的机制。最近我们已经迁移到另外的一套架构了,不过监控这块不是我负责的,我就不说了。

上面这些都是偏运维的东西,我就不再细讲下去了,

总之我们通过docker+k8s就有用了很强大的测试服务能力。 一般我们使用这种机制做一下3种事情。

  • 搭建测试环境(其中包括了CI,CD的构成搭建)
  • 搭建测试服务,比如jira,jenkins,testlink等
  • 搭建测试执行环境,比如UI自动化,接口自动化,线上线下一致性对比测试的执行环境。

这些是我们对于测试的基础PAAS平台的架构设计, 对于像机器学习这种复杂度如此之高的系统来说, 我们对这方面有着很高的要求。尤其是在后期我们的产品引入微服务的架构以后,它的复杂度到达了一种空前恐怖的程度。 所以这就需要我们的测试人员能够支撑住这样的基础设施。

CICD

一个高度工程化的团队离不开CICD,但是面对机器学习平台和微服务架构,我们的CICD又有了不一样的难度。 之前说过我们在微服务架构下每日大量的构建次数对CICD的性能和自动化流程的要求是很高的。这里我们使用的是k8s+bamboo+jenkins的架构。 当然了,所有的构建都是基于k8s来做的,使用k8s的分布式调度来做横向扩展,解决环境对性能的要求。 利用bamboo和jenkins的pipeline衔接整个CICD自动化流程。整个的流程如图:

  • 研发人员任何的提交代码行为都会触发bamboo上的plan,plan会拉取代码运行单元测试和集成测试,运行成功后会打出一个包作为产物上传到仓库中。如果运行失败,将中断流程,该模块自动报警。
  • 上传成功后触发bamboo的deployment任务去制作镜像并push到镜像仓库中。 这里有一点,如果这里有CD的需求,如果这个分支是联调分支,或者是稳定环境。就会触发CD任务,利用最新的镜像升级环境。
  • 同时jenkins上的pipeline每日或触发或定时, 在k8s集群上拉取最新的镜像,部署环境,执行UI自动化测试,API自动化测试。并汇总测试报告。

通过这样的cicd流程加上k8s强大的调度能力,我们支撑着每日千量级别的构建次数的同时,保证了最快的响应速度。 托了k8s的自动化运维能力,我们也能够用最少的人去维护这样一个庞大的系统。

机器学习平台下的自动化测试

我们说机器学习平台已经是一个产品了。 所以一个产品该有的东西都会有,我们测试人员关心的UI和API都会有。 该有的模块也都会有, 比如用户,权限,quota,项目,计划,监控,数据管理等等等。 所以除了机器学习特有的一些特性外,他跟我们普通的测试策略也比较像。 构建测试体系的时候,也符合金字塔原理。 但在这里我们的CICD流程中就有了
另一个挑战,那就是测试运行的速度。做机器学习的研发也好,测试也好。 都要面对一个无法避免的问题。 那就是任务运行很慢。 在大数据的情况下,一个模型要用几个小时甚至几天的情况也是不奇怪的。即便我们用一个很小的数据走通业务流程,一般也需要数分钟甚至数十分钟。 所以如果我们像以前一样,使用一个浏览器运行的话,在海量的case面前,我们的自动化测试会跑到天荒地老的。 所以在我们早期的时候就会使用多浏览器的方式来为我们的自动化测试加速。 我们使用的技术栈是java+testng+selenide。 testng的并发测试模式大家可以了解一下,调整并发的线程数我们就可以在一台机器上启动多个浏览器进行测试。

通过类似上面这样的配置,我们把测试分成了两个组并且并发的去测试他们。 这样在初期的时候还是可以接受的。 但是随着case慢慢的增长, 算法越来越多。 我们又出现了一个问题, 那就是单台机器已经无法支撑更多的浏览器了。我们说架构扩容有纵向扩展和横向扩展两种,纵向扩展是通过提高节点的硬件配置来提高性能,但是即便是再优秀的机器它的纵向扩展也是有极限的。 而横向扩展是通过增加节点的方式分担压力。那么目前我们面对的就是在纵向扩展遇到极限的时候,引入横向扩展的能力。 而testng本身是不支持跨越多台机器进行分布式执行的。 所以后来我们引入了selenium gird。 这是一种基于selenium的分布式UI自动化测试解决方案。 它通过启动一个grid hub作为master节点,负责接收测试请求以及分发任务。 再通过启动多个node 节点向hub 进行注册。 这样grid hub会接收到我们的测试请求,并适当的把测试任务均匀的发送到各个node上去。 在测试结束后汇总结果返回给testng。 它的架构是下面这个样子的。

通过这样的架构,我们的自动化测试就拥有了横向扩展的能力。 支持更多的浏览器帮助我们进行并发测试。当然大家可以看到在这个图里我们各个节点上都带有docker的标记, 为了方便管理和节省资源。 我们也使用容器管理的方式来建设自动化设施。 我们讲grid hub制作成镜像,可以运行在k8s中当做一个服务。 然后将不同的浏览器制作成不同的镜像。 比如把chrome和Firefox做成不同的node镜像,也通过k8s启动服务,注册到grid hub中。 当然大家可以发现我们在上面的这个图里在IE的镜像那里画一个叉,因为目前docker还是无法制作ie的镜像的,这涉及到docker的原理,docker使用的是宿主机的内核也就是linux系统的内核。 还是无法驱动windows的GUI的。 所以这部分没有办法,只能够通过在外面启动虚拟机的方式接入ie浏览器了。 不过其他两种浏览器还是可以很简单的使用的。 并且通过k8s的自动分片的功能,我们可以很方便的启动多个node来支持自动化测试。 k8s强大的调度能力也会在资源上做权衡,做到资源利用最大化,而不会把这些node都启动到一个节点上。 同时健康检查机器可以监控到每个node和hub的状态,如果出现异常会自动的找到可用的节点重新部署。 资源管理能力也可以限制和申请适当的资源,很方便实现的超卖功能。 这样通过k8s的自动化运维的能力,我们可以很好的管理我们的浏览器集群。 当然也许会有同学问都是使用docker启动的浏览器,那么我们怎么样能够实时的看到浏览器在发生什么呢?以前使用windows的时候我们是可以通过界面看到发生的所有事情,方便调试。 其实我们在这里也是可以做到的。 我们在一个node镜像中都安装vnc服务,并向外暴露端口。 我们在自己的本机上使用vnc viewer就可以看到浏览器上发生的一切。 

这时候我们的整体工作流程就是下面这个样子的了。

这里我们有两种做法, 一种是jenkins直接调用k8s的job,job执行自动化测试,执行完毕后jenkins拉取report。 另一种做法是直接利用的jenkins的slave机制,把slave直接用k8s中的容器的形式启动起来。 这样j8s中的一个容器就是jenkins的slave了。 如果为了简单,我们可以选择后者,利用k8s的自动化运维能力,提供稳定的服务。 在jenkins上的job上绑定测试的repo,jenkings触发测试的时候,jenkins的salve会到git上拉取代码,并执行自动化测试, 由于我们的slave就是启动在k8s里的容器,所以他们的访问是无障碍的,甚至完全不需要端口映射和7层路由。 slave的测试请求会发送到同样是启动在k8s中的grid hub上,gird hub会把任务发送到同样在k8s中作为容器启动的node上面去执行。 之后将测试结果一层层的返回,汇总到jenkins的job中。 这里使用k8s的优势就是完全的自动化运维管理,出现任何异常k8s都会帮我们处理,这比搭建多个虚拟机来管理要方便便捷很多。 通过这样的架构,在我们的公司的日常测试中使用40个浏览器并发测试的方式,可以在30分钟内结束战斗。 极大的提升了测试效率。这里在安利一波selenide,这是一个github上面的开源项目, 基于selenium做了一层封装。 可以很好的支持我们的这种架构。我们只需要两行代码,就可以对接这种分布式架构了。 如下:

上面的第一个配置指定使用的浏览器类型,第二个配置指定grid hub的地址。 这样就可以让我们的代码测试代码迁移到这个分布式架构上了。 其他的任何代码都不需要改变。 之后的工作只需要在testng的配置文件中指定并发数量就可以了。

 

猜你喜欢

转载自blog.csdn.net/hualusiyu/article/details/85212411
今日推荐