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

分支管理是那些在大型工作室内部被熟知、而在其他地方几乎完全没有记录的实践之一。拥有它的团队很少会把它写下来,因为它是建立一次后就变得隐形了。没有它的团队通常会在一个尴尬的时刻发现它的重要性。
本文将这些常见实践集中在一处:
前半部分涵盖了在构建任何东西之前值得理解的少数几个概念。
后半部分描述了在不同可用资源水平下的三种具体结构:一种适用于独立开发者,一种适用于工作团队,还有一种适用于拥有正式发布流程的工作室。
是三个而不是一个,因为正确的结构取决于你能在其上投入多少工程时间。为五十人工作室设计的流程会被一个三人团队默默放弃。这里没有唯一的正确答案,而在早期过度设计会付出真正的代价。行之有效的结构是你的团队实际上会遵守的那一个。
分支管理意味着什么
剥离掉工具,问题就简化为三个部分。
制品(Artifact)是一个构建好的服务器镜像。一旦存在,它就永远不会改变。
指针(Pointer)是一个名称,表示对于给定的受众哪个制品是当前的。“生产环境”(Production)是一个指针,而不是一个具体的事物。
决策(Decision)是将指针从一个制品移动到另一个制品的行为。
其他的一切都是围绕这三点包装的工具。
有些平台会给你命名的分支和一个晋升(promote)按钮。其他平台则给你版本,并让你决定这些名字的含义。无论哪种方式,你都在组装相同的三个部分,而下面的方法主要区别在于围绕第三个部分的仪式感有多少。
这里的一个区别起到了关键作用:制品是不可变的,而指针是可变的。大多数发布事故都源于混淆了这两者。

