欢迎来到Tungsten Fabric用户案例系列文章,一起发现TF的更多应用场景。“揭秘LOL”系列的主人公是Tungsten Fabric用户Riot Games游戏公司,作为LOL《英雄联盟》的开发和运营商,Riot Games面临全球范围复杂部署的挑战,让我们一起揭秘LOL背后的“英雄们”,看他们是如何运行在线服务的吧。
作者:Nicolas Tittley和Ala Shiban(文章来源:Riot Games)译者:TF编译组

揭秘LOL背后的IT基础架构丨产品而非服务_第1张图片
这个长系列的文章,探讨并记录了Riot Games如何开发、部署和运营后端基础架构的历程。我们是Riot 开发体验团队的软件架构师兼产品经理Nicolas Tittley和Ala Shiban。我们团队负责帮助Riot开发人员在玩家所处的任何地方构建、部署和运营游戏,同时专注于云无感(cloud-agnostic)平台,这些平台使游戏的发行和运营变得更加轻松。

在过去的两年前的一篇文章中,Maxfield Stewart介绍了有关开发生态系统,以及当时使用的许多工具。这里我们将更新一些最新的内容,包括面临的新挑战,如何解决问题,以及我们从中学到的东西。

快速回顾

我们强烈建议您回过头阅读以前的文章,但是如果您想直接阅读本文,这里有一个超级精简版本来帮您赶上进度。

Riot使用裸金属和云基础架构的组合来在全球范围内运行后端系统。这些后端系统被分散到不同的地理位置,基于完全不同的部署,运行着允许玩家与LOL《英雄联盟》互动的整套服务。像大多数游戏的后端系统一样,LOL后端开始时作为一个整体,由专门的运营团队来负责运营。随着时间的推移,Riot逐渐拥抱了DevOps实践和基于微服务的体系架构。本系列的第一篇文章做了详细介绍,为了帮助我们的开发人员更快地服务玩家,Riot大量依赖于Docker容器这种打包服务,并开始在集群调度程序中运行它们。直到最近一篇文章,讨论了为实现此目的而使用的许多工具。

运行效果如何?

非常牛,但,痛并快乐着。

在上篇文章发表之时(编者按:发表时间为2017年12月),我们运营着5000多个生产容器。这个数字并没有停止增长,今天,仅在Riot自主运营的区域(Riot-operated regions),我们就运行了14,500多个容器。Riot的开发人员喜欢为玩家创造新事物,当他们越容易编写、部署和运营服务,他们就越能创造出令人兴奋的新体验。

开发团队以真正的DevOps方式,拥有并负责他们的服务。他们创建了工作流来部署、监视和运营那些服务,当他们找不到所需的东西时,就干脆自己发明再造。对于开发人员来说,这是一个非常自由的时期,他们很少遇到无法自行解决的问题。

但慢慢地,我们开始注意到一些令人担忧的趋势。每个月的QA和负载测试环境变得越来越不稳定。我们不得不花费更多的时间,来查找错误的配置或过时的依赖关系。这些孤立的事件并不是关键,但总的来说,它们耗费了团队很多时间和精力——我们更愿意将其花费在创造玩家价值上。

更糟糕的是,在非Riot自主运营的分片区域(non-Riot shards),不仅开始出现类似的困难,而且还爆出一系列其它问题。合作伙伴们必须与越来越多的开发人员进行对接,并采用越来越多的微服务,每种微服务都有不同的方式,彼此各不一样。现在,运营人员必须比以往更加努力,以创造出有效且稳定的分片区域。在这些非Riot自主运营的分片区域,问题发生率要高得多,直接原因就是微服务的实时版本不兼容,或者其它类似的跨界问题。

Riot的DevOps现状

在讨论如何解决打包、部署和运营之前,让我们花一点时间来探讨Riot的运行环境。这些都不是Riot所独有的,但所有这些细节的重叠,都说明了我们是如何组织起来,以便为所有玩家提供价值的。

开发者模式

Riot的工程师们喜欢自己建造东西!为了帮助他们做到这一点,我们采用了强大的DevOps思维方式。团队建立并拥有了自己的后端服务,确保对其提供支持,并在服务表现不如预期时进行分流。总的来说,Riot工程师很高兴能够实现快速迭代,也很乐意对自己的实时服务负责。这是一个非常标准的DevOps设置,Riot并没有在任何方面逆势而上。

