Skip to content

Commit 6563a7f

Browse files
authored
✨ Document and test client-only install pattern (controller.enabled=false) (llm-d#747)
Add multi-controller isolation docs and Helm template tests for client-only install workflow. Signed-off-by: Andy Anderson <andy@clubanderson.com> Signed-off-by: Andrew Anderson <andy@clubanderson.com>
1 parent 5982616 commit 6563a7f

2 files changed

Lines changed: 214 additions & 0 deletions

File tree

docs/user-guide/multi-controller-isolation.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,59 @@ wva:
4848
4949
Each team's controller only manages VAs in their designated namespace with matching labels.
5050
51+
### Adding Models to an Existing Controller
52+
53+
The most common multi-model pattern uses a **single controller** with multiple model
54+
installations. Install the controller once, then add models using `controller.enabled=false`:
55+
56+
```bash
57+
# Step 1: Install the WVA controller (once per cluster or namespace)
58+
helm upgrade -i wva-controller ./charts/workload-variant-autoscaler \
59+
--namespace wva-system \
60+
--create-namespace \
61+
--set controller.enabled=true \
62+
--set va.enabled=false \
63+
--set hpa.enabled=false \
64+
--set vllmService.enabled=false
65+
```
66+
67+
```bash
68+
# Step 2: Add Model A (only VA + HPA resources, no controller)
69+
helm upgrade -i wva-model-a ./charts/workload-variant-autoscaler \
70+
--namespace wva-system \
71+
--set controller.enabled=false \
72+
--set va.enabled=true \
73+
--set hpa.enabled=true \
74+
--set llmd.namespace=team-a \
75+
--set llmd.modelName=my-model-a \
76+
--set llmd.modelID="meta-llama/Llama-3.1-8B"
77+
```
78+
79+
```bash
80+
# Step 3: Add Model B (same controller manages both models)
81+
helm upgrade -i wva-model-b ./charts/workload-variant-autoscaler \
82+
--namespace wva-system \
83+
--set controller.enabled=false \
84+
--set va.enabled=true \
85+
--set hpa.enabled=true \
86+
--set llmd.namespace=team-b \
87+
--set llmd.modelName=my-model-b \
88+
--set llmd.modelID="meta-llama/Llama-3.1-70B"
89+
```
90+
91+
With `controller.enabled=false`, the chart deploys only:
92+
93+
- **VariantAutoscaling** CR (if `va.enabled=true`)
94+
- **HorizontalPodAutoscaler** (if `hpa.enabled=true`)
95+
- **Service** and **ServiceMonitor** for vLLM metrics (if `vllmService.enabled=true`)
96+
- **RBAC** ClusterRoles for VA resources (viewer, editor, admin)
97+
98+
It skips all controller infrastructure: Deployment, ServiceAccount, ConfigMaps, RBAC
99+
bindings, leader election roles, and prometheus CA certificates.
100+
101+
> **Tip:** If using `controllerInstance` for metric isolation, set the same value on both the
102+
> controller install and all model installs so the HPA metric selectors match.
103+
51104
### Canary/Blue-Green Deployments
52105

53106
Test new WVA versions alongside production:
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
Copyright 2025.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package chart_test
18+
19+
import (
20+
"os"
21+
"os/exec"
22+
"strings"
23+
"testing"
24+
)
25+
26+
const chartPath = "../../charts/workload-variant-autoscaler"
27+
28+
// helmTemplate runs "helm template" with the given set values and returns the rendered output.
29+
func helmTemplate(t *testing.T, releaseName string, setValues map[string]string) string {
30+
t.Helper()
31+
32+
args := []string{"template", releaseName, chartPath}
33+
for k, v := range setValues {
34+
args = append(args, "--set", k+"="+v)
35+
}
36+
37+
cmd := exec.Command("helm", args...)
38+
cmd.Stderr = os.Stderr
39+
out, err := cmd.Output()
40+
if err != nil {
41+
t.Fatalf("helm template failed: %v", err)
42+
}
43+
return string(out)
44+
}
45+
46+
// TestClientOnlyInstall verifies that controller.enabled=false produces only
47+
// workload-specific resources (VA, HPA, Service, ServiceMonitor, RBAC ClusterRoles)
48+
// and excludes all controller infrastructure.
49+
func TestClientOnlyInstall(t *testing.T) {
50+
output := helmTemplate(t, "wva-model-b", map[string]string{
51+
"controller.enabled": "false",
52+
"va.enabled": "true",
53+
"hpa.enabled": "true",
54+
"llmd.namespace": "team-b",
55+
"llmd.modelName": "my-model",
56+
"llmd.modelID": "meta-llama/Llama-3.1-8B",
57+
"vllmService.enabled": "true",
58+
})
59+
60+
// Resources that MUST be present in client-only mode
61+
mustContain := []string{
62+
"kind: VariantAutoscaling",
63+
"kind: HorizontalPodAutoscaler",
64+
"kind: Service",
65+
"kind: ServiceMonitor",
66+
}
67+
for _, resource := range mustContain {
68+
if !strings.Contains(output, resource) {
69+
t.Errorf("client-only install should contain %q", resource)
70+
}
71+
}
72+
73+
// Resources that MUST NOT be present (controller infrastructure).
74+
// Note: "kind: Deployment" appears inside scaleTargetRef blocks (VA, HPA),
75+
// so we check for controller-specific markers instead.
76+
mustNotContain := []struct {
77+
marker string
78+
reason string
79+
}{
80+
{"kind: ServiceAccount", "controller service account should be excluded"},
81+
{"leader-election", "leader election RBAC should be excluded"},
82+
{"controller-manager", "controller manager resources should be excluded"},
83+
{"prometheus-ca", "prometheus CA configmaps should be excluded"},
84+
}
85+
for _, check := range mustNotContain {
86+
if strings.Contains(output, check.marker) {
87+
t.Errorf("client-only install should NOT contain %q: %s", check.marker, check.reason)
88+
}
89+
}
90+
}
91+
92+
// TestFullInstall verifies that controller.enabled=true (default) produces
93+
// controller infrastructure in addition to workload resources.
94+
func TestFullInstall(t *testing.T) {
95+
output := helmTemplate(t, "wva-full", map[string]string{
96+
"controller.enabled": "true",
97+
"va.enabled": "true",
98+
"hpa.enabled": "true",
99+
})
100+
101+
mustContain := []string{
102+
"kind: Deployment",
103+
"kind: ServiceAccount",
104+
"kind: VariantAutoscaling",
105+
"kind: HorizontalPodAutoscaler",
106+
"leader-election",
107+
"controller-manager",
108+
}
109+
for _, resource := range mustContain {
110+
if !strings.Contains(output, resource) {
111+
t.Errorf("full install should contain %q", resource)
112+
}
113+
}
114+
}
115+
116+
// TestClientOnlyNoVA verifies that controller.enabled=false with va.enabled=false
117+
// and hpa.enabled=false produces minimal output (only service/servicemonitor/RBAC).
118+
func TestClientOnlyNoVA(t *testing.T) {
119+
output := helmTemplate(t, "wva-minimal", map[string]string{
120+
"controller.enabled": "false",
121+
"va.enabled": "false",
122+
"hpa.enabled": "false",
123+
"vllmService.enabled": "true",
124+
})
125+
126+
if strings.Contains(output, "kind: VariantAutoscaling") {
127+
t.Error("should not contain VariantAutoscaling when va.enabled=false")
128+
}
129+
if strings.Contains(output, "kind: HorizontalPodAutoscaler") {
130+
t.Error("should not contain HPA when hpa.enabled=false")
131+
}
132+
if strings.Contains(output, "kind: Deployment") {
133+
t.Error("should not contain Deployment when controller.enabled=false")
134+
}
135+
}
136+
137+
// TestClientOnlyControllerInstance verifies that controllerInstance label
138+
// is applied to VA resources in client-only mode.
139+
func TestClientOnlyControllerInstance(t *testing.T) {
140+
output := helmTemplate(t, "wva-model-c", map[string]string{
141+
"controller.enabled": "false",
142+
"va.enabled": "true",
143+
"hpa.enabled": "true",
144+
"wva.controllerInstance": "my-team",
145+
"llmd.namespace": "team-c",
146+
"llmd.modelName": "my-model",
147+
})
148+
149+
if !strings.Contains(output, "kind: VariantAutoscaling") {
150+
t.Fatal("should contain VariantAutoscaling")
151+
}
152+
if !strings.Contains(output, "wva.llmd.ai/controller-instance: \"my-team\"") {
153+
t.Error("VA should have controller-instance label matching controllerInstance value")
154+
}
155+
if !strings.Contains(output, `controller_instance: "my-team"`) {
156+
t.Error("HPA metric selector should filter by controller_instance")
157+
}
158+
if strings.Contains(output, "controller-manager") {
159+
t.Error("should not contain controller Deployment in client-only mode")
160+
}
161+
}

0 commit comments

Comments
 (0)