Skip to content

Commit b8e9413

Browse files
clubandersonclaude
andcommitted
✨ Document and test client-only install pattern (controller.enabled=false)
Add documentation for the recommended workflow of installing the WVA controller once and adding models via controller.enabled=false. This pattern avoids resource collisions in shared namespaces and is already used in CI for multi-model e2e tests. Add Helm template tests that verify: - Client-only mode produces only VA, HPA, Service, ServiceMonitor - Client-only mode excludes all controller infrastructure - controllerInstance label propagates to VA and HPA metric selectors - Full install includes all controller resources - Minimal install (no VA, no HPA) works correctly Closes #746 (asks 2 and 3) Signed-off-by: Andy Anderson <andy@clubanderson.com> Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Andrew Anderson <andy@clubanderson.com>
1 parent aff15ca commit b8e9413

File tree

2 files changed

+198
-0
lines changed

2 files changed

+198
-0
lines changed

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,58 @@ 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+
```
65+
66+
```bash
67+
# Step 2: Add Model A (only VA + HPA resources, no controller)
68+
helm upgrade -i wva-model-a ./charts/workload-variant-autoscaler \
69+
--namespace wva-system \
70+
--set controller.enabled=false \
71+
--set va.enabled=true \
72+
--set hpa.enabled=true \
73+
--set llmd.namespace=team-a \
74+
--set llmd.modelName=my-model-a \
75+
--set llmd.modelID="meta-llama/Llama-3.1-8B"
76+
```
77+
78+
```bash
79+
# Step 3: Add Model B (same controller manages both models)
80+
helm upgrade -i wva-model-b ./charts/workload-variant-autoscaler \
81+
--namespace wva-system \
82+
--set controller.enabled=false \
83+
--set va.enabled=true \
84+
--set hpa.enabled=true \
85+
--set llmd.namespace=team-b \
86+
--set llmd.modelName=my-model-b \
87+
--set llmd.modelID="meta-llama/Llama-3.1-70B"
88+
```
89+
90+
With `controller.enabled=false`, the chart deploys only:
91+
92+
- **VariantAutoscaling** CR (if `va.enabled=true`)
93+
- **HorizontalPodAutoscaler** (if `hpa.enabled=true`)
94+
- **Service** and **ServiceMonitor** for vLLM metrics (if `vllmService.enabled=true`)
95+
- **RBAC** ClusterRoles for VA resources (viewer, editor, admin)
96+
97+
It skips all controller infrastructure: Deployment, ServiceAccount, ConfigMaps, RBAC
98+
bindings, leader election roles, and prometheus CA certificates.
99+
100+
> **Tip:** If using `controllerInstance` for metric isolation, set the same value on both the
101+
> controller install and all model installs so the HPA metric selectors match.
102+
51103
### Canary/Blue-Green Deployments
52104

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

0 commit comments

Comments
 (0)