有状态分片模式

由于历史原因、规模性问题,以及法律方面你的因素,Riot产品的后端系统按照分片的方式进行组织。其中,生产分片通常在地理位置上靠近目标受众。这样做有许多好处,包括改进的延迟问题,更好的匹配性,有限的故障域,以及清晰的非高峰时间窗口(可在其中执行维护操作)。当然,我们还在内部和外部运行着许多开发和QA分片,例如《英雄联盟》公开测试服(PBE)。
https://technology.riotgames.com/sites/default/files/engineeringesports_4.gif

运营模式

这是事情变得更加复杂的地方。尽管Riot是开发者,但出于合规性和专有技术的原因,我们与一些本地运营商合作以提供一些服务分片。实际上,这意味着Riot的开发人员必须打包分片的每个组件,将其交付给运营人员,并指导他们如何部署、配置和运营所有分片。Riot的开发人员不会自己去操作、访问甚至查看这些分片。(编者按:文中的分片逻辑可以理解成分块分区域的定义)

迭代解决方案

尝试1-新的联盟部署工具

我们第一次尝试改善情况,就采取了全新的方法,尝试利用开源组件和最少Riot定制功能,来推进Riot的部署和运营工作。尽管这项工作成功地部署了完整的《英雄联盟》分片,但工具的设计方式并没有达到开发人员和运营人员的期望。团队表达了对工具的不满——这种工具被证明对运营来说太难采用,对开发者来说太受约束。

因此,在第一个分片部署后,我们就做出了痛苦的决定——让这些工具退役。这看起来好像很激进,但由于所有团队仍然拥有自己维护的部署系统并且尚未完全过渡,因此我们能够快速淘汰新的工具。

尝试2-更多流程

由于第一次尝试并不如预期的成功,我们转向传统,通过添加流程来达到要求。广泛的沟通,明确的发布日期,文档化的流程,变更管理会议和仪式,以及永远存在的电子表格,在某种程度上取得了一点点进展,但始终感觉不佳。团队喜欢他们的自由DevOps,巨大的变化量和变化速度,都使他们的工作更加繁重。尽管合作伙伴的情况有所改善,但我们仍未达到所期望的运营水准。

尝试3-元数据

我们决定尝试另一种方法。之前我们一直将开发人员作为工具的主要受众,现在则开始研究针对于合作伙伴的运营人员,部署/运营系统将如何工作。我们精心设计了一种工具,允许开发人员向其Docker容器的打包微服务添加标准化元数据,例如所需的配置和扩展特性。这带来了进步,运营人员可以采用更加标准化的方式来理解所需的服务配置和部署特性,并且在日常运营中减少对开发人员的依赖。

此时,本地和合作伙伴运营站点的故障率、事件率和额外的停机时间都有所改善,但我们仍然频繁遇到部署和运营故障,这些故障本来都是可以避免的。

尝试4-Riot的应用程序和环境模式

我们最终采用了一种新方法,将关注点从个人服务转移到了整个产品。我们创建了一个高级别的声明性规范,以及可对其执行操作的工具集,让规范和工具变得与众不同。在详细介绍之前,我们先来看一下前三次的尝试中出了什么问题。

反思错误之处

部署和运营的是产品,而非服务

尽管拥抱DevOps和微服务给我们带来了许多好处,但它创建了一个危险的反馈环路。开发团队创建微服务,对其进行部署、运营,并对其性能负责。这意味着他们为自己优化了日志、度量标准和流程,并且通常很少考虑其服务能否为其他人所理解,包括没有开发背景甚至工程能力的人。

随着开发人员创建出越来越多的微服务,运营整体产品变得非常困难,并导致越来越多的失败。最重要的是,Riot的流动团队结构,使一些微服务的所有权变得不清晰,很难在分流时搞清楚应该与谁联系,从而导致出现很多属性错误的页面。越来越多的异构微服务、部署流程和组织变更,使得合作伙伴地区的运营团队不知所措。

搞清楚“为什么”

我们检查了Riot运营区域和非Riot运营区域的故障,并将故障频率的差异提炼为一项关键的观察结果:

允许不连续的变更流进入分布式系统最终将导致可预防的事件。

