Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions braintrust/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,12 @@ api:

When enabled for a pool, that pool's `replicas` setting is ignored and the HPA controls the replica count. With `api.workloadIsolation.enabled`, ingest and background pools inherit these settings and can override `minReplicas` / `maxReplicas` under `api.workloadIsolation.<pool>.autoscaling`.

## AI Gateway Autoscaling

The AI Gateway autoscales by default at 70% CPU and 75% memory, the same as the AWS module. `minReplicas` defaults to `aiGateway.replicas` (2) and `maxReplicas` to the larger of 6 and `minReplicas`, so a custom `replicas` count is kept as the floor. It needs metrics-server, which GKE and AKS include; without it the gateway stays at `minReplicas`. Set `aiGateway.autoscaling.enabled: false` to use a fixed `aiGateway.replicas`.

Turning on autoscaling for a running gateway briefly drops it to 1 replica, for roughly 30-60 seconds: up to about 15 seconds for the HPA to restore the count, then image pull and the readiness delay. Setting `aiGateway.autoscaling.enabled: false` only postpones this until autoscaling is turned on.

## API workload isolation

`api.workloadIsolation.enabled` creates fixed-capacity `braintrust-api-ingest`
Expand Down
2 changes: 2 additions & 0 deletions braintrust/templates/ai-gateway-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ metadata:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if not (dig "enabled" true (.Values.aiGateway.autoscaling | default dict)) }}
replicas: {{ .Values.aiGateway.replicas }}
{{- end }}
strategy:
type: RollingUpdate
rollingUpdate:
Expand Down
49 changes: 49 additions & 0 deletions braintrust/templates/ai-gateway-hpa.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{{- $autoscaling := .Values.aiGateway.autoscaling | default dict }}
{{- if and .Values.aiGateway.enabled (dig "enabled" true $autoscaling) }}
{{- /* Bounds default from replicas so a custom count is not reset to 2-6. */}}
{{- $minReplicas := int (dig "minReplicas" (dig "replicas" 2 .Values.aiGateway) $autoscaling) }}
{{- $maxReplicas := int (dig "maxReplicas" (max 6 $minReplicas) $autoscaling) }}
{{- $cpuTarget := int (dig "cpu" "targetAverageUtilization" 70 $autoscaling) }}
{{- $memoryTarget := int (dig "memory" "targetAverageUtilization" 75 $autoscaling) }}
{{- $requests := (.Values.aiGateway.resources | default dict).requests | default dict }}
{{- if not (and (ge $minReplicas 1) (ge $maxReplicas $minReplicas) (gt $cpuTarget 0) (gt $memoryTarget 0) $requests.cpu $requests.memory) }}
{{- fail "aiGateway.autoscaling needs 1 <= minReplicas <= maxReplicas, positive CPU and memory targets, and aiGateway.resources.requests for CPU and memory" }}
{{- end }}
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ .Values.aiGateway.name }}
namespace: {{ include "braintrust.namespace" . }}
{{- with (merge (deepCopy .Values.aiGateway.labels) .Values.global.labels) }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add isolation tests for the new label merges

Add cross-template label-isolation coverage for both new resources: the current test only checks that disjoint component and global labels appear on one HPA, while neither the HPA nor PDB is tested for mutation leakage or conflicting-label precedence against other rendered resources. The repository explicitly requires a corresponding isolation test for every new template that merges labels.

AGENTS.md reference: AGENTS.md:L32-L35

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added HPA label-isolation tests in 0443689 (labels-merge-autoscaling_test.yaml): global/gateway precedence on the HPA, and gateway labels absent from another rendered component. The PDB was removed from this PR in a675059.

labels:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ .Values.aiGateway.name }}
minReplicas: {{ $minReplicas }}
maxReplicas: {{ $maxReplicas }}
metrics:
- type: ContainerResource
containerResource:
name: cpu
container: ai-gateway
target:
type: Utilization
averageUtilization: {{ $cpuTarget }}
- type: ContainerResource
containerResource:
name: memory
container: ai-gateway
target:
type: Utilization
averageUtilization: {{ $memoryTarget }}
behavior:
scaleUp:
stabilizationWindowSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Simulate values retained by `helm upgrade --reuse-values` from a chart release
# that predates AI Gateway autoscaling. An explicit null removes the new chart
# default during Helm value coalescing.
aiGateway:
enabled: true
autoscaling: null
319 changes: 319 additions & 0 deletions braintrust/tests/ai-gateway-autoscaling_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
suite: AI Gateway autoscaling
templates:
- ai-gateway-hpa.yaml
- ai-gateway-deployment.yaml
- ai-gateway-configmap.yaml
values:
- __fixtures__/base-values.yaml
release:
namespace: braintrust
tests:
- it: renders no HPA when the gateway is disabled
template: ai-gateway-hpa.yaml
asserts:
- hasDocuments:
count: 0

