了解Edgegap的工作原理

了解Edgegap的工作原理

如何构建游戏服务器版本:分支管理最佳实践

已发布

已发布

已发布

Edgegap 洞察系列标题“分支管理最佳实践”,展示了游戏服务器版本发布推广图表

分支管理是一种在大厂工作室内部广为人知、但在其他地方几乎完全没有文档记录的实践。拥有它的团队很少把它写下来,因为它是建立一次后就变得无形了。而没有它的团队,通常是在不方便的时刻才发现其重要性。

本文将这些常见实践集中在一处:

  • 前半部分涵盖了在构建任何内容之前值得理解的少数几个概念。

  • 后半部分描述了针对不同可用资源水平的三种具体结构:一种适用于独立开发者,一种适用于工作团队,还有一种适用于拥有正式发布流程的工作室。

是三个而不是一个,因为正确的结构取决于你能在其上投入多少工程时间。为50人工作室设计的流程,会被一个3人团队悄然放弃。这里没有唯一的正确答案,在早期过度设计会付出真正的代价。行之有效的结构是你的团队真正会遵循的那一个。

分支管理意味着什么

剥离掉工具,问题就简化为三个部分。

  • 制品 (Artifact) 是一个构建好的服务器镜像。一旦存在,它就永远不会改变。

  • 指针 (Pointer) 是一个名称,表示哪个制品对于给定的受众是当前的。“生产环境 (Production)”是一个指针,而不是一个实物。

  • 决策 (Decision) 是将指针从一个制品移动到另一个制品的行为。

其他一切都是围绕这三者包装的工具。

有些平台会直接给你命名好的分支和一个晋升按钮。有些平台则给你版本号,让你自己决定这些名称的含义。无论哪种方式,你都在组装相同的三个部分,而以下方法的区别主要在于围绕第三部分的仪式感有多少。

一个区别起到了关键作用:制品是不可变的;指针则不是。大多数发布事故都源于混淆了这两者。

Diagram of game server build promotion: immutable build artifacts stay in place while the mutable "prod" pointer moves from version 2.3.0 to 2.4.0

在选择前值得了解的概念

一次构建,多个目的地

在测试中运行的镜像应该与在生产中运行的镜像是同一个。

不是从同一个提交重新构建,不是用不同的标志重新编译,而是完全相同的字节。按目的地重新构建会在验证的内容和发布的内容之间引入差距,而这个差距是不可见的,直到有什么东西依赖它。团队很早就趋向于达成这一共识,因为不这样做的替代方案是一种除了在玩家面前之外、在任何地方都无法复现的 Bug 类别。

标签唯一性

被重复使用的标签将不再是标识符,而变成了标签 (Label)。

“我们部署了 v2.3” 这一表述直到有人在同一个标签下推送了不同的镜像之前都是精确的,此后它描述的就是两个构建,具体取决于你什么时候问。在每个标签上附加一些唯一的东西(如构建号或提交哈希),可以保留以后回答该问题的能力。这在构建时不需要任何成本,而且很难事后补救。

这里有一个细节很容易弄错。像 prod-2026.09.14-v2.3.0 这样的标签看起来很合理,但它把目的地放到了标识符内部,这意味着 QA 批准的构建并不是发布的构建。将环境排除在标签之外(如 2026.09.14-v2.3.0),转而放在指针中,这才是无需重新构建即可进行晋升的原因。

指针及其指向的对象

一旦制品确定,你就需要一些可变的东西来表示哪个是线上的。

这就是指针,在大多数平台上,它是一个命名的版本、一个分支,或者你的匹配系统读取的配置值。它的全部工作就是可以廉价且可逆地更改,这就是使接下来的两个概念发挥作用的原因。

事物如何向前推进

任何东西都不应该通过定时器、合并或作为其他事情的副作用,而自动从测试升级到生产环境。

晋升作为一项独立的操作效果最好,它与构建和部署分离开来,因为这种分离可以让人员或检查站处于构建和玩家之间。团队在由谁来执行这一操作上存在分歧。但他们大多同意,这应该是一个可识别的行为,而不是流水线自然产生的结果。

回滚到以前的构建

回滚意味着将玩家置于之前在线的构建上。 这发生得有多快取决于早先做出的一个选择:之前的制品是否还存在于可以访问的地方。

如果存在,回滚就是与晋升相同的指针更改,只是反向运行。只需几秒钟,而且不会有新东西损坏。如果不存在,返回的路径就必须通过构建任务,这需要耗费与构建相同的时间,并且可能以其自身的方式失败。区别不在于工具或技能,而在于旧的构建是否被保留了。

保留少量最近的构建会占用镜像库空间。大多数团队发现这比替代方案更便宜。

配置存在于何处

某些设置在测试和生产环境之间是不同的:要在哪些区域运行、要连接哪个数据库、要使用哪些密钥。这些值既可以固化到构建中,也可以在启动时传递给它。

如果固化在构建中,测试构建和生产构建就是两个不同的构建,“一次构建,多个目的地”就会悄然失效。如果在启动时传入(通常作为环境变量),单个构建在任何地方都保持有效。