当团队希望跨边界进行协调时,就会开始发生故障,因为依赖关系需要将发布与多个更改捆绑在一起。团队要么使用人工流程来创建发布周期,通过项目管理仪式协调发布,要么临时发布较小规模的发布更改,导致团队在找出兼容版本的过程中陷入混乱。

两者各有其优缺点,但是在大型组织中往往会崩溃。想象一下,数十个团队需要以协调的方式,连续交付代表共享产品的数百个微服务,并且允许这些微服务使用不同的开发实践。更糟的是,对于合作伙伴来说,尝试应用这些流程非常困难,他们的操作人员缺乏关于各个部分如何组合起来的上下文。

新解决方案:Riot的应用程序和环境模型

鉴于先前的尝试未能产生预期的结果,我们决定通过创建一个自用固有对的(opinionated)声明性规范来消除部分状态操纵,该声明性规范可以捕获整个分布式产品——环境。环境包含完全指定、部署、配置、运行和运营一组分布式微服务所需的所有声明性元数据,这些微服务共同代表一种产品,并且是完整且不变的版本。我们之所以选择“环境”这个名字,是因为它是Riot 最不会过度使用的一个词。命名实在是一件难事。

随着游戏《符文之地传奇》LOR的发布,我们证明了可以描述整个的微服务游戏后端(包括游戏服务器),并使其在Riot自主运营地区以及全球合作伙伴的数据中心中,作为产品进行部署、运行和运营。我们还展示了实现这一目标的能力,同时改善了已经广受喜爱的DevOps方法的优势。

对什么进行规定描述(OPINIONATED ON WHAT)

该规范描述了服务捆绑包或环境之间的层次关系。

揭秘LOL背后的IT基础架构丨产品而非服务_第2张图片

捆绑到环境规范中的应用程序规范

声明性与高等级

声明性规范的好处之一是它易于操作。对于合作伙伴的运营人员,他们的其中一个困难,就是无法理解、调整和潜在地自动化整个游戏后端的部署方式。规范的声明性性质,意味着它不需要工程师具有脚本或编程专业知识,就可以对规范中的大多数内容进行更改。

保持规范的高等级,有助于将游戏后端的定义与基础实现脱钩。这使我们能够在对游戏工作室影响最小的情况下,从名为Admiral的内部编排器/调度程序,迁移到基于Mesos的调度程序,以及考虑迁移到Kubernetes。它还使我们的合作伙伴运营人员可以在需要时交换其基础架构组件。例如,它允许运营人员可以使用不同的指标聚合系统,而不需要更改微服务工具。

不可变与版本化

我们发现,要在快速发展的DevOps世界中有效部署和运营,使用共享语言来引用服务和环境至关重要。版本控制服务和环境及其关联的元数据,使我们能够确保所有位置都部署了正确的版本。它使我们的合作伙伴运营人员可以确定地知道正在运行哪个版本,并且回传给我们。此外,当应用于整个环境时,它提供了一组众所周知的服务,可以对其进行质量检查并标记为“好”。这种捆绑消除了在向合作伙伴传达新版本时遗失依赖项的任何可能性。

使这些版本不可变,可以确保我们维持这种通用语言。当相同版本的服务被部署在两个不同的分片中,我们现在也可以确定它们是完全相同的。

专注于运营

鉴于我们的目标是提高合作伙伴运营人员服务玩家的水平,我们很快意识到,部署软件只是第一步。了解如何对实时系统进行分类、运营和维护,是同样重要的事情。

从历史上看,我们非常依赖于运行手册。手册由开发人员维护,并取得了不同程度的成功,他们记录了从必需的配置值到高等级体系架构的所有内容。为了使合作伙伴运营人员具备配置和操作每种服务所需的全部知识,我们决定将这些运行手册中包含的尽可能多的信息带到服务规范的最前面。这大大减少了合作伙伴地区投入新服务的时间,并确保他们在微服务更新时被告知所有重要变化。

如今,合作伙伴运营人员可以使用该规范来了解有关操作元数据的信息,包括所需/可选配置、扩展特性、维护操作,重要指标/警报定义、部署策略,服务间依存关系,以及越来越多的其它有用信息。

处理分片差异

当然,分片不是彼此完全相同的副本。尽管我们希望使它们尽可能地接近,但总有一些配置必须有所不同。数据库密码、支持的语言、扩展参数,以及特定的调整参数必须随每个分片而变化。为了支持该模式,我们的工具使用分层的覆盖系统部署环境规范,使运营人员可以专门化特定的部署,同时仍然知道它们都源自已知的良好版本。让我们看看它是如何工作的!

