Skip to content

第 18 章 · CI/CD 集成

本章目标:将 Locust 压测嵌入 GitHub Actions 流水线,实现 headless 自动执行、性能阈值判定、结果归档与定时压测。

18.1 Headless 模式运行

CI 环境没有图形界面,Locust 提供了 --headless 参数跳过 Web UI 直接执行:

bash
# 基本用法:10 用户,每秒孵化 2 个,持续 60 秒
locust -f locustfile.py --headless \
  --users 10 \
  --spawn-rate 2 \
  --run-time 60s \
  --host https://staging.example.com

关键参数说明:

参数缩写说明
--headless不启动 Web UI
--users / -u-u目标并发用户数
--spawn-rate / -r-r用户孵化速率(用户/秒)
--run-time / -t-t测试持续时间(如 60s5m
--host-H被测系统的基础 URL

退出码机制

Locust 默认在有请求失败时以非零码退出。配合第 16 章的 events.quitting 监听器设置 process_exit_code = 1,可以让 CI 在失败率或 p95 超标时正确标记构建失败。

18.2 GitHub Actions 工作流

以下是一个完整的 GitHub Actions 配置示例:

yaml
# .github/workflows/load-test.yml
name: Performance Test

on:
  workflow_dispatch:        # 手动触发
    inputs:
      target_url:
        description: 'Target URL'
        required: true
        default: 'https://staging.example.com'
      user_count:
        description: 'Concurrent users'
        required: false
        default: '50'
  schedule:
    - cron: '0 6 * * 1'     # 每周一 UTC 6 点自动执行

jobs:
  load-test:
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install dependencies
        run: pip install locust

      - name: Run load test
        id: locust
        run: |
          locust -f locustfile.py --headless \
            --users ${{ github.event.inputs.user_count || '50' }} \
            --spawn-rate 10 \
            --run-time 120s \
            --host ${{ github.event.inputs.target_url }} \
            --csv results/perf_report \
            --html results/report.html \
            --only-summary
        continue-on-error: true   # 不让失败中断后续归档步骤

      - name: Check performance threshold
        if: always()
        run: |
          python scripts/check_threshold.py \
            --csv results/perf_report_stats.csv \
            --max-p95 500 \
            --max-fail-ratio 0.05

      - name: Upload results as artifact
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: perf-results-${{ github.run_number }}
          path: results/

关键设计决策

  • continue-on-error: true——即使 Locust 退出码非零也继续执行后续步骤(确保结果被上传);
  • 单独的阈值检查步骤——用 Python 脚本精确控制通过/失败逻辑;
  • if: always()——无论前面是否成功都归档结果。

18.3 性能阈值判定脚本

python
#!/usr/bin/env python3
"""check_threshold.py — 从 CSV 结果判断是否超过性能阈值"""
import csv
import argparse
import sys


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--csv", required=True)
    parser.add_argument("--max-p95", type=int, default=500, help="p95 响应时间上限 (ms)")
    parser.add_argument("--max-fail-ratio", type=float, default=0.05, help="最大失败率")
    args = parser.parse_args()

    with open(args.csv) as f:
        reader = csv.DictReader(f)
        rows = [row for row in reader if row["Type"] == "Aggregated"]

    if not rows:
        print("❌ 未找到聚合统计数据")
        sys.exit(1)

    stats = rows[0]
    p95 = int(stats.get("95%", "0"))
    fail_ratio = float(stats.get("Failure Count", "0")) / max(int(stats["Request Count"]), 1)

    passed = True

    if p95 > args.max_p95:
        print(f"❌ p95 响应时间 {p95}ms 超过阈值 {args.max_p95}ms")
        passed = False
    else:
        print(f"✅ p95 响应时间 {p95}ms ≤ {args.max_p95}ms")

    if fail_ratio > args.max_fail_ratio:
        print(f"❌ 失败率 {fail_ratio:.1%} 超过阈值 {args.max_fail_ratio:.0%}")
        passed = False
    else:
        print(f"✅ 失败率 {fail_ratio:.1%}{args.max_fail_ratio:.0%}")

    sys.exit(0 if passed else 1)


if __name__ == "__main__":
    main()

18.4 结果输出与归档

Locust 支持多种格式保存测试结果:

bash
# CSV 输出(3 个文件)
locust --csv results/report --csv-full-history ...
# 生成:
#   results/report_stats.csv       — 汇总统计
#   results/report_failures.csv    — 失败详情
#   results/report_exceptions.csv  — 异常列表
#   results/report_stats_history.csv — 时间序列(需 --csv-full-history)

# HTML 报告(Locust 2.9+)
locust --html results/report.html ...

# JSON 输出(自定义事件钩子)
locust ... 2>&1 | tee raw_output.log

在 CI 中推荐同时保存 CSV 和 HTML——CSV 用于程序化断言,HTML 用于人工审阅。

18.5 定时压测与趋势追踪

利用 GitHub Actions 的 schedule 触发器可以定期自动压测并追踪性能趋势:

  • 每周基线测试:固定负载量级,对比历史数据发现性能退化;
  • 每次发版后冒烟压测:小负载快速验证新版本没有引入严重回归;
  • 夜间深度压测:高负载长时间运行,不影响白天正常使用。

注意事项

不要对生产环境做高强度压测!定时压测应指向 staging 环境。如果必须对生产做测试,请:

  1. 使用极低负载(如 5 用户);
  2. 选择业务低谷时段;
  3. 提前通知运维团队;
  4. 准备好紧急停止方案。

本章小结

  • --headless + --csv + --html 是 CI 场景的三件套;
  • events.quitting 中设置退出码让 CI 正确判定压测结果;
  • 用独立 Python 脚本做阈值检查比内联 shell 更可控;
  • 定时压测用于追踪性能趋势,但务必指向 staging 环境。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. 在 CI 中运行 Locust 时必须加哪个参数?

2. 如何让 GitHub Actions 在 p95 超过 500ms 时标记构建失败?

3. --csv 参数会生成哪些文件?

4. 关于在生产环境执行定时压测,正确的做法是?

🛠️ 动手实践

  1. 编写一个 GitHub Actions workflow,在你的 FastAPI 项目每次 push 后自动执行 30 秒的 20 并发压测。
  2. 实现 check_threshold.py 脚本,要求支持检查 RPS 下限(如 RPS < 100 则失败)和平均延迟上限。
  3. 设置一个每周一凌晨 2 点运行的 cron job,对 httpbin.org 做 50 并发 2 分钟的压测,并将结果存为 Artifact。

完成练习后,进入下一章:监控与结果分析