欢迎来到Tungsten Fabric用户案例系列文章,一起发现TF的更多应用场景。“揭秘LOL”系列的主人公是Tungsten Fabric用户Riot Games游戏公司,作为LOL《英雄联盟》的开发和运营商,Riot Games面临全球范围复杂部署的挑战,让我们一起揭秘LOL背后的“英雄们”,看他们是如何运行在线服务的吧。
作者:Maxfield Stewart(文章来源:Riot Games)译者:TF编译组
欢迎阅读本系列文章,我叫Maxfield Stewart。本文将深入探讨微服务成为Riot容器平台上实时运行的应用程序的五个关键需求。Riot的每项微服务都必须:
要满足所有这些要求,就需要支持其他服务和工具。有些工具是为“开发人员”构建的,而有些则是为“运营人员”构建的。在Riot,这些职位不只是职务,而是工程师可以在其中进行切换的角色。一个工程师可能会在今天开发一项服务,然后在明天部署它,以推出新的功能。我将深入研究这五个需求以及支持它们的工具,并概述我们的方法。
如果你准备研究我们的微服务秘密“调味料”的制作方法,请继续阅读!
Riot在全球范围内拥有庞大的部署范围。我们将服务部署到全球数十个数据中心,每个数据中心都可以托管多个区域。我们希望“一次构建,随地迁移”,这意味着微服务必须具有高度的便携性。
为了使我们的服务具有便携性,首先要决定对其进行容器化。我们已经详细讨论了容器及其无数的用例,以及诸如Docker之类的有价值的技术,但是将东西放到容器中并不能解决所有的问题。我们仍然必须将这些打包的容器交付到全球的数据中心。
我们通过利用JFrog的Artifactory的强大功能,托管我们自己的全局复制的Docker注册库来实现此目标。下图显示了构建的容器映像的生产生命周期:
我们之前在技术博客中讨论过在Riot构建软件。我们每年的构建次数超过125万。其中一部分,是作为旨在用于生产的Docker映像而构建的微服务。一旦从我们的持续交付过程中发出,它们便被停在内部Docker注册库中。而一旦准备好进行生产,便将其标记为“已升级”并移至复制存储库,复制存储库立即开始将Docker映像散播到我们的数据中心。
由于这些Docker映像基于可重用的层构建,因此它们可以在几分钟内在世界范围内复制。它们本身很小,变化的范围就像一个bit。你可以从以下链接阅读更多:
https://docs.docker.com/storage/storagedriver/
目前,在Riot生产环境中运行的容器超过10,000个。任何一项微服务都可能包含多个容器。所有这些运行中的容器,都非常像刚出生的婴儿应用程序,沐浴在它们生产环境的光辉中。它们需要根据这些信息,快速确定自己的位置,以及学习如何进行自我配置。
在传统的部署系统中,你可能会在应用程序中包含配置有效负载,并使用诸如Chef或Puppet之类的工具,来随着时间的推移保持该配置的收敛性。但是要保持便携性,我们的应用程序必须做到可部署,并且具备在运行时在任何环境中可运行的能力,一切都有条不紊。
这就是“配置即服务”的用武之地。我们想使用自己的应用程序作用域方案,并且在分析了许多开源解决方案之后,我们意识到,编写自己的配置服务给了我们最大的灵活性。
事实证明,解决命名方案相对简单。当我们的应用程序启动时,它们知道它们是谁,以及它们在哪里,因为调度程序会通过简单的环境变量注入通知它们。
让我们看一下我们的作用域方案,它分为两个宏部分:
环境作用域分为三部分,应用程序作用域分为两部分,如下所示:
我将使用一个绰号为“MyApp”的简单小工具进行演示。MyApp已部署为可用于第二个Vegas数据中心内的所有Riot服务。它仅由服务器组件组成,可能看起来像这样:
称为“myappprod1”的环境组件很重要。我可能还会将应用程序的QA版本(myappqa1)或开发版本(myappdev1)部署到同一集群中。我甚至还可能运行两个生产版本。作用域方案使我们能够在集群内部创建环境。
为了使它能够用作配置查找方案,我们必须使用作用域将数据推送到配置服务。例如,如果我想将适用于已部署到“globalriot.las2.myappprod1”的所有应用程序进行数据推送,则可以将配置数据推送到:
当“Myapp”运转起来并标识自己时,它与作用域的前三个部分匹配,并获取通配符的配置数据。如果我想将特定配置应用于特定实例,则可以将该数据推送至:
任何以这个完整作用域来标识自己的东西,都将获取数据。数据本身实际上只是一组属性,仅仅是简单的“键/值对”就可以了。这是针对目标作用域的一些数据的示例:
http.ProxyType=http
http.ListenPort=80
http.DomainNames=myapp.somedomain.io
配置数据也可以实时更新。想象一个速率限制属性是怎样的:
ratelimit.txsec=1000
我可以推送新的ratelimit.txsec值,应用程序将在实时检查配置后进行动态调整。这为我们提供了影响live服务的强大功能。曾经有一段时间,修复《英雄联盟》冠军数据,需要重新部署游戏。现在,我们可以将这些数据推送到“服务即配置”中,我们的游戏服务器会在游戏开始时将其提取,并自动应用调整来解决均衡问题、启用/禁用冠军问题等等,所有这些都不会令玩家失望。
如果我们有一个配置服务,它本身仅仅是一个微服务,那么应用程序启动时如何知道在哪里可以找到它?如果一个微服务需要与其它微服务通信,如何找到它们?这就是发现的“先有鸡还是先有蛋的问题”。
我们的微服务不需要域名。实际上,它们可以任何时间、在集群中的任何位置启动时获得随机IP。我们选择通过发现服务或“单一服务统治所有人”来解决此问题。发现服务位于一个已知的域名中,新服务知道在哪里寻找它。
当我们第一次踏上这一旅程时,受到了Netflix的Eureka解决方案的启发。实际上,我们的第一个部署就是新的Eureka实例。Eureka非常出色,但是随着时间的流逝,我们开始感到需要一些工具,可以让我们的操作环境具备更加原生的理解。
新的应用程序启动后,它将查找发现服务,以找出配置服务所在的位置,下面将详细介绍这个过程。在执行下一步之前,应用必须进行自我配置,这一点很重要:在Discovery中注册自己。这使其他服务可以查找和查询新服务,以及了解其服务的合约。这里有一个示例,说明一项指标服务是如何针对QA环境进行报告的:
一旦应用程序找到发现服务,找到配置服务,并报告了自身,它也就可以继续使用发现服务来查找需要与之通信的其它服务。
这听起来很简单,但是我们需要牢记一些复杂的情况。例如,如果某个服务关闭,则我们必须注销该服务,否则将冒请求服务的风险(即哪个服务正在侦听什么IP和端口)。如果服务的IP发生更改,我们必须对其进行更新,否则就有将流量路由到错误位置的风险。
我们通过简单的心跳模式来处理这些情况。在指定的时间内无法回叫的服务被视为M.I.A,将被从发现服务中删除。
但是,在生产环境中事情可能并没有那么简单。如果数据中心出现严重错误,则系统需要具备基本的意识才能做出适当的反应。如果发现服务注意到其大量注册客户端停止了心跳,则可以断言存在某种大规模的网络或通信故障,并陷入“保留”模式。在保留模式下,发现服务会保留其数据,并立即向运营人员呼叫,从而有助于保护我们免受各种注册风暴或日益流行的“网络故障”的影响。
Riot的所有微服务都会在已知端点上发出运行状况报告,类似于“健康”,“降级”,“失败”等。即使是简单的报告,也允许我们使用基本的REST调用查询发现服务,并检查所有服务的运行状况。
但这还不够。如果服务注册失败了怎么办?或者服务因崩溃而注销了该怎么办?如果不在发现服务中,我们如何知道它应该是什么状态(up or down)?
这就是我们的告警和指标度量系统的来源。
指标度量系统可以查询应用程序的指标有效负载,然后将数据吸收到指标通道中进行检索,再将指标推送到位于每个数据中心的数据收集器。接下来,数据被转发到Elasticsearch存储引擎,在该引擎中注册的watcher协助触发警报。
应用程序维护出现相关报警的端点。告警服务将其注册,然后通过监视服务监视其指标的状态变更。如果应用程序的状态从“健康”变更为“降级”,并且该应用程序已针对这个状态注册了警报,则告警服务将通知注册的联系点(通过呼叫、电子邮件等方式)。
指标度量系统如何知道收集器在哪里?通过发现服务!有创造力的开发人员甚至可以通过配置服务,来设置服务发出的度量质保,它们的间隔,或者是服务的警报配置,从而实时更改指标和警报。报警声太吵了吗?你是否注意到总是引起虚假报警的特定警报?将配置更改推送到你的应用程序作用域,并告诉它取消注册警报。
然后,可以将汇总的指标合并到数据仓库中。在Riot,我们正在将数据移动到一个“实时数据管道”当中,该管道由Elasticsearch支持,并由Riot的数据产品和解决方案团队托管。一旦数据进入管道,我们就可以轻松构建仪表盘了。请记住,所有的应用程序都报告其命名作用域和指标数据,因此我们可以轻松地从特定区域或特定名称的数据中心中,查询某个特定的应用程序的指标。
这是我们配置服务中指标的捕获情况。不是每个指标都有,但它是一个很好的示例。你可以看到它的CPU负载(真的很轻),并且每分钟收到大约20,000个请求。这个特定的实例来自我们的阿姆斯特丹数据中心,它的“集群”是“lolriot.ams1.configurous1”(部署作用域),而应用程序是“infrastructurous.configurous”(应用作用域)。
如果需要,我们可以根据这些指标构建报警。例如,使用“已启用实例计数”,当我们看到实例数量少于预期的“3”个时,可以创建一个报警来呼叫相关人员。
在发现服务和配置服务中正确注册其应用程序,前期成本是很小的,Riot开发人员可以免费获得这种报告。
到目前为止,我们避开了一个关键问题没谈:安全性。安全地进行通信,是任何高度便携、可动态配置的微服务系统的必要条件。必须锁定用于HTTPS流量或API身份验证令牌的SSL证书。我们希望将这些数据保存在配置服务中,以便可以轻松访问,但是以纯文本格式存储数据肯定不会给自己带来任何好处。那么我们该怎么办?
如果我们将数据加密并存储在配置服务中,会怎么样?那么一旦找到它,我们将需要一种解密的方法,并且还需要一种方法来确保,检索它的应用程序是唯一具有解密密钥的应用程序。这就进入到我们可操作难题的最后一部分:秘密管理。
https://xkcd.com/538/
为此,我们选择围绕HashiCorps出色的Vault服务创建服务包装。Vault实际上完成的工作远远超出我们的需求,因为我们实际上只想存储解密的密钥,以便我们的服务可以检索它并解密其数据。因此,我们的服务包装为此基本上只启用了REST端点。
从理论上讲,使用它是很简单的,开发人员使用应用程序命名作用域,将特定服务的解密密钥放入秘密服务中。我们的容器调度程序Admiral在启动时将密钥注入到应用程序容器中(通过命名作用域查找它们)。一旦应用程序容器具有其解密密钥,它就可以解密从配置服务中检索到的配置属性。服务配置的所有者需要使用加密密钥来加密数据,然后再将其推送到配置存储。
该流程的详细信息及其工作方式,不在本文的讨论范围之内,但是工作流程至关重要。有了这个系统,服务现在可以实现高度便携、动态配置、自我感知、可知、可发现,同时在必要时还可以处理安全数据位。
至此,我们已经讨论了所有服务,就像对生产集群中运行的服务的bot-lane支持一样,但是我们的生态系统还有很多其他服务。毕竟,如果我们的开发人员不能有效地使用它,那么这一切有什么用呢?为了帮助大家利用此系统,我们创建了许多Web和CLI工具。如果刚才谈论的是生产环境的生态系统,那么我们仍然需要讨论开发者生态系统。但这是下一篇文章的故事!现在先留下一些预告。这是来自我们的一个Web应用程序部件的屏幕截图,我们使用它来访问生态系统中的工具,并查看刚刚提供给你的那些数据:
如果你想知道其中一些工具是什么,敬请期待下一篇文章!
更多“揭秘LOL”系列文章
揭秘LOL背后的IT基础架构丨踏上部署多样性的征程
揭秘LOL背后的IT基础设施丨关键角色“调度”
揭秘LOL背后的IT基础架构丨SDN解锁新基础架构
揭秘LOL背后的IT基础架构丨基础设施即代码