大促期间别再随手发布,部署冻结窗口该怎么配

极狐GitLab
2026年11月5日
18300
分享:

大促期间别再随手发布,部署冻结窗口该怎么配

先明确一件事:部署冻结(Deploy Freeze)不是一个「写进公约里让大家自觉遵守」的约定,而是平台级的硬约束——在冻结窗口内,极狐GitLab 会阻止所有部署发生,作业不会真的把新版本推上去。冻结期用 cron 表达式描述起止时间,可绑定 cron_timezone(默认 UTC),基础版、专业版、旗舰版都可用。配好之后,下一个即将生效的冻结期会显示在项目「环境部署列表」页面顶部,谁都不用去翻日历。

为什么每年大促前,运维都在群里喊「这几天别发布」

做过大促的人都熟悉这套流程:提前两周发一封邮件,主题是「XX 期间冻结发布」,抄送所有人;提前三天在群里再喊一遍;大促前一天晚上,值班同学盯着流水线,看到有人合并就私聊问一句「这个能等吗」。

这套流程能撑住,靠的是人的注意力。而人的注意力在大促期间恰好是最稀缺的资源。

真正出事的往往不是「有人故意违规发布」,而是这些场景:

  • 晚上十点,某个业务线为了修一个文案问题,走了「小改动不走发布窗口」的例外;

  • 假期第一天,值班同学休假,一条定时流水线按既定计划把主干最新代码部署到了生产;

  • 大促当天上午,两个流水线的部署作业并发跑,后完成的那个把先完成的覆盖了,版本号对不上;

  • 更隐蔽的一种:大促前一周主干上累积了三十多个合并请求,全部挤在解冻后第一晚发布,出问题时没人说得清是哪个改动引起的。

这些问题的共同点是:它们都不是「纪律问题」,而是「机制缺位」。口头约定只能约束愿意遵守的人,而流水线不认口头约定。冻结这件事必须由平台来兜底,让人不需要记住,也让例外必须留下痕迹。

第一步:想清楚冻哪些项目,而不是冻整个实例

冻结最常见的误用是「一刀切」:全实例冻结,结果内部工具、文档站、测试环境的发布全被挡住,团队为了发个内网工具得找管理员解冻,最后冻结这件事被当成麻烦,没人愿意维护。

合理的做法是按「故障影响半径」分层:

项目类型

是否冻结

说明

直接承载交易/订单/支付的生产项目

冻结,且窗口覆盖大促全时段

事故成本最高,冻结收益最大

面向用户的内容、活动页

建议冻结,可留人工审批通道

有营销时效需求,需要例外流程

内部工具、后台管理

不冻结

事故影响可控,冻结只会增加摩擦

基础设施(IaC、K8s 配置)

冻结,但保留应急变更通道

变更影响面最大,反而更要冻结

判断标准很简单:这个项目出问题,大促当天有没有人能修。答案是「没人能修」的,就该冻。

第二步:用 API 建冻结期,把窗口交给 cron

界面上可以在项目 设置 → CI/CD → 部署冻结 里加,但真正常用的方式是用冻结期 API,因为它可以写进脚本、进版本库,也方便一次性给一批项目套同样的窗口:

curl --request POST \
  --header "PRIVATE-TOKEN: <your_access_token>" \
  --header "Content-Type: application/json" \
  --data '{
    "freeze_start": "0 18 * * 5",
    "freeze_end":   "0 9 * * 1",
    "cron_timezone": "Asia/Shanghai"
  }' \
  --url "https://gitlab.example.com/api/v4/projects/19/freeze_periods"

几个必须注意的点:

  • freeze_start 与 freeze_end 都是 cron 五段式,表示的是「从这一时刻起到那一时刻止」;

  • cron_timezone 不填时默认 UTC,这是最容易踩的坑,后文会展开;

  • 创建冻结期需要项目的维护者或所有者角色,普通开发者调这个接口会 403。

上面这条配置的语义是:每周五 18:00 进入冻结,到下周一 09:00 解除——正好覆盖周末没人值守的时间段。大促这种一次性长窗口,就把日期算准后单独建一条,别和周末规则混在一起。

第三步:让流水线知道「现在是冻结期」,而不是等着失败

冻结生效后,最容易被忽略的副作用是:部署作业被挡下来了,但流水线本身还在跑——构建、测试、扫描全部做完,最后卡在部署那一步失败。一次大促窗口能攒出几十条红色流水线,值班同学的告警列表里全是「部署失败」,真正的故障反而淹没在里面。

