DEV Community

Matthew Wimpelberg
Matthew Wimpelberg

Posted on

k6: The Tool, The Philosophy, and Your First Test

I've been going deep on k6, Grafana's open-source load and performance testing tool. This is the first in a four-part series documenting that journey, from first principles to a full test suite running against a live Kubernetes environment.

Why k6?

Most load testing tools treat tests as configuration. k6 treats them as code, JavaScript, version-controlled, modular, and reviewable like any other engineering artifact. That's a meaningful philosophical difference. It means your performance tests live in the same repo as your application, go through the same review process, and can be maintained by the same team.

For those of us already in the Grafana ecosystem, there's another compelling reason: k6 is a Grafana Labs product. Test results stream natively into Grafana Cloud. Custom metrics you define in your scripts become queryable Prometheus time series. Your load test data lives alongside your infrastructure metrics, traces, and logs in one place with one query language and one alerting system.

That unified observability story is what made me want to understand k6 deeply, not just at the surface level.

What k6 covers

Most people think of k6 as a load testing tool. It's actually much broader:

  • Smoke testing: Is the app up and returning the right things?
  • Load testing: How does it behave under realistic traffic?
  • Stress testing: Where does it break?
  • Soak testing: Does it degrade over hours?
  • Browser testing: Real Chromium, Web Vitals, frontend performance
  • Synthetic monitoring: Scheduled availability checks from global probe locations

One tool, one scripting language, the full testing lifecycle from development through production monitoring.

Your first k6 script

Once you have k6 installed, a minimal test looks like this:

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

export const options = {
  vus: 10,
  duration: '30s',
  thresholds: {
    http_req_failed:   ['rate<0.01'],
    http_req_duration: ['p(95)<500'],
  },
};

export default function () {
  const res = http.get('https://test-api.k6.io/public/crocodiles/');
  check(res, {
    'status 200':       (r) => r.status === 200,
    'response < 500ms': (r) => r.timings.duration < 500,
  });
  sleep(1);
}
Enter fullscreen mode Exit fullscreen mode

Three things are happening here:

options tells k6 how to run β€” 10 virtual users for 30 seconds, and two thresholds that define pass/fail: less than 1% of requests can fail, and the 95th percentile response time must stay under 500ms. If either threshold is violated, k6 exits with a non-zero code. Your CI pipeline fails. That's your SLO enforced automatically.

checks are per-request assertions. A failing check doesn't stop the test, it increments a failure counter. At the end you see pass rates across all iterations, not just a binary pass/fail.

sleep is think time between requests. Without it, k6 hammers the server as fast as possible with unrealistic load that produces misleading results. Real users read pages. sleep(1) models that.

Run it:

k6 run script.js
Enter fullscreen mode Exit fullscreen mode

The terminal output gives you request count, duration percentiles (p50, p90, p95, p99), error rate, data sent/received, and threshold results. A clean first run looks like this:

βœ“ status 200
βœ“ response < 500ms

http_req_duration: avg=45ms p(95)=112ms
http_req_failed:   0.00%
βœ“ thresholds passed
Enter fullscreen mode Exit fullscreen mode

What's next

My next post will cover building a real test suite with a shared library architecture, smoke testing against a live microservices app running on a homelab Kubernetes cluster, and what happens when your first run doesn't go as expected.

The target app is Google's Online Boutique. It's a realistic e-commerce microservices demo with 11 services.


#k6 #Grafana #LoadTesting #PerformanceTesting #Observability #SRE #Kubernetes

Top comments (0)