CI/CD Integration (15 minutes)⚓
Automate your load tests to run on every deployment or on a schedule.
What you'll learn⚓
- How to run load tests in CI/CD pipelines
- How to create unique test runs per pipeline execution
- How to collect and store test results
- How to fail a pipeline on performance regression
Prerequisites⚓
- Completed the Your First Load Test tutorial
- A Kubernetes cluster accessible from CI (kubeconfig or service account)
- GitHub repository (for GitHub Actions example)
The scenario⚓
You want weekly load tests against your staging environment, plus on-demand tests before releases. Tests should fail the pipeline if error rate exceeds 1%.
Step 1: Prepare the test script⚓
We'll reuse the ecommerce_test.py from Tutorial 1. Store it in your repository:
your-repo/
├── .github/
│ └── workflows/
│ └── load-test.yaml
└── tests/
└── locust/
└── ecommerce_test.py
The test script should be checked into your repository at tests/locust/ecommerce_test.py (same content from Tutorial 1). This ensures version control and consistency across pipeline runs.
Step 2: Create the GitHub Actions workflow⚓
Create .github/workflows/load-test.yaml:
name: Weekly Load Test
on:
schedule:
- cron: '0 2 * * 1' # Every Monday at 2 AM UTC
workflow_dispatch: # Allow manual trigger from GitHub UI
jobs:
load-test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.28.0'
- name: Configure kubeconfig
run: |
# Create .kube directory
mkdir -p $HOME/.kube
# Write kubeconfig from GitHub secret
echo "${{ secrets.KUBECONFIG }}" > $HOME/.kube/config
# Verify connectivity
kubectl cluster-info
- name: Create/update test ConfigMap
run: |
# Use --dry-run + kubectl apply for idempotency
kubectl create configmap ecommerce-test \
--from-file=tests/locust/ecommerce_test.py \
--dry-run=client -o yaml | kubectl apply -f -
- name: Deploy LocustTest with unique name
run: |
# Generate unique test name with timestamp
TEST_NAME="ecommerce-ci-$(date +%Y%m%d-%H%M%S)"
kubectl apply -f - <<EOF
apiVersion: locust.io/v2
kind: LocustTest
metadata:
name: ${TEST_NAME}
spec:
image: locustio/locust:2.43.3
testFiles:
configMapRef: ecommerce-test
master:
command: |
--locustfile /lotest/src/ecommerce_test.py
--host https://api.staging.example.com
--users 100
--spawn-rate 10
--run-time 5m
worker:
command: "--locustfile /lotest/src/ecommerce_test.py"
replicas: 5
EOF
# Store test name for later steps
echo "TEST_NAME=${TEST_NAME}" >> $GITHUB_ENV
- name: Wait for test completion
run: |
# Timeout accounts for pod scheduling + 5m run time + autoquit grace period
TIMEOUT=600 # 10 minutes
ELAPSED=0
while true; do
PHASE=$(kubectl get locusttest ${TEST_NAME} -o jsonpath='{.status.phase}')
case "$PHASE" in
Succeeded) echo "Test passed"; break ;;
Failed) echo "Test failed"; exit 1 ;;
*) echo "Phase: $PHASE -- waiting..."; sleep 10 ;;
esac
ELAPSED=$((ELAPSED + 10))
if [ $ELAPSED -ge $TIMEOUT ]; then
echo "Timed out after ${TIMEOUT}s"; exit 1
fi
done
- name: Collect test results
if: always() # Run even if test fails
run: |
# Get master pod logs (job/ selector works because master always has exactly 1 pod)
kubectl logs job/${TEST_NAME}-master > results.log
# Get test status YAML
kubectl get locusttest ${TEST_NAME} -o yaml > test-status.yaml
# Display summary
echo "=== Test Summary ==="
kubectl get locusttest ${TEST_NAME}
- name: Check for performance regression
run: |
# Extract final statistics from master logs
# NOTE: This grep regex is version-specific to Locust's log format.
# For more robust failure detection, consider using --exit-code-on-error
# in the Locust command, which makes Locust exit with code 1 on errors.
FAILURE_RATE=$(kubectl logs job/${TEST_NAME}-master | \
grep -oP 'Total.*Failures.*\K[\d.]+%' | tail -1 | sed 's/%//')
echo "Failure rate: ${FAILURE_RATE}%"
# Fail pipeline if error rate > 1%
if (( $(echo "$FAILURE_RATE > 1.0" | bc -l) )); then
echo "ERROR: Failure rate ${FAILURE_RATE}% exceeds threshold of 1%"
exit 1
fi
echo "✓ Performance acceptable: ${FAILURE_RATE}% failures"
- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: load-test-results-${{ env.TEST_NAME }}
path: |
results.log
test-status.yaml
retention-days: 30
- name: Cleanup test resources
if: always()
run: kubectl delete locusttest ${TEST_NAME} --ignore-not-found
Key workflow features:
- Scheduled execution:
cron: '0 2 * * 1'runs every Monday at 2 AM - Manual trigger:
workflow_dispatchallows on-demand runs from GitHub UI - Unique test names:
$(date +%Y%m%d-%H%M%S)prevents name conflicts - Idempotent ConfigMap:
--dry-run=client -o yaml | kubectl applyupdates existing ConfigMap - Result collection: Logs and YAML saved as GitHub artifacts
- Regression detection: Pipeline fails if error rate exceeds 1%
Step 3: Configure GitHub secrets⚓
Your workflow needs a kubeconfig to access the cluster. Add it as a GitHub secret:
Copy your kubeconfig content:
In GitHub: Go to Settings → Secrets and variables → Actions → New repository secret
Create secret
KUBECONFIGand paste the raw kubeconfig YAML content (GitHub secrets handle arbitrary text, no encoding needed)
Security note: For production, use a service account with minimal permissions instead of full admin kubeconfig.
Step 4: Run and verify⚓
Trigger the workflow manually⚓
- Go to Actions tab in GitHub
- Select Weekly Load Test workflow
- Click Run workflow → Run workflow
Monitor execution⚓
Watch the workflow run in real-time. Check each step's output:
- ✓ ConfigMap created/updated
- ✓ LocustTest deployed with unique name
- ✓ Test completed successfully
- ✓ Performance within acceptable limits
Check artifacts⚓
After completion (or failure), download artifacts:
- Click on the workflow run
- Scroll to Artifacts section
- Download
load-test-results-ecommerce-ci-YYYYMMDD-HHMMSS.zip
The artifact contains: - results.log — Full Locust master output with statistics - test-status.yaml — Complete LocustTest CR status
Step 5: Make tests fail on regression⚓
The workflow already includes regression detection in the "Check for performance regression" step. It:
- Extracts error rate from master logs using
grep - Compares to threshold (1% in this example)
- Fails pipeline with
exit 1if threshold exceeded
Customizing thresholds:
# Fail on error rate > 1%
if (( $(echo "$FAILURE_RATE > 1.0" | bc -l) )); then
exit 1
fi
# Or fail on response time > 500ms
AVG_RESPONSE=$(kubectl logs job/${TEST_NAME}-master | \
grep -oP 'Average response time.*\K[\d.]+' | tail -1)
if (( $(echo "$AVG_RESPONSE > 500" | bc -l) )); then
exit 1
fi
What you learned⚓
✓ How to run Kubernetes-based load tests in CI/CD pipelines ✓ How to create unique test names for traceability ✓ How to collect and store test results as artifacts ✓ How to fail pipelines on performance regression ✓ How to configure scheduled and manual test execution
Next steps⚓
- Production Deployment — Configure production-grade load tests
- Configure resources — Optimize pod resource allocation
- Set up OpenTelemetry — Export metrics for long-term analysis