Set up the Test Lab on your own cluster

Provision an isolated test environment using your own Kubernetes cluster.

How-to · Updated 2026-10-17 · Describes release 0.1.164

What you are about to build

You give the platform one kubeconfig. It installs six demo estates, a full observability stack, and wires that stack's alertmanager to post alerts back into your Test Lab workspace. Nothing here asks you to write a manifest.

When it finishes, the lab looks like this:

Namespace What lands there Pods
hotel DeathStarBench hotelReservation 21
boutique Google Online Boutique 12
socialnetwork DeathStarBench socialNetwork 27
pgdemo a small Postgres and the app that pools against it 3
logsearch single-node OpenSearch and a log writer 2
edge an SSH server that answers like a network device 1
observability Prometheus, Alertmanager, Loki, Tempo, Grafana, promtail, kube-state-metrics, blackbox 10

The estates are real applications with real dependency chains, not stubs — that is the point. A case that kills a cache has to produce the same confusing symptoms your production incidents do, or the accuracy number it earns means nothing.

Before you start

Creating the lab workspace needs members.manage. Connecting, validating, provisioning, changing guards and teardown need datasources.manage. Launching or cancelling live runs and replays needs benchmark.manage; reading results and certifying needs benchmark.view.

A cluster you are willing to break. Faults are injected on purpose: pods are killed, deployments are scaled to zero, images are corrupted, network paths are cut. Use a cluster that hosts nothing you care about.

Step 1 — a kubeconfig the platform can build with

Provisioning creates namespaces, deployments, services, cluster roles and custom resources, so this credential has to be cluster-admin. The platform checks: it runs kubectl auth can-i '*' '*' and refuses to start if the answer is anything but yes. A narrower role fails before the first manifest is applied, rather than half way through.

kubectl create namespace lab-access
kubectl create serviceaccount lab-provisioner -n lab-access
kubectl create clusterrolebinding lab-provisioner \
  --clusterrole=cluster-admin \
  --serviceaccount=lab-access:lab-provisioner

# a non-expiring token: a projected one would expire mid-run
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Secret
metadata:
  name: lab-provisioner
  namespace: lab-access
  annotations:
    kubernetes.io/service-account.name: lab-provisioner
type: kubernetes.io/service-account-token
EOF

Then build a kubeconfig from it:

SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
CA=$(kubectl -n lab-access get secret lab-provisioner -o jsonpath='{.data.ca\.crt}')
TOKEN=$(kubectl -n lab-access get secret lab-provisioner -o jsonpath='{.data.token}' | base64 -d)

cat > lab-kubeconfig.yaml <<EOF
apiVersion: v1
kind: Config
clusters: [{name: lab, cluster: {server: ${SERVER}, certificate-authority-data: ${CA}}}]
users: [{name: lab, user: {token: ${TOKEN}}}]
contexts: [{name: lab, context: {cluster: lab, user: lab}}]
current-context: lab
EOF

If the platform runs inside the same cluster, use https://kubernetes.default.svc as the server instead of the public API endpoint — the call never leaves the cluster.

Step 2 — register it as a write-role datasource

Open Agent quality → Test Lab, and on the Lab cluster card choose Connect lab cluster. The form opens on Kubernetes (kubectl + kubeconfig) with the role fixed to Lab provisioning and fault tests · write credential: paste the file, then Save lab connection. No automatic read probe runs; the saved credential's access is not yet verified. For this Test Lab setup, continue to Provision below: the provisioning operation and its log show whether the required write access works.

The role matters more than it looks. A write datasource is a remediation-class credential: it is excluded from every agent registry, so no investigation, x-ray scan or chat turn can see it or use it. Only the Test Lab's own provisioning path executes with it. The Lab refuses a read datasource for exactly this reason — a cluster-admin token an agent could reach for is a different product.

Step 3 — provision

Test Lab → Provision, choose the datasource, and confirm. A few minutes. This is the only click; everything in the next section happens inside it.

Before applying anything the platform lists every namespace on the cluster and flags the ones a lab does not normally have. That list is advisory — it exists so you notice before the estates land if you pointed at the wrong cluster.

Three settings decide whether alerts actually come back. Get these right and everything else is automatic:

What Provision does for you

Nothing below is a step you take. It is what the one click does, listed so you know what lands on your cluster:

Two things to know about that last one. These are cluster-internal addresses, so they only work when this platform runs in the same cluster as the lab; if it does not, the provision log names each one it skipped and you add reachable addresses yourself. And the hostnames it did register are added to your workspace's allowed-hosts list — without that, the datasource works for the agent but its Test button reports the SSRF guard, because the guard's job is to stop anyone pointing a datasource at an internal address that was never meant to be reached.

If a chaos install fails, the provision still succeeds. The estate is up and the cases that scale, patch and delete still run; only fault injection is missing, and the log says so. Re-run provision to retry it.

Verify

Then run two or three cases before the full catalogue. A run that scores zero usually means the alerts never arrived, not that the agent was wrong.

Tearing it down

Test Lab → Teardown removes what provisioning created, and only that:

It does not touch your lab kubeconfig, past runs, or their graded results. A namespace still terminating three minutes after its delete ends the job done with a WARNING naming it: the delete was accepted, so run Teardown again to confirm it is gone. When a step fails — a delete the cluster refused, a namespace still terminating on that second run or whose deletion the cluster reports blocked, a route of yours still using a lab connection — the job ends failed and names what is left. Fix the cause and run Teardown again: it picks up where the last one stopped.

Chaos Mesh is deliberately left installed. Provisioning only installs it when the cluster has none, so it may well have been there before this lab — and it is cluster-scoped: uninstalling deletes the chaos CRDs, and with them any experiment anyone else on that cluster is running. The teardown log shows the command instead, with a copy button, so removing it is a decision you make:

helm uninstall chaos-mesh -n chaos-mesh &&   kubectl delete ns chaos-mesh &&   kubectl get crd -o name | grep chaos-mesh.org | xargs -r kubectl delete

The CRD line matters — helm uninstall leaves custom resource definitions behind by design. If Argo CD installed Chaos Mesh there is no helm release, and self-heal puts back whatever you delete: remove its Application from Git instead, after adding the resources-finalizer.argocd.argoproj.io finalizer to it, or the prune leaves the workloads and CRDs running.

Re-provisioning is idempotent, so tearing down between campaigns to stop paying for the nodes is a reasonable habit.

Next steps