Skip to content

Commit bbb093f

Browse files
committed
busbar 0.2.8: make the chart actually boot on busbar 1.5.3
Chart 0.2.7 / appVersion 1.5.3 could not boot by any documented route. Each defect below was reproduced by running getbusbar/busbar:1.5.3 with the chart's own rendered config mounted read-only at /etc/busbar/config.yaml as uid 65532 under a read-only rootfs, then re-verified green the same way and on a real kind cluster with ct lint + ct install. 1. Manage config.locked / config.overlay (new `configMutability`). 1.5.3 refuses to boot a mutable config whose overlay backend is not writable, and that backend defaults to /etc/busbar/busbar-overlay.json, a read-only ConfigMap subPath mount. Default mode is `locked`: a Helm release is a GitOps deployment and it is the only mode that is correct at replicaCount > 1. `overlay` and `none` are documented alternatives, with the ephemerality / replica-divergence tradeoff spelled out. 2. `config` is now required; an empty one fails the render with the minimal 1.5.x config inline, instead of rendering a listener stub that exits 1 with "missing field `providers`". Both READMEs no longer claim a bare install boots. 3. Every config example is busbar 1.5.x syntax: secret references (api_key: { env: VAR }), an identity-providers admin-tokens definition referenced by name from auth.admin_auth, and pool members keyed on `model:` rather than the retired `target:`. 4. governance.enabled no longer emits the dissolved top-level `governance:` block. It renders identity-providers.admin-tokens + auth.admin_auth + store. New governance.store.module defaults to `memory` because every durable store in 1.5.x is a signed store plugin and the 1.5.3 image ships none; a non-memory module without config.plugins fails the render. 5. adminTLS / dataTLS emit 1.5.x secret references. cert_file / key_file / client_ca_file are now cert: { file } / key: { file } / client_ca: { file }; the old fields made every TLS-enabled install exit 1. Also: the bundled helm test now retries (it could fire before the Service endpoint propagated and report a healthy gateway as broken), and ci/overlay-values.yaml covers the overlay mode in ct install. appVersion stays at 1.5.3.
1 parent 87b045b commit bbb093f

14 files changed

Lines changed: 552 additions & 96 deletions

