level 10 / k6-hands-on

k6 Hands-On

Write and run k6 load tests — virtual users, scenarios, thresholds, and CI integration.

What Is k6?

k6 is a developer-friendly open-source load testing tool. Scripts are written in JavaScript, and it runs as a single binary with no JVM or complex setup. Tests integrate naturally with CI pipelines.

# Install
brew install k6              # macOS
choco install k6             # Windows
docker pull grafana/k6       # Docker

# Run a test
k6 run script.js

Basic Script

// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 10,           // virtual users
  duration: '30s',   // test duration
};

export default function () {
  const res = http.get('https://test.k6.io');

  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });

  sleep(1);  // 1 second think time between iterations
}
k6 run load-test.js
# Output: req/s, p95, p99, error rate, check pass rate

Scenarios

More control than vus + duration:

export const options = {
  scenarios: {
    ramp_up: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 100 },   // ramp to 100
        { duration: '5m', target: 100 },   // hold
        { duration: '2m', target: 0 },     // ramp down
      ],
    },
  },
};

Executor Types

ExecutorUse case
constant-vusFixed users for fixed duration
ramping-vusRamp up/down — load & stress tests
constant-arrival-rateFixed RPS regardless of response time
ramping-arrival-rateRamp RPS — spike tests
per-vu-iterationsEach VU runs N times

Thresholds (Pass/Fail Gates)

export const options = {
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'],  // p95 < 500ms
    http_req_failed: ['rate<0.01'],                  // error rate < 1%
    checks: ['rate>0.95'],                           // 95% checks pass
  },
};

k6 exits with code 1 if any threshold fails — CI pipeline fails automatically.

REST API Load Test

import http from 'k6/http';
import { check } from 'k6';

const BASE_URL = __ENV.BASE_URL || 'https://restful-booker.herokuapp.com';

export const options = {
  scenarios: {
    bookings_load: {
      executor: 'constant-arrival-rate',
      rate: 10,              // 10 RPS
      timeUnit: '1s',
      duration: '2m',
      preAllocatedVUs: 20,
    },
  },
  thresholds: {
    http_req_duration: ['p(95)<800'],
    http_req_failed: ['rate<0.05'],
  },
};

export function setup() {
  // Get auth token once — shared across VUs
  const res = http.post(`${BASE_URL}/auth`, JSON.stringify({
    username: 'admin', password: 'password123',
  }), { headers: { 'Content-Type': 'application/json' } });
  return { token: res.json('token') };
}

export default function ({ token }) {
  const res = http.get(`${BASE_URL}/booking`, {
    headers: { Cookie: `token=${token}` },
  });
  check(res, {
    'bookings returned': (r) => r.status === 200,
    'has results': (r) => r.json().length > 0,
  });
}

k6 in CI (GitHub Actions)

- name: Run load test
  uses: grafana/k6-action@v0.3.1
  with:
    filename: tests/load-test.js
  env:
    BASE_URL: ${{ vars.STAGING_URL }}
    K6_CLOUD_TOKEN: ${{ secrets.K6_CLOUD_TOKEN }}  # optional: stream to Grafana Cloud

Output Formats

k6 run --out json=results.json load-test.js     # raw metrics
k6 run --out csv=results.csv load-test.js       # spreadsheet-friendly
k6 run --out influxdb=http://localhost:8086 ...  # Grafana dashboard
k6 run --out cloud load-test.js                  # Grafana Cloud k6