- it: renders an HPA with AWS-parity defaults
set:
aiGateway.enabled: true
template: ai-gateway-hpa.yaml
asserts:
- isKind:
of: HorizontalPodAutoscaler
- equal:
path: spec.scaleTargetRef.name
value: braintrust-ai-gateway
- equal:
path: spec.minReplicas
value: 2
- equal:
path: spec.maxReplicas
value: 6
- equal:
path: spec.metrics[0].containerResource
value:
name: cpu
container: ai-gateway
target:
type: Utilization
averageUtilization: 70
- equal:
path: spec.metrics[1].containerResource
value:
name: memory
container: ai-gateway
target:
type: Utilization
averageUtilization: 75

- it: lets the HPA own replicas by default
set:
aiGateway.enabled: true
template: ai-gateway-deployment.yaml
asserts:
- notExists:
path: spec.replicas

- it: applies custom bounds and targets
set:
aiGateway.enabled: true
aiGateway.autoscaling.minReplicas: 4
aiGateway.autoscaling.maxReplicas: 40
aiGateway.autoscaling.cpu.targetAverageUtilization: 60
aiGateway.autoscaling.memory.targetAverageUtilization: 80
template: ai-gateway-hpa.yaml
asserts:
- equal:
path: spec.minReplicas
value: 4
- equal:
path: spec.maxReplicas
value: 40
- equal:
path: spec.metrics[0].containerResource.target.averageUtilization
value: 60
- equal:
path: spec.metrics[1].containerResource.target.averageUtilization
value: 80

- it: keeps a custom replicas count as the floor
set:
aiGateway.enabled: true
aiGateway.replicas: 4
template: ai-gateway-hpa.yaml
asserts:
- equal:
path: spec.minReplicas
value: 4
- equal:
path: spec.maxReplicas
value: 6

- it: raises maxReplicas to a replicas count above 6
set:
aiGateway.enabled: true
aiGateway.replicas: 10
template: ai-gateway-hpa.yaml
asserts:
- equal:
path: spec.minReplicas
value: 10
- equal:
path: spec.maxReplicas
value: 10

- it: raises maxReplicas to a minReplicas above 6
set:
aiGateway.enabled: true
aiGateway.autoscaling.minReplicas: 8
template: ai-gateway-hpa.yaml
asserts:
- equal:
path: spec.minReplicas
value: 8
- equal:
path: spec.maxReplicas
value: 8

- it: prefers explicit bounds over replicas
set:
aiGateway.enabled: true
aiGateway.replicas: 10
aiGateway.autoscaling.minReplicas: 3
aiGateway.autoscaling.maxReplicas: 5
template: ai-gateway-hpa.yaml
asserts:
- equal:
path: spec.minReplicas
value: 3
- equal:
path: spec.maxReplicas
value: 5

- it: uses 2 replicas when replicas is null
set:
aiGateway.enabled: true
aiGateway.replicas: null
template: ai-gateway-hpa.yaml
asserts:
- equal:
path: spec.minReplicas
value: 2
- equal:
path: spec.maxReplicas
value: 6

- it: rejects zero replicas
set:
aiGateway.enabled: true
aiGateway.replicas: 0
template: ai-gateway-hpa.yaml
asserts:
- failedTemplate:
errorMessage: aiGateway.autoscaling needs 1 <= minReplicas <= maxReplicas, positive CPU and memory targets, and aiGateway.resources.requests for CPU and memory

- it: rejects empty replicas
set:
aiGateway.enabled: true
aiGateway.replicas: ""
template: ai-gateway-hpa.yaml
asserts:
- failedTemplate:
errorMessage: aiGateway.autoscaling needs 1 <= minReplicas <= maxReplicas, positive CPU and memory targets, and aiGateway.resources.requests for CPU and memory

- it: rejects zero minReplicas
set:
aiGateway.enabled: true
aiGateway.autoscaling.minReplicas: 0
Comment on lines +165 to +168

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Complete the required autoscaling input-matrix tests

The validation tests cover only one input shape per setting, leaving null/empty replica bounds, null utilization targets, and partial nested CPU/memory maps on reused values untested. These exercise different Helm coalescing and dig paths from the covered zero/scalar cases, so the existing tests do not establish their intended rendering or rejection behavior. Add explicit cases for these shapes; the repository requires null/missing/empty/zero and partial reused-value coverage and explicitly treats unchecked input shapes as release blockers.

AGENTS.md reference: AGENTS.md:L14-L17

Useful? React with 👍 / 👎.

