Merged
46 changes: 46 additions & 0 deletions braintrust/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,52 @@ Size the request for the pod's full local-storage usage:

When you enable `tmpVolume`, make sure the `ephemeralStorage.request` still covers that extra space.

## API workload isolation

`api.workloadIsolation.enabled` creates fixed-capacity `braintrust-api-ingest`
and `braintrust-api-background` Deployments and Services alongside the existing
default `braintrust-api` pool. The pools share the same image and base
configuration, while allowing independent replicas, resources, probes, rollout
settings, environment overrides, topology spreading, and disruption budgets.

The product-owned route contract is defined in
[`files/contracts/api-workload-isolation-routes.yaml`](files/contracts/api-workload-isolation-routes.yaml).
An ingress or gateway must preserve `braintrust-api` as its default backend and
route these paths:

| Pool | Paths |
| --- | --- |
| `braintrust-api` (default) | All requests not matched by an explicit ingest or background route |
| `braintrust-api-ingest` | `POST /logs3`, `POST /otel/v1/traces`, `POST /attachment`, `POST /attachment/status` |
| `braintrust-api-background` | `POST /v1/eval`, `POST /v1/eval/*`, `POST /function/eval`, `POST /function/sandbox`, `POST /function/use`, `POST /function/invoke-async-batch`, `POST /function/insert-functions`, `POST /automation/logs/trigger`; all methods for `/v1/proxy/chat/completions`, `/v1/proxy/responses` |

By default, Brainstore's internal `BRAINSTORE_AI_PROXY_URL` targets the
background Service while isolation is enabled. For an existing deployment,
first create the pools with
`api.workloadIsolation.brainstoreAiProxyToBackground: false`, and keep public
paths on the default Service. Verify the background pool is ready, then route
the classified public paths and set `brainstoreAiProxyToBackground: true` in a
later release. To roll back, first return both the public paths and
`brainstoreAiProxyToBackground` to the default API Service and verify it is
serving them. Only then disable workload isolation in the chart; the chart
cannot update an external ingress or gateway on its own.

When using the chart-managed Istio `VirtualService`, set
`virtualService.workloadIsolation.enabled: true` during the second release.
The chart then renders the same product-owned route contract ahead of the
existing `virtualService.http` rules, which remain available for fallback or
custom routing of non-classified paths. The product-owned routes take
precedence, so do not use `virtualService.http` to override a classified path.
This option requires both `virtualService.enabled: true` and
`api.workloadIsolation.enabled: true`. It preserves the AWS route methods:
ingest, eval, function, and automation routes match `POST`; proxy routes match
all methods. GKE Ingress cannot route by method, so its equivalent integration
classifies matching paths for all methods.

This feature does not enable autoscaling. Configure fixed replica counts under
`api.replicas`, `api.workloadIsolation.ingest.replicas`, and
`api.workloadIsolation.background.replicas`.

## Testing

