Skip to content

Architecture Overview

Varroa manages Jenkins controllers as Kubernetes resources. One namespaced Controller creates one Jenkins StatefulSet, Service, persistent volume, mite sidecar, and optional Ingress.

flowchart LR
    U[User or API client] --> F[Dashboard]
    F --> B[BFF API]
    B <--> N[(NATS JetStream)]
    O[Operator] <--> N
    G[Gateway] <--> N
    O --> K[Kubernetes API]
    K --> P[Jenkins pod]
    P --> J[Jenkins]
    P --> M[mite]
    M <-->|gRPC mTLS| G
    M -->|localhost| J
ComponentFunction
OperatorProvisions Jenkins resources.
GatewayTerminates mite mTLS.
BFFServes APIs and activity.
DashboardProvides the browser client.
NATS JetStreamCarries messages and shared state. Components re-read their bus credential on every reconnect, so a rotated password needs no restart.
miteObserves and configures Jenkins.
DexBrokers identity into OIDC.
Update centerServes pinned plugins.

Only the dashboard, BFF, and Jenkins routes need external HTTP access. The gateway remains a cluster-internal Service. The mite initiates its connection, so the control plane does not require inbound access to Jenkins.

The Jenkins plugin, gated by the build record store, sends three kinds of traffic to the mite over a localhost listener: build events, build claims, and console log chunks.

flowchart LR
    PL[Jenkins plugin] -->|localhost| M[mite]
    M <-->|gRPC mTLS| G[Gateway]
    G -->|publish| S[(varroa_builds stream)]
    G <-->|claim request/reply| L[Ledger]
    G -->|append| B[(Blob store)]
    S --> R[Recorder]
    R --> L

Build events travel one way, from the plugin through the mite to the gateway, which publishes them onto the varroa_builds JetStream stream. The recorder consumes that stream and writes runs, stages, and test summaries.

Build claims travel a round trip. A queued build asks the gateway, over the same mite stream, whether it may run. The gateway forwards the request to the recorder’s ledger and returns the ledger’s answer: a granted build number, a refusal, or a request to retry. See Build claims for the claim decisions, the timeout budget, and how to turn the gate on.

Console log chunks travel one way, from the plugin through the mite to the gateway, which appends each chunk straight to the blob store and acknowledges it once the write lands. This path never touches the varroa_builds stream: a chunk that entered JetStream would evict real audit history within days. Archived artifacts follow a separate path, straight from the uploading process to the blob store under the writing cell’s own object prefix. See Build logs and artifacts for the full log path, the live-tail mechanism, and the per-cell artifact prefix.

Activity events publish onto NATS subjects activity.<cluster>.<ns>.<ctrl> and activity.<cluster>._global. Events include lifecycle changes, user actions, and mite-observed changes. Two consumers read that traffic independently. BusFanout subscribes it for live SSE. The recorder’s own JetStream stream, varroa_activity, holds a fixed 72-hour buffer that the recorder drains into the Postgres activity table. GET /activity and MCP list_activity read that table through deps.Backfill, whenever the build record store is enabled. Without it, they answer empty, and live SSE is all that remains.

Job schedules travel the build data plane instead: the mite polls the plugin’s trigger inventory, and the gateway republishes it on the varroa_builds stream into the job_schedules table. See Build records.

Varroa resolves a controller from installation defaults, an optional ControllerClass, and the Controller spec. The controller spec has the highest precedence.

Jenkins configuration comes from a ComposedBundle. Its ordered inputs can reference catalog items, Git repositories, or OCI artifacts. An omitted spec.composedBundleRef selects the built-in varroa-starter bundle. A JenkinsVersionProfile supplies the compatible plugin set and any version-specific JCasC overlay. JenkinsRole and JenkinsRoleBinding resources define Jenkins authorization.

See Composed bundles, Jenkins versions, and Jenkins RBAC.

Read status.phase and status.conditions when operating a controller.

PhaseMeaning
PendingWaiting for reconciliation.
ProvisioningResolving configuration or creating Kubernetes resources.
RunningJenkins is running, but the mite stream is not ready or is lost.
ConnectedThe mite stream is active. This is the normal steady state.
Stoppedspec.powerState: Stopped scaled the StatefulSet to zero.
HibernatedInactivity policy parked the controller.
FailedA blocking provisioning or operation error occurred.

A phase says where the controller is in its lifecycle. It does not say whether the controller is stuck there. Varroa reports “needs attention” separately, on the dashboard, the controllers list, and the detail page. It is derived from these status signals, in precedence order.

SignalShown asMeaning
status.phase: FailedFailedA blocking provisioning or operation error. See the Failed condition.
ReconcileBlocked conditionBlockedThe operator cannot proceed. The bundle is unreadable, or a plugin pin conflicts with the version-profile lock (a cell ignores a pin on job-dsl). The message names the fix.
JenkinsBootFailed conditionBoot failedThe Jenkins container is crash-looping, or its image cannot be pulled. The message carries the exit code and restart count. Read the container logs.
PluginRollFailed conditionPlugin roll failedThe plugins-init step failed.
status.lastApplyResult.succeeded: falseApply failedThe mite could not apply the last desired state.
MiteConnected=False, reason StreamLostMite unreachableThe mite stream is lost. The pod stays in place.

status.lastReconciledAt advances on every reconcile pass that completes without error, in every phase. Read the attention signal first, not this timestamp. Most wedged controllers still reconcile cleanly and so keep a fresh stamp: a Boot failed, Plugin roll failed, or Apply failed controller records the problem in its status and the pass still succeeds. The stamp goes stale only for a Blocked controller, whose pass ends in an error, and during a Failed phase dwell. Use lastReconciledAt for one distinction: a stale value with no attention signal means the operator is not reconciling this controller at all, rather than reconciling it and being blocked.

A lost mite stream keeps the pod and the phase Running. The operator rolls the pod only when the gateway CA or endpoint changed. A cell also rolls after its unready grace period.

Reloadable configuration can apply without replacing the pod. Changes to plugins, images, or other restart-class settings can return the controller to Provisioning while Varroa rolls it.

Terminal window
kubectl get controller <name> -n <namespace>
kubectl describe controller <name> -n <namespace>

Use Troubleshooting when a controller does not reach Connected.

All resources use varroa.dev/v1alpha1.

ResourceScopePurpose
ControllerNamespacedDeclares one Jenkins controller.
ComposedBundle, CatalogSource, CatalogItemNamespacedSupply Jenkins configuration.
PodTemplateNamespacedDefines reusable Kubernetes agents.
ProvisioningDefaults, ControllerClassClusterSupply reusable controller defaults.
JenkinsVersionProfileClusterPins Jenkins and plugin compatibility.
VarroaRole, VarroaRoleBindingClusterAuthorize dashboard and API actions.
JenkinsRole, JenkinsRoleBindingClusterAuthorize actions inside Jenkins.
UpdateCenterClusterDeclares the optional plugin service.

Continue with The mite, Scaling, or Your first controller.