在当今全球一体化的经济环境下,数字娱乐产业正日益成为文化和商业交流的有力代表。在此背景下大量游戏厂商尝试游戏出海并取得了令人瞩目的成绩,许多游戏以全球同服架构吸引着世界各地广泛的玩家群体。游戏全球化部署不仅扩大了单个产品的市场规模,而且提高了游戏厂商在全球范围的影响力,但与此同时也带来了许多技术挑战:
游戏服务所要求的高频交互和低延迟性质,使得在全球同服框架下,游戏服务器需要在多个地区进行部署。在实际运营过程中,我们通常需要根据目标用户群的地理位置和对延迟的容忍度预先规划服务器的设置地点。一般而言,以下这些区域是我们优先考虑的服务器地址——
美东
地区人口稠密,能为大批北美玩家提供服务;
法兰克福
地区是欧洲互联网的交汇点,能有效服务整个欧洲的玩家网络体验;
新加坡
地区则广泛覆盖东南亚的玩家群体;
东京
地区主要为日本和韩国的玩家提供支持
面对不同地区可能存在的配置差异、游戏版本更新以及服务器数目的不一致,如何在全球范围内有效实现游戏服的一致性交付,成为我们在全球同服架构设计时必须面对和解决的核心挑战。本文将通过一个示例来为大家讲解全球游戏服多地域一致性交付最佳实践。
在示例中,我们计划在上海、东京、法兰克福三地开服,因此我们需要这三个地区的基础设施资源。面对基础设施异构且复杂的情景,云原生带来的声明式 API、一致性交付的特性可以充分屏蔽底层资源的差异性,让游戏运维工程师专注应用本身,大幅度提高游戏服交付效率。从区域自治稳定性、用户调度复杂性的角度考虑,每个开服地域独立部署Kubernetes集群,并通过多集群的能力统一运维管理是我们认为游戏服一致性交付的最佳方式。
在本次实践中,我们选择阿里云分布式云容器平台 ACK One 管理多地域 Kubernetes 集群。ACK One 作为阿里云面向混合云、多集群、容灾等场景退出的企业级云原生平台,可以连接和管理任何地域、任何基础设施上的 Kubernetes 集群,并提供一致的管理,支持对应用、流量、安全、存储、可观测等进行统一管控。
本示例的部署架构如图所展示,包括 3 个不同地域的生产环境和 1 个开发测试环境。通常来说,通过在研发测试环境中验证并确认版本稳定后,再将其部署到生产环境,这一流程有助于确保项目的整体稳定性,并有效预防潜在的风险。
示例采用多集群混合云架构。具体地,Shanghai 集群、Frankfurt 集群、及 ShangHai Dev 集群为阿里云 ACK 集群;而 Japan 集群为非阿里云 Kubernetes 集群,其以注册集群的方式集成和纳管。在每个集群内部,我们采用了 GameServerSet 来部署游戏服务器。GameServerSet 是由云原生计算基金会(CNCF)孵化中的开源项目 OpenKruise 所提供的游戏专项工作负载。与原生的 Deployment 和 StatefulSet 工作负载相比,GameServerSet 具备游戏语意,更贴近游戏场景,使得对游戏服务器的运维管理更加方便和高效。
Kubernetes 集群准备完成后,我们使用 ACK One 舰队统一管理云上云下集群:
首先,通过 ACK One 注册集群
[
1]
将 IDC 或第三方公共云集群注册到阿里云,具体地:
1. 创建注册集群
[
2]
,并在创建好的注册集群,右侧单击
操作
列下的
详情
。
2. 在
集群信息
页面单击
连接信息
页签。
3. 在
集群导入代理配置
区域根据需要选择
公网
或者
私网
,然后单击右侧的
复制
,将公网或私网页签的内容复制到一个文件中,并执行 kubectl 命令,将目标集群注册至新集群中。例如,新建 agent.yaml 文件,将以上内容复制到 agent.yaml 文件中,并在目标集群中执行 kubectl apply -f agent.yaml 命令。
通过此步骤,Japan 集群已经被注册到阿里云。
其次,开启 ACK One 多集群舰队
[
3]
,并在 ACK One 控制台
[
4]
上关联注册集群和云上集群。由于集群跨多个地域,所以 ACK One 舰队会使用公网的方式关联集群,舰队所在 VPC 则需配置公网 NAT 网关。
至此,一个多集群舰队已经 Ready。示例对应的示意图如下:
在执行示例具体的发布操作之前,我们一起认识下云原生的交付思想 —— 声明式而非面向过程,这也就意味着云原生式的应用交付关注的并不是应用的部署过程而是对应用的定义。而应用的定义就是 Yaml,它通过配置参数化的方式描述这个应用该是什么样子。因此,一切有关应用的变更和发布实际上都是对应用描述(Yaml)的更改。
至此我们发现,我们需要一个仓库将 Yaml 落盘,记录当前对应用的描述,并且能够追溯和审计过去 Yaml 的变更。说到这里我相信大家会发现 git repo 天然符合该特点,运维工程师可以通过提交 Commit 和 Merge Request 的方式将 Yaml 上传并落盘,权限管理、审计都遵循 git 规范。在理想状态下,我们只需要维护一套对游戏服描述的 Yaml,一键触发全球多地域的游戏服发布,无需面向过程一一操作集群,去执行部署动作。这就是 GitOps 的思想。
在 GitOps 落地过程中最富有挑战的事情实际上是对游戏服应用的描述抽象。正如文章开头提到的,每个地域的游戏服或多或少存在着一些差异,似乎难以通过一个 Yaml 将所有游戏服都概括。举个例子,在上海我希望发布 10 个游戏区服,而在法兰克福我只希望发布 3 个区服,这样一来,一个 Yaml 因为 replicas 字段的差异就无法描述不同地域的游戏服。难道我们需要为每一个地域维护一个 Yaml 吗?这样也非合理的做法,当进行非差异性字段变更时(例如,为所有地域的游戏服打上一个标签),我们需要重复地执行多个 Yaml 的更改,集群数量多的时候容易造成遗漏或者错误,这并不符合云原生交付思想。
实际上,我们可以通过 Helm Chart 进行游戏服应用的进一步抽象,将差异性的部分作为 Value 提取出来。在我们本次的示例中(GitHub 游戏服 Helm Chart 示例
[
5]
),我们抽象出这样几个差异性字段:
-
-
sidecar 镜像 —— 每个地域/集群的 sidecar 镜像可能存在差异
-
副本数 —— 每个地域/集群的发布的游戏服数量可能存在差异
-
是否自动伸缩 —— 每个地域/集群对于自动伸缩的要求可能存在差异
除此之外的其他字段都保持一致,意味着不存在地域差异性影响。
在理解了 GitOps 并对游戏服应用进行了抽象、描述后,我们利用 ACK One GitOps 进行应用交付的实操。接下来我们展开具体的操作:
登录 ArgoCD UI:在 ACK One 控制台左侧导航栏
舰队 -> GitOps -> GitOps 控制台
,并在登录页面,单击
LOG IN VIA ALIYUN
,登录到 ArgoCD UI。需要公网访问则需要开通公网访问 GitOps
[
6]
。
1. 在 ArgoCD UI 左侧导航栏选择 Settings,然后选择 Repositories > + Connect Repo。
2. 在弹出的面板中配置以下信息,然后单击 CONNECT 添加连接。
PvE 类型游戏通常存在区服概念,大多情况下由运维工程师手动控制各地域的开服数量。关于 PvE 游戏云原生化最佳实践可参考 OKG PvE 游戏最佳实践文档
[
7]
。
白屏化管理应用
在初次尝试 ArgoCD 时,我们可以使用白屏控制台为每个地域的集群分别创建 Application:
1. 在 ArgoCD UI 左侧导航栏选择
Applications
,然后单击+
NEW APP。
2. 在弹出的面板配置以下信息,然后单击
CREATE
进行创建。(以 opengame-demo-shanghai-dev 为例)。
3. 创建完成后,在
Application
页面,即可看到 opengame-demo-shanghai-dev 的应用状态。如果
SYNC POLICY
选择的是
Manual
方式,需要手动点击
SYNC
,将应用同步部署至目标集群。应用的
Status
为
Healthy 和 Synced
,表示已经成功同步。
4. 单击 opengame-demo-shanghai-dev 应用名称,即可查看应用详情,展示应用相关的 Kubernetes 资源的拓扑结构及相应状态。
通过 ApplicationSet 一键发布
对 ArgoCD 有所熟悉了之后,我们也可以通过 ApplicationSet 对象来一键发布游戏服。各个集群的差异性通过elements抽象出来,例如下面 Yaml 中,以集群维度抽象出三个字段:cluster 集群名称用于区分 Application 名称;url 用于区分目标集群地址;replicas 用于区别不同集群发布的游戏服数量。
编写完成该 ApplicationSet Yaml 后,将其部署到 ACK One 舰队集群即可自动创建出四个 Application。
kubectl apply -f pve.yaml -n argocd
# pve.yaml 内容如下:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: minecraft
spec:
generators:
- list:
elements:
- cluster: shanghai-dev
url:
replicas: '1'
- cluster: shanghai-prod
url:
replicas: '3'
- cluster: frankfurt-prod
url:
replicas: '2'
- cluster: japan-prod
url:
replicas: '2'
template:
metadata:
name: '{{cluster}}-minecraft'
spec:
project: default
source:
repoURL: ''
targetRevision: HEAD
path: manifests/helm/open-game
helm:
valueFiles:
- values.yaml
parameters: #对应helm chart中提取的value参数
- name: replicas
value: '{{replicas}}'
- name: scaled.enabled
value: 'false'
destination:
server: '{{url}}'
namespace: game-server #部署到对应集群的game-server命名空间下
syncPolicy:
syncOptions:
- CreateNamespace=true #若集群中命名空间不存在则自动创建
在该 Yaml 中,所有的镜像版本都一致,若希望各集群镜像版本出现差异,可以仿照 replicas 的方式,添加新的 parameters 参数。
对于 PvP 类型的游戏,房间服的数量由自身伸缩器调配,而非运维工程师手动指定。有关 PvP 类型游戏的云原生化最佳实践可参考
OKG PvP 游戏最佳实践文档
[
8]
。
在 OKG 中我们通过为 GameServerSet 配置 ScaledObject 对象来实现房间服的弹性伸缩。因此,scaled.enabled 在此场景下需要开启。此外,房间服的副本数有 ArgoCD 和 OKG 2 个控制者而冲突,可以通过让 ArgoCD 忽略 GameServerSet 资源的副本数变化来解决,具体在 spec.ignoreDifferences 设置相应字段即可。考虑以上情况,该 pvp.yaml 如下所示:
apiVersion: argoproj.io/v1alpha1