Motivation
LLMKube currently exposes inference latency, error rate, and queue wait metrics (per #409 / PR #410) but has no first-class concept of an SLO. Operators who want to commit to "TTFT p95 ≤ 2s, availability ≥ 99.5% over 28 days" today have to hand-roll Prometheus recording rules and alert thresholds and own the error-budget arithmetic themselves.
For LLMKube to be a credible Tier-1 platform, declarative SLOs and error budgets need to be a built-in capability. This issue scopes a v0.1 of that capability that ships before LLMKube is asked to commit to enterprise SLOs externally.
Proposed approach: integrate Pyrra rather than build a new CRD
Pyrra is a CNCF-incubating project that takes a declarative SLO spec via its own ServiceLevelObjective CRD and generates Prometheus recording rules + alert rules from it. Mature, multi-vendor, and integrates cleanly with kube-prometheus-stack which is already what LLMKube ships.
Why Pyrra over a new LLMKube SLO CRD:
- The error-budget math is non-trivial and Pyrra has been getting it right for years
- We do not want to maintain another CRD when an upstream solves the same problem
- Operators already familiar with Pyrra get zero learning curve
- We can layer LLMKube-specific UX on top (e.g., a
spec.slo shorthand on InferenceService) without owning the underlying engine
Why not a custom LLMKube CRD:
- Reinvents the wheel on burn-rate alerts and multi-window multi-burn-rate logic
- Larger surface area for us to maintain forever
- No interoperability story with non-LLMKube workloads in the same cluster
Why not just document "use Pyrra alongside LLMKube":
- Ergonomic gap: an operator declaring an InferenceService should not have to also write a separate Pyrra CRD that duplicates
service.namespace references
- We can do better than that with a thin shorthand
Scope
v0.1 (this issue)
- Add
spec.slo shorthand to InferenceService with two minimal fields:
name: SLO name (e.g., tinyllama-availability)
objective: target as decimal (e.g., 0.995 for 99.5% availability)
- Optionally a
window defaulting to 28d and an indicator enum (availability | latency_p95 | error_rate)
- Controller renders a Pyrra
ServiceLevelObjective resource on apply, in the same namespace as the InferenceService, owner-referenced for garbage collection
- Helm chart values to enable / disable the Pyrra integration (off by default; opt-in via
pyrra.enabled=true)
- Documentation at
docs/observability/slo.md covering: when to enable, supported indicators, how to view error budgets in Grafana, what happens when budgets burn down
- Reference Grafana dashboard showing error budget per SLO + burn rate
Explicitly out of scope for v0.1 (defer to follow-up issues)
- Auto-remediation on SLO breach (that's #10)
- Custom indicators beyond availability / latency / error rate
- Multi-cluster SLO aggregation
- A LLMKube-native SLO CRD that supplants Pyrra (we genuinely do not need this)
Implementation sketch
// api/v1alpha1/inferenceservice_types.go
type InferenceServiceSpec struct {
// ... existing fields ...
// SLO declares a service-level objective for this inference service.
// When set, the controller creates a Pyrra ServiceLevelObjective
// resource in the same namespace; Pyrra generates the recording +
// alert rules. Requires the cluster to have Pyrra installed.
// +optional
SLO *SLOSpec `json:"slo,omitempty"`
}
type SLOSpec struct {
// Name is the SLO identifier; appears in Pyrra dashboards.
// +kubebuilder:validation:Required
Name string `json:"name"`
// Objective is the target ratio (e.g., 0.995 for 99.5%).
// +kubebuilder:validation:Minimum=0.5
// +kubebuilder:validation:Maximum=0.99999
Objective float64 `json:"objective"`
// Window is the rolling window the objective is measured over.
// +kubebuilder:default="28d"
Window string `json:"window,omitempty"`
// Indicator selects which signal the objective is measured against.
// +kubebuilder:validation:Enum=availability;latency_p95;error_rate
// +kubebuilder:default=availability
Indicator string `json:"indicator,omitempty"`
}
The controller's reconcile path adds a small Pyrra resource render step after the existing Deployment + Service apply. PromQL templates per indicator type are bundled in internal/controller/slo/templates.go so behavior stays predictable.
Testing
Unit
- Render path: given a sample InferenceService with
spec.slo set, controller produces an exactly-equal Pyrra resource with the right PromQL template
- Validation:
objective outside [0.5, 0.99999] is rejected at admission
- Owner reference: deleting the InferenceService garbage-collects the Pyrra resource
Integration / e2e
- Deploy a TinyLlama with
spec.slo.objective=0.995 against the e2e Kind suite (Pyrra installed via Helm dep), confirm the Pyrra resource exists, confirm Pyrra generates Prometheus rules from it, hit the endpoint with induced failures, confirm error budget burn registers
Manual
- Deploy on a real cluster, force an SLO breach, screenshot the Pyrra dashboard for the docs
Acceptance criteria
References
Motivation
LLMKube currently exposes inference latency, error rate, and queue wait metrics (per #409 / PR #410) but has no first-class concept of an SLO. Operators who want to commit to "TTFT p95 ≤ 2s, availability ≥ 99.5% over 28 days" today have to hand-roll Prometheus recording rules and alert thresholds and own the error-budget arithmetic themselves.
For LLMKube to be a credible Tier-1 platform, declarative SLOs and error budgets need to be a built-in capability. This issue scopes a v0.1 of that capability that ships before LLMKube is asked to commit to enterprise SLOs externally.
Proposed approach: integrate Pyrra rather than build a new CRD
Pyrra is a CNCF-incubating project that takes a declarative SLO spec via its own
ServiceLevelObjectiveCRD and generates Prometheus recording rules + alert rules from it. Mature, multi-vendor, and integrates cleanly with kube-prometheus-stack which is already what LLMKube ships.Why Pyrra over a new LLMKube SLO CRD:
spec.sloshorthand onInferenceService) without owning the underlying engineWhy not a custom LLMKube CRD:
Why not just document "use Pyrra alongside LLMKube":
service.namespacereferencesScope
v0.1 (this issue)
spec.sloshorthand toInferenceServicewith two minimal fields:name: SLO name (e.g.,tinyllama-availability)objective: target as decimal (e.g.,0.995for 99.5% availability)windowdefaulting to28dand anindicatorenum (availability|latency_p95|error_rate)ServiceLevelObjectiveresource on apply, in the same namespace as the InferenceService, owner-referenced for garbage collectionpyrra.enabled=true)docs/observability/slo.mdcovering: when to enable, supported indicators, how to view error budgets in Grafana, what happens when budgets burn downExplicitly out of scope for v0.1 (defer to follow-up issues)
Implementation sketch
The controller's reconcile path adds a small Pyrra resource render step after the existing Deployment + Service apply. PromQL templates per indicator type are bundled in
internal/controller/slo/templates.goso behavior stays predictable.Testing
Unit
spec.sloset, controller produces an exactly-equal Pyrra resource with the right PromQL templateobjectiveoutside [0.5, 0.99999] is rejected at admissionIntegration / e2e
spec.slo.objective=0.995against the e2e Kind suite (Pyrra installed via Helm dep), confirm the Pyrra resource exists, confirm Pyrra generates Prometheus rules from it, hit the endpoint with induced failures, confirm error budget burn registersManual
Acceptance criteria
InferenceServiceSpec.SLOfield exists with the documented validationServiceLevelObjectiveresources following the InferenceService lifecycledocs/observability/slo.mdcovers all four supported indicators with examplesdocs/grafana/llmkube-slo.jsonimports cleanlyReferences
charts/llmkube/templates/prometheusrule.yaml(added in PR #410)