This Helm chart includes comprehensive automated unit tests.
Expand Down
55 changes: 55 additions & 0 deletions braintrust/files/contracts/api-workload-isolation-routes.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
# Canonical public route ownership for API workload isolation.
#
# This is product-controlled rather than a chart value: changing it changes
# which API pool receives customer traffic. Ingress and gateway integrations
# must implement this contract and retain the default API Service for all
# unmatched requests.
version: v1
pools:
ingest:
routes:
- path: /logs3
pathType: exact
method: POST
- path: /otel/v1/traces
pathType: exact
method: POST
- path: /attachment
pathType: exact
method: POST
- path: /attachment/status
pathType: exact
method: POST
background:
routes:
# Keep this distinct from the trailing-slash prefix. Istio URI
# prefixes are string prefixes, so /v1/eval would also match
# /v1/evaluate.
- path: /v1/eval
pathType: exact
method: POST
- path: /v1/eval/
pathType: prefix
method: POST
- path: /function/eval
pathType: exact
method: POST
- path: /function/sandbox
pathType: exact
method: POST
- path: /function/use
pathType: exact
method: POST
- path: /function/invoke-async-batch
pathType: exact
method: POST
- path: /function/insert-functions
pathType: exact
method: POST
- path: /automation/logs/trigger
pathType: exact
method: POST
- path: /v1/proxy/chat/completions
pathType: exact
- path: /v1/proxy/responses
pathType: exact
232 changes: 232 additions & 0 deletions braintrust/templates/_api-deployment.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
{{/* Render one API Deployment from a merged pool configuration. */}}
{{- define "braintrust.apiDeployment" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $customCA := $api.customCA -}}
{{- $customCAMountPath := "" -}}
{{- $customCAFilename := "" -}}
{{- $customCASecretName := "" -}}
{{- $customCASecretKey := "" -}}
{{- if $customCA.enabled -}}
{{- $customCAMountPath = required "api.customCA.mountPath is required when api.customCA.enabled is true" $customCA.mountPath -}}
{{- $customCAFilename = required "api.customCA.filename is required when api.customCA.enabled is true" $customCA.filename -}}
{{- $customCASecretName = required "api.customCA.secretName is required when api.customCA.enabled is true" $customCA.secretName -}}
{{- $customCASecretKey = required "api.customCA.secretKey is required when api.customCA.enabled is true" $customCA.secretKey -}}
{{- end -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
{{- $podLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) (deepCopy $api.podLabels) (dict "app" $api.name) $poolLabels -}}
{{- if eq $root.Values.cloud "azure" -}}
{{- $_ := set $podLabels "azure.workload.identity/use" "true" -}}
{{- end -}}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth -}}
{{- $_ := set $podLabels "gke-workload-identity/use" "true" -}}
{{- end -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.deployment }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
replicas: {{ $api.replicas }}
strategy:
type: {{ $api.strategy.type }}
{{- with $api.strategy.rollingUpdate }}
rollingUpdate:
{{- toYaml . | nindent 6 }}
{{- end }}
selector:
matchLabels:
app: {{ $api.name }}
template:
metadata:
labels:
{{- with $podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
annotations:
checksum/config: {{ include (print $root.Template.BasePath "/api-configmap.yaml") $root | sha256sum }}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth }}
iam.gke.io/gcp-service-account: {{ required "api.serviceAccount.googleServiceAccount is required when api.enableGcsAuth is true" $api.serviceAccount.googleServiceAccount }}
{{- end }}
{{- with $api.annotations.pod }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
serviceAccountName: {{ $api.serviceAccount.name }}
{{- with $api.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if $api.topologySpread.enabled }}
topologySpreadConstraints:
- maxSkew: {{ $api.topologySpread.maxSkew }}
topologyKey: {{ $api.topologySpread.topologyKey | quote }}
whenUnsatisfiable: {{ $api.topologySpread.whenUnsatisfiable }}
labelSelector:
matchLabels:
app: {{ $api.name }}
{{- end }}
{{- with $api.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: api
image: "{{ $api.image.repository }}:{{ $api.image.tag }}"
imagePullPolicy: {{ $api.image.pullPolicy }}
{{- with $api.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- containerPort: {{ $api.service.port }}
resources:
{{- toYaml $api.resources | nindent 12 }}
{{- with $api.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with $api.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
envFrom:
- configMapRef:
name: {{ $root.Values.api.name }}
env:
- name: PG_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: PG_URL
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: REDIS_URL
- name: FUNCTION_SECRET_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: FUNCTION_SECRET_KEY
- name: BRAINSTORE_LICENSE_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: BRAINSTORE_LICENSE_KEY
{{- if eq $root.Values.cloud "azure" }}
- name: AZURE_STORAGE_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: AZURE_STORAGE_CONNECTION_STRING
{{- end }}
{{- if and (eq $root.Values.cloud "google") (not $api.enableGcsAuth) }}
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_ACCESS_KEY_ID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_SECRET_ACCESS_KEY
{{- end }}
- name: TS_API_HEALTHSERVER_HOST
value: {{ $api.healthServer.host | quote }}
- name: TS_API_HEALTHSERVER_PORT
value: {{ $api.healthServer.port | quote }}
{{- if $customCA.enabled }}
{{- $customCAPath := printf "%s/%s" $customCAMountPath $customCAFilename }}
- name: NODE_EXTRA_CA_CERTS
value: {{ $customCAPath | quote }}
- name: REQUESTS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: SSL_CERT_FILE
value: {{ $customCAPath | quote }}
- name: CURL_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: AWS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: PIP_CERT
value: {{ $customCAPath | quote }}
{{- end }}
{{- with $api.extraEnvVars }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled }}
volumeMounts:
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
mountPath: /tmp
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
mountPath: "/mnt/secrets-store"
readOnly: true
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
mountPath: {{ $customCAMountPath | quote }}
readOnly: true
{{- end }}
{{- end }}
{{- with $api.extraContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled $api.extraVolumes }}
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
emptyDir:
{{- if $api.tmpVolume.sizeLimit }}
sizeLimit: {{ $api.tmpVolume.sizeLimit | quote }}
{{- else }}
{}
{{- end }}
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: {{ $root.Values.azure.keyVaultName }}
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
secret:
secretName: {{ $customCASecretName | quote }}
items:
- key: {{ $customCASecretKey | quote }}
path: {{ $customCAFilename | quote }}
{{- end }}
{{- with $api.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- else }}
[]
{{- end }}
{{- end -}}
33 changes: 33 additions & 0 deletions braintrust/templates/_api-service.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
{{/* Render one API Service from a merged pool configuration. */}}
{{- define "braintrust.apiService" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $api.service.name | default $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.service }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
app: {{ $api.name }}
ports:
- name: {{ $api.service.portName }}
protocol: TCP
port: {{ $api.service.port }}
targetPort: {{ $api.service.port }}
type: {{ $api.service.type }}
{{- end -}}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
46 changes: 46 additions & 0 deletions braintrust/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,52 @@ Size the request for the pod's full local-storage usage:

When you enable `tmpVolume`, make sure the `ephemeralStorage.request` still covers that extra space.

## API workload isolation

`api.workloadIsolation.enabled` creates fixed-capacity `braintrust-api-ingest`
and `braintrust-api-background` Deployments and Services alongside the existing
default `braintrust-api` pool. The pools share the same image and base
configuration, while allowing independent replicas, resources, probes, rollout
settings, environment overrides, topology spreading, and disruption budgets.

The product-owned route contract is defined in
[`files/contracts/api-workload-isolation-routes.yaml`](files/contracts/api-workload-isolation-routes.yaml).
An ingress or gateway must preserve `braintrust-api` as its default backend and
route these paths:

| Pool | Paths |
| --- | --- |
| `braintrust-api` (default) | All requests not matched by an explicit ingest or background route |
| `braintrust-api-ingest` | `POST /logs3`, `POST /otel/v1/traces`, `POST /attachment`, `POST /attachment/status` |
| `braintrust-api-background` | `POST /v1/eval`, `POST /v1/eval/*`, `POST /function/eval`, `POST /function/sandbox`, `POST /function/use`, `POST /function/invoke-async-batch`, `POST /function/insert-functions`, `POST /automation/logs/trigger`; all methods for `/v1/proxy/chat/completions`, `/v1/proxy/responses` |

By default, Brainstore's internal `BRAINSTORE_AI_PROXY_URL` targets the
background Service while isolation is enabled. For an existing deployment,
first create the pools with
`api.workloadIsolation.brainstoreAiProxyToBackground: false`, and keep public
paths on the default Service. Verify the background pool is ready, then route
the classified public paths and set `brainstoreAiProxyToBackground: true` in a
later release. To roll back, first return both the public paths and
`brainstoreAiProxyToBackground` to the default API Service and verify it is
serving them. Only then disable workload isolation in the chart; the chart
cannot update an external ingress or gateway on its own.

When using the chart-managed Istio `VirtualService`, set
`virtualService.workloadIsolation.enabled: true` during the second release.
The chart then renders the same product-owned route contract ahead of the
existing `virtualService.http` rules, which remain available for fallback or
custom routing of non-classified paths. The product-owned routes take
precedence, so do not use `virtualService.http` to override a classified path.
This option requires both `virtualService.enabled: true` and
`api.workloadIsolation.enabled: true`. It preserves the AWS route methods:
ingest, eval, function, and automation routes match `POST`; proxy routes match
all methods. GKE Ingress cannot route by method, so its equivalent integration
classifies matching paths for all methods.

This feature does not enable autoscaling. Configure fixed replica counts under
`api.replicas`, `api.workloadIsolation.ingest.replicas`, and
`api.workloadIsolation.background.replicas`.

## Testing

This Helm chart includes comprehensive automated unit tests.
Expand Down
55 changes: 55 additions & 0 deletions braintrust/files/contracts/api-workload-isolation-routes.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
# Canonical public route ownership for API workload isolation.
#
# This is product-controlled rather than a chart value: changing it changes
# which API pool receives customer traffic. Ingress and gateway integrations
# must implement this contract and retain the default API Service for all
# unmatched requests.
version: v1
pools:
ingest:
routes:
- path: /logs3
pathType: exact
method: POST
- path: /otel/v1/traces
pathType: exact
method: POST
- path: /attachment
pathType: exact
method: POST
- path: /attachment/status
pathType: exact
method: POST
background:
routes:
# Keep this distinct from the trailing-slash prefix. Istio URI
# prefixes are string prefixes, so /v1/eval would also match
# /v1/evaluate.
- path: /v1/eval
pathType: exact
method: POST
- path: /v1/eval/
pathType: prefix
method: POST
- path: /function/eval
pathType: exact
method: POST
- path: /function/sandbox
pathType: exact
method: POST
- path: /function/use
pathType: exact
method: POST
- path: /function/invoke-async-batch
pathType: exact
method: POST
- path: /function/insert-functions
pathType: exact
method: POST
- path: /automation/logs/trigger
pathType: exact
method: POST
- path: /v1/proxy/chat/completions
pathType: exact
- path: /v1/proxy/responses
pathType: exact
232 changes: 232 additions & 0 deletions braintrust/templates/_api-deployment.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
{{/* Render one API Deployment from a merged pool configuration. */}}
{{- define "braintrust.apiDeployment" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $customCA := $api.customCA -}}
{{- $customCAMountPath := "" -}}
{{- $customCAFilename := "" -}}
{{- $customCASecretName := "" -}}
{{- $customCASecretKey := "" -}}
{{- if $customCA.enabled -}}
{{- $customCAMountPath = required "api.customCA.mountPath is required when api.customCA.enabled is true" $customCA.mountPath -}}
{{- $customCAFilename = required "api.customCA.filename is required when api.customCA.enabled is true" $customCA.filename -}}
{{- $customCASecretName = required "api.customCA.secretName is required when api.customCA.enabled is true" $customCA.secretName -}}
{{- $customCASecretKey = required "api.customCA.secretKey is required when api.customCA.enabled is true" $customCA.secretKey -}}
{{- end -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
{{- $podLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) (deepCopy $api.podLabels) (dict "app" $api.name) $poolLabels -}}
{{- if eq $root.Values.cloud "azure" -}}
{{- $_ := set $podLabels "azure.workload.identity/use" "true" -}}
{{- end -}}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth -}}
{{- $_ := set $podLabels "gke-workload-identity/use" "true" -}}
{{- end -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.deployment }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
replicas: {{ $api.replicas }}
strategy:
type: {{ $api.strategy.type }}
{{- with $api.strategy.rollingUpdate }}
rollingUpdate:
{{- toYaml . | nindent 6 }}
{{- end }}
selector:
matchLabels:
app: {{ $api.name }}
template:
metadata:
labels:
{{- with $podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
annotations:
checksum/config: {{ include (print $root.Template.BasePath "/api-configmap.yaml") $root | sha256sum }}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth }}
iam.gke.io/gcp-service-account: {{ required "api.serviceAccount.googleServiceAccount is required when api.enableGcsAuth is true" $api.serviceAccount.googleServiceAccount }}
{{- end }}
{{- with $api.annotations.pod }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
serviceAccountName: {{ $api.serviceAccount.name }}
{{- with $api.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if $api.topologySpread.enabled }}
topologySpreadConstraints:
- maxSkew: {{ $api.topologySpread.maxSkew }}
topologyKey: {{ $api.topologySpread.topologyKey | quote }}
whenUnsatisfiable: {{ $api.topologySpread.whenUnsatisfiable }}
labelSelector:
matchLabels:
app: {{ $api.name }}
{{- end }}
{{- with $api.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: api
image: "{{ $api.image.repository }}:{{ $api.image.tag }}"
imagePullPolicy: {{ $api.image.pullPolicy }}
{{- with $api.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- containerPort: {{ $api.service.port }}
resources:
{{- toYaml $api.resources | nindent 12 }}
{{- with $api.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with $api.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
envFrom:
- configMapRef:
name: {{ $root.Values.api.name }}
env:
- name: PG_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: PG_URL
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: REDIS_URL
- name: FUNCTION_SECRET_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: FUNCTION_SECRET_KEY
- name: BRAINSTORE_LICENSE_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: BRAINSTORE_LICENSE_KEY
{{- if eq $root.Values.cloud "azure" }}
- name: AZURE_STORAGE_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: AZURE_STORAGE_CONNECTION_STRING
{{- end }}
{{- if and (eq $root.Values.cloud "google") (not $api.enableGcsAuth) }}
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_ACCESS_KEY_ID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_SECRET_ACCESS_KEY
{{- end }}
- name: TS_API_HEALTHSERVER_HOST
value: {{ $api.healthServer.host | quote }}
- name: TS_API_HEALTHSERVER_PORT
value: {{ $api.healthServer.port | quote }}
{{- if $customCA.enabled }}
{{- $customCAPath := printf "%s/%s" $customCAMountPath $customCAFilename }}
- name: NODE_EXTRA_CA_CERTS
value: {{ $customCAPath | quote }}
- name: REQUESTS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: SSL_CERT_FILE
value: {{ $customCAPath | quote }}
- name: CURL_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: AWS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: PIP_CERT
value: {{ $customCAPath | quote }}
{{- end }}
{{- with $api.extraEnvVars }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled }}
volumeMounts:
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
mountPath: /tmp
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
mountPath: "/mnt/secrets-store"
readOnly: true
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
mountPath: {{ $customCAMountPath | quote }}
readOnly: true
{{- end }}
{{- end }}
{{- with $api.extraContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled $api.extraVolumes }}
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
emptyDir:
{{- if $api.tmpVolume.sizeLimit }}
sizeLimit: {{ $api.tmpVolume.sizeLimit | quote }}
{{- else }}
{}
{{- end }}
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: {{ $root.Values.azure.keyVaultName }}
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
secret:
secretName: {{ $customCASecretName | quote }}
items:
- key: {{ $customCASecretKey | quote }}
path: {{ $customCAFilename | quote }}
{{- end }}
{{- with $api.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- else }}
[]
{{- end }}
{{- end -}}
33 changes: 33 additions & 0 deletions braintrust/templates/_api-service.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
{{/* Render one API Service from a merged pool configuration. */}}
{{- define "braintrust.apiService" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $api.service.name | default $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.service }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
app: {{ $api.name }}
ports:
- name: {{ $api.service.portName }}
protocol: TCP
port: {{ $api.service.port }}
targetPort: {{ $api.service.port }}
type: {{ $api.service.type }}
{{- end -}}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
46 changes: 46 additions & 0 deletions braintrust/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,52 @@ Size the request for the pod's full local-storage usage:

When you enable `tmpVolume`, make sure the `ephemeralStorage.request` still covers that extra space.

## API workload isolation

`api.workloadIsolation.enabled` creates fixed-capacity `braintrust-api-ingest`
and `braintrust-api-background` Deployments and Services alongside the existing
default `braintrust-api` pool. The pools share the same image and base
configuration, while allowing independent replicas, resources, probes, rollout
settings, environment overrides, topology spreading, and disruption budgets.

The product-owned route contract is defined in
[`files/contracts/api-workload-isolation-routes.yaml`](files/contracts/api-workload-isolation-routes.yaml).
An ingress or gateway must preserve `braintrust-api` as its default backend and
route these paths:

| Pool | Paths |
| --- | --- |
| `braintrust-api` (default) | All requests not matched by an explicit ingest or background route |
| `braintrust-api-ingest` | `POST /logs3`, `POST /otel/v1/traces`, `POST /attachment`, `POST /attachment/status` |
| `braintrust-api-background` | `POST /v1/eval`, `POST /v1/eval/*`, `POST /function/eval`, `POST /function/sandbox`, `POST /function/use`, `POST /function/invoke-async-batch`, `POST /function/insert-functions`, `POST /automation/logs/trigger`; all methods for `/v1/proxy/chat/completions`, `/v1/proxy/responses` |

By default, Brainstore's internal `BRAINSTORE_AI_PROXY_URL` targets the
background Service while isolation is enabled. For an existing deployment,
first create the pools with
`api.workloadIsolation.brainstoreAiProxyToBackground: false`, and keep public
paths on the default Service. Verify the background pool is ready, then route
the classified public paths and set `brainstoreAiProxyToBackground: true` in a
later release. To roll back, first return both the public paths and
`brainstoreAiProxyToBackground` to the default API Service and verify it is
serving them. Only then disable workload isolation in the chart; the chart
cannot update an external ingress or gateway on its own.

When using the chart-managed Istio `VirtualService`, set
`virtualService.workloadIsolation.enabled: true` during the second release.
The chart then renders the same product-owned route contract ahead of the
existing `virtualService.http` rules, which remain available for fallback or
custom routing of non-classified paths. The product-owned routes take
precedence, so do not use `virtualService.http` to override a classified path.
This option requires both `virtualService.enabled: true` and
`api.workloadIsolation.enabled: true`. It preserves the AWS route methods:
ingest, eval, function, and automation routes match `POST`; proxy routes match
all methods. GKE Ingress cannot route by method, so its equivalent integration
classifies matching paths for all methods.

This feature does not enable autoscaling. Configure fixed replica counts under
`api.replicas`, `api.workloadIsolation.ingest.replicas`, and
`api.workloadIsolation.background.replicas`.

## Testing

This Helm chart includes comprehensive automated unit tests.
Expand Down
55 changes: 55 additions & 0 deletions braintrust/files/contracts/api-workload-isolation-routes.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
# Canonical public route ownership for API workload isolation.
#
# This is product-controlled rather than a chart value: changing it changes
# which API pool receives customer traffic. Ingress and gateway integrations
# must implement this contract and retain the default API Service for all
# unmatched requests.
version: v1
pools:
ingest:
routes:
- path: /logs3
pathType: exact
method: POST
- path: /otel/v1/traces
pathType: exact
method: POST
- path: /attachment
pathType: exact
method: POST
- path: /attachment/status
pathType: exact
method: POST
background:
routes:
# Keep this distinct from the trailing-slash prefix. Istio URI
# prefixes are string prefixes, so /v1/eval would also match
# /v1/evaluate.
- path: /v1/eval
pathType: exact
method: POST
- path: /v1/eval/
pathType: prefix
method: POST
- path: /function/eval
pathType: exact
method: POST
- path: /function/sandbox
pathType: exact
method: POST
- path: /function/use
pathType: exact
method: POST
- path: /function/invoke-async-batch
pathType: exact
method: POST
- path: /function/insert-functions
pathType: exact
method: POST
- path: /automation/logs/trigger
pathType: exact
method: POST
- path: /v1/proxy/chat/completions
pathType: exact
- path: /v1/proxy/responses
pathType: exact
232 changes: 232 additions & 0 deletions braintrust/templates/_api-deployment.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
{{/* Render one API Deployment from a merged pool configuration. */}}
{{- define "braintrust.apiDeployment" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $customCA := $api.customCA -}}
{{- $customCAMountPath := "" -}}
{{- $customCAFilename := "" -}}
{{- $customCASecretName := "" -}}
{{- $customCASecretKey := "" -}}
{{- if $customCA.enabled -}}
{{- $customCAMountPath = required "api.customCA.mountPath is required when api.customCA.enabled is true" $customCA.mountPath -}}
{{- $customCAFilename = required "api.customCA.filename is required when api.customCA.enabled is true" $customCA.filename -}}
{{- $customCASecretName = required "api.customCA.secretName is required when api.customCA.enabled is true" $customCA.secretName -}}
{{- $customCASecretKey = required "api.customCA.secretKey is required when api.customCA.enabled is true" $customCA.secretKey -}}
{{- end -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
{{- $podLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) (deepCopy $api.podLabels) (dict "app" $api.name) $poolLabels -}}
{{- if eq $root.Values.cloud "azure" -}}
{{- $_ := set $podLabels "azure.workload.identity/use" "true" -}}
{{- end -}}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth -}}
{{- $_ := set $podLabels "gke-workload-identity/use" "true" -}}
{{- end -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.deployment }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
replicas: {{ $api.replicas }}
strategy:
type: {{ $api.strategy.type }}
{{- with $api.strategy.rollingUpdate }}
rollingUpdate:
{{- toYaml . | nindent 6 }}
{{- end }}
selector:
matchLabels:
app: {{ $api.name }}
template:
metadata:
labels:
{{- with $podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
annotations:
checksum/config: {{ include (print $root.Template.BasePath "/api-configmap.yaml") $root | sha256sum }}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth }}
iam.gke.io/gcp-service-account: {{ required "api.serviceAccount.googleServiceAccount is required when api.enableGcsAuth is true" $api.serviceAccount.googleServiceAccount }}
{{- end }}
{{- with $api.annotations.pod }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
serviceAccountName: {{ $api.serviceAccount.name }}
{{- with $api.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if $api.topologySpread.enabled }}
topologySpreadConstraints:
- maxSkew: {{ $api.topologySpread.maxSkew }}
topologyKey: {{ $api.topologySpread.topologyKey | quote }}
whenUnsatisfiable: {{ $api.topologySpread.whenUnsatisfiable }}
labelSelector:
matchLabels:
app: {{ $api.name }}
{{- end }}
{{- with $api.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: api
image: "{{ $api.image.repository }}:{{ $api.image.tag }}"
imagePullPolicy: {{ $api.image.pullPolicy }}
{{- with $api.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- containerPort: {{ $api.service.port }}
resources:
{{- toYaml $api.resources | nindent 12 }}
{{- with $api.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with $api.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
envFrom:
- configMapRef:
name: {{ $root.Values.api.name }}
env:
- name: PG_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: PG_URL
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: REDIS_URL
- name: FUNCTION_SECRET_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: FUNCTION_SECRET_KEY
- name: BRAINSTORE_LICENSE_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: BRAINSTORE_LICENSE_KEY
{{- if eq $root.Values.cloud "azure" }}
- name: AZURE_STORAGE_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: AZURE_STORAGE_CONNECTION_STRING
{{- end }}
{{- if and (eq $root.Values.cloud "google") (not $api.enableGcsAuth) }}
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_ACCESS_KEY_ID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_SECRET_ACCESS_KEY
{{- end }}
- name: TS_API_HEALTHSERVER_HOST
value: {{ $api.healthServer.host | quote }}
- name: TS_API_HEALTHSERVER_PORT
value: {{ $api.healthServer.port | quote }}
{{- if $customCA.enabled }}
{{- $customCAPath := printf "%s/%s" $customCAMountPath $customCAFilename }}
- name: NODE_EXTRA_CA_CERTS
value: {{ $customCAPath | quote }}
- name: REQUESTS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: SSL_CERT_FILE
value: {{ $customCAPath | quote }}
- name: CURL_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: AWS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: PIP_CERT
value: {{ $customCAPath | quote }}
{{- end }}
{{- with $api.extraEnvVars }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled }}
volumeMounts:
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
mountPath: /tmp
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
mountPath: "/mnt/secrets-store"
readOnly: true
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
mountPath: {{ $customCAMountPath | quote }}
readOnly: true
{{- end }}
{{- end }}
{{- with $api.extraContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled $api.extraVolumes }}
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
emptyDir:
{{- if $api.tmpVolume.sizeLimit }}
sizeLimit: {{ $api.tmpVolume.sizeLimit | quote }}
{{- else }}
{}
{{- end }}
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: {{ $root.Values.azure.keyVaultName }}
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
secret:
secretName: {{ $customCASecretName | quote }}
items:
- key: {{ $customCASecretKey | quote }}
path: {{ $customCAFilename | quote }}
{{- end }}
{{- with $api.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- else }}
[]
{{- end }}
{{- end -}}
33 changes: 33 additions & 0 deletions braintrust/templates/_api-service.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
{{/* Render one API Service from a merged pool configuration. */}}
{{- define "braintrust.apiService" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $api.service.name | default $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.service }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
app: {{ $api.name }}
ports:
- name: {{ $api.service.portName }}
protocol: TCP
port: {{ $api.service.port }}
targetPort: {{ $api.service.port }}
type: {{ $api.service.type }}
{{- end -}}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
46 changes: 46 additions & 0 deletions braintrust/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,52 @@ Size the request for the pod's full local-storage usage:

When you enable `tmpVolume`, make sure the `ephemeralStorage.request` still covers that extra space.

## API workload isolation

`api.workloadIsolation.enabled` creates fixed-capacity `braintrust-api-ingest`
and `braintrust-api-background` Deployments and Services alongside the existing
default `braintrust-api` pool. The pools share the same image and base
configuration, while allowing independent replicas, resources, probes, rollout
settings, environment overrides, topology spreading, and disruption budgets.

The product-owned route contract is defined in
[`files/contracts/api-workload-isolation-routes.yaml`](files/contracts/api-workload-isolation-routes.yaml).
An ingress or gateway must preserve `braintrust-api` as its default backend and
route these paths:

| Pool | Paths |
| --- | --- |
| `braintrust-api` (default) | All requests not matched by an explicit ingest or background route |
| `braintrust-api-ingest` | `POST /logs3`, `POST /otel/v1/traces`, `POST /attachment`, `POST /attachment/status` |
| `braintrust-api-background` | `POST /v1/eval`, `POST /v1/eval/*`, `POST /function/eval`, `POST /function/sandbox`, `POST /function/use`, `POST /function/invoke-async-batch`, `POST /function/insert-functions`, `POST /automation/logs/trigger`; all methods for `/v1/proxy/chat/completions`, `/v1/proxy/responses` |

By default, Brainstore's internal `BRAINSTORE_AI_PROXY_URL` targets the
background Service while isolation is enabled. For an existing deployment,
first create the pools with
`api.workloadIsolation.brainstoreAiProxyToBackground: false`, and keep public
paths on the default Service. Verify the background pool is ready, then route
the classified public paths and set `brainstoreAiProxyToBackground: true` in a
later release. To roll back, first return both the public paths and
`brainstoreAiProxyToBackground` to the default API Service and verify it is
serving them. Only then disable workload isolation in the chart; the chart
cannot update an external ingress or gateway on its own.

When using the chart-managed Istio `VirtualService`, set
`virtualService.workloadIsolation.enabled: true` during the second release.
The chart then renders the same product-owned route contract ahead of the
existing `virtualService.http` rules, which remain available for fallback or
custom routing of non-classified paths. The product-owned routes take
precedence, so do not use `virtualService.http` to override a classified path.
This option requires both `virtualService.enabled: true` and
`api.workloadIsolation.enabled: true`. It preserves the AWS route methods:
ingest, eval, function, and automation routes match `POST`; proxy routes match
all methods. GKE Ingress cannot route by method, so its equivalent integration
classifies matching paths for all methods.

This feature does not enable autoscaling. Configure fixed replica counts under
`api.replicas`, `api.workloadIsolation.ingest.replicas`, and
`api.workloadIsolation.background.replicas`.

## Testing

This Helm chart includes comprehensive automated unit tests.
Expand Down
55 changes: 55 additions & 0 deletions braintrust/files/contracts/api-workload-isolation-routes.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
# Canonical public route ownership for API workload isolation.
#
# This is product-controlled rather than a chart value: changing it changes
# which API pool receives customer traffic. Ingress and gateway integrations
# must implement this contract and retain the default API Service for all
# unmatched requests.
version: v1
pools:
ingest:
routes:
- path: /logs3
pathType: exact
method: POST
- path: /otel/v1/traces
pathType: exact
method: POST
- path: /attachment
pathType: exact
method: POST
- path: /attachment/status
pathType: exact
method: POST
background:
routes:
# Keep this distinct from the trailing-slash prefix. Istio URI
# prefixes are string prefixes, so /v1/eval would also match
# /v1/evaluate.
- path: /v1/eval
pathType: exact
method: POST
- path: /v1/eval/
pathType: prefix
method: POST
- path: /function/eval
pathType: exact
method: POST
- path: /function/sandbox
pathType: exact
method: POST
- path: /function/use
pathType: exact
method: POST
- path: /function/invoke-async-batch
pathType: exact
method: POST
- path: /function/insert-functions
pathType: exact
method: POST
- path: /automation/logs/trigger
pathType: exact
method: POST
- path: /v1/proxy/chat/completions
pathType: exact
- path: /v1/proxy/responses
pathType: exact
232 changes: 232 additions & 0 deletions braintrust/templates/_api-deployment.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
{{/* Render one API Deployment from a merged pool configuration. */}}
{{- define "braintrust.apiDeployment" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $customCA := $api.customCA -}}
{{- $customCAMountPath := "" -}}
{{- $customCAFilename := "" -}}
{{- $customCASecretName := "" -}}
{{- $customCASecretKey := "" -}}
{{- if $customCA.enabled -}}
{{- $customCAMountPath = required "api.customCA.mountPath is required when api.customCA.enabled is true" $customCA.mountPath -}}
{{- $customCAFilename = required "api.customCA.filename is required when api.customCA.enabled is true" $customCA.filename -}}
{{- $customCASecretName = required "api.customCA.secretName is required when api.customCA.enabled is true" $customCA.secretName -}}
{{- $customCASecretKey = required "api.customCA.secretKey is required when api.customCA.enabled is true" $customCA.secretKey -}}
{{- end -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
{{- $podLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) (deepCopy $api.podLabels) (dict "app" $api.name) $poolLabels -}}
{{- if eq $root.Values.cloud "azure" -}}
{{- $_ := set $podLabels "azure.workload.identity/use" "true" -}}
{{- end -}}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth -}}
{{- $_ := set $podLabels "gke-workload-identity/use" "true" -}}
{{- end -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.deployment }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
replicas: {{ $api.replicas }}
strategy:
type: {{ $api.strategy.type }}
{{- with $api.strategy.rollingUpdate }}
rollingUpdate:
{{- toYaml . | nindent 6 }}
{{- end }}
selector:
matchLabels:
app: {{ $api.name }}
template:
metadata:
labels:
{{- with $podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
annotations:
checksum/config: {{ include (print $root.Template.BasePath "/api-configmap.yaml") $root | sha256sum }}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth }}
iam.gke.io/gcp-service-account: {{ required "api.serviceAccount.googleServiceAccount is required when api.enableGcsAuth is true" $api.serviceAccount.googleServiceAccount }}
{{- end }}
{{- with $api.annotations.pod }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
serviceAccountName: {{ $api.serviceAccount.name }}
{{- with $api.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if $api.topologySpread.enabled }}
topologySpreadConstraints:
- maxSkew: {{ $api.topologySpread.maxSkew }}
topologyKey: {{ $api.topologySpread.topologyKey | quote }}
whenUnsatisfiable: {{ $api.topologySpread.whenUnsatisfiable }}
labelSelector:
matchLabels:
app: {{ $api.name }}
{{- end }}
{{- with $api.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: api
image: "{{ $api.image.repository }}:{{ $api.image.tag }}"
imagePullPolicy: {{ $api.image.pullPolicy }}
{{- with $api.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- containerPort: {{ $api.service.port }}
resources:
{{- toYaml $api.resources | nindent 12 }}
{{- with $api.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with $api.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
envFrom:
- configMapRef:
name: {{ $root.Values.api.name }}
env:
- name: PG_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: PG_URL
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: REDIS_URL
- name: FUNCTION_SECRET_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: FUNCTION_SECRET_KEY
- name: BRAINSTORE_LICENSE_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: BRAINSTORE_LICENSE_KEY
{{- if eq $root.Values.cloud "azure" }}
- name: AZURE_STORAGE_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: AZURE_STORAGE_CONNECTION_STRING
{{- end }}
{{- if and (eq $root.Values.cloud "google") (not $api.enableGcsAuth) }}
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_ACCESS_KEY_ID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_SECRET_ACCESS_KEY
{{- end }}
- name: TS_API_HEALTHSERVER_HOST
value: {{ $api.healthServer.host | quote }}
- name: TS_API_HEALTHSERVER_PORT
value: {{ $api.healthServer.port | quote }}
{{- if $customCA.enabled }}
{{- $customCAPath := printf "%s/%s" $customCAMountPath $customCAFilename }}
- name: NODE_EXTRA_CA_CERTS
value: {{ $customCAPath | quote }}
- name: REQUESTS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: SSL_CERT_FILE
value: {{ $customCAPath | quote }}
- name: CURL_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: AWS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: PIP_CERT
value: {{ $customCAPath | quote }}
{{- end }}
{{- with $api.extraEnvVars }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled }}
volumeMounts:
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
mountPath: /tmp
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
mountPath: "/mnt/secrets-store"
readOnly: true
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
mountPath: {{ $customCAMountPath | quote }}
readOnly: true
{{- end }}
{{- end }}
{{- with $api.extraContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled $api.extraVolumes }}
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
emptyDir:
{{- if $api.tmpVolume.sizeLimit }}
sizeLimit: {{ $api.tmpVolume.sizeLimit | quote }}
{{- else }}
{}
{{- end }}
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: {{ $root.Values.azure.keyVaultName }}
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
secret:
secretName: {{ $customCASecretName | quote }}
items:
- key: {{ $customCASecretKey | quote }}
path: {{ $customCAFilename | quote }}
{{- end }}
{{- with $api.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- else }}
[]
{{- end }}
{{- end -}}
33 changes: 33 additions & 0 deletions braintrust/templates/_api-service.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
{{/* Render one API Service from a merged pool configuration. */}}
{{- define "braintrust.apiService" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $api.service.name | default $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.service }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
app: {{ $api.name }}
ports:
- name: {{ $api.service.portName }}
protocol: TCP
port: {{ $api.service.port }}
targetPort: {{ $api.service.port }}
type: {{ $api.service.type }}
{{- end -}}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
46 changes: 46 additions & 0 deletions braintrust/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,52 @@ Size the request for the pod's full local-storage usage:

When you enable `tmpVolume`, make sure the `ephemeralStorage.request` still covers that extra space.

## API workload isolation

`api.workloadIsolation.enabled` creates fixed-capacity `braintrust-api-ingest`
and `braintrust-api-background` Deployments and Services alongside the existing
default `braintrust-api` pool. The pools share the same image and base
configuration, while allowing independent replicas, resources, probes, rollout
settings, environment overrides, topology spreading, and disruption budgets.

The product-owned route contract is defined in
[`files/contracts/api-workload-isolation-routes.yaml`](files/contracts/api-workload-isolation-routes.yaml).
An ingress or gateway must preserve `braintrust-api` as its default backend and
route these paths:

| Pool | Paths |
| --- | --- |
| `braintrust-api` (default) | All requests not matched by an explicit ingest or background route |
| `braintrust-api-ingest` | `POST /logs3`, `POST /otel/v1/traces`, `POST /attachment`, `POST /attachment/status` |
| `braintrust-api-background` | `POST /v1/eval`, `POST /v1/eval/*`, `POST /function/eval`, `POST /function/sandbox`, `POST /function/use`, `POST /function/invoke-async-batch`, `POST /function/insert-functions`, `POST /automation/logs/trigger`; all methods for `/v1/proxy/chat/completions`, `/v1/proxy/responses` |

By default, Brainstore's internal `BRAINSTORE_AI_PROXY_URL` targets the
background Service while isolation is enabled. For an existing deployment,
first create the pools with
`api.workloadIsolation.brainstoreAiProxyToBackground: false`, and keep public
paths on the default Service. Verify the background pool is ready, then route
the classified public paths and set `brainstoreAiProxyToBackground: true` in a
later release. To roll back, first return both the public paths and
`brainstoreAiProxyToBackground` to the default API Service and verify it is
serving them. Only then disable workload isolation in the chart; the chart
cannot update an external ingress or gateway on its own.

When using the chart-managed Istio `VirtualService`, set
`virtualService.workloadIsolation.enabled: true` during the second release.
The chart then renders the same product-owned route contract ahead of the
existing `virtualService.http` rules, which remain available for fallback or
custom routing of non-classified paths. The product-owned routes take
precedence, so do not use `virtualService.http` to override a classified path.
This option requires both `virtualService.enabled: true` and
`api.workloadIsolation.enabled: true`. It preserves the AWS route methods:
ingest, eval, function, and automation routes match `POST`; proxy routes match
all methods. GKE Ingress cannot route by method, so its equivalent integration
classifies matching paths for all methods.

This feature does not enable autoscaling. Configure fixed replica counts under
`api.replicas`, `api.workloadIsolation.ingest.replicas`, and
`api.workloadIsolation.background.replicas`.

## Testing

This Helm chart includes comprehensive automated unit tests.
Expand Down
55 changes: 55 additions & 0 deletions braintrust/files/contracts/api-workload-isolation-routes.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
# Canonical public route ownership for API workload isolation.
#
# This is product-controlled rather than a chart value: changing it changes
# which API pool receives customer traffic. Ingress and gateway integrations
# must implement this contract and retain the default API Service for all
# unmatched requests.
version: v1
pools:
ingest:
routes:
- path: /logs3
pathType: exact
method: POST
- path: /otel/v1/traces
pathType: exact
method: POST
- path: /attachment
pathType: exact
method: POST
- path: /attachment/status
pathType: exact
method: POST
background:
routes:
# Keep this distinct from the trailing-slash prefix. Istio URI
# prefixes are string prefixes, so /v1/eval would also match
# /v1/evaluate.
- path: /v1/eval
pathType: exact
method: POST
- path: /v1/eval/
pathType: prefix
method: POST
- path: /function/eval
pathType: exact
method: POST
- path: /function/sandbox
pathType: exact
method: POST
- path: /function/use
pathType: exact
method: POST
- path: /function/invoke-async-batch
pathType: exact
method: POST
- path: /function/insert-functions
pathType: exact
method: POST
- path: /automation/logs/trigger
pathType: exact
method: POST
- path: /v1/proxy/chat/completions
pathType: exact
- path: /v1/proxy/responses
pathType: exact
232 changes: 232 additions & 0 deletions braintrust/templates/_api-deployment.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
{{/* Render one API Deployment from a merged pool configuration. */}}
{{- define "braintrust.apiDeployment" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $customCA := $api.customCA -}}
{{- $customCAMountPath := "" -}}
{{- $customCAFilename := "" -}}
{{- $customCASecretName := "" -}}
{{- $customCASecretKey := "" -}}
{{- if $customCA.enabled -}}
{{- $customCAMountPath = required "api.customCA.mountPath is required when api.customCA.enabled is true" $customCA.mountPath -}}
{{- $customCAFilename = required "api.customCA.filename is required when api.customCA.enabled is true" $customCA.filename -}}
{{- $customCASecretName = required "api.customCA.secretName is required when api.customCA.enabled is true" $customCA.secretName -}}
{{- $customCASecretKey = required "api.customCA.secretKey is required when api.customCA.enabled is true" $customCA.secretKey -}}
{{- end -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
{{- $podLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) (deepCopy $api.podLabels) (dict "app" $api.name) $poolLabels -}}
{{- if eq $root.Values.cloud "azure" -}}
{{- $_ := set $podLabels "azure.workload.identity/use" "true" -}}
{{- end -}}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth -}}
{{- $_ := set $podLabels "gke-workload-identity/use" "true" -}}
{{- end -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.deployment }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
replicas: {{ $api.replicas }}
strategy:
type: {{ $api.strategy.type }}
{{- with $api.strategy.rollingUpdate }}
rollingUpdate:
{{- toYaml . | nindent 6 }}
{{- end }}
selector:
matchLabels:
app: {{ $api.name }}
template:
metadata:
labels:
{{- with $podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
annotations:
checksum/config: {{ include (print $root.Template.BasePath "/api-configmap.yaml") $root | sha256sum }}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth }}
iam.gke.io/gcp-service-account: {{ required "api.serviceAccount.googleServiceAccount is required when api.enableGcsAuth is true" $api.serviceAccount.googleServiceAccount }}
{{- end }}
{{- with $api.annotations.pod }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
serviceAccountName: {{ $api.serviceAccount.name }}
{{- with $api.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if $api.topologySpread.enabled }}
topologySpreadConstraints:
- maxSkew: {{ $api.topologySpread.maxSkew }}
topologyKey: {{ $api.topologySpread.topologyKey | quote }}
whenUnsatisfiable: {{ $api.topologySpread.whenUnsatisfiable }}
labelSelector:
matchLabels:
app: {{ $api.name }}
{{- end }}
{{- with $api.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: api
image: "{{ $api.image.repository }}:{{ $api.image.tag }}"
imagePullPolicy: {{ $api.image.pullPolicy }}
{{- with $api.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- containerPort: {{ $api.service.port }}
resources:
{{- toYaml $api.resources | nindent 12 }}
{{- with $api.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with $api.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
envFrom:
- configMapRef:
name: {{ $root.Values.api.name }}
env:
- name: PG_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: PG_URL
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: REDIS_URL
- name: FUNCTION_SECRET_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: FUNCTION_SECRET_KEY
- name: BRAINSTORE_LICENSE_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: BRAINSTORE_LICENSE_KEY
{{- if eq $root.Values.cloud "azure" }}
- name: AZURE_STORAGE_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: AZURE_STORAGE_CONNECTION_STRING
{{- end }}
{{- if and (eq $root.Values.cloud "google") (not $api.enableGcsAuth) }}
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_ACCESS_KEY_ID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_SECRET_ACCESS_KEY
{{- end }}
- name: TS_API_HEALTHSERVER_HOST
value: {{ $api.healthServer.host | quote }}
- name: TS_API_HEALTHSERVER_PORT
value: {{ $api.healthServer.port | quote }}
{{- if $customCA.enabled }}
{{- $customCAPath := printf "%s/%s" $customCAMountPath $customCAFilename }}
- name: NODE_EXTRA_CA_CERTS
value: {{ $customCAPath | quote }}
- name: REQUESTS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: SSL_CERT_FILE
value: {{ $customCAPath | quote }}
- name: CURL_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: AWS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: PIP_CERT
value: {{ $customCAPath | quote }}
{{- end }}
{{- with $api.extraEnvVars }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled }}
volumeMounts:
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
mountPath: /tmp
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
mountPath: "/mnt/secrets-store"
readOnly: true
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
mountPath: {{ $customCAMountPath | quote }}
readOnly: true
{{- end }}
{{- end }}
{{- with $api.extraContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled $api.extraVolumes }}
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
emptyDir:
{{- if $api.tmpVolume.sizeLimit }}
sizeLimit: {{ $api.tmpVolume.sizeLimit | quote }}
{{- else }}
{}
{{- end }}
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: {{ $root.Values.azure.keyVaultName }}
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
secret:
secretName: {{ $customCASecretName | quote }}
items:
- key: {{ $customCASecretKey | quote }}
path: {{ $customCAFilename | quote }}
{{- end }}
{{- with $api.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- else }}
[]
{{- end }}
{{- end -}}
33 changes: 33 additions & 0 deletions braintrust/templates/_api-service.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
{{/* Render one API Service from a merged pool configuration. */}}
{{- define "braintrust.apiService" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $api.service.name | default $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.service }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
app: {{ $api.name }}
ports:
- name: {{ $api.service.portName }}
protocol: TCP
port: {{ $api.service.port }}
targetPort: {{ $api.service.port }}
type: {{ $api.service.type }}
{{- end -}}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
46 changes: 46 additions & 0 deletions braintrust/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,52 @@ Size the request for the pod's full local-storage usage:

When you enable `tmpVolume`, make sure the `ephemeralStorage.request` still covers that extra space.

## API workload isolation

`api.workloadIsolation.enabled` creates fixed-capacity `braintrust-api-ingest`
and `braintrust-api-background` Deployments and Services alongside the existing
default `braintrust-api` pool. The pools share the same image and base
configuration, while allowing independent replicas, resources, probes, rollout
settings, environment overrides, topology spreading, and disruption budgets.

The product-owned route contract is defined in
[`files/contracts/api-workload-isolation-routes.yaml`](files/contracts/api-workload-isolation-routes.yaml).
An ingress or gateway must preserve `braintrust-api` as its default backend and
route these paths:

| Pool | Paths |
| --- | --- |
| `braintrust-api` (default) | All requests not matched by an explicit ingest or background route |
| `braintrust-api-ingest` | `POST /logs3`, `POST /otel/v1/traces`, `POST /attachment`, `POST /attachment/status` |
| `braintrust-api-background` | `POST /v1/eval`, `POST /v1/eval/*`, `POST /function/eval`, `POST /function/sandbox`, `POST /function/use`, `POST /function/invoke-async-batch`, `POST /function/insert-functions`, `POST /automation/logs/trigger`; all methods for `/v1/proxy/chat/completions`, `/v1/proxy/responses` |

By default, Brainstore's internal `BRAINSTORE_AI_PROXY_URL` targets the
background Service while isolation is enabled. For an existing deployment,
first create the pools with
`api.workloadIsolation.brainstoreAiProxyToBackground: false`, and keep public
paths on the default Service. Verify the background pool is ready, then route
the classified public paths and set `brainstoreAiProxyToBackground: true` in a
later release. To roll back, first return both the public paths and
`brainstoreAiProxyToBackground` to the default API Service and verify it is
serving them. Only then disable workload isolation in the chart; the chart
cannot update an external ingress or gateway on its own.

When using the chart-managed Istio `VirtualService`, set
`virtualService.workloadIsolation.enabled: true` during the second release.
The chart then renders the same product-owned route contract ahead of the
existing `virtualService.http` rules, which remain available for fallback or
custom routing of non-classified paths. The product-owned routes take
precedence, so do not use `virtualService.http` to override a classified path.
This option requires both `virtualService.enabled: true` and
`api.workloadIsolation.enabled: true`. It preserves the AWS route methods:
ingest, eval, function, and automation routes match `POST`; proxy routes match
all methods. GKE Ingress cannot route by method, so its equivalent integration
classifies matching paths for all methods.

This feature does not enable autoscaling. Configure fixed replica counts under
`api.replicas`, `api.workloadIsolation.ingest.replicas`, and
`api.workloadIsolation.background.replicas`.

## Testing

This Helm chart includes comprehensive automated unit tests.
Expand Down
55 changes: 55 additions & 0 deletions braintrust/files/contracts/api-workload-isolation-routes.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
# Canonical public route ownership for API workload isolation.
#
# This is product-controlled rather than a chart value: changing it changes
# which API pool receives customer traffic. Ingress and gateway integrations
# must implement this contract and retain the default API Service for all
# unmatched requests.
version: v1
pools:
ingest:
routes:
- path: /logs3
pathType: exact
method: POST
- path: /otel/v1/traces
pathType: exact
method: POST
- path: /attachment
pathType: exact
method: POST
- path: /attachment/status
pathType: exact
method: POST
background:
routes:
# Keep this distinct from the trailing-slash prefix. Istio URI
# prefixes are string prefixes, so /v1/eval would also match
# /v1/evaluate.
- path: /v1/eval
pathType: exact
method: POST
- path: /v1/eval/
pathType: prefix
method: POST
- path: /function/eval
pathType: exact
method: POST
- path: /function/sandbox
pathType: exact
method: POST
- path: /function/use
pathType: exact
method: POST
- path: /function/invoke-async-batch
pathType: exact
method: POST
- path: /function/insert-functions
pathType: exact
method: POST
- path: /automation/logs/trigger
pathType: exact
method: POST
- path: /v1/proxy/chat/completions
pathType: exact
- path: /v1/proxy/responses
pathType: exact
232 changes: 232 additions & 0 deletions braintrust/templates/_api-deployment.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
{{/* Render one API Deployment from a merged pool configuration. */}}
{{- define "braintrust.apiDeployment" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $customCA := $api.customCA -}}
{{- $customCAMountPath := "" -}}
{{- $customCAFilename := "" -}}
{{- $customCASecretName := "" -}}
{{- $customCASecretKey := "" -}}
{{- if $customCA.enabled -}}
{{- $customCAMountPath = required "api.customCA.mountPath is required when api.customCA.enabled is true" $customCA.mountPath -}}
{{- $customCAFilename = required "api.customCA.filename is required when api.customCA.enabled is true" $customCA.filename -}}
{{- $customCASecretName = required "api.customCA.secretName is required when api.customCA.enabled is true" $customCA.secretName -}}
{{- $customCASecretKey = required "api.customCA.secretKey is required when api.customCA.enabled is true" $customCA.secretKey -}}
{{- end -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
{{- $podLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) (deepCopy $api.podLabels) (dict "app" $api.name) $poolLabels -}}
{{- if eq $root.Values.cloud "azure" -}}
{{- $_ := set $podLabels "azure.workload.identity/use" "true" -}}
{{- end -}}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth -}}
{{- $_ := set $podLabels "gke-workload-identity/use" "true" -}}
{{- end -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.deployment }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
replicas: {{ $api.replicas }}
strategy:
type: {{ $api.strategy.type }}
{{- with $api.strategy.rollingUpdate }}
rollingUpdate:
{{- toYaml . | nindent 6 }}
{{- end }}
selector:
matchLabels:
app: {{ $api.name }}
template:
metadata:
labels:
{{- with $podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
annotations:
checksum/config: {{ include (print $root.Template.BasePath "/api-configmap.yaml") $root | sha256sum }}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth }}
iam.gke.io/gcp-service-account: {{ required "api.serviceAccount.googleServiceAccount is required when api.enableGcsAuth is true" $api.serviceAccount.googleServiceAccount }}
{{- end }}
{{- with $api.annotations.pod }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
serviceAccountName: {{ $api.serviceAccount.name }}
{{- with $api.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if $api.topologySpread.enabled }}
topologySpreadConstraints:
- maxSkew: {{ $api.topologySpread.maxSkew }}
topologyKey: {{ $api.topologySpread.topologyKey | quote }}
whenUnsatisfiable: {{ $api.topologySpread.whenUnsatisfiable }}
labelSelector:
matchLabels:
app: {{ $api.name }}
{{- end }}
{{- with $api.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: api
image: "{{ $api.image.repository }}:{{ $api.image.tag }}"
imagePullPolicy: {{ $api.image.pullPolicy }}
{{- with $api.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- containerPort: {{ $api.service.port }}
resources:
{{- toYaml $api.resources | nindent 12 }}
{{- with $api.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with $api.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
envFrom:
- configMapRef:
name: {{ $root.Values.api.name }}
env:
- name: PG_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: PG_URL
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: REDIS_URL
- name: FUNCTION_SECRET_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: FUNCTION_SECRET_KEY
- name: BRAINSTORE_LICENSE_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: BRAINSTORE_LICENSE_KEY
{{- if eq $root.Values.cloud "azure" }}
- name: AZURE_STORAGE_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: AZURE_STORAGE_CONNECTION_STRING
{{- end }}
{{- if and (eq $root.Values.cloud "google") (not $api.enableGcsAuth) }}
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_ACCESS_KEY_ID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_SECRET_ACCESS_KEY
{{- end }}
- name: TS_API_HEALTHSERVER_HOST
value: {{ $api.healthServer.host | quote }}
- name: TS_API_HEALTHSERVER_PORT
value: {{ $api.healthServer.port | quote }}
{{- if $customCA.enabled }}
{{- $customCAPath := printf "%s/%s" $customCAMountPath $customCAFilename }}
- name: NODE_EXTRA_CA_CERTS
value: {{ $customCAPath | quote }}
- name: REQUESTS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: SSL_CERT_FILE
value: {{ $customCAPath | quote }}
- name: CURL_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: AWS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: PIP_CERT
value: {{ $customCAPath | quote }}
{{- end }}
{{- with $api.extraEnvVars }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled }}
volumeMounts:
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
mountPath: /tmp
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
mountPath: "/mnt/secrets-store"
readOnly: true
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
mountPath: {{ $customCAMountPath | quote }}
readOnly: true
{{- end }}
{{- end }}
{{- with $api.extraContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled $api.extraVolumes }}
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
emptyDir:
{{- if $api.tmpVolume.sizeLimit }}
sizeLimit: {{ $api.tmpVolume.sizeLimit | quote }}
{{- else }}
{}
{{- end }}
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: {{ $root.Values.azure.keyVaultName }}
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
secret:
secretName: {{ $customCASecretName | quote }}
items:
- key: {{ $customCASecretKey | quote }}
path: {{ $customCAFilename | quote }}
{{- end }}
{{- with $api.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- else }}
[]
{{- end }}
{{- end -}}
33 changes: 33 additions & 0 deletions braintrust/templates/_api-service.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
{{/* Render one API Service from a merged pool configuration. */}}
{{- define "braintrust.apiService" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $api.service.name | default $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.service }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
app: {{ $api.name }}
ports:
- name: {{ $api.service.portName }}
protocol: TCP
port: {{ $api.service.port }}
targetPort: {{ $api.service.port }}
type: {{ $api.service.type }}
{{- end -}}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
46 changes: 46 additions & 0 deletions braintrust/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,52 @@ Size the request for the pod's full local-storage usage:

When you enable `tmpVolume`, make sure the `ephemeralStorage.request` still covers that extra space.

## API workload isolation

`api.workloadIsolation.enabled` creates fixed-capacity `braintrust-api-ingest`
and `braintrust-api-background` Deployments and Services alongside the existing
default `braintrust-api` pool. The pools share the same image and base
configuration, while allowing independent replicas, resources, probes, rollout
settings, environment overrides, topology spreading, and disruption budgets.

The product-owned route contract is defined in
[`files/contracts/api-workload-isolation-routes.yaml`](files/contracts/api-workload-isolation-routes.yaml).
An ingress or gateway must preserve `braintrust-api` as its default backend and
route these paths:

| Pool | Paths |
| --- | --- |
| `braintrust-api` (default) | All requests not matched by an explicit ingest or background route |
| `braintrust-api-ingest` | `POST /logs3`, `POST /otel/v1/traces`, `POST /attachment`, `POST /attachment/status` |
| `braintrust-api-background` | `POST /v1/eval`, `POST /v1/eval/*`, `POST /function/eval`, `POST /function/sandbox`, `POST /function/use`, `POST /function/invoke-async-batch`, `POST /function/insert-functions`, `POST /automation/logs/trigger`; all methods for `/v1/proxy/chat/completions`, `/v1/proxy/responses` |

By default, Brainstore's internal `BRAINSTORE_AI_PROXY_URL` targets the
background Service while isolation is enabled. For an existing deployment,
first create the pools with
`api.workloadIsolation.brainstoreAiProxyToBackground: false`, and keep public
paths on the default Service. Verify the background pool is ready, then route
the classified public paths and set `brainstoreAiProxyToBackground: true` in a
later release. To roll back, first return both the public paths and
`brainstoreAiProxyToBackground` to the default API Service and verify it is
serving them. Only then disable workload isolation in the chart; the chart
cannot update an external ingress or gateway on its own.

When using the chart-managed Istio `VirtualService`, set
`virtualService.workloadIsolation.enabled: true` during the second release.
The chart then renders the same product-owned route contract ahead of the
existing `virtualService.http` rules, which remain available for fallback or
custom routing of non-classified paths. The product-owned routes take
precedence, so do not use `virtualService.http` to override a classified path.
This option requires both `virtualService.enabled: true` and
`api.workloadIsolation.enabled: true`. It preserves the AWS route methods:
ingest, eval, function, and automation routes match `POST`; proxy routes match
all methods. GKE Ingress cannot route by method, so its equivalent integration
classifies matching paths for all methods.

This feature does not enable autoscaling. Configure fixed replica counts under
`api.replicas`, `api.workloadIsolation.ingest.replicas`, and
`api.workloadIsolation.background.replicas`.

## Testing

This Helm chart includes comprehensive automated unit tests.
Expand Down
55 changes: 55 additions & 0 deletions braintrust/files/contracts/api-workload-isolation-routes.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
# Canonical public route ownership for API workload isolation.
#
# This is product-controlled rather than a chart value: changing it changes
# which API pool receives customer traffic. Ingress and gateway integrations
# must implement this contract and retain the default API Service for all
# unmatched requests.
version: v1
pools:
ingest:
routes:
- path: /logs3
pathType: exact
method: POST
- path: /otel/v1/traces
pathType: exact
method: POST
- path: /attachment
pathType: exact
method: POST
- path: /attachment/status
pathType: exact
method: POST
background:
routes:
# Keep this distinct from the trailing-slash prefix. Istio URI
# prefixes are string prefixes, so /v1/eval would also match
# /v1/evaluate.
- path: /v1/eval
pathType: exact
method: POST
- path: /v1/eval/
pathType: prefix
method: POST
- path: /function/eval
pathType: exact
method: POST
- path: /function/sandbox
pathType: exact
method: POST
- path: /function/use
pathType: exact
method: POST
- path: /function/invoke-async-batch
pathType: exact
method: POST
- path: /function/insert-functions
pathType: exact
method: POST
- path: /automation/logs/trigger
pathType: exact
method: POST
- path: /v1/proxy/chat/completions
pathType: exact
- path: /v1/proxy/responses
pathType: exact
232 changes: 232 additions & 0 deletions braintrust/templates/_api-deployment.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
{{/* Render one API Deployment from a merged pool configuration. */}}
{{- define "braintrust.apiDeployment" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $customCA := $api.customCA -}}
{{- $customCAMountPath := "" -}}
{{- $customCAFilename := "" -}}
{{- $customCASecretName := "" -}}
{{- $customCASecretKey := "" -}}
{{- if $customCA.enabled -}}
{{- $customCAMountPath = required "api.customCA.mountPath is required when api.customCA.enabled is true" $customCA.mountPath -}}
{{- $customCAFilename = required "api.customCA.filename is required when api.customCA.enabled is true" $customCA.filename -}}
{{- $customCASecretName = required "api.customCA.secretName is required when api.customCA.enabled is true" $customCA.secretName -}}
{{- $customCASecretKey = required "api.customCA.secretKey is required when api.customCA.enabled is true" $customCA.secretKey -}}
{{- end -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
{{- $podLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) (deepCopy $api.podLabels) (dict "app" $api.name) $poolLabels -}}
{{- if eq $root.Values.cloud "azure" -}}
{{- $_ := set $podLabels "azure.workload.identity/use" "true" -}}
{{- end -}}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth -}}
{{- $_ := set $podLabels "gke-workload-identity/use" "true" -}}
{{- end -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.deployment }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
replicas: {{ $api.replicas }}
strategy:
type: {{ $api.strategy.type }}
{{- with $api.strategy.rollingUpdate }}
rollingUpdate:
{{- toYaml . | nindent 6 }}
{{- end }}
selector:
matchLabels:
app: {{ $api.name }}
template:
metadata:
labels:
{{- with $podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
annotations:
checksum/config: {{ include (print $root.Template.BasePath "/api-configmap.yaml") $root | sha256sum }}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth }}
iam.gke.io/gcp-service-account: {{ required "api.serviceAccount.googleServiceAccount is required when api.enableGcsAuth is true" $api.serviceAccount.googleServiceAccount }}
{{- end }}
{{- with $api.annotations.pod }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
serviceAccountName: {{ $api.serviceAccount.name }}
{{- with $api.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if $api.topologySpread.enabled }}
topologySpreadConstraints:
- maxSkew: {{ $api.topologySpread.maxSkew }}
topologyKey: {{ $api.topologySpread.topologyKey | quote }}
whenUnsatisfiable: {{ $api.topologySpread.whenUnsatisfiable }}
labelSelector:
matchLabels:
app: {{ $api.name }}
{{- end }}
{{- with $api.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: api
image: "{{ $api.image.repository }}:{{ $api.image.tag }}"
imagePullPolicy: {{ $api.image.pullPolicy }}
{{- with $api.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- containerPort: {{ $api.service.port }}
resources:
{{- toYaml $api.resources | nindent 12 }}
{{- with $api.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with $api.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
envFrom:
- configMapRef:
name: {{ $root.Values.api.name }}
env:
- name: PG_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: PG_URL
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: REDIS_URL
- name: FUNCTION_SECRET_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: FUNCTION_SECRET_KEY
- name: BRAINSTORE_LICENSE_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: BRAINSTORE_LICENSE_KEY
{{- if eq $root.Values.cloud "azure" }}
- name: AZURE_STORAGE_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: AZURE_STORAGE_CONNECTION_STRING
{{- end }}
{{- if and (eq $root.Values.cloud "google") (not $api.enableGcsAuth) }}
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_ACCESS_KEY_ID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_SECRET_ACCESS_KEY
{{- end }}
- name: TS_API_HEALTHSERVER_HOST
value: {{ $api.healthServer.host | quote }}
- name: TS_API_HEALTHSERVER_PORT
value: {{ $api.healthServer.port | quote }}
{{- if $customCA.enabled }}
{{- $customCAPath := printf "%s/%s" $customCAMountPath $customCAFilename }}
- name: NODE_EXTRA_CA_CERTS
value: {{ $customCAPath | quote }}
- name: REQUESTS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: SSL_CERT_FILE
value: {{ $customCAPath | quote }}
- name: CURL_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: AWS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: PIP_CERT
value: {{ $customCAPath | quote }}
{{- end }}
{{- with $api.extraEnvVars }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled }}
volumeMounts:
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
mountPath: /tmp
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
mountPath: "/mnt/secrets-store"
readOnly: true
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
mountPath: {{ $customCAMountPath | quote }}
readOnly: true
{{- end }}
{{- end }}
{{- with $api.extraContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled $api.extraVolumes }}
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
emptyDir:
{{- if $api.tmpVolume.sizeLimit }}
sizeLimit: {{ $api.tmpVolume.sizeLimit | quote }}
{{- else }}
{}
{{- end }}
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: {{ $root.Values.azure.keyVaultName }}
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
secret:
secretName: {{ $customCASecretName | quote }}
items:
- key: {{ $customCASecretKey | quote }}
path: {{ $customCAFilename | quote }}
{{- end }}
{{- with $api.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- else }}
[]
{{- end }}
{{- end -}}
33 changes: 33 additions & 0 deletions braintrust/templates/_api-service.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
{{/* Render one API Service from a merged pool configuration. */}}
{{- define "braintrust.apiService" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $api.service.name | default $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.service }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
app: {{ $api.name }}
ports:
- name: {{ $api.service.portName }}
protocol: TCP
port: {{ $api.service.port }}
targetPort: {{ $api.service.port }}
type: {{ $api.service.type }}
{{- end -}}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
46 changes: 46 additions & 0 deletions braintrust/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -217,6 +217,52 @@ Size the request for the pod's full local-storage usage:

When you enable `tmpVolume`, make sure the `ephemeralStorage.request` still covers that extra space.

## API workload isolation

`api.workloadIsolation.enabled` creates fixed-capacity `braintrust-api-ingest`
and `braintrust-api-background` Deployments and Services alongside the existing
default `braintrust-api` pool. The pools share the same image and base
configuration, while allowing independent replicas, resources, probes, rollout
settings, environment overrides, topology spreading, and disruption budgets.

The product-owned route contract is defined in
[`files/contracts/api-workload-isolation-routes.yaml`](files/contracts/api-workload-isolation-routes.yaml).
An ingress or gateway must preserve `braintrust-api` as its default backend and
route these paths:

| Pool | Paths |
| --- | --- |
| `braintrust-api` (default) | All requests not matched by an explicit ingest or background route |
| `braintrust-api-ingest` | `POST /logs3`, `POST /otel/v1/traces`, `POST /attachment`, `POST /attachment/status` |
| `braintrust-api-background` | `POST /v1/eval`, `POST /v1/eval/*`, `POST /function/eval`, `POST /function/sandbox`, `POST /function/use`, `POST /function/invoke-async-batch`, `POST /function/insert-functions`, `POST /automation/logs/trigger`; all methods for `/v1/proxy/chat/completions`, `/v1/proxy/responses` |

By default, Brainstore's internal `BRAINSTORE_AI_PROXY_URL` targets the
background Service while isolation is enabled. For an existing deployment,
first create the pools with
`api.workloadIsolation.brainstoreAiProxyToBackground: false`, and keep public
paths on the default Service. Verify the background pool is ready, then route
the classified public paths and set `brainstoreAiProxyToBackground: true` in a
later release. To roll back, first return both the public paths and
`brainstoreAiProxyToBackground` to the default API Service and verify it is
serving them. Only then disable workload isolation in the chart; the chart
cannot update an external ingress or gateway on its own.

When using the chart-managed Istio `VirtualService`, set
`virtualService.workloadIsolation.enabled: true` during the second release.
The chart then renders the same product-owned route contract ahead of the
existing `virtualService.http` rules, which remain available for fallback or
custom routing of non-classified paths. The product-owned routes take
precedence, so do not use `virtualService.http` to override a classified path.
This option requires both `virtualService.enabled: true` and
`api.workloadIsolation.enabled: true`. It preserves the AWS route methods:
ingest, eval, function, and automation routes match `POST`; proxy routes match
all methods. GKE Ingress cannot route by method, so its equivalent integration
classifies matching paths for all methods.

This feature does not enable autoscaling. Configure fixed replica counts under
`api.replicas`, `api.workloadIsolation.ingest.replicas`, and
`api.workloadIsolation.background.replicas`.

## Testing

This Helm chart includes comprehensive automated unit tests.
Expand Down
55 changes: 55 additions & 0 deletions braintrust/files/contracts/api-workload-isolation-routes.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
# Canonical public route ownership for API workload isolation.
#
# This is product-controlled rather than a chart value: changing it changes
# which API pool receives customer traffic. Ingress and gateway integrations
# must implement this contract and retain the default API Service for all
# unmatched requests.
version: v1
pools:
ingest:
routes:
- path: /logs3
pathType: exact
method: POST
- path: /otel/v1/traces
pathType: exact
method: POST
- path: /attachment
pathType: exact
method: POST
- path: /attachment/status
pathType: exact
method: POST
background:
routes:
# Keep this distinct from the trailing-slash prefix. Istio URI
# prefixes are string prefixes, so /v1/eval would also match
# /v1/evaluate.
- path: /v1/eval
pathType: exact
method: POST
- path: /v1/eval/
pathType: prefix
method: POST
- path: /function/eval
pathType: exact
method: POST
- path: /function/sandbox
pathType: exact
method: POST
- path: /function/use
pathType: exact
method: POST
- path: /function/invoke-async-batch
pathType: exact
method: POST
- path: /function/insert-functions
pathType: exact
method: POST
- path: /automation/logs/trigger
pathType: exact
method: POST
- path: /v1/proxy/chat/completions
pathType: exact
- path: /v1/proxy/responses
pathType: exact
232 changes: 232 additions & 0 deletions braintrust/templates/_api-deployment.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
{{/* Render one API Deployment from a merged pool configuration. */}}
{{- define "braintrust.apiDeployment" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $customCA := $api.customCA -}}
{{- $customCAMountPath := "" -}}
{{- $customCAFilename := "" -}}
{{- $customCASecretName := "" -}}
{{- $customCASecretKey := "" -}}
{{- if $customCA.enabled -}}
{{- $customCAMountPath = required "api.customCA.mountPath is required when api.customCA.enabled is true" $customCA.mountPath -}}
{{- $customCAFilename = required "api.customCA.filename is required when api.customCA.enabled is true" $customCA.filename -}}
{{- $customCASecretName = required "api.customCA.secretName is required when api.customCA.enabled is true" $customCA.secretName -}}
{{- $customCASecretKey = required "api.customCA.secretKey is required when api.customCA.enabled is true" $customCA.secretKey -}}
{{- end -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
{{- $podLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) (deepCopy $api.podLabels) (dict "app" $api.name) $poolLabels -}}
{{- if eq $root.Values.cloud "azure" -}}
{{- $_ := set $podLabels "azure.workload.identity/use" "true" -}}
{{- end -}}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth -}}
{{- $_ := set $podLabels "gke-workload-identity/use" "true" -}}
{{- end -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.deployment }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
replicas: {{ $api.replicas }}
strategy:
type: {{ $api.strategy.type }}
{{- with $api.strategy.rollingUpdate }}
rollingUpdate:
{{- toYaml . | nindent 6 }}
{{- end }}
selector:
matchLabels:
app: {{ $api.name }}
template:
metadata:
labels:
{{- with $podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
annotations:
checksum/config: {{ include (print $root.Template.BasePath "/api-configmap.yaml") $root | sha256sum }}
{{- if and (eq $root.Values.cloud "google") $api.enableGcsAuth }}
iam.gke.io/gcp-service-account: {{ required "api.serviceAccount.googleServiceAccount is required when api.enableGcsAuth is true" $api.serviceAccount.googleServiceAccount }}
{{- end }}
{{- with $api.annotations.pod }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
serviceAccountName: {{ $api.serviceAccount.name }}
{{- with $api.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $api.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if $api.topologySpread.enabled }}
topologySpreadConstraints:
- maxSkew: {{ $api.topologySpread.maxSkew }}
topologyKey: {{ $api.topologySpread.topologyKey | quote }}
whenUnsatisfiable: {{ $api.topologySpread.whenUnsatisfiable }}
labelSelector:
matchLabels:
app: {{ $api.name }}
{{- end }}
{{- with $api.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: api
image: "{{ $api.image.repository }}:{{ $api.image.tag }}"
imagePullPolicy: {{ $api.image.pullPolicy }}
{{- with $api.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
ports:
- containerPort: {{ $api.service.port }}
resources:
{{- toYaml $api.resources | nindent 12 }}
{{- with $api.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with $api.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
envFrom:
- configMapRef:
name: {{ $root.Values.api.name }}
env:
- name: PG_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: PG_URL
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: REDIS_URL
- name: FUNCTION_SECRET_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: FUNCTION_SECRET_KEY
- name: BRAINSTORE_LICENSE_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: BRAINSTORE_LICENSE_KEY
{{- if eq $root.Values.cloud "azure" }}
- name: AZURE_STORAGE_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: AZURE_STORAGE_CONNECTION_STRING
{{- end }}
{{- if and (eq $root.Values.cloud "google") (not $api.enableGcsAuth) }}
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_ACCESS_KEY_ID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: braintrust-secrets
key: GCS_SECRET_ACCESS_KEY
{{- end }}
- name: TS_API_HEALTHSERVER_HOST
value: {{ $api.healthServer.host | quote }}
- name: TS_API_HEALTHSERVER_PORT
value: {{ $api.healthServer.port | quote }}
{{- if $customCA.enabled }}
{{- $customCAPath := printf "%s/%s" $customCAMountPath $customCAFilename }}
- name: NODE_EXTRA_CA_CERTS
value: {{ $customCAPath | quote }}
- name: REQUESTS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: SSL_CERT_FILE
value: {{ $customCAPath | quote }}
- name: CURL_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: AWS_CA_BUNDLE
value: {{ $customCAPath | quote }}
- name: PIP_CERT
value: {{ $customCAPath | quote }}
{{- end }}
{{- with $api.extraEnvVars }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled }}
volumeMounts:
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
mountPath: /tmp
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
mountPath: "/mnt/secrets-store"
readOnly: true
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
mountPath: {{ $customCAMountPath | quote }}
readOnly: true
{{- end }}
{{- end }}
{{- with $api.extraContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if or $api.tmpVolume.enabled (and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver) $customCA.enabled $api.extraVolumes }}
{{- if $api.tmpVolume.enabled }}
- name: tmp-volume
emptyDir:
{{- if $api.tmpVolume.sizeLimit }}
sizeLimit: {{ $api.tmpVolume.sizeLimit | quote }}
{{- else }}
{}
{{- end }}
{{- end }}
{{- if and (eq $root.Values.cloud "azure") $root.Values.azure.enableAzureKeyVaultDriver }}
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: {{ $root.Values.azure.keyVaultName }}
{{- end }}
{{- if $customCA.enabled }}
- name: custom-ca-bundle
secret:
secretName: {{ $customCASecretName | quote }}
items:
- key: {{ $customCASecretKey | quote }}
path: {{ $customCAFilename | quote }}
{{- end }}
{{- with $api.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- else }}
[]
{{- end }}
{{- end -}}
33 changes: 33 additions & 0 deletions braintrust/templates/_api-service.tpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
{{/* Render one API Service from a merged pool configuration. */}}
{{- define "braintrust.apiService" -}}
{{- $root := .root -}}
{{- $api := .api -}}
{{- $role := .role -}}
{{- $poolLabels := dict -}}
{{- if or $root.Values.api.workloadIsolation.enabled (ne $role "default") -}}
{{- $_ := set $poolLabels "braintrust.dev/api-pool" $role -}}
{{- end -}}
{{- $resourceLabels := mergeOverwrite (deepCopy $root.Values.global.labels) (deepCopy $api.labels) $poolLabels -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $api.service.name | default $api.name }}
namespace: {{ include "braintrust.namespace" $root }}
{{- with $resourceLabels }}
labels:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with $api.annotations.service }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
app: {{ $api.name }}
ports:
- name: {{ $api.service.portName }}
protocol: TCP
port: {{ $api.service.port }}
targetPort: {{ $api.service.port }}
type: {{ $api.service.type }}
{{- end -}}
Loading
Loading