feat(controller): add GPUQuota validating webhook for InferenceService#1118
Merged
Defilan merged 1 commit intoJul 14, 2026
Merged
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
d4159eb to
2d12716
Compare
…cation Fix 1: Three tests expected DENY when incoming GPU equals the quota cap (0+4=4, cap=4), but quota.Decide uses an exclusive > comparison: current+incoming > cap. Since 4 is NOT > 4, the webhook correctly allows. Changed the three deny tests to genuinely exceed the cap by setting incoming GPU to 5 (5 > 4 -> deny). Fix 2: gpuCount returned only isvc.Spec.Resources.GPU, ignoring replicas. Total GPU usage is gpu-per-pod * replicas (default 1). Added replica multiplication and two regression tests proving gpu:2 * replicas:3 = 6 exceeds a quota cap of 5. Fixes defilantech#1095 Signed-off-by: Foreman Bot <chris@mahercode.io>
2d12716 to
a7cf2fb
Compare
Merged
Merged
7 tasks
eleboucher
pushed a commit
to eleboucher/homelab
that referenced
this pull request
Jul 14, 2026
…5 ➔ 0.9.6) (#1591) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/defilantech/charts/llmkube](https://github.com/defilantech/LLMKube) | patch | `0.9.5` → `0.9.6` | --- ### Release Notes <details> <summary>defilantech/LLMKube (ghcr.io/defilantech/charts/llmkube)</summary> ### [`v0.9.6`](https://github.com/defilantech/LLMKube/blob/HEAD/CHANGELOG.md#096-2026-07-14) [Compare Source](defilantech/LLMKube@v0.9.5...v0.9.6) ##### Features - **api:** add GPUQuota CRD types for multi-tenant GPU governance ([#​1101](defilantech/LLMKube#1101)) ([5dd867a](defilantech/LLMKube@5dd867a)) - **controller:** add GPUQuota status reconciler ([#​1117](defilantech/LLMKube#1117)) ([1d583e4](defilantech/LLMKube@1d583e4)) - **controller:** add GPUQuota validating webhook for InferenceService ([#​1118](defilantech/LLMKube#1118)) ([26804bd](defilantech/LLMKube@26804bd)) - **controller:** add s3:// model source via curl --aws-sigv4 ([#​1098](defilantech/LLMKube#1098)) ([#​1125](defilantech/LLMKube#1125)) ([ed35142](defilantech/LLMKube@ed35142)) - **foreman:** preserve a coder's gate-failed branch instead of discarding it ([#​1115](defilantech/LLMKube#1115)) ([bc39c77](defilantech/LLMKube@bc39c77)) - **helm:** gate InferenceService quota webhook and tenant RBAC behind multitenancy toggle ([#​1122](defilantech/LLMKube#1122)) ([26a994e](defilantech/LLMKube@26a994e)) - **quota:** add GPUQuota admission decision function ([#​1107](defilantech/LLMKube#1107)) ([4897093](defilantech/LLMKube@4897093)) - **runtime:** close out the SGLang kitchen-sink ([#​1060](defilantech/LLMKube#1060)): minor flags, accept thresholds, typed LoRA adapters, LoRAAdapter CRD ([#​1103](defilantech/LLMKube#1103)) ([8edf8bd](defilantech/LLMKube@8edf8bd)) ##### Bug Fixes - **foreman:** honor GateProfile source extensions in scope-overlap issue-ref extraction ([#​1120](defilantech/LLMKube#1120)) ([0c20431](defilantech/LLMKube@0c20431)) ##### Documentation - **samples:** add air-gapped local file-path model source example ([#​1099](defilantech/LLMKube#1099)) ([#​1124](defilantech/LLMKube#1124)) ([913ff3d](defilantech/LLMKube@913ff3d)) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/1591
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds the GPUQuota validating webhook for InferenceService (
internal/controller/inferenceservice_quota_webhook.go), the generatedconfig/webhookmanifest and RBAC, and fake-client unit tests. This is the enforcement half of the multi-tenancy epic (#416): it rejects an InferenceService whose admission would exceed an applicableGPUQuota.InferenceServiceQuotaValidatorimplementsadmission.Validator[*InferenceService](create + update; delete is a no-op). For eachGPUQuotawhose scope covers the incoming InferenceService's namespace, it computes current usage, builds the incoming request, calls the purequota.Decide(#1094), and denies with the returned reason if any quota rejects.failurePolicy: fail,sideEffects: None.Why
Fixes #1095
Types (#1092) and the decision function (#1094) are merged; this is the piece that actually enforces a quota at admission. It calls
Decideso the allow/deny rules stay in one place.How
Usage is computed fresh at admission (list InferenceServices in the quota's scope, sum
resources.gpu * replicas, excluding the incoming object so an update does not double-count), so the decision does not depend on the status reconciler (#1117) having run. Selector scope usesmetav1.LabelSelectorAsSelector(honors matchLabels + matchExpressions).Documented deferrals (consistent with the rest of the epic):
costBudgetBreachedis alwaysfalsefor now: InferCostTokenBudgetintegration is a follow-up. Commented at the call site.GPUQuota.Status.AdmissionDenials: a validating webhook issideEffects: None, so the denial counter is deferred (a metric or a reconciler-observed counter). Commented.Incoming.VRAMBytesis0: InferenceService exposes no VRAM field (mirrors [FEATURE] GPUQuota status reconciler (#416 story 2) #1093).Testing / verification
Verified locally in an isolated worktree:
go build ./...,go vet ./internal/controller/... ./cmd/...cleango test ./internal/controller/fake-client unit tests pass: in-quota admit; over-gpuCount deny (with reason); priority-floor deny; a quota that does not cover the namespace is ignored; an update that stays within quota is admitted; andgpu:2 * replicas:3 = 6counts correctlymake manifests generate chart-crdsthengit statusstable: the webhook manifest and RBAC role are regenerated and in syncgofmtclean;golangci-lint --new-from-rev0 new issuesmake test) passed in the clean-room gateNotes for the reviewer
config/webhook/manifests.yamlships theValidatingWebhookConfiguration, but activation is gated by the Helmmultitenancy.enabledtoggle in [FEATURE] Helm multitenancy toggle + tenant RBAC (#416 story 5) #1096 (default off), so this PR alone does not intercept admission until that toggle lands.current + incoming > cap), so exactly-at-cap is admitted.AI assistance disclosure
Assisted-by: Foreman, the project's local agentic coding harness (coder model qwopus3.6-35B on-prem). Foreman generated the webhook and tests; a review pass I directed found that three of its own "over-gpuCount" tests asserted a deny at exactly-at-cap (which
Decidecorrectly admits) and thatgpuCountignoredreplicas. Those were fed back as a revision cycle that corrected the tests and the replicas math and added agpu*replicascase. I reviewed the result and ran the verification above. I own this change and the review conversation; perAGENTS.mdthe commit carries no attribution trailer and the DCO sign-off is mine.Checklist
go test ./internal/controller/, incl. the full envtest suite in the gate)golangci-lint --new-from-rev0 new issues;gofmtclean)git commit -s) per DCO