
1. 规定到底该管什么先把边界划清楚再谈条款我待过的大大小小团队里凡是版本管理出问题的几乎都不是没规定而是规定写得像口号。文件名叫《软件版本管理规定》正文三页纸第一条写着所有发版必须严格遵循规范第二条写着确保版本可追溯然后就没有然后了。开发看完不知道明天该做什么测试看完不知道该卡谁运维看完不知道该找谁要包。这种规定最大的问题是它没有管动作只描述了状态。所以我在动手写这份规定之前会先做一件事把过去半年所有跟版本有关的扯皮记录翻出来按类型分堆。通常堆出来就那么几类一是版本号撞车或者乱跳两个团队各自发了个 2.3.0现场装完互相覆盖二是环境和制品对不上测试说验的是新包生产上的是三天前的旧包三是出了问题找不到该回退到哪一版回退脚本写了半页还是跑不起来四是发布过程没有记录谁在什么时候用什么参数改了什么全凭记忆。这四类问题就是规定真正的靶子条款必须能一条条打到它们身上否则写了也是摆设。我个人的经验是一份能落地的规定篇幅控制在五到八页最合适其中必须包含三张表版本号对照表、发布准入清单表、变更分级表。剩下的内容用流程段说明就行写太长没人看。规定里最忌讳的是出现原则上一般情况下这种词一线同事看到这类词就知道这地方可以商量那这条就废了。1.1 先分清规定约束的是人还是制品很多规定写得很别扭是因为把两件完全不同的事混在了一份文件里。一件是人怎么协作比如谁有权批准发布、谁必须在发布单上签字、什么时间点之后不能合代码另一件是制品怎么流转比如制品仓库里允许存在哪些版本、标签能不能覆盖、归档保留多久。这两件事的约束手段完全不一样约束人的靠流程和权限约束制品的靠工具和校验脚本。我在实际落地时会把它拆成两个部分写进同一份文件但分章节。人这部分的条款写得硬一些直接写清楚谁不签字就不发制品那部分写成可校验的规则比如版本标签一旦推送到远端仓库即视为不可变如需修正必须新增标签并在台账中登记原因。后一条之所以要加上新增标签而不是删除重打是因为删除远端标签这个动作在很多平台上是静默的删掉之后别人本地还留着旧标签下次拉取就对不上排查起来能耗掉半天。还有一点值得单独说规定里必须明确版本的所有权人是谁。不是指某个人而是指某个角色。我的做法是明确每个制品有且只有一个发布责任人这个人对版本号的正确性、制品的完整性、发布说明的准确性负责。多人共享一个发布权是版本管理的灾难开端因为责任一分散就没人真正核对那个数字了。1.2 角色分工不写协作写交付物规定里写开发与测试密切配合等于没写。我习惯用交付物来定义角色因为交付物可以检查态度不能检查。开发角色交付可构建的源码、变更说明、影响范围评估、回滚脚本测试角色交付测试报告、遗留问题清单含每个问题的严重等级和处理结论发布角色交付发布单、制品校验值、灰度观测记录运维角色交付环境版本台账、回滚演练记录这张表放进规定里每次发版照着勾勾不齐就不进入下一环节。有人会觉得这太重了小团队根本跑不动。我的应对办法是按变更分级来决定哪些交付物是必需的日常小改只保留源码、变更说明和测试结论三项涉及数据结构或核心链路的变更才全量要求。这样规定就不是一把死尺子而是一套按风险伸缩的尺子。2. 版本号命名规则一套能被记住的编号体系版本号是这份规定里最容易写复杂的地方。我见过有团队用主版本.次版本.修订号.构建号.日期.渠道六段式结果开发自己也记不住每次发版都要翻文档翻文档的功夫还不如直接问一句。段数越多出错概率越高这是硬规律。我的建议是三段主号加两个可选后缀主干部分用语义化的思路。主版本在你做了不兼容的对外变更时加一次版本在向后兼容的前提下新增功能时加一修订号只做向后兼容的缺陷修复。这个判定的核心不是改了多少行代码而是外部依赖方需不需要改代码。很多人在这点上判断错误把大重构当成主版本升级把加了一个字段当成次版本升级结果下游根本不知道要不要动。判断标准最好写进规定里写得直白一点调用方必须改代码或改配置才能继续用的就是主版本调用方什么都不用动就能用的就是次版本或修订号。2.1 用具体例子把判定标准钉死抽象标准一线同事永远争论不休配上例子就安静了。我在规定里会放一张对照表把常见变更类型和版本号动作直接对应起来。变更类型版本号动作判定理由删除或改名对外接口字段主版本 1调用方必须改代码修改鉴权方式、权限模型主版本 1接入方配置需重做新增可选请求参数次版本 1旧调用方式仍可用新增一个独立功能模块次版本 1向后兼容修复计算结果偏差修订号 1接口契约未变修改日志文案、提示语修订号 1不涉及契约仅调整内部实现、性能优化修订号 1对外无感知只改部署脚本、不改代码修订号 1 或不变视是否产出新制品最后一行特意留了个活口因为部署脚本单独发版的情况很常见。我的处理方式是看它是否产生新的可执行制品如果只是改了同一份制品的启动参数版本号不动走配置变更流程如果重新打了包就加修订号。预发布标识也要在规则里写清楚。常见的做法是-rc.1、-beta.2、-alpha.1按稳定性递增排列。关键是要规定同一版本号的预发布标识不能重复使用1.4.0-rc.1构建失败修正后必须叫1.4.0-rc.2不能覆盖。覆盖的后果很直接测试拿到的包和你说的是同一个号实际内容却不一样这类问题排查起来最浪费时间因为双方都坚信自己没错。2.2 内部构建号的写法与冲突规避对外发布的版本号最好干净但内部流水线跑出来的构建产物需要唯一标识这就需要构建号。我在规定里会明确构建号只出现在制品元数据、镜像标签后缀和台账里不出现在用户可见的界面或接口上。构建号的常见写法有两种一种是纯流水线序号比如build.1847一种是时间戳加序号比如20240612.7。单用流水线序号的问题在于流水线一旦重建、编号重置历史构建号可能重复归档时会产生歧义。单用日期的问题在于同一天多次构建会碰撞。所以我一般要求两者结合写成20240612.7这种形式日期段保证人能一眼看出新旧序号段保证同一天内唯一。还有一个容易忽略的细节构建号生成的时钟源要统一。如果流水线跑在多个执行节点上各节点系统时间不一致跨节点生成的构建号就可能乱序。我的做法是统一从流水线平台的服务器时间取不使用执行节点的本地时间这条也写进规定因为它是典型的不出问题想不到出了问题查一天的类型。2.3 版本号与分支的对应关系光有版本号还不够得说清楚哪个版本号从哪条分支出来。我采用的结构比较常见主干分支承担日常集成每个待发布的次版本拉一条发布分支热修复从发布分支或标签拉出临时分支修完同时回合主干。对应关系写进规定时要具体到什么情况下从哪里拉常规迭代从主干拉发布分支命名带上次版本号如release/3.8紧急修复从对应发布标签拉修复分支命名带版本号与问题编号如hotfix/3.8.2-1187主干继续开发下一个次版本不受发布分支影响命名里带问题编号这条我强烈建议保留。原因很实际三个月后有人问3.8.2 这个版本到底修了什么你只要看分支名就能顺藤摸瓜找到问题单不用去翻提交历史猜。不带编号的hotfix/fix在半年后会变成考古难题。3. 发布流程的卡点设计把口子收在提交之前流程最容易失效的地方是事后检查。代码已经合了、包已经打了、环境已经更新了这时候再说这个变更没审批没人愿意往回退规定就成了一张废纸。所以卡点必须前置卡在动作发生之前让不合规的路径走不通。我把发布流程的卡点设计成三道合入卡点、构建卡点、上环境卡点。合入卡点管的是代码能不能进发布分支构建卡点管的是打出来的包能不能进制品仓库上环境卡点管的是这个包能不能装到目标环境。三道卡点各管一段互相不重叠任何一道没过就停在原地。3.1 发布准入清单与签字规则准入清单是发布流程的核心工具我通常做成一张表每行一个检查项每项写明检查人和检查方式。下面这份是我用过多轮之后收敛出来的版本按变更分级启用不同行。检查项检查方式常规变更重大变更版本号符合命名规则脚本自动校验必需必需变更说明完整模板必填字段必需必需测试结论明确测试报告必需必需遗留问题已定级问题清单必需必需回滚方案可执行演练记录可选必需影响范围评估变更单可选必需数据兼容性确认评审记录视情况必需灰度观测计划发布单可选必需签字规则我倾向从简谁负责什么就签什么不做多级会签。常见做法是开发签变更说明的准确性测试签测试结论的有效性发布责任人签整体发布决定。三个人签完就可以发不需要拉一堆人凑热闹。人越多签字越像走过场这是我很早就想明白的事。3.2 制品归档与可追溯链路制品归档这块规定里必须写死一件事制品仓库里的每个版本对应的制品必须是不可覆盖的。同一个版本号第二次上传要直接拒绝而不是静默覆盖。这条要靠制品仓库的配置来保证很多仓库默认允许覆盖需要手动关掉。可追溯链路是这样串起来的问题单编号 → 代码提交 → 分支 → 标签 → 制品哈希值 → 部署记录 → 环境台账。这条链上任一环断开追溯就断了。我在规定里会要求每个制品的元数据里至少带三项信息源码标签、构建号、构建时的提交哈希。有了提交哈希即使标签被误删也能重新拉出对应的源码这是最后一道保险。# 制品上传前做一次校验确认标签与源码一致 git rev-parse --verify refs/tags/v3.8.2 git rev-list -n 1 refs/tags/v3.8.2 # 计算制品校验值并写入元数据 sha256sum app-3.8.2.tar.gz app-3.8.2.tar.gz.sha256这段脚本我放在构建流水线的收尾阶段跑跑不过就中断发布。用哈希值而不是文件大小或修改时间来标识制品是因为后两者太容易巧合一致哈希值几乎不会。3.3 发布窗口与冻结期发布窗口这件事经常被当成形式主义但它实际解决的是人力协调问题。我的经验是明确两个时间点代码冻结点和发布执行点。代码冻结之后发布分支只接受阻断级缺陷的修复其他一律进下一个版本。这条要在规定里写得毫不含糊。冻结期的长度按版本规模定我一般定两个工作日第一天做回归和制品准备第二天执行发布和观测。热修复不受冻结期约束但要走简化流程且必须有回滚方案。这里有个细节值得注意冻结不等于禁止提交主干分支照常开发只是发布分支锁住。把冻结理解成全仓库锁住的团队最后往往靠加班解冻来赶进度反而更容易出事。4. 变更控制与回滚规定里最容易写虚的部分变更控制和回滚这两块是版本管理规定里最容易被写成套话的地方。什么变更需经评审回滚需保证业务连续性听着都对用起来全空。要写好这块必须落到分级和可执行两个点上。4.1 变更分级与审批粒度分级的意义在于用不同的成本处理不同的风险。级别分得太细判断成本比风险本身还高分得太粗重大变更和小改动走同一个流程要么拖慢要么放水。我通常分四级一级配置与文案类不改代码逻辑比如调整超时时间、修改提示语。走自助发布事后登记。二级功能小改单模块逻辑调整影响面在模块内。开发自测加测试抽验即可。三级核心链路或对外接口变更涉及主流程、接口契约、第三方对接。必须完整测试加回归。四级数据结构、权限模型、协议变更影响面跨系统或不可逆。必须有兼容方案、演练记录、分批发布计划。分级判定的关键问句是这个变更出问题影响多少用户多久能恢复影响面和恢复时间两个维度一卡级别基本就定了。这四个级别对应的审批人不同但都不超过两级避免审批链拉长。4.2 回滚方案写到可执行的程度我见过太多回滚方案写着如出现异常回滚至上一版本。这句话等于什么也没说因为真正回滚的时候要回答四个问题回滚到哪个具体版本号、回滚操作由谁执行、回滚需要多久、回滚后怎么确认成功了。这四个问题在方案里必须都有答案。对于涉及数据结构的变更回滚的难点通常在数据上而不是代码上。代码可以切回旧版本但已经按新结构写入的数据不会自己变回去。所以规定里要明确凡涉及表结构或字段语义变更必须满足以下条件之一才能发布。新增字段可空且有默认值旧代码读写不受影响新旧结构双写读取侧按开关切换提供可执行的向下迁移脚本并在预发环境验证过变更拆成两步发布先写后读观察期不少于一个发布周期这几条里我最看重双写和两步发布因为它们把不可逆操作变成了可逆操作。向下迁移脚本虽然也可以但删列、改类型这类操作在数据量大时执行时间不可控风险高。规定里最好注明删除类操作必须延后至少一个发布周期执行也就是这一版弃用、下一版才删中间留出观察期。回滚演练这条也建议写进规定。不用每次都演练但重大变更必须做一次演练记录作为发布准入的一部分。演练的价值在于暴露那些写在方案里但根本跑不通的步骤我在实际工作中至少遇到过三次回滚脚本里引用的镜像已经不存在的情况。5. 工具链落地让规定从纸面变成硬约束规定写完贴在文档库里没人看这是常态。要让它真正起作用唯一的办法是把能自动检查的部分全部交给工具人只负责工具检查不了的部分。这也是我判断一份规定是否成熟的标准能被脚本校验的条款越多规定活得越久。5.1 版本号与标签的自动化校验第一件事是让版本号只有一个来源。常见做法是在代码库里放一个版本文件比如version.txt或者构建配置里的版本字段构建脚本从这里读取而不是让每个人手动填写。手动填写的字段一定会出现某个人的分支里是 3.8.1、另一个人的分支里是 3.8.2 这种错位。第二件事是在提交阶段做格式校验。用前置钩子拦一道不符合规则的版本号格式直接拒绝提交。#!/usr/bin/env bash # 校验版本号格式主版本.次版本.修订号可选 -rc.N 后缀 VERSION$(cat version.txt | tr -d \n) if ! echo $VERSION | grep -Eq ^[0-9]\.[0-9]\.[0-9](-(rc|beta|alpha)\.[0-9])?$; then echo version.txt 格式不合法$VERSION exit 1 fi第三件事是保证标签与版本号一致。打标签的流水线里加一段判断标签名去掉前缀后必须等于版本文件的内容不一致就中断。这条能挡掉大量手快打错标签的情况。# 流水线片段打标签前的版本一致性校验 stages: - verify - build - publish verify-version: stage: verify script: - VERSION_FILE$(cat version.txt) - TAG_VERSION${CI_COMMIT_TAG#v} - | if [ $VERSION_FILE ! $TAG_VERSION ]; then echo 标签版本 $TAG_VERSION 与 version.txt 的 $VERSION_FILE 不一致 exit 1 fi rules: - if: $CI_COMMIT_TAG5.2 制品与环境的绑定关系管理环境版本台账是我强烈建议单独维护的一份东西它回答的问题是此刻每个环境上跑的是哪个版本。这份台账最好由部署系统自动写入而不是人工登记人工登记必然滞后。台账至少包含这些字段环境标识、版本号、构建号、部署时间、操作人、部署批次。有了这份数据出问题时第一件事就是查台账对齐各方认知很多争议在这一步就消解了因为大家看的不是同一个东西。部署系统里还要加一条约束只允许部署制品仓库中已归档且校验值匹配的版本。禁止从本地直接上传包部署禁止用临时构建上生产。这两条在很多团队是靠自觉执行的只要有一次例外后面就收不住了。我的做法是直接把本地部署的入口关掉需要用本地包的时候必须走一个审批分支麻烦一点但能挡住绝大多数顺手为之的违规。5.3 用提交信息把变更和版本关联起来提交信息规范看起来是小事但它直接决定了版本发布说明能不能自动生成。我在规定里会要求提交信息带上问题单编号格式不做过多限制能解析就行。feat(order): 支持拆单发货 #1187 fix(pay): 修复重复回调导致的重复入账 #1203 docs(api): 补充查询接口的字段说明 #1210有了这个发布时用一段脚本就能把某个版本区间内的提交按类型整理出来发布说明基本不用手写。手写发布说明的问题是它会漏而漏掉的往往正是后来出问题的那个变更。另外问题单编号的存在让谁提的需求、谁改的代码、谁测的、什么时候发的这条链在一处就能看全。6. 常见问题与排查技巧实录规定落地过程中遇到的问题翻来覆去就那么几种。我把自己踩过的和帮别人排查过的整理成一份速查表出问题时对着查比从头分析快得多。6.1 典型问题速查表现象常见原因排查动作处理方式两个版本号相同但内容不同制品被覆盖或标签被重打比对制品哈希值与构建记录重新构建并新增版本号测试环境版本与预期不符部署时选错制品或缓存未清查环境台账与部署记录重新部署并刷新缓存回滚后仍有异常数据已按新结构写入查数据迁移记录执行兼容脚本或双写回退版本号跳号断层流水线中断或构建失败查流水线执行历史补登台账说明原因热修复后主干缺少修复只改了发布分支未回合比对分支差异回合并记录依赖锁文件未同步本地装包后未提交锁文件比对锁文件差异重新生成并提交灰度机型未更新批次配置遗漏查批次名单与机器清单补发并核对覆盖这张表我一般放进规定的附录而不是正文。放正文会显得文件冗长放附录方便遇到问题时直接翻。关于灰度机器未更新这一条我再多说一句。批量发布的时候批次名单和实际机器清单很容易脱节尤其是手工维护的清单。我的做法是让部署系统从注册中心动态拉取机器列表再切分批次避免人工列清单。曾经有一次一台机器因为改名没被清单独覆盖到结果它一直在跑旧版本日志里出现了新旧两种格式的记录排查了两个小时才定位到。6.2 几条写规定时容易忽略的坑第一条别在发布分支上改版本号。版本号应该在拉发布分支的那一刻就确定之后不再变动。如果在发布过程中改了版本号再往主干回合很容易造成主干版本号回退或者重复。我见过最糟的情况是同一次发布产生了三个不同的版本号因为在三个地方各改了一次。第二条标签的删除要有痕迹。允许删除标签的团队最后都会遇到标签对不上源码的问题。要么直接禁止删除要么要求删除后立即在台账登记并说明原因同时新建标签。禁止删除是最省事的做法。第三条归档保留期要跟法务和客户合同确认不要自己拍脑袋定。保留太短客户要历史版本的时候拿不出来保留太久存储成本堆上去。我一般定对外发布版本长期保留内部构建和预发布制品保留三到六个月具体数字以实际要求为准。第四条规定里要给例外留一个明确的口子。完全没有例外通道的规定遇到紧急情况时会被整体绕过一旦绕过一次后面就都绕。正确的做法是设置一条紧急发布通道条件、审批人、事后补办要求都写清楚。紧急通道用得越多说明正常流程越不顺这本身也是一个观察指标。第五条版本管理规定的修订本身也要有版本。规定文件自己不带版本号和修订记录讨论现在执行的是哪一版时就会吵起来。我习惯在文件头写清楚版本号、生效日期、修订摘要改一次记一次谁改的也记上。最后分享一个我自己用着比较顺的做法把版本管理规定里所有能自动检查的条款列一张清单逐条问这条能不能写成脚本。能写的就写写完挂到流水线上写不了的就在清单里注明由谁在什么时候人工确认。半年之后回头看这张清单能自动化的比例会慢慢提上去剩下的人工项也就那么几条维护起来轻松很多。版本管理的混乱从来不是一次规定就能根治的它是靠一条条卡点慢慢收窄的每收窄一点线上的意外就少一点。