template: ai-gateway-hpa.yaml
asserts:
- failedTemplate:
errorMessage: aiGateway.autoscaling needs 1 <= minReplicas <= maxReplicas, positive CPU and memory targets, and aiGateway.resources.requests for CPU and memory

- it: rejects maxReplicas below minReplicas
set:
aiGateway.enabled: true
aiGateway.autoscaling.maxReplicas: 1
template: ai-gateway-hpa.yaml
asserts:
- failedTemplate:
errorMessage: aiGateway.autoscaling needs 1 <= minReplicas <= maxReplicas, positive CPU and memory targets, and aiGateway.resources.requests for CPU and memory

- it: rejects an empty CPU target
set:
aiGateway.enabled: true
aiGateway.autoscaling.cpu.targetAverageUtilization: ""
template: ai-gateway-hpa.yaml
asserts:
- failedTemplate:
errorMessage: aiGateway.autoscaling needs 1 <= minReplicas <= maxReplicas, positive CPU and memory targets, and aiGateway.resources.requests for CPU and memory

- it: rejects a zero memory target
set:
aiGateway.enabled: true
aiGateway.autoscaling.memory.targetAverageUtilization: 0
template: ai-gateway-hpa.yaml
asserts:
- failedTemplate:
errorMessage: aiGateway.autoscaling needs 1 <= minReplicas <= maxReplicas, positive CPU and memory targets, and aiGateway.resources.requests for CPU and memory

- it: rejects a missing memory request
set:
aiGateway.enabled: true
aiGateway.resources.requests.memory: null
template: ai-gateway-hpa.yaml
asserts:
- failedTemplate:
errorMessage: aiGateway.autoscaling needs 1 <= minReplicas <= maxReplicas, positive CPU and memory targets, and aiGateway.resources.requests for CPU and memory

- it: renders no HPA when autoscaling is disabled
set:
aiGateway.enabled: true
aiGateway.autoscaling.enabled: false
template: ai-gateway-hpa.yaml
asserts:
- hasDocuments:
count: 0

- it: keeps fixed replicas when autoscaling is disabled
set:
aiGateway.enabled: true
aiGateway.autoscaling.enabled: false
aiGateway.replicas: 3
template: ai-gateway-deployment.yaml
asserts:
- equal:
path: spec.replicas
value: 3

- it: uses default bounds for reused values from an older chart
values:
- __fixtures__/base-values.yaml
- __fixtures__/pre-ai-gateway-autoscaling-values.yaml
template: ai-gateway-hpa.yaml
asserts:
- equal:
path: spec.minReplicas
value: 2
- equal:
path: spec.maxReplicas
value: 6
- equal:
path: spec.metrics[1].containerResource.target.averageUtilization
value: 75

- it: fills defaults around a partial --set on reused values
values:
- __fixtures__/base-values.yaml
- __fixtures__/pre-ai-gateway-autoscaling-values.yaml
set:
aiGateway.autoscaling.maxReplicas: 20
template: ai-gateway-hpa.yaml
asserts:
- equal:
path: spec.minReplicas
value: 2
- equal:
path: spec.maxReplicas
value: 20

- it: keeps a reused custom replicas count as the floor
values:
- __fixtures__/base-values.yaml
- __fixtures__/pre-ai-gateway-autoscaling-values.yaml
set:
aiGateway.replicas: 4
template: ai-gateway-hpa.yaml
asserts:
- equal:
path: spec.minReplicas
value: 4
- equal:
path: spec.maxReplicas
value: 6

- it: keeps fixed replicas when reused values disable autoscaling
values:
- __fixtures__/base-values.yaml
- __fixtures__/pre-ai-gateway-autoscaling-values.yaml
set:
aiGateway.autoscaling.enabled: false
template: ai-gateway-deployment.yaml
asserts:
- equal:
path: spec.replicas
value: 2

- it: renders the HPA on aws
values:
- __fixtures__/base-values.yaml
- __fixtures__/aws-values.yaml
set:
aiGateway.enabled: true
template: ai-gateway-hpa.yaml
asserts:
- isKind:
of: HorizontalPodAutoscaler

- it: renders the HPA on azure
values:
- __fixtures__/base-values.yaml
- __fixtures__/azure-values.yaml
set:
aiGateway.enabled: true
template: ai-gateway-hpa.yaml
asserts:
- isKind:
of: HorizontalPodAutoscaler

- it: renders the HPA on google without the AutoscalingMetric API
values:
- __fixtures__/base-values.yaml
- __fixtures__/google-values.yaml
set:
aiGateway.enabled: true
template: ai-gateway-hpa.yaml
asserts:
- isKind:
of: HorizontalPodAutoscaler
Loading
Loading