如何在 GitLab CI 中通过 Gatling 的 Maven 插件自动生成性能报告并做回归对比

解读

面试官问的是“一条命令能跑起来”之后的工程化落地:

  1. 让 GitLab CI 在每次代码合并请求(MR)或定时任务中自动触发 Gatling 脚本;
  2. 跑完后把 HTML/JSON 报告自动归档,供产品、开发、测试在线查看;
  3. 把本次关键指标(P99、均值、错误率、TPS)与上一次主干构建做差值对比,若劣化超过阈值则流水线失败,并给出可读的回归告警。
    国内公司普遍用私有 GitLab + Maven 仓库,CI Runner 以 Docker-in-Docker 或 Shell 模式部署在 K8s/裸金属上,因此答案必须兼顾“私有网络无公网包拉取”“大报告上传慢”“阈值策略可配置”三大痛点。

知识点

  1. Gatling Maven 插件生命周期:test -> gatling:execute,生成 target/gatling/ 目录。
  2. GitLab CI 产物(artifacts)与流水线缓存(cache)区别:artifacts 跨 job 持久化,cache 跨流水线加速依赖。
  3. GitLab Pages 或 GitLab 自带 artifacts 浏览:国内私有 GitLab 常关 Pages,需把报告打成 zip 上传到自建对象存储或 Nginx 目录。
  4. 回归对比思路:
    a) 解析 simulation.log 或 global_stats.json 提取指标;
    b) 把指标以 JSON 落库(文件、MinIO、InfluxDB、Postgres 均可);
    c) 本次构建读取“基准”指标,计算差值百分比;
    d) 通过 JUnit XML 或 exit code 让 GitLab CI 识别失败。
  5. 阈值策略:相对阈值(+10%)、绝对阈值(P99>500 ms)、错误率=0,支持变量注入以便开发在 MR 中临时调整。
  6. 资源控制:Maven 指定 -Dgatling.core.directory.resources=/opt/conf 把脚本与数据文件分离,CI 镜像提前放入基础数据,减少每次拉取 2 GB 流量。
  7. 国内镜像加速:settings.xml 里把 central 换成阿里云,Docker 镜像基于 maven:3.9-eclipse-temurin-17 预装 Gatling 依赖,降低 Runner 下载时间。

答案

以下示例基于私有 GitLab 16.x、Runner 以 Docker 模式注册到项目,Maven 3.9,Gatling 3.10,脚本放在 src/test/scala 目录。

步骤 1:pom.xml 关键片段

<plugin>
  <groupId>io.gatling</groupId>
  <artifactId>gatling-maven-plugin</artifactId>
  <version>4.8.2</version>
  <configuration>
    <!-- 让 CI 通过 -Dgatling.simulationClass 动态传入 -->
    <simulationClass>${gatling.simulationClass}</simulationClass>
    <!-- 输出 JSON 方便后续解析 -->
    <reportsOnly>false</reportsOnly>
  </configuration>
  <executions>
    <execution>
      <phase>integration-test</phase>
      <goals><goal>execute</goal></goals>
    </execution>
  </executions>
</plugin>

步骤 2:阈值对比工具(示例用 Python,CI 镜像里预装)

#!/usr/bin/env python3
import json, sys, os
THRESHOLDS = {'p99': 10, 'mean': 10, 'errors': 0}  # 百分比
baseline_file = 'baseline.json'
current_file = 'target/gatling/global_stats.json'

def load(f): return json.load(open(f))
base, curr = load(baseline_file), load(current_file)
code = 0
for k in THRESHOLDS:
    delta = (curr[k] - base[k]) / base[k] * 100
    if delta > THRESHOLDS[k]:
        print(f'REGRESSION: {k} +{delta:.1f}% > {THRESHOLDS[k]}%')
        code = 1
if code == 0:
    # 把本次结果存为新的 baseline
    json.dump(curr, open(baseline_file, 'w'), indent=2)
sys.exit(code)

步骤 3:.gitlab-ci.yml

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2 -Dgatling.simulationClass=com.demo.StressSimulation"
  BASELINE_BUCKET: "s3://perf-baseline/$CI_PROJECT_NAME"

stages: [performance, compare]

performance:
  stage: performance
  image: registry.xxx.com/perf/gatling:3.10-maven3.9  # 公司预置镜像
  cache:
    key: m2-$CI_COMMIT_REF_SLUG
    paths: [.m2/]
  script:
    - mvn gatling:execute -q
    - mv target/gatling/* target/report/
  artifacts:
    when: always
    paths: [target/report/]
    expire_in: 30 days
  only: [merge_requests, schedules]

compare:
  stage: compare
  image: registry.xxx.com/perf/python:3.11
  needs: [performance]
  script:
    - aws s3 cp $BASELINE_BUCKET/baseline.json baseline.json || echo "no baseline, will create"
    - python3 scripts/diff.py
    - aws s3 cp baseline.json $BASELINE_BUCKET/baseline.json
  only: [merge_requests, schedules]

步骤 4:效果

  • 每次 MR 会触发性能阶段,跑完产出报告;
  • compare 阶段自动拉取上一条主干 baseline,若 P99 上涨超过 10% 或出现错误,流水线失败,MR 无法合并;
  • 报告通过 artifacts 在线浏览,若公司关闭 Pages,可在 Nginx 目录挂载 /var/opt/gitlab/gitlab-rails/shared/artifacts,实现内网秒开。

拓展思考

  1. 多环境基准:测试、预发、生产配置不同,可在 baseline.json 里按 env 分片,CI 变量注入 ENV=testing 读取对应阈值。
  2. 趋势看板:把 global_stats.json 通过 Telegraf 写入 InfluxDB,Grafana 绘制近 30 天 P99 曲线,比只看单次回归更直观。
  3. 增量场景:只对修改过的接口做基准对比,解析 simulation.log 按请求名分组,避免“全局 P99 被新接口拉低”的误报。
  4. 云原生扩展:Runner 换成 K8s executor,使用 Helm 把 Gatling 脚本打成 ConfigMap,Pod 弹性伸缩到 4-8 并发节点,压测万级 TPS 时不再单机瓶颈。
  5. 合规与审计:在 CI 里加入“性能门禁”标签,合并记录留存于 GitLab 审计事件,方便后续监管抽查。