README.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@ The official Helm chart repository for [busbar](https://getbusbar.com), a Rust L
99
```console
1010
helm repo add busbar https://getbusbar.github.io/helm-charts
1111
helm repo update
12-
helm install my-busbar busbar/busbar
12+
helm install my-busbar busbar/busbar -f my-values.yaml
1313
```
1414

15+
`config` is **required** and has no default. busbar is a gateway in front of upstream LLM
16+
providers, so there is no meaningful zero-config install: with no `providers` map the binary
17+
exits 1 at boot. `helm install` with no `config` therefore **fails the render** and prints the
18+
minimal config you need, rather than handing you a `CrashLoopBackOff`.
19+
1520
See the [chart README](charts/busbar/README.md) for the full values reference and examples.
1621

1722
## The two-listener model
@@ -23,20 +28,34 @@ busbar always runs **two separate listeners**:
2328
- **Admin plane** (`admin_listen`, default loopback `127.0.0.1:8081`) — the runtime admin API.
2429

2530
busbar enforces a **boot-guard**: a non-loopback `admin_listen` *refuses to boot* unless the admin
26-
listener requires **mTLS** (`admin_tls` with a `client_ca_file`) **or** an explicit `admin_insecure:
31+
listener requires **mTLS** (`admin_tls` with a `client_ca`) **or** an explicit `admin_insecure:
2732
true` waiver is set.
2833

29-
The chart is secure by default: the admin plane stays on loopback and is **not** exposed, so
30-
`helm install my-busbar busbar/busbar` always boots. To expose the admin plane (`service.admin.enabled=true`)
34+
The chart is secure by default: the admin plane stays on loopback and is **not** exposed, so the
35+
admin boot-guard is always satisfied. To expose the admin plane (`service.admin.enabled=true`)
3136
you must also enable `adminTLS.enabled=true` (mTLS, ideally via cert-manager) or `adminInsecure=true`,
3237
or the chart fails the render with a clear message.
3338

39+
## Config mutability
40+
41+
busbar 1.5.3 refuses to boot a *mutable* config (`config.locked: false`, its default) whose overlay
42+
backend is not writable, and that backend defaults to `busbar-overlay.json` next to the resolved
43+
`config.yaml`. This chart mounts `config.yaml` from a read-only ConfigMap under
44+
`readOnlyRootFilesystem`, so the chart manages that setting for you: `configMutability.mode`
45+
defaults to `locked` (a Helm release is a GitOps deployment; you change config with
46+
`helm upgrade`). Set `configMutability.mode=overlay` to re-enable admin-API config mutation, and
47+
read the tradeoff in `values.yaml` first: the default overlay path is pod-local and ephemeral.
48+
3449
## Governance
3550

36-
With `governance.enabled=true`, busbar owns a single-writer SQLite DB, so the chart deploys a
37-
**StatefulSet with a PVC and `replicas: 1`**. Horizontal scale of a shared SQLite governance store is
51+
With `governance.enabled=true`, busbar owns single-writer state, so the chart deploys a
52+
**StatefulSet with a PVC and `replicas: 1`**. Horizontal scale of a shared single-writer store is
3853
not supported. Stateless (no-governance) deployments use a Deployment and support the HPA.
3954

55+
busbar 1.5.x moved every durable store behind a signed **store** plugin, and the
56+
`getbusbar/busbar:1.5.3` image ships no plugin tarballs, so `governance.store.module` defaults to
57+
`memory` (ephemeral). Naming any other module without also wiring `config.plugins` fails the render.
58+
4059
## Contributing
4160

4261
Charts live under `charts/`. Pull requests are linted and smoke-tested with

charts/busbar/CHANGELOG.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,53 @@
11
# Changelog
22

3+
## 0.2.8
4+
5+
Chart 0.2.7 / appVersion 1.5.3 could not boot by any documented route. Three defects, all
6+
reproduced by running `getbusbar/busbar:1.5.3` with the chart's own rendered config mounted
7+
read-only at the chart's mount path as the chart's uid (65532), plus a fourth found on the way.
8+
9+
- **The chart now manages `config.locked` / `config.overlay` (`configMutability`).** busbar 1.5.3
10+
refuses to boot a mutable config (`config.locked: false`, its default) whose overlay backend is
11+
not writable, and that backend defaults to `busbar-overlay.json` next to the resolved
12+
`config.yaml`, i.e. `/etc/busbar/busbar-overlay.json` - a read-only ConfigMap subPath mount under
13+
`readOnlyRootFilesystem: true`. Every install, including a correct one, died with
14+
`[error] config is mutable (config.locked: false) but the overlay backend ... is not writable`.
15+
New `configMutability.mode` defaults to **`locked`**: a Helm release is a GitOps deployment, the
16+
ConfigMap is the source of truth, and locked is the only mode that behaves correctly at
17+
`replicaCount > 1`. `mode: overlay` re-enables admin-API config mutation against the chart's
18+
writable `/tmp` emptyDir, with the ephemerality and replica-divergence tradeoff spelled out in
19+
`values.yaml` and the README. `mode: none` hands the keys back to you. `.Values.config.config`
20+
always wins over the chart's value.
21+
- **`config` is now required, and an empty one fails the render.** `helm install my-busbar
22+
busbar/busbar` with no values rendered a two-line listener stub that busbar rejects with
23+
`[error] config.yaml: invalid YAML: missing field 'providers'`, while both READMEs claimed the
24+
defaults "always boot". There is no honest bootable zero-config default for a gateway that
25+
proxies upstream providers, so the chart fails fast with the minimal 1.5.x config inline in the
26+
error, and both READMEs were corrected.
27+
- **Every config example is now busbar 1.5.x syntax.** The chart README's "Minimal" example was a
28+
1.x config that 1.5.3 hard-rejects (`auth.client_tokens:`, `providers.openai.api_key_env:`).
29+
Both READMEs, the `values.yaml` comments and all `ci/` fixtures now use secret references
30+
(`api_key: { env: VAR }`), an `identity-providers` admin-tokens definition referenced by name
31+
from `auth.admin_auth`, and pool members keyed on `model:` rather than the retired `target:`.
32+
- **`governance.enabled=true` no longer renders a config busbar refuses to boot.** It emitted a
33+
top-level `governance:` block, which 1.5.3 flags as a 1.x marker
34+
(`governance: block (dissolved into store / rate_card / per_request_fee / groups / advanced /
35+
auth)`). It now renders `identity-providers.admin-tokens` + `auth.admin_auth: [admin-tokens]` +
36+
`store:`. New `governance.store.module` defaults to `memory` because every durable store in
37+
1.5.x is a signed store plugin and the 1.5.3 image ships none; naming another module without
38+
wiring `config.plugins` fails the render with the exact error busbar would have printed.
39+
- **`adminTLS`/`dataTLS` now emit 1.5.x secret references.** Caught by `ct install` on a real kind
40+
cluster: the chart still rendered the 1.4.x plaintext path fields, so every TLS-enabled install
41+
died with `[error] config.yaml: invalid YAML: admin_tls: unknown field 'cert_file', expected one
42+
of 'cert', 'key', 'client_ca'`. `cert_file`/`key_file`/`client_ca_file` are now
43+
`cert: { file: ... }` / `key: { file: ... }` / `client_ca: { file: ... }`.
44+
- Added `ci/overlay-values.yaml` so `configMutability.mode=overlay` is covered by `ct install`.
45+
- The bundled `helm test` now retries. It could fire before the data Service endpoint had
46+
propagated and fail instantly with `curl: (7) ... Could not connect to server`, reporting a
47+
healthy gateway as broken.
48+
49+
`appVersion` stays at `1.5.3`.
50+
351
## 0.2.3
452

553
Fixes an Artifact Hub scan failure (`image not found (package busbar:0.2.2)`) and a real

charts/busbar/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apiVersion: v2
22
name: busbar
33
description: A production-grade Helm chart for busbar, a Rust LLM gateway with a data plane and a separate, loopback-by-default, mTLS-gated admin plane.
44
type: application
5-
version: 0.2.7
5+
version: 0.2.8
66
appVersion: "1.5.3"
77
kubeVersion: ">=1.24.0-0"
88
home: https://getbusbar.com

charts/busbar/README.md

Lines changed: 124 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,82 @@ guard — see [Admin plane](#admin-plane) below.
1818
```console
1919
helm repo add busbar https://getbusbar.github.io/helm-charts
2020
helm repo update
21-
helm install my-busbar busbar/busbar
21+
helm install my-busbar busbar/busbar -f my-values.yaml
2222
```
2323

24-
The defaults render a secure, bootable deployment: the admin plane stays on loopback and is not
25-
exposed, so the gateway always boots. Supply a provider key and a client token to make it useful:
24+
`config` is **required and has no default.** busbar is a gateway in front of upstream LLM
25+
providers, and it exits 1 at boot without a `providers` map
26+
(`[error] config.yaml: invalid YAML: missing field 'providers'`), so there is no honest
27+
zero-config default to ship: any "bootable" one would mean inventing credentials for an upstream
28+
nobody configured. Installing with no `config` therefore **fails the render** and prints the
29+
minimal config you need, instead of giving you a `CrashLoopBackOff`.
30+
31+
Everything else is secure by default: the admin plane stays on loopback and is not exposed, so the
32+
admin boot-guard is satisfied out of the box.
2633

2734
```console
28-
helm install my-busbar busbar/busbar \
29-
--set-string secrets.data.OPENAI_API_KEY=sk-... \
30-
--set-string secrets.data.BUSBAR_CLIENT_TOKEN=my-client-token \
31-
--set 'config.auth.chain[0]=client-tokens' \
32-
--set 'config.auth.client_tokens[0]=${BUSBAR_CLIENT_TOKEN}' \
33-
--set config.providers.openai.api_key_env=OPENAI_API_KEY \
34-
--set config.models.gpt-4o.provider=openai \
35-
--set config.models.gpt-4o.max_concurrent=8
35+
helm install my-busbar busbar/busbar -f - <<'EOF'
36+
secrets:
37+
data:
38+
ANTHROPIC_KEY: sk-ant-...
39+
BUSBAR_ADMIN_TOKEN: a-long-random-admin-token
40+
config:
41+
identity-providers:
42+
admin-tokens:
43+
module: admin-tokens
44+
token: { env: BUSBAR_ADMIN_TOKEN }
45+
auth:
46+
chain: []
47+
admin_auth: [admin-tokens]
48+
providers:
49+
anthropic:
50+
api_key: { env: ANTHROPIC_KEY }
51+
models:
52+
claude:
53+
provider: anthropic
54+
pools:
55+
default:
56+
members:
57+
- model: claude
58+
EOF
3659
```
3760

3861
Secrets are injected as environment variables from a Kubernetes Secret and referenced from the
39-
config with `${VAR}` interpolation. The provider catalog ships inside the image at
40-
`/etc/busbar/providers.yaml`; only set `providersCatalog` to override it.
62+
config by **secret reference** (`api_key: { env: ANTHROPIC_KEY }`) - busbar 1.5.x removed the old
63+
`*_env` string fields. The provider catalog ships inside the image at `/etc/busbar/providers.yaml`;
64+
only set `providersCatalog` to override it.
65+
66+
`auth.chain: []` is an **open relay** and is for a first boot only; configure a real auth chain
67+
before you expose the data plane.
68+
69+
## Config mutability
70+
71+
busbar 1.5.3 **refuses to boot** a mutable config (`config.locked: false`, its default) whose
72+
overlay backend is not writable, and that backend defaults to `busbar-overlay.json` next to the
73+
resolved `config.yaml` - here, `/etc/busbar/busbar-overlay.json`, which is a read-only ConfigMap
74+
mount under `readOnlyRootFilesystem: true`:
75+
76+
```
77+
[error] config is mutable (config.locked: false) but the overlay backend
78+
'/etc/busbar/busbar-overlay.json' is not writable (is the config directory read-only?).
79+
```
80+
81+
So the chart manages this for you via `configMutability.mode`:
82+
83+
| mode | renders | meaning |
84+
|---|---|---|
85+
| `locked` (default) | `config: { locked: true }` | The ConfigMap is the source of truth. The admin API refuses **config mutations** (it still serves keys, stats, `/config/reload` and the rest); you change config with `helm upgrade`, and `reloadOnConfigChange` rolls the pods. |
86+
| `overlay` | `config: { locked: false, overlay: { file: <overlayFile> } }` | Admin-API config mutation works again, persisted to `configMutability.overlayFile` (default `/tmp/busbar-overlay.json`, on the chart's emptyDir). |
87+
| `none` | nothing | You own `config.locked` / `config.overlay` inside `.Values.config`. |
88+
89+
`locked` is the default because a Helm release **is** a GitOps deployment, and because it is the
90+
only mode that is correct at `replicaCount > 1`. In `overlay` mode with the default path the
91+
overlay lives on a per-pod `emptyDir`: mutations are lost on restart, on `helm upgrade` and on any
92+
reschedule, and with more than one replica **each replica keeps its own overlay and the replicas
93+
will diverge**. Use `overlay` at `replicaCount: 1`, or point `overlayFile` at durable storage that
94+
every replica mounts read-write.
95+
96+
Anything you set under `.Values.config.config` wins over the chart's value in every mode.
4197

4298
## Admin plane
4399

@@ -52,10 +108,22 @@ If you enable the admin Service without either, the chart **fails the render** w
52108

53109
## Governance
54110

55-
When `governance.enabled=true`, busbar owns a single-writer SQLite DB — per-replica state. The chart
56-
switches to a **StatefulSet with a PVC and `replicas: 1`**. **Horizontal scale of a shared SQLite
57-
governance store is not supported.** Stateless (no governance) deployments use a Deployment and may
58-
scale out (HPA supported).
111+
When `governance.enabled=true`, busbar owns single-writer state, so the chart switches to a
112+
**StatefulSet with a PVC and `replicas: 1`**. **Horizontal scale of a shared single-writer store is
113+
not supported.** Stateless (no governance) deployments use a Deployment and may scale out (HPA
114+
supported).
115+
116+
busbar 1.5.x dissolved the old top-level `governance:` config block (it is now a hard 1.x marker
117+
the binary refuses to boot) into `store:` plus an admin-tokens identity provider referenced from
118+
`auth.admin_auth`. The chart renders that shape for you.
119+
120+
Every durable store in 1.5.x is a signed **store** plugin, and the `getbusbar/busbar:1.5.3` image
121+
ships no plugin tarballs (`busbar --list-plugins` prints `no plugin tarballs found`), so
122+
`governance.store.module` defaults to `memory`: keys, group usage and ledgers are **ephemeral** and
123+
reset on restart. To get durability, mount the signed store plugin into the pod yourself, set
124+
`config.plugins.enabled: true` and `config.plugins.dir`, then set `governance.store.module`. The
125+
chart fails the render if you name a non-`memory` module without wiring the plugin, because busbar
126+
would otherwise exit 1.
59127

60128
## Examples
61129

@@ -64,37 +132,59 @@ scale out (HPA supported).
64132
```yaml
65133
secrets:
66134
data:
67-
OPENAI_API_KEY: sk-...
68-
BUSBAR_CLIENT_TOKEN: my-client-token
135+
ANTHROPIC_KEY: sk-ant-...
136+
BUSBAR_ADMIN_TOKEN: a-long-random-admin-token
69137
config:
138+
identity-providers:
139+
admin-tokens:
140+
module: admin-tokens
141+
token: { env: BUSBAR_ADMIN_TOKEN }
70142
auth:
71-
chain: ["client-tokens"]
72-
client_tokens: ["${BUSBAR_CLIENT_TOKEN}"]
143+
chain: [] # open relay; set a real chain before exposing the data plane
144+
admin_auth: [admin-tokens]
73145
providers:
74-
openai:
75-
api_key_env: OPENAI_API_KEY
146+
anthropic:
147+
api_key: { env: ANTHROPIC_KEY }
76148
models:
77-
gpt-4o:
78-
provider: openai
79-
max_concurrent: 8
149+
claude:
150+
provider: anthropic
151+
pools:
152+
default:
153+
members:
154+
- model: claude # `model:`, not the retired 1.4.x `target:`
80155
```
81156
82157
### With governance
83158
84-
Governance requires an admin token put it in `secrets.data` under the key named by
159+
Governance requires an admin token: put it in `secrets.data` under the key named by
85160
`governance.adminTokenEnv` (default `BUSBAR_ADMIN_TOKEN`); the chart wires
86-
`governance.admin_token` for you. (`helm install` fails fast if it is missing.)
161+
`identity-providers.admin-tokens` and `auth.admin_auth` for you. (`helm install` fails fast if it
162+
is missing.)
87163

88164
```yaml
89165
governance:
90166
enabled: true
167+
store:
168+
module: memory # see the note above about durable store plugins
91169
dbPath: /var/lib/busbar/governance.db
92170
persistence:
93171
size: 5Gi
94172
storageClass: fast-ssd
95173
secrets:
96174
data:
175+
ANTHROPIC_KEY: sk-ant-...
97176
BUSBAR_ADMIN_TOKEN: super-secret-admin-token
177+
config:
178+
providers:
179+
anthropic:
180+
api_key: { env: ANTHROPIC_KEY }
181+
models:
182+
claude:
183+
provider: anthropic
184+
pools:
185+
default:
186+
members:
187+
- model: claude
98188
```
99189

100190
### With ingress (data plane)
@@ -156,14 +246,18 @@ networkPolicy:
156246
| `nodeSelector` | Node selector | `{}` |
157247
| `tolerations` | Tolerations | `[]` |
158248
| `affinity` | Affinity | `{}` |
159-
| `config` | Structured map rendered to `config.yaml` (chart injects `listen`/`admin_listen`) | `{}` |
249+
| `config` | **Required.** Structured map rendered to `config.yaml` (chart injects `listen`/`admin_listen`) | `{}` (fails the render) |
250+
| `configMutability.mode` | `locked` / `overlay` / `none` (see [Config mutability](#config-mutability)) | `locked` |
251+
| `configMutability.overlayFile` | Overlay backend path, `overlay` mode only. Pod-local and ephemeral by default | `/tmp/busbar-overlay.json` |
160252
| `providersCatalog` | Optional map rendered to `providers.yaml` (else the image's catalog is used) | `{}` |
161253
| `existingConfigMap` | Use an existing ConfigMap instead of rendering config | `""` |
162254
| `secrets.create` | Create a Secret from `secrets.data` | `true` |
163255
| `secrets.existingSecret` | Use an existing Secret | `""` |
164256
| `secrets.data` | Key/value secret data injected as env vars | `{}` |
165257
| `governance.enabled` | Enable governance (StatefulSet + PVC, replicas=1) | `false` |
166-
| `governance.dbPath` | Governance SQLite DB path | `/var/lib/busbar/governance.db` |
258+
| `governance.adminTokenEnv` | Secret key holding the admin token | `BUSBAR_ADMIN_TOKEN` |
259+
| `governance.store.module` | `store:` module. Non-`memory` needs a signed store plugin you supply | `memory` |
260+
| `governance.dbPath` | Governance DB path (`store.settings.db_path`, non-`memory` only) | `/var/lib/busbar/governance.db` |
167261
| `governance.persistence.size` | Governance PVC size | `1Gi` |
168262
| `governance.persistence.storageClass` | Governance PVC storage class | `""` |
169263
| `governance.persistence.accessMode` | Governance PVC access mode | `ReadWriteOnce` |

charts/busbar/ci/admin-mtls-values.yaml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
11
# CI: exposed admin plane with cert-manager mTLS. Requires cert-manager + a
22
# ClusterIssuer named "busbar-ca" in the cluster (the workflow installs both).
33
config:
4+
identity-providers:
5+
admin-tokens:
6+
module: admin-tokens
7+
token:
8+
env: BUSBAR_ADMIN_TOKEN
9+
auth:
10+
chain: []
11+
admin_auth: [admin-tokens]
412
providers:
513
anthropic:
6-
api_key_env: ANTHROPIC_KEY
14+
api_key:
15+
env: ANTHROPIC_KEY
716
models:
817
claude-sonnet:
918
provider: anthropic
1019
max_concurrent: 20
11-
max_requests: -1
20+
pools:
21+
default:
22+
members:
23+
- model: claude-sonnet
1224
secrets:
1325
create: true
1426
data:
1527
ANTHROPIC_KEY: ci-dummy-anthropic-key
28+
BUSBAR_ADMIN_TOKEN: ci-dummy-admin-token
1629
service:
1730
admin:
1831
enabled: true

0 commit comments

Comments
 (0)