Self-hosted service
grafana/k6-operator avatar
grafana/k6-operator

k6 Operator turns a load test into a Kubernetes object

An operator for running distributed k6 tests.

800 stars215 forksGoApache-2.0

At a glance

What is it?
The grafana/k6-operator is a controller runtime that lets you describe a distributed k6 test as a TestRun custom resource and, if you pay for Grafana Cloud, a set of in-cluster load generators as a PrivateLoadZone, and it is the operator layer rather than k6 itself that this project is.
Who is it for?
k6 Operator suits a performance engineer who needs to run k6 from inside the cluster under test, because load generators on the same network remove the network variable from the measurement, and it suits anyone who wants load tests scheduled and run like any other Kubernetes workload.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Two CRDs, one of which needs a paid account

The operator introduces two custom resource definitions and they are not equivalent in what they demand. The TestRun CRD is a representation of a single k6 test executed once, and it supports various configuration options so you can adapt it to different Kubernetes setups, with the common options described in the Grafana documentation and the full list in the generated CRD reference in the repository at docs/crd-generated.md. The PrivateLoadZone CRD is a representation of a load zone, which the README defines as a k6 term for a set of nodes within a cluster designated to execute k6 test runs. That one is integrated with Grafana Cloud k6 and requires a Grafana Cloud account, with a setup guide and a separate page for the billing details. So the free half of the project is the TestRun resource, and the PrivateLoadZone resource is the path to a managed offering, which is worth knowing before you design around it.

The controller runs k6 inside the cluster it tests

The reason to run load generators inside the cluster is that it removes a variable. A test that runs from a laptop measures the laptop's network path to the target, and when you are testing a service's behaviour under load, the difference between a remote generator and a local one is often larger than the difference between the two load levels you wanted to compare. The operator puts the k6 process into the cluster so the traffic originates where the application runs. The repository layout shows the pieces of that: there are three Dockerfiles, Dockerfile.controller, Dockerfile.runner and Dockerfile.starter, which correspond to the controller binary, the pod that executes the k6 script and the pod that starts it, and a cmd/ directory plus internal/ and pkg/ for the Go implementation. The go.mod pulls in the k6 module itself at go.k6.io/k6/v2, so the operator does not reimplement the load generator, it schedules it.

Controller runtime, Helm chart and a bundle for distribution

This is a kubebuilder style operator project and the files show the standard parts. The api/ directory holds the custom resource types, the internal/ directory holds the controllers, and the PROJECT file describes the scaffolding metadata. On the packaging side there is a charts/ directory for the Helm chart, a bundle.yaml produced by the operator SDK bundle tooling, a catalog-info.yaml for the OperatorHub catalog, and a Dockerfile.controller as the default image build target. The Makefile carries the version and the image coordinates.

bash
VERSION ?= 1.7.0
IMG_NAME ?= ghcr.io/grafana/k6-operator
IMG_TAG ?= latest
DOCKERFILE ?= "Dockerfile.controller"
BUNDLE_IMG ?= k6-controller-bundle:$(VERSION)
CRD_OPTIONS ?= "crd:maxDescLen=0"

The images are pushed to ghcr.io/grafana/k6-operator, and the bundle target produces a k6-controller-bundle image tagged with the operator version. The Makefile also wires up a test target that runs the unit tests with the race detector, excluding the e2e package, and a separate e2e target that deploys the operator into a cluster first, which tells you the e2e tests are real cluster tests rather than mocks.

Local development uses devspace, and the toolchain is current

The developer workflow has its own entry point. There is a devspace.yaml and a devspace_start.sh in the repository root, which is how you get a controller running against a local cluster with a hot reload loop, and the docs/ directory plus an mkdocs.yml means the documentation is written in Markdown and built with mkdocs. The Go module targets go 1.26.0 with a toolchain directive of go1.26.9, and it depends on controller-runtime v0.25.2 with client-go and apimachinery at v0.37.1, which puts it squarely on current Kubernetes libraries. Test dependencies are ginkgo and gomega for the e2e suite and testify for unit tests, and the k6 cloud client, github.com/grafana/k6-cloud-openapi-client-go, is a pseudo-version pinned to a specific commit, which is the part of the dependency list that will need attention when you upgrade.

