Events¶
Scale Sentry emits Kubernetes Events on every meaningful state transition of a ScaleValidation. Events are the operator's primary UX for "what happened during my run, in the order it happened" without kubectl describe chasing or log scraping.
kubectl get events --field-selector involvedObject.name=my-validation,involvedObject.kind=ScaleValidation -w
Lifecycle at a glance¶
Where each Event reason fires during a run:
sequenceDiagram
autonumber
actor User
participant CR as ScaleValidation
participant C as Controller
participant L as Loadgen Job
participant O as Observer
participant T as Target + HPA
User->>CR: kubectl apply
C->>T: wait for ready replicas
Note over C,T: TargetReadyTimeout (Warning) if readiness never comes
C->>L: create Job
Note over C: LoadgenJobCreated (Normal)
L->>T: h1 / h2 / gRPC load (warmup, then measured phases)
opt spec.disruption enabled
C->>T: delete one healthy pod at the trigger point
Note over C,T: ChaosInjected (Normal) / ChaosSkipped (Warning)
end
O->>T: watch HPA + EndpointSlices, scrape cAdvisor
L-->>O: run report (shared volume)
O-->>C: verdict
alt SLA met, traffic intact
Note over C,CR: VerdictPass (Normal), phase Succeeded
else SLA breached or integrity failed
Note over C,CR: VerdictFail (Warning), phase Failed
end
opt Delete mid-run
User->>CR: kubectl delete
Note over C,L: FinalizerDraining (Normal), child Jobs torn down
end
Error paths not shown above fire on their own transitions: LoadgenJobFailed (pod exited non-zero), LoadgenJobVanished (Job deleted out from under the run), TLSCABundleMissing (referenced ConfigMap absent), and RunErrored (non-recoverable reconciler error). All four land the run in phase Error.
Reason taxonomy¶
The controller writes exactly eleven reasons. They are stable strings; alerts and dashboards can match on them safely.
| Reason | Type | When fired |
|---|---|---|
LoadgenJobCreated |
Normal | The reconciler successfully created the loadgen Job for this CR. |
LoadgenJobFailed |
Warning | The Job's pod terminated non-zero; the load run produced no usable result file. |
LoadgenJobVanished |
Warning | The Job referenced by status disappeared (manual delete, owner GC). Reset state. |
TargetReadyTimeout |
Warning | The target Deployment did not reach ready state before SLA + grace expired. |
TLSCABundleMissing |
Warning | spec.target.tls.caBundle references a ConfigMap that does not exist. |
VerdictPass |
Normal | SLA met, traffic integrity intact. Terminal success. |
VerdictFail |
Warning | SLA breached or traffic-integrity check failed. Terminal failure. |
RunErrored |
Warning | The reconciler hit a non-recoverable error (e.g. API server unreachable). |
FinalizerDraining |
Normal | A user-initiated delete is in-flight; the finalizer is tearing down child Jobs. |
ChaosInjected |
Normal | spec.disruption deleted one healthy target pod at the trigger point. |
ChaosSkipped |
Warning | Disruption was configured but fewer than minReplicasForChaos replicas were healthy. |
Why Events, not a CRD message field¶
Three reasons:
- Ordered timeline: Events carry their own timestamp and
countfield, so a single transition that fires repeatedly is visible without pollutingstatus.messagewith a string that tells you only the latest line. - Selector-friendly:
kubectl get events --field-selector involvedObject.uid=...is built in; replicating that on a CRD-level field would require custom tooling. - Audit-friendly: kube-state-metrics and the audit log both index Events by Reason, so a downstream alert like "no VerdictPass in 24h" is one PromQL query.
What is NOT emitted as an Event¶
- Per-request loadgen output. Volume is too high; the run-summary JSON in the loadgen pod logs is the right channel.
- Reconcile-loop chatter. Only state transitions emit events; idempotent reconciliations are silent.
- Analysis findings. Diagnostics from the analyzers land in
status.diagnostics, not as Events; an alert can match on theVerdictFailreason and inspect status for detail.
Wiring at startup¶
The controller takes its record.EventRecorder from mgr.GetEventRecorderFor("scale-sentry"). The events.v1 API is intentionally not used: its required action and related fields do not fit the single-line reason UX, no FakeRecorder exists for it yet, and the established operators (cert-manager, karpenter) made the same call.
Operators running scale-sentry with a custom manager wire-up should reuse the same legacy recorder rather than chasing the deprecation; see cmd/scale-sentry/controller.go for the canonical setup.