欢迎来到Tungsten Fabric用户案例系列文章,一起发现TF的更多应用场景。“揭秘LOL”系列的主人公是Tungsten Fabric用户Riot Games游戏公司,作为LOL《英雄联盟》的开发和运营商,Riot Games面临全球范围复杂部署的挑战,让我们一起揭秘LOL背后的“英雄们”,看他们是如何运行在线服务的吧。
作者:Kyle Allan和Carl Quinn(文章来源:Riot Games)
我们是Kyle Allan和Carl Quinn,在Riot的基础架构团队工作。欢迎阅读这个系列的第二篇文章,详细介绍我们如何在全球范围内部署和操作后端功能。在本文中,我们将深入探讨部署生态系统的第一个核心组件:容器调度。
在Jonathan的第一篇系列文章中,讨论了Riot的部署历史和我们面临的挑战。特别是,他概述了当我们为《英雄联盟》不断添加基础架构设施时,尤其是面对“为每个应用程序手动配置服务器”这样的场景下,我们软件部署难度不断加剧。后来,出现了一个名为Docker的工具,改变了我们的服务器部署方法——进一步在我们内部就迭代出来Admiral,它是我们用于集群调度和管理的内部工具。
重要的是,应用程序部署的旅程还远远没有结束,它还在不断发展,我们正在为下一个阶段做准备(可能采用DC/OS,稍后讨论)。本文介绍了如何到达这一步,以及为什么做出这样的决定,希望其他人也可以从这个故事中有所收益。
当Docker横空出世,并且Linux容器化成为一种更广为人知的技术时,我们意识到,可以通过容器化基础架构的实施而受益。Docker容器映像提供了一个不变的、可部署的“神器”,它可以一次构建并部署在开发、测试和生产中。此外,它还保证生产环境中运行的映像的依赖性,与测试期间的依赖性完全相同。
另一个好处尤其重要:Docker允许将部署单元(容器)与计算单元(主机)解耦,它通过利用调度程序将容器分配给主机(希望以一种智能的方式),从而消除了服务器与应用程序之间的耦合——给定的容器可以在任意数量的可能的服务器上运行。
通过将后端服务打包成Docker映像,并且可以随时将其部署并扩展到服务器集群,我们应该能够迅速适应变化。我们可以添加新的玩家功能,当流量增加时进行扩容,并快速推出更新和修复程序。在考虑将容器内的服务部署到生产环境时,需要解决三个主要问题:
这三个问题的答案是,我们需要一个调度程序——一种在服务集群层面运行并执行我们的容器策略的服务。调度程序是维护集群、确保容器在正确的位置运行,以及在容器退出时重新启动它们的关键组件。
例如,我们可能要启动诸如Hextech Crafting之类的服务,该服务需要六个容器实例来处理其负载。调度程序负责查找具有足够内存和CPU资源以支持这些容器的主机,并执行使这些容器运行所需的任何操作。如果这些服务器之一发生故障,调度程序还负责为受影响的容器查找替换主机。
当我们决定使用调度程序时,就快速进行原型设计,以便了解容器化服务在生产中是否适合我们。此外,我们需要确保现有的开放源代码选项可以在目前的环境中运行,或者确保维护人员愿意接受我们的调整。
在开始编写Admiral调度程序之前,我们调查了现有集群管理器和调度程序的状况。都有谁在Docker主机集群之间调度容器,它们是如何做到的?它们的技术还能解决我们的问题吗?
在最初的研究中,我们调研了一些项目:
Mesos + Marathon
LMCTFY => Kubernetes
Fleet
我们还原型化了一个小型命令行工具,该工具可通过REST与Docker API进行通信,并且成功演示了如何使用此工具来协调部署。然后,我们决定继续编写自己的调度程序。
我们借鉴了研究过的系统的一些最佳功能,包括Kubernetes的Pods和Marathon的约束系统背后的核心思想。我们的愿景是跟踪这些系统的体系结构和功能,在可能的情况下影响它们,并最终尝试在将来与其中之一融合。
在创建了一个基于JSON的基础部署元数据语言(我们称为CUDL,ClUster描述语言)之后,我们开始编写Admiral。CUDL成为Admiral在其REST API中使用的语言,两个主要组成部分如下:
集群和打包具有两个不同的方面:spec和live。每个方面都代表对容器生命周期不同阶段的描述。
Spec,表示元素所需的状态
Live,表示元素已实现的状态
Admiral用Go编写,并且在生产数据中心中运行时,被编译并打包到Docker容器中。Admiral有几个内部子系统,其中大部分如下图所示。
从用户的角度来看,与Admiral的交互是通过其提供的admiralctl命令行工具进行的,该工具通过REST API与Admiral进行通信。借助admiralctl,用户可以通过标准指令访问Admiral的所有功能:POST新的Spec打包以进行调度,DELETE旧包(Packs),以及GET当前状态。
在生产过程中,Admiral将使用Hashicorp的Consul存储Spec状态,定期对其进行备份,以防发生灾难性故障。万一完全丢失数据,Admiral还能使用从各个Docker守护程序检索到的Live状态中的信息,来部分重建其Spec状态。
协调器(reconciler)属于Admiral的核心,是驱动调度工作流程的关键子系统。协调器会周期性地将实际的Live状态与所需的Spec状态进行比较,并且在出现差异时,调度所需的操作,以便将Live状态恢复正常。
Live状态及其驱动程序包通过缓存Live主机和容器状态,并通过其REST API提供与集群主机上所有Docker守护程序的通信,来支持协调器。
Admiral的协调器可对Spec打包进行操作,有效地将其转换为Live打包。在将Spec打包提交给Admiral时,协调器将创建容器并使用Docker守护程序启动它们。正是通过这种机制,协调器实现了我们前面所述的最重要的两个高级调度目标。当协调器收到Spec打包时,它将:
让我们看一下在Docker主机上启动容器的示例。在此示例中,我们将使用本地Docker守护程序作为Docker主机,并与Admiral服务器的本地实例进行交互。
首先,我们使用“admiral pack create <cluster name> <pack file>”命令启动一个打包。此命令针对特定集群,并将Spec打包的JSON 提交到Admiral服务器。
你能注意到,几乎在刚刚运行命令后,容器就已经在我的机器上启动。这个容器是使用我的打包文件中的参数启动的,如下所示:
接下来,在调用“admiral pack create”之后,我们可以使用“show”命令来查看Admiral创建的Live打包。这里的命令是“admiral pack show <cluster name> <pack name>”。
最后,通过点击容器中的服务,我们可以验证打包是否正常工作。使用来自“admiral pack show”命令的信息,我们可以通过一个简单的curl来拼出我们的服务:
在Admiral内部,协调器始终处于运行状态,以确保集群的Live状态始终与所需的Spec状态相匹配。这样,当容器由于崩溃而失败并退出,或者由于硬件故障而导致整个服务器不可用时,我们还可以进行恢复业务。协调器努力确保状态匹配,以便玩家永远不会遇到中断问题。此功能解决了我们前面提到的第三个,也是最后一个问题:当容器意外退出时,我们可以快速恢复,并且将影响控制到最小。
下面将展示通过“admiral pack create”命令启动的现有容器。然后,我将终止该容器,并停止其执行。在几秒钟内,协调器启动了一个新的容器(具有不同的ID),因为它意识到Live状态与Spec状态不匹配。
为了最好地分配容器,调度程序必须洞悉主机集群。解决此问题有两个关键组件:
资源——服务器可用资源的一种表示形式,包括内存、CPU、I/O,以及网络等其他资源。
约束——打包随附的一组条件,可为调度程序提供有关可放置打包的限制的详细信息。例如,我们可能要放置一个打包实例:
通过在主机上定义资源,我们使调度程序可以灵活地决定将容器放置在何处。
通过在打包集(packs)上定义约束,我们可以限制调度程序的选择,以便将特定的模式强制应用到集群中。
对于Riot而言,Admiral是我们部署技术不断发展的重要组成部分。通过利用Docker和调度系统的功能,我们能够比以前更快地向玩家交付后端功能。
在本文中,我们深入研究了Admiral的一些功能,并展示了如何在一组机器集群之间调度容器。就像Jonathan在他的第一篇文章中提到的那样,开源世界已经迅速转向非常相似的模型。展望未来,我们将转移Admiral的工作,并专注于部署DC/OS,它已成为调度容器工作负载的领先的开源应用程序之一。
如果你经历了类似的旅程,或者觉得自己有话要补充,非常欢迎与我们取得联系。
更多“揭秘LOL”系列文章
● 揭秘LOL背后的IT基础架构丨踏上部署多样性的征程
关注微信:TF中文社区
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。