这里的启示很明显。如果你发现自己因为两个环境需要不同的设置而构建了两次,那么这些设置放错地方了。

冷构建和缓存窗口

全球分发构建的平台会保持最近使用的构建处于随时待命状态,并让未使用的构建脱离该就绪状态。一段时间没有部署的构建可能需要在启动前重新获取,这会增加在沉寂一段时间后的首次部署时间。

这对于回滚最为重要,因为你想要回滚到的构建,根据定义,是你已经停止部署的构建。在 Edgegap 上,缓存的镜像在连续 72 小时没有部署后就会过期,因此在漫长周末闲置的构建到周一就会变冷。

重视回滚速度的团队,要么偶尔部署一下之前的构建以保持其就绪,要么接受这种延迟并围绕其进行规划。两者都是合理的。让人难受的是不知道自己选了哪一个。

还有三个值得提及而不深究的概念。客户端构建和服务器版本是同步移动的,因此晋升也是一个兼容性决定。镜像库清理需要有下限和上限,因为“删除所有超过 30 天的内容”最终会删除你想要回滚到的内容。而在发生事故后,你真正需要的其实很少:哪张镜像、由谁晋升、何时晋升。

方法 1:两个版本和一个规则

适用于独立开发者以及两到三人的团队,此时发布决策是在一个人的脑海中做出的。

在这种规模下,瓶颈不是协调,而是记忆。没有 QA 团队来签字,也没有第二位工程师来发现错误,所以结构必须足够小,以至于跳过它比遵循它更难。两个版本几乎是依然成其为结构的前提下最小的结构了。

    标签       2026.09.14-1, 2026.09.14-2, 2026.09.15-1

    指针   test  ->  2026.09.15-1

               prod  ->  2026.09.14-2

关键建议

采取行动

严格保持两个版本。

test 和 prod。不要三个。在这种规模下增加一个 staging,是走向无人维护流程的第一步。

每次构建都采用一个从未用过的标签。一个日期加一个计数器就足够了。

重点不在于优雅,而在于确保六周后标签绝不会产生歧义。

将构建部署给自己,意味着将 test 指向新标签。

发布它意味着将 prod 指向 test 当前所在的任何位置。第二步(“指向 prod”)就是整个晋升流程,因为它是刻意为之,而不是构建的自然结果,所以未测试的构建绝不会意外接触到玩家。

回滚是反向执行相同的移动,这只有在你能记住上一个标签时才有效。

把它写下来。在仓库的一个文本文件中记录一行,每次晋升时更新,这虽然不起眼,但已足够。

每月手动清理一次。

删除旧标签,保留指针当前引用的任何内容。

这可以防止未经审核的构建接触玩家这一特定失败情况。

它无法为你提供的是关于为什么进行晋升或更改了什么的任何记录。

在三个人的情况下,这通常没问题,因为你可以直接问。而当“谁晋升了这版?”的答案不再显而易见时,它就失效了。

设置应该控制在一小时以内。后续成本是每次发布一到两分钟。

方法 2:CI 拥有 Staging,人员拥有 Production

适用于大约 5 到 30 人的工作室团队,此时构建的速度超出了任何单个人的精力范围。

一旦构建发生的速度超过了任何人的追踪速度,有用的划分就不是增加更多的环境。更理想的划分是将自动化的部分与保持手动的部分分开。自动化生产环境之前的一切,可以消除单调乏味的工作。保持最后一步为手动,可以让人员在出错代价高昂的关键节点参与决策。这种不对称性就是核心思想,以下的大部分结构都是为了支持它而存在的。

    标签       v1.4.2-a91c3f      (语义化版本 + 短提交哈希,由 CI 生成)

    指针   dev      ->  每次向功能分支推送时构建

               staging  ->  在合并到 main 分支时自动更新

               prod     ->  仅通过手动触发的 CI 任务进行更新

关键建议

表头 2

标签来自 CI,绝不来自个人

版本号告诉人类改变了什么。哈希使构建仅凭标签即可复现,这比听起来更重要。

合并到 main 分支会自动更新 staging,其他任何事情都不是自动的

生产环境通过一个单独的手动触发任务进行更新,该任务接受一个标签作为输入,并将其写入 prod 指针。通过 CI 而不是控制面板路由这一操作,会产生两个属性:晋升被记录了下来,触发它的人也被记录了下来。这基本上免费提供了大部分审计追踪。

这里需要了解一下平台层。

在 Edgegap 上,应用版本是可变的,因此将 prod 更新为指向新构建是对现有版本的更新,而不是一个新对象。这很方便,也是为什么通过 CI 路由更改很重要的原因。更改本身不会留下痕迹。但做出更改的任务会留下记录。

  • 注意:Edgegap 还在应用和版本上都公开了一个 is_active 标志,该标志作为防止开发者出错的防护措施而记录在文档中。这值得接入到事故流程中,因为关闭一个坏版本比决定回滚到什么版本要快,而且这两个决定不需要在同一分钟内做出。编排平台通过晋升任务已经使用的相同 API 公开了这两者。


