Getting started
Install the operator from the published Helm chart, instrument a running app with
the basquin CLI, fuzz it, replay the interesting inputs under load, and read the
result — live images, nothing to build.
1 What you need
- A Kubernetes cluster — kind is the easiest way to get one locally for evaluation.
helmandkubectlon your path.- The
basquinCLI. Install a release binary, or build it from source:
# a) Download a release binary — linux/macOS/Windows × amd64/arm64.
# Swap the suffix for your platform: basquin-{linux,darwin,windows}-{amd64,arm64}(.exe)
curl -sSL -o basquin https://github.com/ianp94/basquin/releases/latest/download/basquin-linux-amd64
chmod +x basquin && sudo mv basquin /usr/local/bin/basquin
# (each release also ships checksums.txt to verify against)
# b) go install — pure Go, nothing to package.
go install github.com/ianp94/basquin/operator/cmd/basquin@latest
Everything below pulls published ghcr.io/ianp94/basquin-* images — there's no
git clone, no Gradle, no local jar in this path.
2 Install the operator
The chart is published to a GitHub Pages Helm repo, and its default images to GitHub Container
Registry — a plain helm install pulls real images at the chart's appVersion,
with nothing to build:
helm repo add basquin https://ianp94.github.io/basquin/charts
helm repo update
helm install basquin basquin/basquin-operator \
--namespace basquin-system --create-namespace --set fullnameOverride=basquin
The operator watches and mutates only its own namespace — it refuses to start cluster-wide.
The standing privilege is a Role/RoleBinding, never a
ClusterRole. To instrument another namespace, install another instance. See the
Operator guide for the two-CRD model this installs.
3 Instrument an app
Point basquin instrument at a Deployment already running in the cluster. It applies
a BasquinTarget, which patches the pod template to load the Basquin agents via an
initContainer + shared volume — no rebuild, no image change, and fully
reversible (deleting the target restores the Deployment exactly):
# 1. Instrument a running app — no rebuild, no image changes.
basquin instrument -n basquin-system --deployment jpetstore \
--jvm-opts-var CATALINA_OPTS --coverage-includes 'org.mybatis.jpetstore.*' --coverage-service --wait
--wait blocks until the target's status.phase reaches
Injected. basquin instrument -h lists every flag; the full field-by-field
reference is in the Operator guide and
OPERATOR-USAGE.md.
4 Run a campaign
A BasquinCampaign drives an already-instrumented target. It runs in one of two
modes — explore first, to discover the interesting inputs, then load, to hammer them:
# 2. Fuzz it: coverage-guided exploration, emitting a corpus of interesting inputs.
# (The campaign name defaults to <target>-campaign, i.e. jpetstore-campaign.)
basquin run -n basquin-system --target jpetstore \
--base-url http://jpetstore-app.basquin-system.svc.cluster.local:8080 \
--iterations 500 --grammar examples/grammar/jpetstore.grammar \
--corpus examples/corpus/jpetstore --watch
# 3. Replay what it found, under load: --corpus-from reuses the corpus step 2 emitted.
basquin run -n basquin-system --name jpetstore-load --mode load --target jpetstore \
--base-url http://jpetstore-app.basquin-system.svc.cluster.local:8080 \
--duration 30m --concurrency 50 --corpus-from jpetstore-campaign --watch
explore (default) mutates requests, keeps the ones that reach new code in the
app's own JaCoCo coverage, and on completion emits its cost-ranked replay corpus as the
<campaign>-corpus-out ConfigMap — which --corpus-from
<campaign> feeds straight into a load run (use --corpus <dir>
instead to replay local files). load replays a saved corpus verbatim at a fixed concurrency for a
duration — no mutation, no coverage — watching the same latency/heap/thread invariants under
sustained traffic. --watch tails the campaign to completion, printing
coverage/findings/dashboard as they land.
5 Read results
Read status straight from the cluster, or open the per-campaign dashboard:
# 4. Read results / open the per-campaign dashboard.
basquin status -n basquin-system
basquin dashboard -n basquin-system --campaign jpetstore-campaign # then open :7070
basquin status renders targets and campaigns together (add --watch to
follow); basquin dashboard finds the dashboard pod, port-forwards it, and prints a
tokenized http://localhost:7070 URL — reads are token-gated, so this is the way to
open it rather than a bare kubectl port-forward. The dashboard outlives the run: it's
garbage-collected with the campaign, not the driver Job, so results stay viewable after the driver
completes.
Reading the status panel
Whether you're watching basquin run --watch, the dashboard, or a standalone runner's
terminal screen, the same rows show up. What they mean:
| Row | What it tells you |
|---|---|
| execs / execs/sec | Iterations completed and throughput. |
| crashes | Genuine faults only — unhandled exceptions and 5xx. Expected input rejections are counted separately as rejected, not here. |
| invariant finds | Iterations that tripped a latency / heap / thread invariant. On an instrumented target these are harvested server-side. |
| rejected | Inputs a target's CrashClassifier declared as expected validation failures — not bugs. |
| corpus / last find | Corpus size and time since the last interesting input, during exploration. |
| coverage % | Real "% of code explored" in the app under test, read from its JaCoCo agent — lights up only when a coverage source reports. |
Want the whole picture — the iteration boundary, invariants, the JVMTI agent, and
coverage-guided exploration? Read How it works. For the full
BasquinTarget/BasquinCampaign field reference, load mode, and the
dashboard's security model, see the Operator guide and
OPERATOR-USAGE.md.
Running without Kubernetes, or hacking on Basquin itself? →
Developing covers building from source and driving the standalone
runner against localhost. Full operator reference →
Operator /
OPERATOR-USAGE.md.