第 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 | 测试持续时间(如 60s、5m) |
--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 环境。如果必须对生产做测试,请:
- 使用极低负载(如 5 用户);
- 选择业务低谷时段;
- 提前通知运维团队;
- 准备好紧急停止方案。
本章小结
--headless+--csv+--html是 CI 场景的三件套;events.quitting中设置退出码让 CI 正确判定压测结果;- 用独立 Python 脚本做阈值检查比内联 shell 更可控;
- 定时压测用于追踪性能趋势,但务必指向 staging 环境。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. 在 CI 中运行 Locust 时必须加哪个参数?
2. 如何让 GitHub Actions 在 p95 超过 500ms 时标记构建失败?
3. --csv 参数会生成哪些文件?
4. 关于在生产环境执行定时压测,正确的做法是?
🛠️ 动手实践
- 编写一个 GitHub Actions workflow,在你的 FastAPI 项目每次 push 后自动执行 30 秒的 20 并发压测。
- 实现
check_threshold.py脚本,要求支持检查 RPS 下限(如 RPS < 100 则失败)和平均延迟上限。 - 设置一个每周一凌晨 2 点运行的 cron job,对 httpbin.org 做 50 并发 2 分钟的压测,并将结果存为 Artifact。
完成练习后,进入下一章:监控与结果分析。