Web Application Load Testing with k6
k6 is a modern load testing tool enabling developers and DevOps engineers to test web applications and APIs at scale using JavaScript-based test scripts. With built-in support for realistic traffic patterns, custom metrics, and detailed performance reporting, k6 transforms load testing from a specialist task into an accessible, developer-friendly process. This guide covers k6 installation, script development, and performance testing strategies.
Table of Contents
- k6 Installation and Setup
- Basic Load Testing
- k6 Script Development
- Virtual User Scenarios
- Thresholds and Checks
- Performance Metrics
- HTML Reporting
- Conclusion
k6 Installation and Setup
Installing k6
# Ubuntu/Debian installation
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb 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
# CentOS/RHEL installation
sudo yum install -y https://dl.k6.io/rpm/repo.rpm
sudo yum install -y k6
# Or from source
wget https://github.com/grafana/k6/releases/download/v0.45.0/k6-v0.45.0-linux-amd64.tar.gz
tar xzf k6-v0.45.0-linux-amd64.tar.gz
sudo mv k6-v0.45.0-linux-amd64/k6 /usr/local/bin/
# Verify installation
k6 --version
First Test
# Run simple HTTP request test
k6 run --vus 1 --duration 10s - <<EOF
import http from 'k6/http';
export default function() {
http.get('http://example.com');
}
EOF
# Parameters:
# --vus: Virtual users
# --duration: Test duration
Basic Load Testing
Simple Load Test Script
cat > example_test.js <<'EOF'
import http from 'k6/http';
import { sleep } from 'k6';
export default function() {
http.get('http://example.com/api/users');
sleep(1);
}
EOF
# Run with 10 VUs for 30 seconds
k6 run --vus 10 --duration 30s example_test.js
# Expected output shows:
# http_reqs: Request count
# http_req_duration: Response time
# http_req_failed: Failed requests
# Checks: Custom validation metrics
Ramping Load Pattern
# Script with ramping VU pattern
cat > ramp_test.js <<'EOF'
import http from 'k6/http';
import { sleep } from 'k6';
export let options = {
stages: [
{ duration: '30s', target: 10 }, // Ramp to 10 VUs
{ duration: '1m30s', target: 50 }, // Ramp to 50 VUs
{ duration: '2m', target: 100 }, // Ramp to 100 VUs
{ duration: '1m', target: 0 }, // Ramp down to 0 VUs
],
};
export default function() {
http.get('http://example.com/api/users');
sleep(1);
}
EOF
k6 run ramp_test.js
Spike Testing
# Sudden traffic spike scenario
cat > spike_test.js <<'EOF'
import http from 'k6/http';
export let options = {
stages: [
{ duration: '10s', target: 100 }, // Normal load
{ duration: '1s', target: 1000 }, // Spike!
{ duration: '10s', target: 100 }, // Back to normal
{ duration: '1s', target: 0 }, // Stop
],
};
export default function() {
http.get('http://example.com/api/data');
}
EOF
k6 run spike_test.js
k6 Script Development
Making HTTP Requests
cat > http_requests.js <<'EOF'
import http from 'k6/http';
export default function() {
// GET request
let res = http.get('http://example.com/api/users');
console.log(`Response status: ${res.status}`);
// POST request with payload
const payload = JSON.stringify({
name: 'Test User',
email: '[email protected]',
});
const params = {
headers: {
'Content-Type': 'application/json',
},
};
http.post('http://example.com/api/users', payload, params);
// PUT/PATCH request
http.put('http://example.com/api/users/1', payload, params);
// DELETE request
http.del('http://example.com/api/users/1');
}
EOF
k6 run http_requests.js
Request Validation
cat > validate_requests.js <<'EOF'
import http from 'k6/http';
import { check, sleep } from 'k6';
export default function() {
const res = http.get('http://example.com/api/users');
// Validate response
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
'content type correct': (r) => r.headers['Content-Type'].includes('application/json'),
'response contains data': (r) => r.body.includes('users'),
});
sleep(1);
}
EOF
k6 run validate_requests.js
Dynamic Variables and Parameterization
cat > parameterized_test.js <<'EOF'
import http from 'k6/http';
import { sleep } from 'k6';
const BASE_URL = 'http://example.com';
const USER_IDS = ['1', '2', '3', '4', '5'];
export default function() {
// Random user selection
const userId = USER_IDS[Math.floor(Math.random() * USER_IDS.length)];
const res = http.get(`${BASE_URL}/api/users/${userId}`);
console.log(`Fetched user: ${userId}`);
sleep(Math.random() * 3 + 1); // Random sleep 1-4 seconds
}
EOF
k6 run parameterized_test.js
Virtual User Scenarios
Multi-Scenario Test
cat > multi_scenario.js <<'EOF'
import http from 'k6/http';
import { check, sleep } from 'k6';
const BASE_URL = 'http://example.com';
export let options = {
scenarios: {
// Browse scenario: Light usage
browse: {
executor: 'constant-vus',
vus: 10,
duration: '3m',
env: { SCENARIO: 'browse' },
},
// Search scenario: Medium load
search: {
executor: 'ramping-vus',
stages: [
{ duration: '1m', target: 20 },
{ duration: '3m', target: 20 },
{ duration: '1m', target: 0 },
],
env: { SCENARIO: 'search' },
},
// Spike scenario: Sudden load
spike: {
executor: 'variable-looping-vus',
startVUs: 0,
stages: [
{ duration: '30s', target: 100 },
{ duration: '1m', target: 0 },
],
env: { SCENARIO: 'spike' },
},
},
};
export default function() {
const scenario = __ENV.SCENARIO;
if (scenario === 'browse') {
http.get(`${BASE_URL}/api/users`);
} else if (scenario === 'search') {
http.get(`${BASE_URL}/api/search?q=test`);
} else if (scenario === 'spike') {
http.post(`${BASE_URL}/api/orders`, JSON.stringify({ items: [] }));
}
sleep(1);
}
EOF
k6 run multi_scenario.js
Thresholds and Checks
Setting Performance Thresholds
cat > thresholds_test.js <<'EOF'
import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = {
vus: 10,
duration: '1m',
thresholds: {
// HTTP request duration thresholds
'http_req_duration': [
'p(95) < 500', // 95% of requests < 500ms
'p(99) < 1000', // 99% of requests < 1000ms
'max < 2000', // No request > 2000ms
],
// HTTP request failure threshold
'http_req_failed': [
'rate < 0.1', // Less than 10% failure rate
],
// Custom check threshold
'checks': [
'rate > 0.95', // 95%+ checks pass
],
},
};
export default function() {
const res = http.get('http://example.com/api/users');
check(res, {
'status 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
EOF
k6 run thresholds_test.js
# Failed thresholds cause non-zero exit code
echo "Exit code: $?"
Performance Metrics
Custom Metrics
cat > custom_metrics.js <<'EOF'
import http from 'k6/http';
import { Counter, Trend, Rate, Gauge } from 'k6/metrics';
// Define custom metrics
const myCounter = new Counter('my_counter');
const myTrend = new Trend('my_trend');
const myRate = new Rate('my_rate');
const myGauge = new Gauge('my_gauge');
export default function() {
const res = http.get('http://example.com/api/users');
// Record metrics
myCounter.add(1); // Increment counter
myTrend.add(res.timings.duration); // Track duration trend
myRate.add(res.status === 200); // Track success rate
myGauge.add(Math.random() * 100); // Set gauge value
}
EOF
k6 run custom_metrics.js
# View metrics in output
# my_counter.......................: X
# my_trend..........................avg=Yms
# my_rate...........................X%
# my_gauge..........................X
Detailed Metrics Report
# Run test with JSON output
k6 run --out json=results.json http_requests.js
# Analyze results with jq
cat results.json | jq '.data.samples[] | select(.metric=="http_req_duration") | .value' | \
jq -s 'add/length' # Calculate average
# Summary statistics
cat results.json | jq '.data.samples | group_by(.metric) | map({metric: .[0].metric, samples: length})'
HTML Reporting
Generating HTML Reports
# Install k6-reporter
npm install -g @kevinsullivan/k6-reporter
# Run test and generate report
k6 run --out json=results.json multi_scenario.js
# Convert to HTML
k6-reporter results.json --title "Load Test Report"
# View report
open summary.html
Grafana Cloud Integration
cat > grafana_test.js <<'EOF'
import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = {
vus: 5,
duration: '5m',
ext: {
loadimpact: {
projectID: 12345, // Your Grafana Cloud project ID
name: 'API Load Test',
},
},
};
export default function() {
const res = http.get('http://example.com/api/users');
check(res, {
'status is 200': (r) => r.status === 200,
});
sleep(1);
}
EOF
# Run against Grafana Cloud
export GRAFANA_CLOUD_API_TOKEN="your-token"
k6 cloud grafana_test.js
Conclusion
k6 modernizes load testing through developer-friendly scripting, making performance validation accessible to entire development teams. By implementing realistic scenarios, setting meaningful thresholds, and continuously validating application performance under load, organizations identify bottlenecks before they impact users. Integration with CI/CD pipelines enables performance regression detection, while Grafana Cloud integration provides persistent monitoring capabilities. Whether conducting pre-deployment validation, capacity planning, or ongoing performance monitoring, k6's combination of ease-of-use and powerful capabilities makes it the modern standard for web application load testing.


