切换之前,先实现一致
原文由 Alex O'Callaghan 于 发布,订阅该博客
我们负责一项被多个团队和服务所依赖的服务。多年来,它以两种形态并存:生产环境中运行的是部署在本地服务器上的 Python 2 版本,而在另一个独立分支上,则是一个现代化的 Python 3 版本,一直等待着通过云迁移来最终取代原有版本。
许多遗留系统的升级都是如此。终态一目了然——新语言版本、新基础设施、旧系统下线——于是团队便直奔终态而去。工作在一个长期存在的分支或所谓“v2”重写中进行,计划是在远离生产环境的地方完成繁重的工作,最后一次性切换过去。
迟迟无法落地的重写
这种计划很少能兑现承诺。新版本迟迟不发布,风险并不会消失,只会不断累积。
每一个紧急修复都必须先在旧版本上落地,然后再移植到新版本——否则就会被遗忘。两者之间每一个行为上的差异,无论是有意还是无意,都是一个等待在切换后被下游团队发现的缺陷,而那时正是最难定位归因的时候。
长期分支看似高效,因为它把所有事情打包成一次事件。实际上,它只是把风险集中到某一个时间点,并推迟了反馈,而这些反馈本可以更早告诉你哪里出了问题。
以退为进
当我们接手这次迁移时,第一直觉是直奔终态:让新版本在云上跑起来,然后开始把调用方迁移过去。而我主张先退一步:让新版本先在旧基础设施的生产环境中跑起来,这样在做任何其他迁移之前,新旧就是同一份代码。
旧主机的操作系统过于陈旧,对 Python 的支持最高只到 3.6,而新版本的目标是 3.7。我们本可以想办法绕过——从源码编译 Python,或在原本并非为容器设计的主机上强行叠加容器——但每一种变通方案都会给这场本已足够复杂的迁移增加额外的复杂度。
于是,我们把服务降级到 3.6,并恢复了本地部署的配置,通过运行时按环境选择不同的设置。一份代码,同时可部署到旧主机和 Kubernetes。
版本降级看似是开倒车。恰恰是它,让后续的迁移变得简单直接。
一次只改变一个变量
实现一致后,每次发布都会同时部署到两个环境。新旧行为之间的差异会立刻以很小的增量暴露出来,而不是在切换时一次性集中爆发。两个版本之间多年积累的分歧,就在旧基础设施仍可作为兜底的这段时间里被逐步消化了。
等到我们切换到云上时,这已经是一次纯粹的基础设施变更。我们无须同时追问“平台迁移成功了吗?”和“重写成功了吗?”——如果出了问题,我们知道一定是平台的问题,因为代码是完全一致的。切换过程平淡无奇,而平淡正是我们想要的。这与在微前端中渐进式升级 React背后的原理如出一辙:对变量进行排序,让每次发布只改变一件事。
交付路径,而非终态
迁移完成后,那个卡了数年的 Python 升级作为一次常规发布就顺利上线了——旧主机的版本上限已不复存在,需要升级的代码库也只剩下一份。终态反而来得更快,正因为我们不再直奔它而去。
如果一个分支已经开了数月,或是一次重写正等待其他团队迁移,那通常意味着这次变更试图一次性做太多事情。去寻找一个现在就能发布的中途状态,哪怕它看起来像是后退了一步。终态从来都不是难点——难点在于路径,而最安全的路径,是可以一步一步发布出来的路径。
随机一篇博客
评论
登录后参与讨论