更合理的做法是在流水线里显式处理冻结,让部署作业在冻结期主动跳过并说明原因:

stages: [build, test, deploy]

deploy:prod:
  stage: deploy
  script:
    - ./scripts/deploy.sh prod
  environment:
    name: production
  resource_group: prod
  rules:
    # 只在默认分支的可发布时机跑;冻结期由平台拦截,
    # 这里提前跳过可以避免流水线整体变红
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $DEPLOY_FROZEN == "true"'
      when: never
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: manual

DEPLOY_FROZEN 这类标记可以由一条定时流水线每天检查冻结期 API 后写入项目变量,也可以由值班同学在进入冻结时手动置位。核心思路是:冻结的判定要能被流水线读到,而不是只存在于平台内部。这样构建和测试照常跑完(这些不会造成生产影响),只有部署这一步被明确跳过。

配套三道闸:冻结之外还得管住并发与顺序

单靠冻结窗口,只能解决「不该发的时候别发」,解决不了「该发的时候别乱发」。生产部署还有三个常见的时序问题,官方在部署安全里给了对应开关:

第一道,同一环境串行。 两条流水线的部署作业并行跑,后完成的覆盖先完成的,版本对不对得看运气。用 resource_group 把同一环境的部署锁成串行,后到的作业排队等前一个结束:

deploy:prod:
  resource_group: production

第二道,挡住过期的部署作业。 流水线的实际执行顺序不一定等于提交顺序,较旧的流水线晚完成,就会把新代码覆盖成旧代码。项目里开启「阻止过期的部署作业」后,较旧的部署作业启动时会直接失败,并在流水线视图里标记为 outdated deployment job。这里有个细节值得记:新旧程度按作业开始时间判定,不是提交时间,所以「新提交的手动部署作业」也可能被判定为过期而被挡住。

第三道,回滚重试要按项目敏感度决定。 「允许回滚部署的作业重试」默认开启,方便快速回到稳定版本;但对敏感项目,官方建议关掉,改走「用先前提交重新跑一条新流水线」的方式回滚——这样每次回滚都留下完整的流水线记录,而不是在旧作业上点一下重试。

三个真会踩到的坑

坑一:不写时区,冻结整整晚八小时。 cron_timezone 默认 UTC,"0 18 * * 5" 在 UTC 下意味着北京时间周六凌晨 2 点才开始冻结。而周五 18:00 到 24:00 恰恰是「赶在放假前把需求发上去」的高峰时段,冻结窗口完美错过。配置里请显式写上 Asia/Shanghai,别依赖默认值。

坑二:以为冻结会给紧急修复留口子。 冻结期间平台阻止的是所有部署,hotfix 一样被挡。真遇到必须发的场景,只能由有权限的人临时结束或调整冻结期——这本身是一次需要留痕的操作。把「谁能解除冻结」写进值班手册,比事后解释为什么绕不过去更省事。

坑三:冻结只管住了发布,没管住合并。 冻结期主干照常接合并请求,一周下来攒二三十个变更,解冻当晚一次性上线,出问题时排查范围和平时完全不是一个量级。冻结窗口应该配一个「合并窗口」:临近冻结时降低合并频率,把风险大的重构挪到解冻之后,而不是全部堆在解冻第一天。

还有一个容易被忘的收尾动作:为大促单独建的一次性长窗口,用完记得清理。留着不管,明年同期它会自己生效,那时候没人记得这条规则是怎么来的。

常见问题

Q:冻结期内手动点「部署」按钮,会生效吗?

不会。冻结是平台级阻止,手动触发同样被拦。所以别指望靠「只允许值班同学手动发」来替代冻结——那只是把纪律换了个执行人。

Q:部署冻结和受保护环境、部署审批是重复的功能吗?

三者管的是不同的维度:部署冻结管时间(什么时候不许发),受保护环境管人(谁能往这个环境部署),部署审批管流程(这次变更要谁签字)。生产环境通常三个都要:平时靠受保护环境 + 审批控制,大促期间再叠加冻结窗口。

Q:冻结配置能批量套到一批项目上吗?

可以,冻结期 API 是项目级的,写个脚本遍历项目列表循环调用即可。建议把「哪些项目该冻结」维护成一份清单放进版本库,每年大促前直接跑一遍,比在界面上一个个点可靠。

要用起来,先看两页官方文档:部署安全 讲清了冻结、串行、过期作业拦截的适用场景,冻结期 API 给了完整的请求参数与返回示例。建议先在一个非核心项目上把 cron 和时区验证一遍,再推到生产项目——时区踩一次就够记一辈子了。