Skip to content

SLOPolicy

apiVersion
governance.nanohype.dev/v1alpha1
kind
SLOPolicy
scope
Namespaced
reconciled by
eks-agent-platform
short names
slo

SLOPolicy declares a Platform's service-level objective and turns a page-tier error-budget burn into a platform action: an event on the kill-switch bus and a hold on the tenant's rollout.

SLOPolicySpec declares one service-level objective for a Platform and what the control loop does when its error budget burns too fast. The threshold rules are validated here rather than left to the reconciler because both failure modes are permanent and quiet: a latency SLI with no threshold builds an invalid query on every tick forever, and a threshold on an availability SLI is silently ignored, so the author believes they narrowed an objective that is in fact measuring everything.

What kubectl get shows

ColumnTypeMeaning
Platformstring
Objectivestring
PageBurnstring
Severitystring
Helddate

Spec

  • objectivestringrequired

    Objective is the target good-event ratio as a decimal string ("0.999"). The error budget is 1 - Objective, and the burn rate is the observed error ratio over a window divided by that budget. Modeled as a string for the same reason BudgetPolicy.monthlyUsd is: a float64 round-trip through JSON would perturb the denominator every burn-rate alert divides by. Bounded below 1 because an objective of 1 leaves a zero budget and an infinite burn rate.

  • platformRefobjectrequired

    LocalRef references a CR by name in the same namespace.

    • namestringrequired

  • sliobjectrequired

    SLI is the ratio this objective is measured on.

    • metricstringrequired

      Metric is the base series name in Prometheus form — the OTLP service name with dashes normalized to underscores, without the _errors_total / _requests_total / _request_duration_seconds_* suffix the Type implies (e.g. "incident_response_webhook").

    • typestringrequired

      availabilitylatency

      Type selects the ratio shape. availability divides an errors counter by a requests counter; latency divides a duration histogram's under-threshold bucket by its count.

    • errorSelectorobject

      ErrorSelector narrows the SAME series the denominator counts down to its error subset, for services that emit one dimensioned counter rather than a separate errors counter. With it set, an availability SLI reads <metric>_requests_total{<selector>,<errorSelector>} over <metric>_requests_total{<selector>} rather than dividing a distinct _errors_total series by _requests_total. That is how a counter with a `status` dimension is normally instrumented, and without this the only way to get an availability objective was to emit a second, redundant counter whose sole purpose was to satisfy the query shape. Same rules as Selector: exact-match label names, values escaped, `le` and `__name__` reserved. Availability only — a latency SLI's numerator is a bucket boundary, not a label selection. One limitation, stated because it is the failure this field can produce. The query defaults an empty numerator to zero only when every key named here is present on the series, so a misspelled KEY reads NoData rather than a permanent healthy zero. A misspelled VALUE cannot be distinguished from a service that is genuinely not erroring, and reads zero. Confirm the selector against the metric store once when authoring the objective.

    • selectorobject

      Selector narrows the SLI to a subset of series as an exact-match label set. Rendered into the query as label="value" with values escaped. Keys are Prometheus label names; a raw matcher string is deliberately not accepted.

    • thresholdSecondsstring

      ThresholdSeconds is the histogram bucket boundary a latency SLI counts as good, as a decimal-string of seconds ("0.5"). Required for type=latency, ignored for type=availability. It must name a bucket the histogram actually publishes — an le value with no matching bucket yields an empty result, which the reconciler reports as NoData rather than as a healthy zero.

  • onPageTierBreachstringdefault "None"

    HoldRolloutNone

    OnPageTierBreach is the automated action taken when a page-tier burn-rate window pair trips. HoldRollout — patch a deny syncWindow onto the tenant's ArgoCD AppProject so a bad rollout stops advancing. The window leaves manualSync open, so an operator can still push a fix by hand. Reversed automatically once the burn clears. None — evaluate, publish, and page, but take no cluster action. The default is None because HoldRollout is only an action for a tenant that has its own AppProject. The hold is written to the AppProject named for the Platform, and a tenant synced under the shared `platform` project — which is every ApplicationSet on the default path — has nothing for it to write to. Such a policy reports RolloutHeld=Unknown/AppProjectAbsent, which is honest but arrives after the fact; defaulting to the action that cannot happen makes the API claim more than it delivers. Set HoldRollout deliberately, on a tenant whose Applications resolve to a per-Platform AppProject.