定时流水线总在整点撞车,调度与幂等该怎么配
定时流水线总在整点撞车,调度与幂等该怎么配
定义框:定时流水线(Pipeline schedule)是按 cron 表达式周期性触发、与代码变更无关的流水线。它适合"无论有没有人提交代码都必须执行"的任务,比如保持部署环境为最新、跑夜间回归、做定期维护。
定时流水线最容易出问题的地方,不是 cron 写错,而是它天然缺少三个约束:没有明确的负责人、没有幂等语义、没有天然的错峰。项目一多,几十条计划全落在 0 * * * * 上,Runner 队列在整点被撑爆,随后的连锁反应是作业超时、流水线互相抢占、失败重试把队列压得更死。
这篇讲清楚四件事:谁该拥有这条计划、什么时候该触发、怎么把负载摊平、失败了怎么不重复跑。
为什么定时流水线会慢慢失控
定时流水线在极狐GitLab 里的运行方式有三个特性,恰好都是失控的源头。
特性 | 官方行为 | 带来的后果 |
|---|---|---|
权限跟随所有者 | 创建计划的用户即所有者,流水线以该用户的权限运行,可以访问受保护环境,并按该用户的访问级别使用 CI/CD 作业令牌 | 人一离职或被移出项目,计划变 Inactive,任务静默停摆 |
与代码变更解耦 | 独立于提交与合并请求运行 | 代码里加了新的必需检查,定时流水线不会自动跟上 |
变量只对本次生效 | 计划上配置的变量仅在该定时流水线运行时可用,其他任何流水线都读不到 | 同一份 |
第二条常被忽略:定时流水线不会因为 rules 里新增了条件就自动改变行为。今天能跑的夜间构建,可能因为三个月前某次 rules 调整就一直被跳过,而没人发现——因为没人盯着"应该跑但没跑"这件事。
怎么把调度配清楚
第一步:把所有权收归团队账号,而不是个人
先决条件是创建者拥有该项目的开发者、维护者或所有者角色,且主邮箱已验证;如果计划指向受保护分支,还必须对该分支有合并权限。这几条决定了你不能随便找个实习号建计划。
实践做法是建一个服务账号(或团队公共账号)专门持有计划,人员变动时只需要交接账号,不需要在几十个项目里逐条"取得所有权"。已经变成 Inactive 的计划,维护者或所有者可以在计划列表里点取得所有权重新激活。
想盘点自己名下有多少条计划,路径在个人资料里:头像 → 编辑个人资料 → 帐户 → 滚动到"您拥有的定时流水线"。这个页面是排查"计划莫名停摆"的第一入口。
第二步:用 rules 把定时流水线和其他触发源切开
如果一份配置既服务于推送触发,也服务于定时触发,务必显式区分,否则夜间任务会和日常构建抢同一批 Runner。
stages: [build, test, nightly]
default:
interruptible: true
# 日常构建:只在推送和合并请求时跑
build:
stage: build
script:
- ./gradlew assemble
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_PIPELINE_SOURCE == "push"
- when: never
# 夜间回归:只在定时流水线里跑
nightly-regression:
stage: nightly
interruptible: false
script:
- ./gradlew regressionSuite
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
- when: neverinterruptible: false 用在夜间长任务上是有意的:它保证新提交触发的自动取消不会把跑到一半的回归任务干掉。代价是这条流水线运行期间无法被自动取消,所以只给真正不能中断的作业加,别整条流水线都加。
第三步:能写进 spec:inputs 的,别塞进变量
计划上可以挂任意数量的 CI/CD 变量,但官方明确建议用 inputs 替代变量来配置流水线,理由是安全性和灵活性更好。一个计划最多可以有 20 个输入。
spec:
inputs:
target_env:
default: staging
suite:
default: smoke
---
deploy-check:
script:
- echo "checking $[[ inputs.target_env ]] with $[[ inputs.suite ]]"$[[ inputs.* ]] 在流水线创建、拉取配置时就被插值替换,比运行时变量更早、更可验证,也不会像变量那样被后续作业继承导致歧义。
第四步:把 cron 摊平
官方在故障排查里给了明确的诊断手段:在实例上导出所有活跃计划的 cron 分布,看有没有大量计划挤在同一个模式上。
outfile=/tmp/gitlab_ci_schedules.tsv
sudo gitlab-psql --command "
COPY (SELECT
ci_pipeline_schedules.cron,
ci_pipeline_schedules.cron_timezone,
namespaces.path AS group,
projects.path AS project,
users.email
FROM ci_pipeline_schedules
JOIN projects ON projects.id = ci_pipeline_schedules.project_id
JOIN namespaces ON namespaces.id = projects.namespace_id
JOIN users ON users.id = ci_pipeline_schedules.owner_id
WHERE ci_pipeline_schedules.active = 't'
) TO '$outfile' CSV HEADER DELIMITER E'\t' ;"
sort "$outfile" | uniq -c | sort -n输出里如果 0 * * * * 出现几十次,就是典型的整点拥堵。官方建议是改成错开的形式,例如 5 * * * *、15 * * * *、25 * * * *,大仓库尤其要注意。另外 cron 的运行频率不能高于实例设置的最大定时流水线频率,超出的部分会被直接忽略——这也是"配了但不生效"的常见原因。
第五步:处理时区,别让 22 点变成 6 点
通过 API 创建计划时,cron_timezone 的默认值是 UTC。这意味着你在页面上填 0 22 * * * 的直觉(本地时间晚上 10 点),落到 UTC 上可能是第二天早上 6 点。
curl --request POST \
--header "PRIVATE-TOKEN: <your_token>" \
--form "description=nightly-regression" \
--form "ref=main" \
--form "cron=17 18 * * *" \
--form "cron_timezone=Asia/Shanghai" \
--form "active=true" \
"https://gitlab.example.com/api/v4/projects/<project_id>/pipeline_schedules"cron_timezone 接受 ActiveSupport::TimeZone 或 TZInfo::Timezone 的写法,Asia/Shanghai 两种都认。新计划一律显式写时区,不要依赖默认值。
几个真实的坑
坑一:计划变 Inactive 没有任何通知。 所有者被阻止或移出项目时,计划状态变为 Inactive,流水线就此停摆。定期在监控里对"过去 N 天该跑却零运行"的计划做告警,比事后追查省事得多。
坑二:variables 里写的值在调试时读不到。 这些变量只在定时流水线里存在,你在本地或在手动触发的流水线里 echo 出来都是空的。误以为是配置没生效,其实配置是对的。
坑三:手动运行用的是你的权限,不是所有者的权限。 计划页面上的运行按钮每分钟最多点一次,它按操作人身份执行。这意味着"我手动跑没问题、定时跑就失败"这类现象,根因几乎总是权限差异,而不是 cron。
坑四:项目或群组被标记删除时,定时流水线立即停止运行。 归档项目同理需要留意,迁移或清理前先导出计划清单。
常见问题
Q1:定时流水线能用受保护环境的部署审批吗?
能,但取决于所有者权限。计划以所有者身份运行,只要所有者有权限访问该受保护环境,审批规则照常生效。这也是为什么服务账号必须给足环境权限,否则夜间部署会卡在审批门口无人处理。
Q2:计划数量有上限吗?
有,受项目流水线计划数量上限约束。到达上限时需要先删除未使用的计划才能新增。清理时优先按 cron 和 owner 两个维度找重复项。
Q3:怎么确认某条计划真的按预期跑了?
两个位置:计划列表里能看到上次运行时间;更可靠的是在流水线列表按来源 schedule 过滤,看是否有缺失的时间点。只看"上次运行"会掩盖"中间漏跑"。
下一步
如果你现在的定时流水线还散落在各个项目、由不同的人建、cron 全挤在整点,建议先做两件事:跑一次上面那段 gitlab-psql 导出,看清分布;再把所有计划的所有权收归到一个服务账号,并统一显式设置 cron_timezone。
想直接上手验证调度行为?可以在测试项目里用 API 建一条
cron=*/5 * * * *的计划,配合CI_PIPELINE_SOURCE == "schedule"的rules观察触发边界。极狐GitLab 的定时流水线属于基础版即可使用的功能,私有化部署和 JihuLab.com 都支持。

