导读:近两年,DevOps的概念一直非常火爆,但是在具体的落地上,CI/CD的实施一直缺乏非常好的案例。本文根据蓝鲸容器服务负责人陈睿所做的《蓝鲸DevOps方案在游戏中的实现》主题演讲内容整理而成,希望能给大家以借鉴与启发。
陈睿:大家下午好,非常荣幸在这里给大家分享。自我介绍一下,我来自腾讯互动娱乐事业群蓝鲸团队,负责后台的设计和开发工作。这次给大家带来的分享主题是《蓝鲸在DevOps方案在游戏中的实践》,我们的方案更多是在游戏中积累和成长起来的。方案范围很广,包括研发、运营等等一系列环节。今天主要介绍下蓝鲸的DevOps方案,并从技术角度看看游戏接入DevOps方案里面的技术难点,以及我们为游戏做了哪些工作。
首先,看一下蓝鲸的整体DevOps方案。腾讯游戏是国内最大的游戏提供商,端游、手游,每款游戏开发架构都不相同,百花齐放。那么,互娱内部如何对多种游戏的DevOps做全量的支撑呢?这是一个非常难的问题,最开始,腾讯游戏运营团队多达几百人,有了蓝鲸之后,就变成持续服务的体系,现在只需要很少的运营人数,就能搞定四五百款游戏。
那么游戏太多,会有什么问题呢?首先整个链条非常长,每个游戏从开发、交付、部署到运营,一系列的链条有很多重复工作,被不同的人员执行,就会导致效率低下。另外游戏本身架构非常复杂,服务多达上百个,英雄联盟这个游戏后台所有服务加起来上百个,每个服务从开发到部署都是非常长的流程。用人工完成这个事情,流程复杂之外,质量也无法保证,也会存在安全风险。
而这些问题都可以在蓝鲸当中得到解决,今天主要从技术角度看一下,我们所面临的DevOps场景有什么样的难点。从技术来看,架构复杂和多样性是主要问题。
上图是蓝鲸整体的架构,蓝鲸的整体结构分两大部分,右边部分为运营PaaS,最下面是原子平台,比如说有蓝鲸DevOps平台,是提供代码编译构建扫描,一直到入库。比如还有容器平台,如何在线上拉起服务,通过什么样的策略或者动作接口、配置,将你的测试环境、生长环境无缝打通。原子平台之上,我们称之为集成平台,它封装了原子平台的能力,除此之外还有登录、权限、流程引擎等,还有腾讯内部,蓝鲸体系外的东西全部穿连起来,会将所有能力分装成接口、服务。最上层是运营SaaS,几百款游戏,每一款游戏都有不一样的个性化服务,我们在SaaS体现了个性化,所有SaaS赋予了以下所有能力。我们用左边的业务PaaS来支撑业务,比如说英雄联盟等游戏都在业务PaaS上。
有了这样的系统之后,游戏如何在这套体系下进行整个生命周期过程呢?我们将一个游戏从代码到运营过程分成三个阶段:CICDCO,传统主要是CICD阶段,CO是指持续运营,游戏上线以后我们需要很多监控、长线的运营策略来保持日活。上图是一个游戏接入蓝鲸之后的链条,可以看到从原材料到版本仓库,有需求和缺陷的管理、代码库、图片资源、音视频资源,到持续集成、编译等等,最后是持续部署,可以通过测试环境、生产环境或者直接将客户端发布给用户,这是整个蓝鲸提供的服务。
从游戏开发的角度来讲,背后需要哪些功能架构才能实现这样的一体化的流程呢?我们简单过一下,首先是代码,大家都是开发者,然后是GIT来管理,经过像jenkins串起来,编译之后换关联需求缺陷管理系统,上面有代码扫描,还有编译加速,中间穿插了一些测试,还有一些系统测试。之后会进入仓库云石,后面怎么办呢?我们会有相应整套CD的环境,CO不在今天演讲范围,所以没有画出来。CD主要有K8S相关的容器方案,还有标准运维、gsekit等,可以运用在预发布环境、生产环境,整个链条是全自动无缝的,游戏代码写完就可以出去了,第二天早上起来发现测试环境变化了,测试报告出来了,这个就称为游戏CICDCO的一体化。
今天在整个的CICO部分就不持续开展了,主要聚焦在游戏的CD,怎么样让不同的异构的业务顺利接入到持续部署的体系里面呢?使用的方案其实对容器而言,主要是K8S以及跟它类似的技术。
对游戏大概分这么几类,一个是完全微服务化的,这种很少见,凤毛麟角。能够达到称之为微服务化的游戏是个位数,更大部分处在部分服务化,一般是存在有状态的模块,比如掉线之后重新登录还回到以前的场景,意味着它在某一个服务上保存用户的状态,这个状态对于容器而言是有挑战的事情。
还有一些是只能做一部分的容器化,这部分容器和非容器需要协同工作,同一个模块容器在工作过程中,既有容器又有进程的,这两部分的服务需要协同工作或者需要通信。因为游戏内部对各种各样性能的追求,往往会使用系统底层的技术,特别像信号、共享内存等,这些当前的容器的支持是有限的。
最后是传统架构,早期的时候很多功能,放再一个大的进程里面,一个进程能干所有的活,见到最夸张的一个游戏是什么样的?美国一个公司,他们有一款游戏,那个进程需要吃掉60个核100G内存,大家有没有见过这么大的?游戏领域服务千奇百怪,什么样的结构,什么样的模型都有。
我们蓝鲸提供的这样一套体系,我们认为完全微服务化和部分服务化我们都是可以很容易的做到的,我们做了一些很有特色的事情,后面会提到。对于传统的单体架构我们是建议改造的,后面有部分的环节会提到怎么针对游戏特殊的依赖或者特性进行改造。
第二环节主要聚焦在持续部署这个环节,特别是对一些特殊业务。为了便于各位理解蓝鲸CD持续部署这部分怎么工作的,现在看一下完全微服务化的业务。上图并非游戏场景,而是内部的一个平台,它是很典型的微服务的架构,上层业务、网关、还有场景,接口。我们在蓝鲸下对应这么多文件,比如说各种各样的文件定义好之后帮你生成域名,可以相互通讯,对应下面的数据库,腾讯的数据库是分开管理,有一个专门的团队负责,数据库是独立于部署集群之外的。
因为这套服务需要被外部访问,它是虚拟网络空间,它一定需要一个类似于流量代理的机制。比如说很多公司都有做网关这个事情,在蓝鲸内部有一些BCSLB的东西,是四层和七层带,我们和公司内部的系统打通,主要使用的技术是macvln或者sriov。另外是基于K8S的ingress+host等等,这是我们提供的。
除了流量代理以外,还有数据库在外面的,IP可能是公司真实IP,虚拟的环境如何访问呢?需要出流量代理,第一我们有一个proxy,它代替虚拟服务容器来访问外部的DB。这钟方式有什么好处呢?因为容器会漂移,实际上我们访问DB的时候有一个真实的IP做权限管理,如果直接使用容器,比如说用容器的IP,这时候肯定是不行的,很多时候是用proxy方案。整个微服务的业务,上层业务可以实现不同的服务,通过流量代理去代理外部访问流量。
下面我们看一下对于有状态的业务,刚才举了例子说,掉线之后回到之前状态,这个有状态的业务,它是怎么进入DevOps的?分成三类,第一部分是实例之间需要相互发现,例如像etcd,每一个节点需要另外一个节点的地址,这个在早期比较难以解决。第二是服务间存在启动顺利的依赖,A必须在B之前启动,否则会启动失败。第三个是有上下文状态,也就是说一个服务可能保留了一些用户状态,在服务重启的时候需要重新加载这个状态,比如玩游戏,本来打王者荣耀排了一把战斗,服务器做运算,这时候服务器挂了,重新登上去不能重新排一遍,肯定还是接着上次打,所以需要把状态存在服务器里。很多游戏为了准求性能,通常是使用共享内存来保存状态,服务器挂了重起时重新加载共享内存。
看一下第三种情况是怎么接入的。刚才说有状态的,在K8S中大家都知道statefulset,每一个POD都有一个独立的名字,对于zk那种每个pod都会配一个独立的,比如说pod的域名,服务配置里面以前配置IP的地方改成域名,K8S会自动的将域名解析为IP,基本上能满足像这样的服务需求。还有需要持续存储的,通过PVC,比如现在内部是共享存储,这个也是比较简单的方法。另外启动依赖需要改造,怎么改造?首先让模块间的相互依赖变为间接依赖,先发给消息列队缓存,B从消息列队获取。也可以不用消息列队,服务做好等待和同传的机制,这部分比较考验开发者的能力。
还有一部分业务确实改造不了,游戏要保证稳定,那怎么办呢?像K8S的operator,它是类似于K8S之上的第三方调度,它应用K8S调度机制做一个个性化的分析,比如说首先看A和B两个模块,当启动B的时候,看B启动没有,就调K8S看A启动没有,启动以后看是否是需要的版本、个数是否为需要的个数,这些条件都满足之后启动,然后把这个逻辑固化成调度器,通过K8S这种模式把它挂进去。就是对这种服务写一个个性化的调度器完成启动,而不是单纯K8S本身的调度。
最后是有强通信上下文,在这里面有另外一种模式,很多时候它并不是给一个服务器用,往往很多服务器同时访问,那里有一个它说接入的服务,玩家进来的时候第一个连的服务器就是那个。后面是对战服务器,他们之间需要保存用户信息的,这个用户在进行什么样的对决,是进行排位还是匹配。但是一个不一定应对一个,可能是多个,所以要进行改造,从多对多变成一对一的关系。我们是通过这样的方式改造最后让游戏接入进来。
第三部分可能讲的跟前面IPC依赖,有一些内容相关是共享内存,还有信号。共享内存说过了,信号是什么东西?游戏来说,做一个活动如今天更新一个皮肤,可能需要有一些配置,针对这个配置需要动态去刷容器,怎么刷进去?把配置丢进来也不能自动,它有活动开启时间,只能那时候开启,发一个信号,让游戏能够触发。
另外是服务版本更新了,版本是老的,但是不能随便退了,还有玩家在玩,只有所有玩家断开连接才能做,谁来进行这个动作?只能做一个预知的动作,先告诉这个服务器,你现在处于这种状态,服务器上的玩家没有的时候就要触发预知的功能。举一个例子,版本更新的问题,容器里面有一个服务进程,分两部分:有一个主进程,有一个SO,SO就有一些游戏。它现在需要在游戏运行的时候动态更新SO。前面讲了持续进程、持续交互,在同时产生两个问题时,一个是需要更新的主文件和SO文件,同时放在文件库里面,同时挂一个主机目录,我们会放需要更新的那部分东西,每一个服务器都会挂上,都会有对应的通道,将这样的文件从仓库里面派发到直接的目录下。会有一个监控进程,监控这个目录,一旦发现这个目录有变化,就通知说这个文件已经到位了。
我们在整个蓝鲸的容器平台打通了这样的通道,用户可以通过指定的API发送信号,某一个RS等等所有的容器里面。它文件就位之后用户就掉一个信号,这个信号发给容器里的进程,就触发一个事件,告诉你准备更新,服务器收到信号开始判断,当我的玩家为零的时候就可以把那个文件读进来,将内部的SO重新加载一下,加载之后就会给新的状态、给新的玩家,这个就完成了整个游戏。那么,如何通过IPC的机制进行更新?传统的方式,通过下载配置文件,滚动更新。总之把老的干掉,但是在游戏里面这种方式是很难接受的。
最后一部分,这也是腾讯游戏的一大特色,很多游戏如英雄联盟,在腾讯内部有几万台服务器,整个中国大区,但是有接近两万台是windows,它是非常典型需要容器和非容器协同工作的业务。蓝鲸方案怎么支持这类业务?大概分为两方面,首先是只需要容器和容器协同工作,比如说可能是同样一个服务,有一部分是以容器的方式部署,有一部分是以传统的方式部署,这两部分需要作为同一方式工作,还有A、B、C模式,只有A是新的,其他两个是传统的。此外,我们不仅需要让他们协同工作,还要让他们编排,K8S是提供容器编排的工作,怎么让他们同时具有编排能力?我们在这里也进行了尝试。
先看一下协同工作,跨模块的,分A模块和B模块,A模块是传统方式部署,通过主机,B模块是在容器环境下跑的,用的是underlay。在集群概念,我们是不分传统或者是容器的,都是代表组,只不过有的集群不能跑容器环境,不属于开发层,有的集群是跑传统的,我们集群这个层面做了什么呢?我们为每个集群提供独立的DNS服务,还有调度器的服务。这个调度器如果是容器的话,DNS会破坏P2P的ABPI,获取IPC里的容器。也就是说当A模块访问B模块,只需要调B模块注册的域名。访问这个域名之后就由集群自动解析为容器里面的域名,反过来也是一样。
我们在传统的集群里面也会提供DNS,这个DNS跟容器里面的DNS又是挂在顶层的DNS上面,它有一个顶层的DNS统一管理所有的DNS,它只需要通过两个DNS解析就可以找到这个实例。
还有一种模式是不需要相互访问的,我们肯定有不同的DNS,通过流量代理等访问容器的时候,通过BCS或者K8S或者代理,将流量打到容器里面,也可以和产生容器进行工作,只不过是单向的,刚才是双向的。
还有另外一种,同一个模块既有主机方式,又有容器的方式。方式一样还是通过DNS,只不过这个DNS一样会用到跨集群的DNS。我们报告的是顶层的DNS,用户访问通过顶层的DNS访问,然后赋予复杂的程序将模式打入。在整个容器服务里面,我们扩容了K8S的概念,之前只能放一些容器的服务,甚至将服务的对象扩容到了进程。对一组进程或者虚拟机定义Serverless一样会有解析的域名,同模块的时候我们使用DNS来实现这个方案。
最后是混合编排,传统的进程编排有一个弊端,在编排生效之前必须有一个动作绑定这个进程应该跑在哪台机器上,以什么样的方式跑在哪台机器上,这个动作非常消耗人力。我们希望它像容器一样有运行时的编排,事先并不需要知道它在哪,只要有一个资源池就可以了。我需要通过统一的编排系统,将容器和进程的资源做一个统一的分配,这里面有一些策略,比如说某些服务只能在CPU里面,这也是我们内部做的事情,我们能够将容器和传统的进程,或者虚拟机单位做一个混合的调度。
以上就是我今天分享的主要内容,整个蓝鲸的DevOps链条非常长,刚才大家看到了,从CICD到CO,今天内容主要聚焦在CD的过程,如果大家需要了解更多,可以关注一下蓝鲸的公众号(Tencent_lanjing)了解相关的内容,谢谢大家!
在ServerlessCloudNative公众号回复"云原生上海" 获取演讲ppt