在选择之前值得了解的概念
一次构建,多个目的地
在测试中运行的同一个镜像,就是应该在生产环境中运行的镜像。
不是从同一个提交重新构建,也不是用不同的标志重新编译,而是相同的字节。为每个目的地重新构建会在验证的内容和交付的内容之间引入差距,而这个差距是隐形的,直到有些东西依赖于它。团队很早就向这一点收拢,因为另一种选择是一种除了在玩家面前之外在任何地方都无法复现的 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
严格保持两个版本。 |
|
每次构建都采用一个从未用过的标签。一个日期加一个计数器就足够了。 | 重点不在于优雅,而在于确保标签在六周后绝不会产生歧义。 |
将构建部署给自己意味着将 | 发布它意味着将 |
回滚是相同的反向移动,这只有在你能记住前一个标签时才有效。 | 把它写下来。在仓库的一个文本文件中记录一行,并在每次晋升时更新,这虽然朴素但已足够。 |
每月手动清理一次。 | 删除旧标签,保留指针当前引用的任何内容。 |
这可以防止未经评审的构建接触到玩家这一特定失败。
它不能为你提供的是关于为什么进行晋升或更改了什么的任何记录。
在三个人时这通常没问题,因为你直接问就行。当对“谁晋升了这个?”的回答不再显而易见时,它就失效了。
设置应该不需要一个小时。持续的成本是每次发布一两分钟。
方法 2:CI 拥有 Staging,人拥有 Production
适用于大约五到三十人工作室的团队,其中构建的速度超出了任何个人的精力范围。
一旦构建发生得比任何人跟踪得都要快,有用的划分就不是增加更多的环境。更理想的划分是将自动化的内容与保持手动的内容分开。将生产环境之前的一切自动化可以消除枯燥。保持最后一步为手动可以让人员在犯错代价高昂的唯一环节中参与进来。这种不对称性就是其核心思想,以下的大部分结构都是为了支持它而存在的。
标签 v1.4.2-a91c3f (语义化版本 + 简短提交哈希,由 CI 生成)
指针 dev -> 在每次向特性分支推送时构建
staging -> 在合并到 main 分支时自动更新
prod -> 仅通过手动触发的 CI 任务更新
标签来自 CI,绝不来自个人: 版本号告诉人类更改了什么。哈希使得仅凭标签就能复现构建,这比听起来更重要。
合并到 main 会自动更新
staging,而其他一切都不是自动的。生产环境通过一个单独的手动触发任务更新,该任务接收一个标签作为输入并将其写入prod指针。通过 CI 而不是控制面板路由这一流程会带来两个属性:记录了晋升,以及谁触发了它。这几乎是免费获得的大部分审计追踪。这里有必要了解一下平台层。在 Edgegap 上,应用版本是可变的,因此将
prod更新为指向新构建是对现有版本的更新,而不是一个新对象。这很方便,也是为什么通过 CI 路由更改很重要的原因。更改本身不会留下痕迹。做出更改的任务会留下痕迹。注:Edgegap 还在应用和版本上公开了一个
is_active标志,作为防止开发者错误的保障记录在文档中。这值得接入到事故流程中,因为关闭一个糟糕的版本比决定回滚到什么版本要快,且这两个决策不需要在同一分钟内做出。编排平台通过晋升任务已经使用的相同 API 公开了这两者。
清理变成了计划任务,而不是每月一次的家务活: 大多数团队想要的形式是“删除超过三十天的版本,绝不少于最后十个,且绝不删除线上部署引用的任何内容。” Edgegap 目前没有保留策略引擎,所以这是针对 API 编写的,而不是配置出来的。大约半天的工作,排除规则是值得注意的部分。
客户端构建和服务器版本仍协同移动: 这种规模的团队通常会在晋升后的一段时间内保持前一个生产版本的可部署性,这样旧客户端上的玩家就不会在会话中途断连。
这带来的好处是,没有人需要记住任何事情,而且交付内容的记录在没有人维护的情况下也存在。
它仍然无法做的是防止不应该发生的晋升。因为流程中有人参与,而人会批准事情。
后端工程师设置大约需要一天时间。持续成本接近于零。
方法 3:可审计的晋升
适用于有实时运营、合规要求或发布流程早于基础设施决策的工作室。
在这种规模下,发布流程通常已经存在,由不从事基础设施工作的人员定义,平台的工作是适应它而不是取代它。这改变了设计问题。目标不再是保持流程轻量化,而是让每一步都可检查,因为最终会有人问交付了什么、在什么时候以及谁批准的,而“我记得”无法应对这种质询。
标签 v1.4.2-a91c3f (人类可读的别名)
标识 sha256:9f2c... (部署实际锁定的内容)
指针 staging -> 摘要值
prod-canary -> 摘要值
prod -> 摘要值,仅通过评审、合并的提交进行更改
摘要值优于标签: 标签是人类选择的标记,人类可以移动标记。摘要值是内容哈希,无法移动。将部署锁定在摘要值上的工作室纯粹将标签视为人类可读的别名。这是提高严谨性最大的一步,除了改变 CI 记录的内容外,不需要任何成本。
版本声明为代码: 通过 Terraform 或 API 而不是控制面板管理版本,将晋升转化为具有作者、时间戳和差异的评审并合并的更改。工作室现有的审批流程随后会自动应用,因为晋升就像其他任何东西一样是一个拉取请求(pull request)。这两种路径在 Edgegap 上都存在,而这正是该层级最重要的地方。对于底层,Edgegap 关于使用 Docker 或 Kubernetes 托管可扩展游戏服务器的指南涵盖了容器本身的构建和运行方式。
在指针移动前的关卡: 灰度测试、负载基准测试、记录在机器可读地方的 QA 签字。重要的区别在于人们遵守的流程与流水线强制执行的流程之间。第二种流程在糟糕的一周里也能存活下来。
金丝雀部署(Canary): 一个单独的指针,在全面晋升前接收一小部分真实流量。要清醒地认识到这涉及什么:将玩家引导到特定的服务器版本是工作室如今在自己的匹配或会话层中构建的东西,而不是编排平台通常会直接提供给你的。一个单独的版本、一条你拥有的路由规则,以及一个带有公认干净定义的观察窗口。
签名和来源: 镜像在构建时签名,在晋升时验证签名,验证失败时拒绝晋升。这越来越多地是合规要求,而不仅仅是锦上添花,特别是对于在主机上发行的工作室。
带排除项的保留策略: 如方法 2 中的自动修剪,并有一条硬性规则:线上或金丝雀指针引用的任何内容都不能成为删除候选,且删除行为本身会被记录下来。
所有这些都可以在公开版本和 API 的任何平台上构建,但如果没有明确的指导方针和对工作室内部流程的实用知识,这些都无法构建。
需要多长时间完全取决于该流程已经是什么样子。
在它们之间做出选择
有用的信号不是直接的团队规模。而是对“谁晋升了它,为什么”的回答有多频繁需要去询问别人。
当答案很明显,因为你们只有三个人时,方法 1 不是妥协,它是正确的。当你发现自己处于询问状态时,方法 2 在大约一个月内就能收回成本。当工程团队之外的人需要答案时,无论你是否构建了它,你都已经在方法 3 的领域中了。
在所有这些之下,有一个值得明确说明的权衡。一个为你提供单一口碑优良的流水线的平台可以帮你免去这个决定,但代价是你失去了以不同方式做出决定的能力。一个为你提供版本和 API 的平台要求你做出选择,只有在没有人选择时,这才会成为负担。大多数对这两种安排不满意的团队都是通过默认而不是通过决策走到这一步的。
刻意选择一个,就像软件开发中的任何事情一样,期待对其进行一次迭代,无论是在达到一定规模时,还是在流程固化之后。一个明确的第一阶段结构是使第二个阶段更容易构建的基础。
书写者
Jakub Motyl(产品经理)和 Gabriel Parent(总监)









