From db9ec1bbbf279bc52c7cf595dd20df44d105c910 Mon Sep 17 00:00:00 2001
From: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf
 <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
Date: Sat, 18 Jul 2026 15:42:41 -0400
Subject: [PATCH] fix(chart): support CPU-only relay autoscaling

Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
---
 deploy/charts/buzz/Chart.yaml              |  6 +++---
 deploy/charts/buzz/README.md               | 14 ++++++++------
 deploy/charts/buzz/templates/_validate.tpl |  4 ++--
 deploy/charts/buzz/templates/hpa.yaml      |  2 ++
 deploy/charts/buzz/tests/hpa_test.yaml     | 16 ++++++++++++++++
 deploy/charts/buzz/values.yaml             |  7 ++++---
 6 files changed, 35 insertions(+), 14 deletions(-)

diff --git a/deploy/charts/buzz/Chart.yaml b/deploy/charts/buzz/Chart.yaml
index 75a1a2db57..a557906a8e 100644
--- a/deploy/charts/buzz/Chart.yaml
+++ b/deploy/charts/buzz/Chart.yaml
@@ -7,7 +7,7 @@ description: |
   PostgreSQL and Redis. Configurable for single-node evaluation
   (subcharts on) and HA production (external services, existingSecret).
 type: application
-version: 0.1.4
+version: 0.1.5
 appVersion: "0.1.0"
 home: https://github.com/block/buzz
 sources:
@@ -23,8 +23,8 @@ maintainers:
     url: https://github.com/block
 annotations:
   artifacthub.io/changes: |
-    - kind: added
-      description: Optional CPU and per-pod WebSocket horizontal autoscaling for relay pods.
+    - kind: changed
+      description: Allow CPU-only relay autoscaling when no custom-metrics adapter is installed.
   artifacthub.io/license: Apache-2.0
 
 # Optional eval-only subcharts. Production deploys disable both and point
diff --git a/deploy/charts/buzz/README.md b/deploy/charts/buzz/README.md
index 447a393320..4cf4b22b24 100644
--- a/deploy/charts/buzz/README.md
+++ b/deploy/charts/buzz/README.md
@@ -89,16 +89,18 @@ autoscaling:
   minReplicas: 5
   maxReplicas: 15
   targetCPUUtilizationPercentage: 65
+  websocketMetricEnabled: true
   websocketMetricName: buzz_ws_connections_active
   targetWebsocketConnections: 5000
 ```
 
-CPU scaling requires Kubernetes Metrics Server. WebSocket scaling additionally
-requires a custom-metrics adapter (for example Prometheus Adapter) configured to
-expose the relay's `buzz_ws_connections_active` gauge as a pod metric with the
-name in `websocketMetricName`. The chart creates the HPA but deliberately does
-not install or configure a cluster-wide metrics adapter. Scale-down is gradual
-by default so long-lived WebSocket connections have time to drain.
+CPU scaling requires Kubernetes Metrics Server. Set `websocketMetricEnabled: false`
+for a CPU-only HPA. WebSocket scaling additionally requires a custom-metrics
+adapter (for example Prometheus Adapter) configured to expose the relay's
+`buzz_ws_connections_active` gauge as a pod metric with the name in
+`websocketMetricName`. The chart creates the HPA but deliberately does not
+install or configure a cluster-wide metrics adapter. Scale-down is gradual by
+default so long-lived WebSocket connections have time to drain.
 
 ## Upgrades
 
diff --git a/deploy/charts/buzz/templates/_validate.tpl b/deploy/charts/buzz/templates/_validate.tpl
index 9185e0e41c..946424f9a3 100644
--- a/deploy/charts/buzz/templates/_validate.tpl
+++ b/deploy/charts/buzz/templates/_validate.tpl
@@ -41,8 +41,8 @@ surface at template time regardless of which manifest helm renders first.
   {{- if lt (.Values.autoscaling.maxReplicas | int) (.Values.autoscaling.minReplicas | int) -}}
     {{- fail "autoscaling.maxReplicas must be greater than or equal to autoscaling.minReplicas" -}}
   {{- end -}}
-  {{- if not .Values.autoscaling.websocketMetricName -}}
-    {{- fail "autoscaling.websocketMetricName is required when autoscaling.enabled=true" -}}
+  {{- if and .Values.autoscaling.websocketMetricEnabled (not .Values.autoscaling.websocketMetricName) -}}
+    {{- fail "autoscaling.websocketMetricName is required when WebSocket scaling is enabled" -}}
   {{- end -}}
 {{- end -}}
 
diff --git a/deploy/charts/buzz/templates/hpa.yaml b/deploy/charts/buzz/templates/hpa.yaml
index f9b2d0deea..b95ca7436a 100644
--- a/deploy/charts/buzz/templates/hpa.yaml
+++ b/deploy/charts/buzz/templates/hpa.yaml
@@ -22,6 +22,7 @@ spec:
         target:
           type: Utilization
           averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+  {{- if .Values.autoscaling.websocketMetricEnabled }}
     - type: Pods
       pods:
         metric:
@@ -29,4 +30,5 @@ spec:
         target:
           type: AverageValue
           averageValue: {{ .Values.autoscaling.targetWebsocketConnections | quote }}
+  {{- end }}
 {{- end }}
diff --git a/deploy/charts/buzz/tests/hpa_test.yaml b/deploy/charts/buzz/tests/hpa_test.yaml
index e35957d808..9cdbc8dcd7 100644
--- a/deploy/charts/buzz/tests/hpa_test.yaml
+++ b/deploy/charts/buzz/tests/hpa_test.yaml
@@ -49,6 +49,22 @@ tests:
           path: spec.behavior.scaleDown.stabilizationWindowSeconds
           value: 600
 
+  - it: supports CPU-only autoscaling without a custom metrics adapter
+    template: templates/hpa.yaml
+    set:
+      autoscaling.enabled: true
+      autoscaling.websocketMetricEnabled: false
+    asserts:
+      - lengthEqual:
+          path: spec.metrics
+          count: 1
+      - equal:
+          path: spec.metrics[0].type
+          value: Resource
+      - equal:
+          path: spec.metrics[0].resource.name
+          value: cpu
+
   - it: leaves Deployment replicas to the HPA and disables in-process huddle audio
     template: templates/deployment.yaml
     set:
diff --git a/deploy/charts/buzz/values.yaml b/deploy/charts/buzz/values.yaml
index 03929ad601..7c1dfe4056 100644
--- a/deploy/charts/buzz/values.yaml
+++ b/deploy/charts/buzz/values.yaml
@@ -37,14 +37,15 @@ image:
 replicaCount: 1
 
 # ── Autoscaling ──────────────────────────────────────────────────────────────
-# Requires Metrics Server for CPU and a custom-metrics adapter exposing the
-# pod-level Prometheus gauge named by websocketMetricName. Kubernetes HPA uses
-# the larger replica recommendation, so either CPU or WebSocket load scales out.
+# Requires Metrics Server for CPU. Optional WebSocket scaling additionally
+# requires a custom-metrics adapter exposing its pod-level Prometheus gauge.
+# Kubernetes HPA uses the larger replica recommendation from enabled metrics.
 autoscaling:
   enabled: false
   minReplicas: 5
   maxReplicas: 15
   targetCPUUtilizationPercentage: 65
+  websocketMetricEnabled: true
   websocketMetricName: buzz_ws_connections_active
   targetWebsocketConnections: 5000
   behavior:
