下载APP
登录
关闭
讲堂
算法训练营
Python 进阶训练营
企业服务
极客商城
客户端下载
兑换中心
渠道合作
推荐作者

第一章 简介

2018-02-27 薛命灯
我是 Jonathan McCaffrey,来自 Riot 公司的基础设施团队。在讲述我们如何进行后端应用的部署和运维之前,首先要先了解一下我们是如何看待我们的应用开发的。游戏玩家的价值对于 Riot 来说是至高无上的,我们的开发团队经常与玩家社区互动,为了给玩家们提供最佳的游戏体验,我们必须具备根据玩家反馈快速做出变更的能力。基础设施团队的使命就是为开发人员提供支持,我们提供支持的力度越大,玩家们就能越快体验到最新的功能。
当然,说起来容易做起来难!因为部署的多元化,我们面临着很多挑战。我们有公有云服务器,也有本地数据中心,还要与腾讯和 Garena合作。这些环境的复杂性给开发团队增加了不少负担,于是就有了基础设施团队。我们使用基于容器的内部云来简化部署,这个云叫作“rCluster”。在这篇文章里,我将介绍 Riot 从手动部署到使用 rCluster 进行部署的演变过程。为了更好地描述 rCluster,我将以海克斯工匠系统(Hextech Crafting System)作为例子。

历史背景简述

我在七年前加入 Riot,当时我们并没有那么多部署流程和服务器管理流程,我们还只是一个怀揣大梦想的初创公司。我们的预算很有限,做什么事情都要求快。我们为《英雄联盟》构建了一套基础设施,努力满足游戏不断增长的需求,为开发团队提供支持,同时还要支持区域团队拓展新的疆域。我们通过手动配置服务器和应用程序,没有太多指南和战略规划之类的东西。
后来,我们使用Chef来完成常见的部署和基础设施任务,使用公有云进行大数据计算和提供 Web 服务。在这一过程中,我们多次重新设计了我们的网络,也更换过供应商,甚至重组了整个团队结构。
我们的数据中心有数千台服务器,每新增一个应用程序就要添加新的服务器。新服务器被接入手动创建的虚拟局域网,并手动配置路由和防火墙规则来提升安全性。虽然这样可以提升安全性,但费时又费力。因为这个痛点的存在,当时的新功能都被设计成小型的 Web 服务,导致《英雄联盟》生态系统的服务数量大肆膨胀。
除此之外,我们的开发团队对应用程序的测试环节缺乏信心,在进行部署时经常出现一些配置或网络连接问题。因为应用程序与物理基础设施的耦合太过紧密,以致于生产环境与测试环境、Staging 环境和公测环境难以保持一致。
我们的应用程序数量一直在增长,我们也在经历着手动配置服务器和网络的艰难过程。与此同时,Docker 开始在我们的开发团队中流行起来,用于解决配置和开发环境一致性问题。我们开始意识到,我们可以用 Docker 做更多的事情,它可以在基础设施方面扮演一个更重要的角色。

2016 全明星赛及其他

基础设施团队的目标是为游戏玩家、开发团队和 2016 全明星赛提供支持。在 2015 年底,我们从手动部署转向了自动部署,比如海克斯工匠系统就使用了自动部署。我们开发了 rCluster——一个全新的系统,它使用了 Docker 和微服务风格的 SDN(Software Defined Networking)。rCluster 解决了不一致性问题和部署流程问题,让产品团队可以集中在他们的产品研发上。
接下来我们将深入探讨 rCluster 是如何支持海克斯工匠系统的。
海克斯工匠系统在我们内部被称为“战利品(Loot)”,由三个核心组件组成。
战利品服务——一个 Java 应用程序,通过基于 HTTP/JSON 的 RESTAPI 处理战利品请求。
战利品缓存——基于 Memcached 的缓存集群,并使用了一个 Go 语言开发的小型边车(sidecar)来监控、配置、启动和关闭缓存集群。
战利品数据库——一个 MySQL 集群,包含了一个主数据库和多个从数据库。
在进入工匠系统时,会发生以下一系列事件:
玩家在客户端打开工匠系统的屏幕。
客户端向前端应用程序发起 RPC 调用,前端应用(也就是“feapp”)就是玩家和后端服务之间的代理。
feapp 向战利品服务器发起调用请求。
feapp 从“服务发现”(Service Discovery)中找到战利品服务器的 IP 地址和端口。
feapp 向战利品服务器发起 HTTP GET 请求。
战利品服务器检查战利品缓存里是否保存着玩家的物品。
玩家物品不在缓存里,于是战利品服务向数据库发起请求,并将返回的结果放进缓存。
战利品服务将结果返回给 feapp。
feapp 将 RPC 响应消息返回给客户端。
通过与战利品团队的合作,我们将服务器和缓存放进了 Docker 容器,并使用 JSON 文件来定义它们的部署配置。战利品服务器的 JSON 配置示例:
{
"name": "euw1.loot.lootserver",
"service": {
"appname": "loot.lootserver",
"location": "lolriot.ams1.euw1_loot"
},
"containers": [
{
"image": "compet/lootserver",
"version": "0.1.10-20160511-1746",
"ports": []
}
],
"env": [
"LOOT_SERVER_OPTIONS=-Dloot.regions=EUW1",
"LOG_FORWARDING=true"
],
"count": 12,
"cpu": 4,
"memory": 6144
}
战利品缓存的 JSON 配置示例:
{
"name": "euw1.loot.memcached",
"service": {
"appname": "loot.memcached",
"location": "lolriot.ams1.euw1_loot"
},
"containers": [
{
"name": "loot.memcached_sidecar",
"image": "rcluster/memcached-sidecar",
"version": "0.0.65",
"ports": [],
"env": [
"LOG_FORWARDING=true",
"RC_GROUP=loot",
"RC_APP=memcached"
]
},
{
"name": "loot.memcached",
"image": "rcluster/memcached",
"version": "0.0.65",
"ports": [],
"env": ["LOG_FORWARDING=true"]
}
],
"count": 12,
"cpu": 1,
"memory": 2048
}
不过要真正部署好它们,我们还需要创建一些集群,它们需要支持南美、北美、欧洲和亚洲的 Docker。我们因此需要解决一大堆问题:
容器调度
Docker 网络
持续交付
动态运行应用程序
后续的章节将会详细介绍这些组件,所以在这里先简要提及。

容器调度

我们自己编写了一款叫作 Admiral 的软件,并把它用在 rCluster 系统里,进行容器调度。Admiral 与一组物理机上的 Docker 后台进程进行通信,以便了解它们的健康状态。运维人员通过 HTTPS 发送上述的 JSON 请求,Admiral 则用它们了解相关容器的状态。Admiral 会持续地更新集群的状态,并在必要的时候采取相应的行动。Admiral 会根据实际情况向 Docker 后台进程发起请求来启动或停止容器,从而达到预期的状态。
如果容器发生崩溃,Admiral 会在另一台主机上启动一个新的容器。这种灵活的机制让服务器的管理变得十分容易,我们可以无缝地“灭掉”它们,做一些维护工作,然后再重启它们。
Admiral 与开源工具Marathon有点像,我们目前也正打算使用 Mesos、Marathon 和 DC/OS 的替代方案。如果这样可行,我们将会在后续的文章中分享我们的经验。

Docker 网络

在容器可以运行了之后,我们还要为战利品应用程序和系统的其他部分提供网络连接。我们使用 Open Contrail 为每个应用设置私有网络,然后让开发团队使用托管在 Git Hub 上的 JSON 文件来配置他们自己的网络策略。
战利品服务器网络配置示例:
{
"source": "platform.beapp:lolriot.ams1.euw1",
"ports": [
"main"
]
},
{
"source": "store.purchase:lolriot.ams1.euw1",
"ports": [
"main"
]
},
{
"source": "pss.psstool:lolriot.ams1.euw1",
"ports": [
"main"
]
},
{
"source": "championmastery.server:lolriot.ams1.euw1",
"ports": [
"main"
]
},
{
"source": "rama.server:lolriot.ams1.euw1",
"ports": [
"main"
]
}
],
"ports": {
"bproxy": [
"1301"
],
"jmx": [{
"inbound": [
{
"source": "loot.loadbalancer:lolriot.ams1.euw1_loot",
"ports": [
"main"
]
},
{
"source": "riot.offices:globalriot.earth.alloffices",
"ports": [
"main",
"jmx",
"jmx_rmi",
"bproxy"
]
},
{
"source": "hmp.metricsd:globalriot.ams1.ams1",
"ports": [
"main",
"logasaurous"
]
},
{
"source": "platform.gsm:lolriot.ams1.euw1",
"ports": [
"main"
]
},
{
"source": "platform.feapp:lolriot.ams1.euw1",
"ports": [
"main"
]
},
"23050"
],
"jmx_rmi": [
"23051"
],
"logasaurous": [
"1065"
],
"main": [
"22050"
]
}
}
战利品缓存网络配置示例:
{
"inbound": [
{
"source": "loot.lootserver:lolriot.ams1.euw1_loot",
"ports": [
"memcached"
]
},
{
"source": "riot.offices:globalriot.earth.alloffices",
"ports": [
"sidecar",
"memcached",
"bproxy"
]
},
{
"source": "hmp.metricsd:globalriot.ams1.ams1",
"ports": [
"sidecar"
]
},
{
"source": "riot.las1build:globalriot.las1.
buildfarm",
"ports": [
"sidecar"
]
}
],
"ports": {
"sidecar": 8080,
"memcached": 11211,
"bproxy": 1301
}
}
一旦 Git Hub 上的配置文件发生变更,就会启动一个传输作业,调用 Contrail 的 API 来创建和更新应用程序的私有网络策略。
Contrail 使用了一种叫作叠加网络(Overlay Network)的技术来实现私有网络。在我们的系统里,Contrail 在计算机主机之间启用了GRE通道,并使用网关路由器来管理流经通道的流量。Open Contrail 系统的灵感来自
于标准 MPLS L3VPN,所以在概念上与之非常相似。
在实现这个系统时,我们必须解决一些关键性挑战:
集成 Contrail 和 Docker;
允许 r Cluster 之外的网络无缝地访问叠加网络;
允许不同集群之间的应用发生交互;
在 AWS 上运行叠加网络;
在叠加网络上构建高可用的面向边缘的应用。
持续集成
战利品应用程序的 CI 流程类似下面这样:
我们的主要目标是这样的:一旦主仓库发生变化,就会创建一个新的应用容器,并将其部署到 QA 环境中。有了这个流程,我们的团队就可以快速迭代他们的代码,并看到代码实际的运行情况。紧凑的反馈闭环加快了改进用户体验的速度,这也正是 Riot 的关键目标——以玩家为中心,为玩家提供最佳体验。

动态地运行应用程序

到目前为止,我们谈论了我们是如何构建和部署应用程序的,但如果你曾经在这样的容器环境里工作过的话,你就会知道,我们要面临的挑战远不止之前提到的那些。
在 rCluster 里,容器有动态的 IP 地址,而且一直在启动和关闭。这与之前提到的静态服务器的部署方式是完全不一样的,所以我们需要新的工具和流程。
一些关键问题:
如何监控容量和端点一直在变化的应用?
如果端点一直在变化,那么应用程序如何才能知道其他应用程序的端点是什么?
如果不能通过 ssh 连接到容器上,而且容器日志在重启之后就会消失,那么该如何进行问题诊断?
如果我们是在构建阶段预热(bake)容器,那么如何配置数据库密码,或者如何为其他地区的容器配置不同的参数?
为了解决这些问题,我们开发了一个微服务平台,用于解决服务发现、配置管理和监控问题,我们将在其他详述该平台的细节,以及它为我们解决了哪些问题。
更多内容,请点击《英雄联盟 | 在线服务运维之道》阅读专题全部文章。
 写留言

精选留言

由作者筛选后的优质留言将会公开显示,欢迎踊跃留言。