3. 部署频率度量实战
部署频率(Deployment Frequency,DORA 四指标之一)衡量的是“单位时间内成功交付到目标环境的部署次数”。它看起来简单,但在 GitLab 中真正落地时,第一个难题不是算频率,而是什么算一次部署。同一个项目里,一次合并可能触发多条流水线,一条流水线可能包含 build、test、deploy 多个 stage,而 deploy 又可能同时打到 staging 和 production。如果不先把“部署事件”的口径定清楚,后面算出来的频率会随流水线结构调整而剧烈波动,失去可比性。
一、先定义部署事件:从流水线到环境
在 GitLab 中,部署事件最可靠的来源是 deployments API,而不是 pipelines API。原因是流水线只表示“跑了一次 CI”,而 deployment 记录明确绑定了 environment、ref、sha、status 和 created_at,天然对应“某次代码被部署到某个环境”。因此建议采用如下口径:
- 部署事件:一条
status = success的 deployment 记录,且其environment属于生产环境集合(如production、prod、prd)。 - 去重规则:同一
environment+ 同一sha在短时间内重复出现时,只保留最早一条,避免重试或回滚造成重复计数。 - 时间归属:以 deployment 的
created_at(或finished_at)为准,统一转换为 UTC+8 后再按自然日/周聚合。 - 排除项:
status为failed、canceled、blocked的记录不计入;非生产环境(如 review app、feature 环境)不计入生产部署频率。
这里有一个容易踩的坑:GitLab 的 deployments 接口默认按项目维度返回,跨项目统计需要遍历 group 下所有项目。如果团队有几十个项目,逐个项目拉取会非常慢,因此实际脚本中通常先用 /groups/:id/projects 拿到项目列表,再并发请求各项目的 deployments。
二、部署频率的定义与计算方式
部署频率通常有两种表达:次数/天 或 次数/周。对于大多数团队,按天聚合再取周平均更稳定,因为周末部署少会拉低单日值。计算逻辑可以写成:
部署频率(次/天) = 统计周期内成功生产部署事件数 / 统计周期天数
部署频率(次/周) = 统计周期内成功生产部署事件数 / (统计周期天数 / 7)
例如某项目在 2024-06-01 至 2024-06-30(30 天)内共有 42 条成功生产部署记录,则日频率为 42/30 = 1.4 次/天,周频率约为 9.8 次/周。这个数值本身没有绝对好坏,关键是同一口径下的趋势变化:是上升、下降还是持平,以及变化是否与发布流程、团队规模、迭代节奏相关。
三、编写查询脚本:从 GitLab API 到聚合结果
下面给出一个可直接改造的 Python 脚本示例。它使用 python-gitlab 库,按 group 遍历项目,拉取指定时间范围内的 deployments,过滤生产环境并去重,最后输出按天聚合的 CSV。示例中的 group、token、环境名均为占位参数,需替换为实际值。
import gitlab, csv, datetime as dt
from collections import defaultdict
GITLAB_URL = "https://gitlab.example.com"
TOKEN = "glpat-xxxxxxxx"
GROUP = "my-group"
PROD_ENVS = {"production", "prod", "prd"}
START = dt.date(2024, 6, 1)
END = dt.date(2024, 6, 30)
gl = gitlab.Gitlab(GITLAB_URL, private_token=TOKEN)
group = gl.groups.get(GROUP)
daily = defaultdict(int)
seen = set()
for project in group.projects.list(iterator=True, include_subgroups=True):
p = gl.projects.get(project.id)
deployments = p.deployments.list(
updated_after=START.isoformat() + "T00:00:00Z",
updated_before=END.isoformat() + "T23:59:59Z",
iterator=True
)
for d in deployments:
if d.status != "success":
continue
if d.environment.get("name") not in PROD_ENVS:
continue
key = (project.id, d.environment["name"], d.sha)
if key in seen:
continue
seen.add(key)
day = d.created_at[:10]
daily[day] += 1
with open("deploy_freq.csv", "w", newline="") as f:
w = csv.writer(f)
w.writerow(["date", "deploy_count"])
for day in sorted(daily):
w.writerow([day, daily[day]])
total = sum(daily.values())
days = (END - START).days + 1
print(f"总部署次数={total}, 日频率={total/days:.2f}, 周频率={total/days*7:.2f}")
脚本中有三个关键判断点:status == "success" 保证只统计成功部署;environment.name in PROD_ENVS 保证只统计生产环境;(project.id, env, sha) 三元组去重保证同一提交不会因重试被重复计数。运行后得到的 CSV 可以直接导入 Grafana、Metabase 或 Excel 做趋势图。
四、可视化趋势与结果判读
拿到按天数据后,建议画两条线:一条是每日部署次数柱状图,一条是 7 日移动平均线。柱状图用于看波动和异常,移动平均线用于看趋势。如果某周移动平均明显低于前几周,需要结合发布冻结、节假日、重大故障等事件一起看,而不是直接下结论说“交付效率下降”。
从图中可以看到,柱状图在月中出现几次尖峰,移动平均线整体平稳。如果某天柱状图突然很高,可能是批量补发或合并发布;如果连续多天为 0,则要检查是否处于发布冻结期或流水线故障。判读时始终回到原始 deployment 记录,确认是真实部署行为变化,而不是统计口径或 API 分页导致的漏数。
五、落地时的几个实用建议
- 统一环境命名:在
.gitlab-ci.yml中固定生产环境名,避免prod、production、prd混用导致过滤遗漏。 - 保留原始明细:聚合结果用于看趋势,原始 deployment 记录用于追溯,两者都要留存。
- 定时任务化:用 GitLab CI Scheduled Pipeline 或外部 cron 每天拉取一次,写入数据库,避免每次手工跑脚本。
- 与变更失败率联动:部署频率单独看容易误导,建议和变更失败率、恢复时间一起看,才能判断“快而稳”还是“快而不稳”。
部署频率度量的核心不是追求一个漂亮数字,而是建立一条可重复、可解释、可对比的数据链路:从 GitLab deployment 记录出发,经过明确口径的过滤与去重,聚合成日/周频率,再用趋势图辅助判断。只要口径稳定,即使绝对值不高,趋势变化也能为交付改进提供可靠依据。