Samples and support live outside the repository

The repository carries its own examples rather than pointing at a separate gallery, with common samples for both the TestRun and the PrivateLoadZone CRDs in the config/samples and e2e/ folders. That is worth knowing because the e2e directory is where the operator is exercised against a real cluster, so the samples there are the closest thing to a reference deployment. Support follows the Grafana structure rather than the project structure. For problems encountered while using the operator, the README directs you to a troubleshooting guide in the Grafana k6 documentation, and for questions about how to use it, to the Grafana community forum, while the contributing guide covers opening issues and development. There is also a link to Grafana Office Hours on load testing with k6 Private Load Zones, which is a video session rather than documentation. The split means the repository holds the operator and the samples, and everything a user needs to read is in the vendor documentation, so a self hosted setup means following two sets of documents.

Where the operator is the wrong tool

The main limit is the overhead. A controller needs a running Kubernetes API server, a deployment, RBAC, CRD installation and a controller watching for changes, and for a load test that takes thirty seconds that is a lot of machinery to own for the sake of a couple of lines in a CI job. If your testing needs are a single script hitting a service, the k6 binary on a runner pod or even in a CI job is the cheaper answer, and the operator adds a control plane rather than removing one. The second limit is the dependency on Grafana Cloud for one of the two resources, so the PrivateLoadZone path is a paid feature, and a team that wants private load zones without a Grafana Cloud account has to plan around that. The third is operational scope. The operator is a Kubernetes controller, so it inherits the entire Kubernetes upgrade question, and a controller runtime pinned to a specific client-go version is something you will have to revisit on every cluster upgrade, unlike a binary that only has to keep working.

Editorial conclusion

k6 Operator suits a performance engineer who needs to run k6 from inside the cluster under test, because load generators on the same network remove the network variable from the measurement, and it suits anyone who wants load tests scheduled and run like any other Kubernetes workload. It is the wrong tool for a single machine test, where the k6 binary alone is simpler, and it is the wrong tool if you cannot run a controller, since the whole design is built on the operator pattern. Before adopting it, read the TestRun configuration options documented by Grafana, and note that the PrivateLoadZone path depends on a Grafana Cloud account, so the self-hosted route is the TestRun side of the project. The last push was on 2026-10-21 and the current release is v1.7.0 from 2026-10-15.

Frequently asked questions

What does the grafana k6-operator do?

It is a Kubernetes operator for running distributed k6 tests in your cluster, introducing two CRDs: TestRun, a representation of a single k6 test executed once, and PrivateLoadZone, a representation of a set of nodes in the cluster designated to execute k6 test runs.

Does the PrivateLoadZone custom resource require a paid service?

The README states that PrivateLoadZone is integrated with Grafana Cloud k6 and requires a Grafana Cloud account, and it links to a setup guide and to a page covering k6 invoice billing details. The TestRun resource is the part that does not need it.

How do I install the k6 Operator?

The README refers to the Grafana documentation page Install k6 Operator for the installation instructions rather than listing commands, and points to the Grafana k6 OSS docs for the operator documentation. Common samples for both CRDs are in the config/samples and e2e/ folders of the repository.

How is the k6 Operator built and tested?

The Makefile defaults to Dockerfile.controller and builds an image at ghcr.io/grafana/k6-operator, with a bundle target producing k6-controller-bundle. The unit test target runs go test with the race detector over the packages excluding e2e, and the e2e target deploys the operator before running the end-to-end tests.

Official sources

  1. grafana/k6-operator on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/grafana-k6-operator.svg)](https://hysenlabs.com/projects/grafana-k6-operator)

Was this useful?