清理变成计划任务,而不是每月的繁琐工作

大多数团队想要的清理形式是“删除超过 30 天的版本,决不保留少于最近 10 个的版本,且决不删除线上部署引用的任何内容。” Edgegap 目前没有保留策略引擎,因此这是针对 API 编写的,而不是配置出来的。大约半天的工作量,排除规则是值得注意的部分。

客户端构建和服务器版本仍然同步移动

这种规模的团队通常在晋升后保留一个窗口,使以前的生产版本仍然可以部署,这样使用旧客户端的玩家就不会在会话中途断开连接。

这样做的好处是,没有人必须记住任何事情,而且发布的记录在没有人专门维护的情况下依然存在。

它仍然无法防止不应该发生的晋升。因为流程中有人参与,而人总会批准事情。

设置应该需要后端工程师大约一天的时间。后续成本接近于零。

方法 3:可审计的晋升

适用于具有 Live-Ops、合规性要求或发布流程早于基础设施决策的工作室。

在这种规模下,发布流程通常已经存在,由不从事基础设施工作的人定义,平台的工作是融入其中而不是取代它。这改变了设计问题。目标不再是保持流程轻量化,而是使每个步骤都可检查,因为最终会有人问发布了什么、何时发布以及由谁批准,而“我记得”在这样的询问面前是行不通的。

    标签       v1.4.2-a91c3f        (人类可读的别名)

    唯一身份   sha256:9f2c...       (部署实际锁定的对象)

    指针   staging      ->  摘要 (digest)

               prod-canary  ->  摘要

               prod         ->  摘要,仅通过经过评审和合并的提交进行更改

关键建议

采取行动

摘要优于标签

标签是人类选择的标记,人类可以移动标记。摘要是内容哈希,不可移动。将部署锁定到摘要的工作室纯粹将标签视为人类可读的别名。这是提高严谨性最大的一步,除了更改 CI 记录的内容外,不需要任何成本。

声明式版本管理(即代码化)

通过 Terraform 或 API 而不是控制面板管理版本,将晋升转变为包含作者、时间戳和 diff(差异)的已评审、已合并的更改。工作室现有的审批流程随后会自动应用,因为晋升就像其他任何 Pull Request 一样。Edgegap 上存在这两种路径,而这正是这一层级最重要的部分。对于底层,Edgegap 关于使用 Docker 或 Kubernetes 托管可扩展游戏服务器的指南涵盖了容器本身的构建和运行方式。

指针移动前的门禁

压力测试、负载基准测试、记录在机器可读地方的 QA 签字。重要的区别在于人们遵循的流程与流水线强制执行的流程之间。后者能在糟糕的一周里存活下来。

金丝雀发布 (Canary)

一个在全面晋升之前获取一小部分真实流量的独立指针。要清醒地认识到这涉及什么:将玩家路由到特定的服务器版本是工作室如今在自己的匹配或会话层中构建的东西,而不是编排平台通常提供给你的。一个独立的版本、一条由你拥有的路由规则,以及一个带有公认干净定义的观察窗口。

签名和来源 (Provenance)

镜像在构建时签名,在晋升时验证签名,验证失败时拒绝晋升。这越来越多地是合规性要求,而不仅仅是锦上添花,特别是对于在主机平台上发行的工作室。

保留策略 (Retention)

如方法 2 中的自动清理,带有硬性规则:即线上或金丝雀指针引用的任何内容都不能作为删除候选对象,并且删除操作本身会被记录日志。

所有这些都可以在任何公开版本和 API 的平台上构建,而如果不具备明确的指南和对工作室内部流程的实用知识,这些都无法构建。

这需要多长时间完全取决于该流程目前的样子。

在它们之间做出选择

有用的信号不直接是团队规模。而是回答“谁晋升了这版,以及为什么”这一问题需要询问某人的频率。

当答案很明显(因为你们只有三个人)时,方法 1 不是妥协,它是正确的。当你发现自己处于询问状态时,方法 2 在大约一个月内就能收回成本。当工程团队之外的人需要答案时,无论你是否已经构建,你都已经处于方法 3 的领域了。

这一切背后都有一个值得坦率说明的权衡。一个直接塞给你固定流水线的平台为你省去了这个决定,但代价是你无法做出不同的选择。一个给你版本和 API 的平台要求你做出选择,而只有在没有人做出选择时,这才会成为负担。大多数对这两种安排都不满意的团队,都是因为默认接受而不是主动决定才走到那一步的。

刻意选择一个,就像软件开发中的任何事情一样,期望对其进行一次迭代,无论是在规模扩大时还是在流程固化时。一个清晰的初始结构是使第二个结构更容易构建的关键。

书写者

Jakub Motyl(产品经理)和 Gabriel Parent(总监)

Get your Game Online Easily & in Minutes

立即开始集成!

轻松在线游戏
且在几分钟内

Get your Game Online Easily & in Minutes