The k6 Kubernetes Operator lets you run k6 load tests directly within your Kubernetes cluster, treating them as native Kubernetes Jobs.
Imagine you’re running a load test with k6, but you want it to be managed by Kubernetes. You don’t want to set up your own k6 runners, manage their scaling, or worry about their lifecycle. That’s where the k6 Kubernetes Operator comes in. It acts as a controller that watches for custom resources you define, specifically k6 resources. When it sees one, it spins up the necessary Kubernetes components—like Pods and Jobs—to execute your k6 script.
Let’s see it in action. First, you need to install the operator itself. This is typically done by applying a YAML manifest:
apiVersion: v1
kind: Namespace
metadata:
name: k6
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: k6-controller-manager
namespace: k6
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: k6-metrics-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: k6-system-leader-election-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: k6-metrics-reader
subjects:
- kind: ServiceAccount
name: k6-controller-manager
namespace: k6
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: k6-controller-manager
namespace: k6
spec:
replicas: 1
selector:
matchLabels:
app: k6-controller-manager
template:
metadata:
labels:
app: k6-controller-manager
spec:
serviceAccountName: k6-controller-manager
containers:
- name: manager
image: grafana/k6-operator:v0.3.0 # Use the latest stable version
command:
- /manager
args:
- --leader-election-id=k6-operator-leader-election
- --metrics-bind-address=127.0.0.1:8080
- --health-probe-bind-address=127.0.0.1:9090
ports:
- containerPort: 9090
name: health
- containerPort: 8080
name: metrics
resources:
limits:
cpu: 500m
memory: 128Mi
requests:
cpu: 100m
memory: 64Mi
Once the operator is running in your k6 namespace, you can define a k6 custom resource to run a test. Here’s an example that runs a simple k6 script against http://your-application.example.com:
apiVersion: k6.io/v1
kind: k6
metadata:
name: my-first-test
spec:
# The k6 script to run. Can be inline or a reference to a ConfigMap.
script:
inline: |
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
vus: 10,
duration: '30s',
};
export default function () {
http.get('http://your-application.example.com');
sleep(1);
}
# Optional: specify test runner image
runnerImage: "grafana/k6:v0.45.0" # Use a specific k6 version
# Optional: specify where to send results
# reporter:
# memungkinkan:
# - type: prometheus
# url: "http://prometheus-k8s.monitoring.svc.cluster.local:9090/metrics"
When you apply this manifest (kubectl apply -f my-test.yaml), the k6 operator will:
- Create a Kubernetes
Job: ThisJobwill have a Pod that runs the k6 test. - Mount the k6 script: The
script.inlinecontent is typically packaged into aConfigMapand then mounted into the k6 runner Pod. If you usedscript.configMaporscript.volume, it would mount those instead. - Configure k6: The operator sets up the k6 runner with the specified options (like
vusandduration) and ensures it uses the correct k6 version (runnerImage). - Monitor the Job: It watches the status of the k6
Job. When the Job completes successfully, the k6 custom resource status will be updated. If it fails, the status will reflect the error.
The beauty of this approach is that your load tests become first-class citizens in Kubernetes. You can manage them with kubectl, integrate them into CI/CD pipelines, and leverage Kubernetes’ built-in features like resource limits, network policies, and secrets.
The operator also supports more advanced scenarios. For example, you can distribute the load across multiple k6 instances by setting parallelism in the Job template, or have k6 write results to external systems like Prometheus or Loki via the reporter configuration.
One detail that often trips people up is how k6 scripts are loaded. While inline is convenient for simple tests, for larger or more complex scripts, you’ll want to use script.configMap or script.volume. The operator will then create or reference the necessary Kubernetes ConfigMap or Volume to make your script available to the k6 Pod. This keeps your k6 resource definition clean and separates the test logic from its execution configuration.
If you configure a reporter, the operator will ensure that the k6 runner Pod is set up to send metrics or logs to your specified endpoint. For instance, a prometheus reporter will configure k6 to expose its internal metrics on a port that can be scraped by Prometheus.
After a test completes, you can inspect its status using kubectl get k6 my-first-test -o yaml. You’ll see information about whether the test passed or failed, and potentially links to logs or other relevant resources.
The next step in leveraging k6 within Kubernetes is exploring distributed load testing with the parallelism field and integrating test execution into your GitOps workflows.