应用案例

一个简单的游戏后端可以包括两个环境,一个用于游戏服务器,另一个用于元游戏服务(排行榜,匹配系统等)。元游戏环境由多种服务组成:排行榜、匹配系统、比赛历史等等。每个服务都包含一个或多个Docker映像,从概念上讲,它们等效于Kubernetes容器。对于所有环境,相同的层次结构都是正确的,并且从哲学上讲,每一个环境都毫无例外地封装了在任何受支持的基础架构或云上部署、运行和运营的游戏后端所需的一切,及其所有的依赖项。

该规范还包括运行和运营整个环境所需的所有元数据。不断增长的集合包括配置、机密、指标、警报、文档、部署及rollout策略、入站网络限制,以及存储、数据库和缓存要求。

下面我们有一个示例,演示在两个区域中进行两个假设的游戏分片部署。您可以看到它们都由元游戏环境和游戏服务器环境组成。在欧洲分片中的游戏服务器产品环境,落后于美国分片中的同类游戏环境。这为游戏和运营团队提供了描述和比较不同游戏分片部署的通用语言。每个环境中不断增加的服务数量可以保持简单性,从而可以可靠地部署数十个分片。
揭秘LOL背后的IT基础架构丨产品而非服务_第3张图片
游戏分片部署示例

我们的下一步:延迟感知调度

我们希望能够描述服务之间的预期和可接受的延迟,并使工具针对基础区域和较低级别的PaaS服务进行优化,使其能够满足这些需求。这将导致某些服务位于同一个机架、主机或云区域中,而不是允许它们分布在其它服务中。

由于游戏服务器和支持服务的性能特点,这件事与我们高度相关。Riot已经是一家多云公司,有我们自己的数据中心,也有AWS以及合作伙伴的云,但是我们依靠静态设计的拓扑。纸牌游戏和射击游戏具有不同的配置文件,不必针对一、两种情况的优化进行手工拓扑,从而节省了工程师们的时间,使他们可以专注在游戏上面。

最后的话

我们在运行游戏过程中面临着稳定性下降的问题,主要是来自合作伙伴经营的游戏分片。工具开发团队捆绑了开源部署工具,并将元数据添加到了容器中,而游戏团队则实施了集中发布流程。这些方法可以解决症状,但不能解决导致问题的根本原因,这意味着我们未能达到目标水准。

我们最终采用的解决方案引入了一个新规范,该规范捕获了整个游戏后端的所有拓扑、层次结构和元数据及其所有依赖项。这种方法之所以有效,是因为它带来了绑定容器的一致的版本发布,它们之间交互方式的依赖关系,以及启动和操作整个游戏所需的所有支持元数据。而不可变性带来了确定性的部署和可预测的操作。

作为一个平台团队,我们的目标是挑选能够产生良性循环的系统和构建模块,在这样的良性循环中,功能开发工作自然会带来易于操作的产品。将DevOps模式的敏捷性与易于操作的整个产品相结合是长期组织敏捷性的关键。我们的环境捆绑方法直接改善了运营指标,更重要的是提高了玩家体验的质量。我们很高兴看到业界其他人士如何解决类似的问题。我们已经看到了来自CNCF(云原生计算基金会)和大型云供应商(例如Microsoft开放应用程序模式规范)的想法和项目。希望其中一些项目能够取代我们自己制定的规范,并朝着全行业解决方案迈进。

在以后的文章中,我们还将更详细地探讨Riot规范,介绍示例,并讨论设计中的权衡以及Riot特定的快捷方式。

谢谢阅读!如果您有任何疑问,非常欢迎与我们取得联系。


更多“揭秘LOL”系列文章
揭秘LOL背后的IT基础架构丨踏上部署多样性的征程
揭秘LOL背后的IT基础设施丨关键角色“调度”
揭秘LOL背后的IT基础架构丨SDN解锁新基础架构
揭秘LOL背后的IT基础架构丨基础设施即代码
揭秘LOL背后的IT基础架构丨微服务生态系统
揭秘LOL背后的IT基础架构丨开发者“打野”工具能做什么?


揭秘LOL背后的IT基础架构丨产品而非服务_第4张图片

揭秘LOL背后的IT基础架构丨产品而非服务_第5张图片