Some checks failed
Veza CI / Backend (Go) (push) Failing after 4m55s
Veza CI / Rust (Stream Server) (push) Successful in 5m37s
Security Scan / Secret Scanning (gitleaks) (push) Failing after 1m16s
E2E Playwright / e2e (full) (push) Failing after 12m18s
Veza CI / Frontend (Web) (push) Failing after 15m31s
Veza CI / Notify on failure (push) Successful in 3s
End of W4. Capacity validation gate before launch : sustain 1650 VU
concurrent (100 upload + 500 streaming + 1000 browse + 50 checkout)
on staging without breaking p95 < 500 ms or error rate > 0.5 %.
Acceptance bar : 3 nuits consécutives green.
- scripts/loadtest/k6_mixed_scenarios.js : 4 parallel scenarios via
k6's executor=constant-vus. Per-scenario p95 thresholds layered on
top of the global gate so a single-flow regression doesn't get
masked. discardResponseBodies=true (memory pressure ; we assert
on status codes + latency, not payload). VU counts overridable via
UPLOAD_VUS / STREAM_VUS / BROWSE_VUS / CHECKOUT_VUS env vars for
local runs.
* upload : 100 VU, initiate + 10 × 1 MiB chunks (10 MiB tracks).
* streaming : 500 VU, master.m3u8 → 256k playlist → 4 .ts segments.
* browse : 1000 VU, mix 60% search / 30% list / 10% detail.
* checkout : 50 VU, list-products + POST orders (rejected at
validation — exercises auth + rate-limit + Redis state, doesn't
burn Hyperswitch sandbox quota).
- .github/workflows/loadtest.yml : Forgejo Actions nightly cron
02:30 UTC. workflow_dispatch lets the operator override duration
+ base_url for ad-hoc capacity drills. Pre-flight GET /api/v1/health
aborts before consuming runner time when staging is already down.
Artifacts : k6-summary.json (30d retention) + the script itself.
Step summary annotates p95/p99 + failed rate so the Action listing
shows the verdict at a glance.
- docs/PERFORMANCE_BASELINE.md §v1.0.9 W4 Day 20 : scenarios table,
thresholds, local-run command, operating notes (token rotation,
upload-scenario approximation, staging-only guard rail), Grafana
cross-reference, acceptance gate spelled out.
Acceptance (Day 20) : workflow file is valid YAML ; k6 script parses
clean (Node test acknowledges k6/* imports as runtime-provided, the
rest of the syntax checks). Real green-night accumulation requires
the workflow running on staging — that's a deployment milestone, not
a code change.
W4 verification gate progress : Lighthouse PWA / HLS ABR / faceted
search / HAProxy failover / k6 nightly capacity all wired ; W4 = done.
W5 (pentest interne + game day + canary + status page) up next.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
127 lines
5.4 KiB
YAML
127 lines
5.4 KiB
YAML
name: k6 nightly load test
|
|
|
|
# v1.0.9 W4 Day 20 — runs the mixed-scenarios k6 script against the
|
|
# staging environment every night at 02:30 UTC. The acceptance gate
|
|
# is "pass green 3 nuits consécutives" before flipping a release —
|
|
# the artifact uploaded by this workflow carries the JSON summary
|
|
# the operator inspects.
|
|
#
|
|
# Scope deliberately narrow : runs ONLY on staging, NEVER on prod.
|
|
# A separate manually-triggered workflow (workflow_dispatch) covers
|
|
# pre-launch capacity drills with a longer ramp.
|
|
|
|
on:
|
|
schedule:
|
|
# 02:30 UTC = 04:30 CEST — minimal overlap with the e2e nightly
|
|
# at 03:00 UTC and well before any business-hours traffic on
|
|
# staging. Scheduled runs use the default branch (main).
|
|
- cron: "30 2 * * *"
|
|
workflow_dispatch:
|
|
inputs:
|
|
duration:
|
|
description: "Duration per scenario (e.g. 5m, 15m, 1h)"
|
|
required: false
|
|
default: "5m"
|
|
type: string
|
|
base_url:
|
|
description: "Override staging URL"
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
|
|
env:
|
|
GIT_SSL_NO_VERIFY: "true"
|
|
# Defaults — override via workflow_dispatch input or repo vars.
|
|
DEFAULT_BASE_URL: "https://staging.veza.fr"
|
|
|
|
jobs:
|
|
loadtest:
|
|
name: k6 mixed scenarios (1650 VU steady)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install k6
|
|
run: |
|
|
set -euo pipefail
|
|
sudo gpg -k
|
|
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
|
|
--keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
|
|
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \
|
|
| sudo tee /etc/apt/sources.list.d/k6.list
|
|
sudo apt-get update
|
|
sudo apt-get install -y k6
|
|
k6 version
|
|
|
|
- name: Resolve test inputs
|
|
id: inputs
|
|
run: |
|
|
set -euo pipefail
|
|
BASE_URL="${{ github.event.inputs.base_url }}"
|
|
if [ -z "$BASE_URL" ]; then
|
|
BASE_URL="${{ vars.STAGING_BASE_URL || env.DEFAULT_BASE_URL }}"
|
|
fi
|
|
DURATION="${{ github.event.inputs.duration }}"
|
|
if [ -z "$DURATION" ]; then
|
|
DURATION="5m"
|
|
fi
|
|
echo "base_url=$BASE_URL" >> "$GITHUB_OUTPUT"
|
|
echo "duration=$DURATION" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Pre-flight — staging is reachable
|
|
run: |
|
|
set -euo pipefail
|
|
url="${{ steps.inputs.outputs.base_url }}/api/v1/health"
|
|
echo "::notice::Pre-flight GET $url"
|
|
status=$(curl -k -sS --max-time 10 -o /dev/null -w "%{http_code}" "$url" || echo "000")
|
|
if [ "$status" != "200" ]; then
|
|
echo "::error::Staging /health returned $status — aborting load test."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Run k6 mixed scenarios
|
|
id: run
|
|
env:
|
|
BASE_URL: ${{ steps.inputs.outputs.base_url }}
|
|
DURATION: ${{ steps.inputs.outputs.duration }}
|
|
USER_TOKEN: ${{ secrets.STAGING_LOADTEST_TOKEN }}
|
|
STREAM_TRACK_ID: ${{ vars.STAGING_LOADTEST_TRACK_ID || '00000000-0000-0000-0000-000000000001' }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -z "$USER_TOKEN" ]; then
|
|
echo "::warning::STAGING_LOADTEST_TOKEN secret is empty — auth-required scenarios will record 401s as errors."
|
|
fi
|
|
k6 run --quiet \
|
|
--summary-export=k6-summary.json \
|
|
scripts/loadtest/k6_mixed_scenarios.js
|
|
|
|
- name: Upload k6 summary artifact
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: k6-summary-${{ github.run_number }}
|
|
path: |
|
|
k6-summary.json
|
|
scripts/loadtest/k6_mixed_scenarios.js
|
|
retention-days: 30
|
|
|
|
- name: Annotate thresholds in summary
|
|
if: always()
|
|
run: |
|
|
set -euo pipefail
|
|
if [ ! -f k6-summary.json ]; then
|
|
echo "::warning::No summary artifact — k6 likely failed before write."
|
|
exit 0
|
|
fi
|
|
echo "## k6 load test summary" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
jq -r '
|
|
(.metrics.http_reqs.values.count // 0) as $reqs
|
|
| (.metrics.http_req_failed.values.rate // 0) as $err
|
|
| (.metrics.http_req_duration.values["p(95)"] // 0) as $p95
|
|
| (.metrics.http_req_duration.values["p(99)"] // 0) as $p99
|
|
| "- requests: \($reqs)\n- failed rate: \($err * 100 | round)/100 %\n- p95: \($p95 | round) ms\n- p99: \($p99 | round) ms"
|
|
' k6-summary.json >> "$GITHUB_STEP_SUMMARY"
|