Understanding the behavior of software under high traffic is crucial for understanding the impact of changes to your system and making informed optimization decisions.
Load testing is a non-functional test that enables developers and Q/A teams to understand the behavior of an application under load by simulation. This is extremely useful for testing changes before deploying them to production. In this tutorial, we’ll look at K6, a tool developed by Grafana Labs that enables users to write HTTP and GRPC APIs using supported programming languages.
As a tool developed by Grafana Labs, K6 integrates seamlessly with Grafana, a popular open-source data visualization and monitoring platform. This integration lets you visualize and analyze load test results in real-time. In addition, K6 uses JavaScript (ES6 syntax specifically) for writing test scripts, making it easy for developers to learn.
In practical scenarios, K6s can be used in an e-commerce platform to simulate a Black Friday sale event with a high number of virtual users. This helps them identify potential bottlenecks in their system before the sale, preventing slowdowns and lost revenue.
Prerequisites
This tutorial assumes some familiarity with Kubernetes as well as a basic understanding of javascript, In addition, you would need the following installed locally:
Creating a Kubernetes Cluster
We’ll begin by creating a Kubernetes cluster, feel free to skip this step if you have a cluster created already.
For simplicity, we will be doing it from the CLI ↓
civo k3s create --create-firewall --nodes 2 -m --save --switch --wait k6-demo
This will launch a two-node cluster and point your kube-context to the cluster we just launched.
Installing K6
K6 is written in Golang and can be installed using the standalone binary or using the docker image; for Mac users, you can install K6 using brew ↓
brew install k6
For Linux users on Debian-based distros, you can install K6 using ↓
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 k6
At the time of writing, this will install version v0.50.0
of K6. You can verify the installation was successful by running the following command ↓
k6 --version
The output is similar to ↓
k6 v0.50.0 (go1.22.1, darwin/arm64)
Writing HTTP Tests
Through its SDK, K6 allows users to write tests for APIs and browser-based applications; for this demonstration, we will write a test for an HTTP service. We’ll begin by creating a deployment for the HTTP service.
Create a Deployment & Apply Manifest
In a directory of your choice, create a file called deployment.yaml
and add the following code ↓
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami-deployment
spec:
replicas: 1
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: traefik/whoami
ports:
- containerPort: 80
name: web
---
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: web
The manifest above creates a single replica of the whoami server and exposes the deployment on port 80
.
Writing a Load Test
K6 test scripts use JavaScript, while K6 is written in Golang. Using a package called goja, an implementation of ECMAScript 5.1 in pure Go, K6 can execute JavaScript while maintaining performance.
We’ll begin by creating a suite of tests to evaluate different parts of the service we deployed earlier.
Create a file called load.js
and add the following code ↓
import http from 'k6/http';
import { check, group } from 'k6';
const BASE_URL = 'http://localhost:8080';
export default function () {
group('health check test (POST 500)', () => {
// set status of response too 500
http.post(`${BASE_URL}/health`, "500");
const res = http.get(`${BASE_URL}/health`);
check(res, {
'is status 500': (r) => r.status === 500,
});
});
group('response time test', () => {
const res = http.get(`${BASE_URL}/?wait=100ms`);
check(res, {
'check request duration': (r) => res.timings.duration <= 5000,
});
});
group('status code test', () => {
const res = http.get(`${BASE_URL}/ip`)
check(res, {
'verify homepage text': (r) =>
r.body.includes('ip'),
});
});
}
Here’s a breakdown of what’s going on:
Using the group
function from K6 to structure related tests. Each group has a descriptive name enclosed in quotes, like "health check test (POST 500)"
.
The check
function ensures the HTTP requests return the expected results. It takes two arguments: the response object (res
) and an object containing assertions (checks
).
r.body.includes('ip')
verifies if the response body (r.body
) of the request to /ip
includes the word "ip". This type of check is particularly useful for validating if the response contains expected text.
Running the Test
Before executing the test script, we need to expose the application we deployed earlier, for simplicity, we would be exposing the application on port 8080
using Kubectl ↓
kubectl port-forward svc/whoami-service 8080:80
With the tests written, we can execute the code using the run
sub-command. Within the directory, run the following command in your test file ↓
k6 run load.js
Output:
In the test run above, all three groups within the test. The test received a total of 852 bytes of data at an average rate of 179 bytes per second, on average, each test iteration took 1.58 seconds to complete. The fastest iteration finished in 362 milliseconds, while the slowest took 3.74 seconds. 90% of iterations completed within 3.12 seconds, and 95% within 3.43 seconds.
Clean up (Optional)
After completing this tutorial, you may want to clean up the resources you created. To delete the Kubernetes cluster, run the following command ↓
civo k3s delete k6-demo
This command will delete the K6-demo cluster from your Civo account.
Summary
Load testing can be extremely useful for testing the impact of small and significant changes to the software; combining K6s portability with your continuous integration of choice makes it a compelling choice for continuously testing changes in your applications.
If you’re looking to take your K6 scripting further, here are some ideas:
- Check out this blog from K6 on testing gRPC APIs
- Grafana has a great blog explaining the types of load testing
- K6 also supports browser-based testing