Skip to content

Commit aa6ced3

Browse files
dforsberclaude
andcommitted
release: v0.10.0 + official Helm chart
- CHANGELOG: DuckDB 1.5.2, Kubernetes Helm chart, cluster-mode mTLS, PGWire `postgresql` ALPN, non-AWS S3 heartbeat resilience, loopback TLS via SNI - README: version placeholders bumped to 0.10.0 - charts/boilstream: vendored official Helm chart (v0.3.0, appVersion 0.10.0) with EKS + Hetzner example overlays - .github/workflows/publish-chart.yaml: OCI push to ghcr.io/boilingdata/charts on `chart-v*` tags Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 595d8bf commit aa6ced3

22 files changed

Lines changed: 1735 additions & 4 deletions
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Publish Helm Chart
2+
3+
on:
4+
push:
5+
tags:
6+
- "chart-v*"
7+
workflow_dispatch:
8+
inputs:
9+
tag:
10+
description: "Chart version tag (e.g. 0.3.0)"
11+
required: true
12+
13+
permissions:
14+
contents: read
15+
packages: write
16+
17+
jobs:
18+
publish:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: Install Helm
24+
uses: azure/setup-helm@v4
25+
with:
26+
version: v3.15.0
27+
28+
- name: Lint
29+
run: helm lint charts/boilstream
30+
31+
- name: Package
32+
run: helm package charts/boilstream -d dist/
33+
34+
- name: Log in to GHCR
35+
run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin
36+
37+
- name: Push chart
38+
run: |
39+
CHART_TGZ=$(ls dist/boilstream-*.tgz | head -n1)
40+
helm push "$CHART_TGZ" oci://ghcr.io/boilingdata/charts

CHANGELOG.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,47 @@ All notable changes to BoilStream will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.10.0] - 2026-04-18
9+
10+
### Features
11+
12+
- **Official Kubernetes Helm Chart**: Production-ready chart for multi-pod cluster deployments
13+
- StatefulSet with headless Service for stable per-pod DNS
14+
- Per-pod ClusterIP Services exposing PGWire, Kafka, FlightRPC, auth, and cluster ports
15+
- Envoy Gateway integration with TLS passthrough + SNI-based routing — one external LoadBalancer IP terminates all protocols across all pods
16+
- cert-manager wiring: public wildcard cert (Let's Encrypt ACME, DNS-01 or HTTP-01) and a separate internal CA for pod-to-pod mTLS
17+
- PodDisruptionBudget, standard `app.kubernetes.io/*` labels, configurable `affinity` / `nodeSelector` / `tolerations` / `topologySpreadConstraints`
18+
- `preStop` hook + `terminationGracePeriodSeconds` for graceful connection drain during rolling updates
19+
- IRSA / Pod Identity annotations for AWS; image pull secrets for private registries
20+
- Superadmin password and MFA secret sourced from pre-created `Secret`s — never committed to values
21+
- Example overlays: `values-eks-example.yaml` (AWS / NLB) and `values-hetzner-example.yaml` (CloudFleet / Hetzner ARM64)
22+
- K8s production-readiness test suite under `tests/k8s/` (pod health, leader election, broker registry, SNI routing, failover)
23+
24+
- **Cluster-Mode mTLS**: Pod-to-pod cluster coordination traffic can now be encrypted with mutual TLS
25+
- Separate trust root from the public-facing cert — internal CA is isolated from browser trust
26+
- `cluster_mode.tls.{cert_path,key_path,ca_cert_path,require_client_cert}` configuration block
27+
- `require_client_cert` is now enforced at the TLS handshake (not just at the application layer)
28+
- Works out of the box with the chart's cert-manager `ClusterIssuer`
29+
30+
- **PGWire Direct-TLS ALPN**: Server now advertises the `postgresql` ALPN token during TLS negotiation, enabling `libpq >= 18` direct-TLS clients to connect without a downgrade round-trip
31+
32+
- **DuckDB 1.5.2**: Upgraded from 1.4.4 LTS
33+
- New PostgreSQL type inference for `format_type(oid, typmod)` parameter binding — now infers `[INT8, INT4]` (was `[TEXT, INT4]`). BI tools that previously relied on the old inference may need to cast explicitly
34+
- Inherits all DuckDB 1.5.x improvements (planner, vector operations, extension loader)
35+
36+
### Fixes
37+
38+
- **Leader heartbeat on non-AWS S3**: Heartbeat now retries with a re-read confirmation and an unconditional PUT fallback when `If-Match` ETag comparisons fail. Fixes stalled leadership on S3 implementations where ETags don't round-trip identically between GET and PUT (Hetzner Object Storage, some MinIO configurations)
39+
- **Cluster-sync mTLS server on promotion**: The internal coordination server now starts when a worker is promoted to leader mid-life (previously only started at boot for pods that booted as leader — caused failover gaps)
40+
- **Auth server loopback TLS**: The auth server on `:8443` now presents a self-signed loopback certificate for `localhost` / `127.0.0.1` SNI connections and the public cert for its real hostname. Fixes in-pod TLS handshakes when the public cert doesn't cover loopback addresses (e.g. Let's Encrypt deployments)
41+
42+
### Improvements
43+
44+
- **WebAuthn / RP config from single source**: The Helm chart now propagates `values.domain` into both `webauthn_rp_id` and `webauthn_rp_origin`, removing one place where the external hostname could drift
45+
- **Helm chart de-localized**: Example charts no longer embed localhost certificates or hard-coded dev hostnames — production deploys use real FQDNs end-to-end
46+
- **boilstream-admin wrapper for K8s**: New `scripts/boilstream-admin-k8s.sh` reads CA, superadmin password, and MFA secret live from Kubernetes `Secret`s; computes TOTP locally and execs the admin CLI against the in-cluster deployment
47+
- **`testMode.disableTurnstile` chart value**: Lets CI/test clusters skip the Turnstile CAPTCHA on `/auth/email/signup` without rebuilding the image
48+
849
## [0.9.0] - 2026-04-08
950

1051
### Features

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,21 @@ Download, start, and connect with any Postgres-compatible BI tool. Data streams
3434

3535
## Start
3636

37+
See [GitHub releases](https://github.com/boilingdata/boilstream/releases) for the latest version.
38+
3739
```bash
3840
# Download (linux-x64, linux-aarch64, darwin-aarch64)
39-
curl -L -o boilstream https://www.boilstream.com/binaries/darwin-aarch64/boilstream-0.9.0
40-
curl -L -o boilstream-admin https://www.boilstream.com/binaries/darwin-aarch64/boilstream-admin-0.9.0
41+
# Replace {VERSION} with the latest release (e.g. 0.10.0)
42+
curl -L -o boilstream https://www.boilstream.com/binaries/darwin-aarch64/boilstream-{VERSION}
43+
curl -L -o boilstream-admin https://www.boilstream.com/binaries/darwin-aarch64/boilstream-admin-{VERSION}
4144
chmod +x boilstream boilstream-admin
4245

4346
SERVER_IP_ADDRESS=1.2.3.4 ./boilstream
4447

45-
# Docker: boilinginsights/boilstream:x64-linux-0.9.0 or :aarch64-linux-0.9.0
48+
# Docker
4649
docker run -v ./config.yaml:/app/config.yaml \
4750
-p 443:443 -p 5432:5432 -p 50051:50051 -p 50250:50250 \
48-
-e SERVER_IP_ADDRESS=1.2.3.4 boilinginsights/boilstream:aarch64-linux-0.9.0
51+
-e SERVER_IP_ADDRESS=1.2.3.4 boilinginsights/boilstream:aarch64-linux-{VERSION}
4952
```
5053

5154
> _Use the accompanying `docker-compose.yml` to start Grafana and MinIO_

charts/README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# BoilStream Helm Charts
2+
3+
Official Helm charts for deploying BoilStream on Kubernetes.
4+
5+
## Charts
6+
7+
- **[boilstream](./boilstream/)** — StatefulSet-based cluster with Envoy Gateway SNI routing, cert-manager TLS, and optional cluster-mode mTLS.
8+
9+
## Install via OCI
10+
11+
```bash
12+
helm install boilstream oci://ghcr.io/boilingdata/charts/boilstream \
13+
--version 0.3.0 \
14+
-n boilstream --create-namespace \
15+
-f my-values.yaml
16+
```
17+
18+
## Install from source
19+
20+
```bash
21+
helm install boilstream ./charts/boilstream \
22+
-n boilstream --create-namespace \
23+
-f my-values.yaml
24+
```
25+
26+
## Prerequisites
27+
28+
- [cert-manager](https://cert-manager.io) >= 1.13
29+
- [Envoy Gateway](https://gateway.envoyproxy.io) >= 1.2
30+
31+
See [docs.boilstream.com/guide/kubernetes](https://docs.boilstream.com/guide/kubernetes.html) for the full deployment guide.
32+
33+
## Releasing
34+
35+
Chart releases are cut via the `publish-chart.yaml` workflow on `chart-v*` tags:
36+
37+
```bash
38+
git tag chart-v0.3.0
39+
git push --tags
40+
```
41+
42+
The workflow lints, packages, and pushes the chart to `oci://ghcr.io/boilingdata/charts`.

charts/boilstream/.helmignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Patterns to ignore when building packages.
2+
# These artifacts will not be added to the chart tarball produced by
3+
# `helm package`. Files matching the patterns below are excluded by default.
4+
.DS_Store
5+
.git/
6+
.gitignore
7+
.bzr/
8+
.hg/
9+
.hgignore
10+
.svn/
11+
*.swp
12+
*.bak
13+
*.tmp
14+
*.orig
15+
*~
16+
# IDE
17+
.vscode/
18+
.idea/
19+
# Tooling
20+
.helmignore
21+
.tool-versions
22+
# Tests / chart development
23+
ci/
24+
tests/
25+
*.tgz

charts/boilstream/Chart.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
apiVersion: v2
2+
name: boilstream
3+
description: |
4+
BoilStream — high-performance streaming data ingestion (PGWire, Kafka,
5+
Arrow Flight) with S3-coordinated cluster mode and DuckLake catalog
6+
support. Each pod participates in S3-based leader election and serves
7+
per-user catalogs; failed pods are recovered from S3 backups.
8+
type: application
9+
version: 0.3.0
10+
appVersion: "0.10.0"
11+
kubeVersion: ">=1.27.0"
12+
keywords:
13+
- streaming
14+
- pgwire
15+
- kafka
16+
- arrow-flight
17+
- ducklake
18+
- duckdb
19+
- s3
20+
home: https://boilstream.com
21+
sources:
22+
- https://github.com/boilingdata/boilstream
23+
maintainers:
24+
- name: BoilStream
25+
url: https://boilstream.com
26+
annotations:
27+
# Documented prerequisites — operators must install these BEFORE the chart.
28+
# Listed as annotations because Helm dependency support assumes a Chart we
29+
# control; cert-manager and Envoy Gateway are independent installs.
30+
boilstream.com/prereqs: |
31+
cert-manager (>=1.13) — issues TLS Secrets via the configured ClusterIssuer
32+
Envoy Gateway (>=1.2) — provides the GatewayClass referenced by gateway.className

charts/boilstream/README.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# BoilStream Helm chart
2+
3+
Deploys a BoilStream cluster on Kubernetes — StatefulSet of N pods sharing
4+
state via S3 (cluster coordination + per-user catalog backups), with optional
5+
Envoy Gateway SNI routing and pod-to-pod mTLS.
6+
7+
## Prerequisites
8+
9+
The chart **does not** install these — operators must provide them:
10+
11+
| Prereq | Why |
12+
|---|---|
13+
| **cert-manager ≥ 1.13** | Issues the public wildcard cert and (optionally) the internal cluster-mTLS cert. Skip if you supply both via `tls.existingSecret` and `clusterTls.existingSecret`. |
14+
| **A `ClusterIssuer`** named in `tls.issuer.name` (default `boilstream-ca-issuer`) | cert-manager needs an issuer to mint certs. Self-signed CA, Let's Encrypt + DNS-01, or ACM Private CA all work. |
15+
| **Envoy Gateway ≥ 1.2** with a `GatewayClass` named in `gateway.className` (default `eg`) | The chart-managed `Gateway` + `TLSRoute` resources reference this class. Skip by setting `gateway.enabled: false` (clients then must reach the Pods/headless Service directly). |
16+
| **An S3-compatible bucket** at `s3.endpoint` (real S3, MinIO, RustFS, R2…) | Stores leader.json, broker registry, per-user catalog backups. |
17+
18+
Quick local install of the prereqs (tested on OrbStack k8s):
19+
20+
```bash
21+
helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.2.1 \
22+
-n envoy-gateway-system --create-namespace
23+
helm install cert-manager jetstack/cert-manager --version v1.16.2 \
24+
-n cert-manager --create-namespace --set crds.enabled=true
25+
kubectl apply -f - <<'YAML'
26+
apiVersion: cert-manager.io/v1
27+
kind: ClusterIssuer
28+
metadata: { name: selfsigned-root }
29+
spec: { selfSigned: {} }
30+
---
31+
apiVersion: cert-manager.io/v1
32+
kind: Certificate
33+
metadata: { name: boilstream-ca, namespace: cert-manager }
34+
spec:
35+
isCA: true
36+
commonName: BoilStream Local CA
37+
secretName: boilstream-ca-tls
38+
privateKey: { algorithm: ECDSA, size: 256 }
39+
issuerRef: { name: selfsigned-root, kind: ClusterIssuer }
40+
---
41+
apiVersion: cert-manager.io/v1
42+
kind: ClusterIssuer
43+
metadata: { name: boilstream-ca-issuer }
44+
spec: { ca: { secretName: boilstream-ca-tls } }
45+
---
46+
apiVersion: gateway.networking.k8s.io/v1
47+
kind: GatewayClass
48+
metadata: { name: eg }
49+
spec: { controllerName: gateway.envoyproxy.io/gatewayclass-controller }
50+
YAML
51+
```
52+
53+
## Install
54+
55+
**Local dev (OrbStack + RustFS, defaults):**
56+
57+
```bash
58+
helm install boilstream ./charts/boilstream
59+
```
60+
61+
**EKS (AWS S3 + ACM/Let's Encrypt + Pod Identity):**
62+
63+
```bash
64+
helm install boilstream ./charts/boilstream \
65+
-f ./charts/boilstream/values-eks-example.yaml \
66+
--set image.repository=<account>.dkr.ecr.<region>.amazonaws.com/boilstream \
67+
--set image.tag=0.10.0 \
68+
--set superadmin.existingSecret=boilstream-superadmin \
69+
--set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::...
70+
```
71+
72+
## Key values
73+
74+
| Group | Key | Default | Notes |
75+
|---|---|---|---|
76+
| Replicas | `replicas` | `3` | Min 1, recommended ≥3 for quorum-style availability. |
77+
| Image | `image.repository`, `image.tag`, `image.pullPolicy`, `image.pullSecrets` | `boilstream:local-1.5.2`, `Never`, `[]` | Set `pullSecrets: [{name: regcred}]` for private registries. |
78+
| Naming | `namespace`, `namespaceCreate`, `nameOverride`, `fullnameOverride` | `boilstream`, `true` | Set `namespaceCreate: false` if using `--create-namespace` or pre-managed namespaces. |
79+
| Public TLS | `tls.existingSecret`, `tls.issuer.{name,kind}` | (chart-managed) | Provide `existingSecret` to use ACM-imported or pre-issued certs. |
80+
| Cluster mTLS | `clusterTls.enabled`, `clusterTls.issuer.*` | `false` | Pod-to-pod gRPC on `:8444`. Separate trust root from public edge. |
81+
| Gateway | `gateway.enabled`, `gateway.className`, `gateway.serviceAnnotations` | `true`, `eg`, `{}` | Annotations propagate to the LB Service Envoy Gateway provisions. |
82+
| ServiceAccount | `serviceAccount.{create,name,annotations}` | `true`, `boilstream`, `{}` | Set `eks.amazonaws.com/role-arn` for IRSA / Pod Identity. |
83+
| Resources | `resources.requests`, `resources.limits` | dev-sized | **Override for production** — see EKS overlay. |
84+
| Disruption | `podDisruptionBudget.{enabled,maxUnavailable\|minAvailable}` | `true`, `1` | Voluntary-disruption guard during drains/upgrades. |
85+
| Lifecycle | `terminationGracePeriodSeconds`, `preStopSleepSeconds` | `120`, `5` | Time for in-flight S3 flush + leader stepdown. |
86+
| S3 | `s3.{endpoint,bucket,region,prefix,forcePathStyle,accessKey,secretKey}` | RustFS dev defaults | Leave creds empty on EKS to use the IRSA role. |
87+
| Superadmin | `superadmin.password` *or* `superadmin.existingSecret` | dev literal | **Always override in prod** — use `existingSecret` from sealed-secrets / ESO / SSM. |
88+
89+
See [`values.yaml`](values.yaml) for the full set with inline comments, and
90+
[`values-eks-example.yaml`](values-eks-example.yaml) for a production-shaped
91+
override.
92+
93+
## Architecture summary
94+
95+
- **StatefulSet** of `replicas` pods. Each pod has an emptyDir for hot-tier
96+
data (`./data/duckdb/`, `./data/tantivy/`); BoilStream uploads to S3
97+
continuously, so pod loss is recoverable.
98+
- **Headless Service** gives every pod a stable per-pod DNS name
99+
`boilstream-N.boilstream-headless.<ns>.svc.cluster.local`. Used as
100+
`cluster_mode.advertised_host`.
101+
- **Per-pod ClusterIP Services** are the backends for SNI-routed TLSRoutes.
102+
- **Envoy Gateway** (when enabled) exposes one TLS-passthrough listener per
103+
protocol; SNI hostname `boilstream-N.<domain>` routes to the matching pod.
104+
- **cert-manager** issues `*.<domain>` (public) and the cluster-mTLS cert.
105+
- **S3** is the source of truth for cluster coordination
106+
(`<prefix>cluster_state/leader.json`, `<prefix>cluster_state/brokers/*.json`)
107+
and per-user catalog backups.
108+
109+
## Verify
110+
111+
After install:
112+
113+
```bash
114+
kubectl -n <ns> rollout status statefulset/boilstream
115+
kubectl -n <ns> get pods,gateway,certificate,secret
116+
```
117+
118+
Production-readiness suite (from `tests/k8s/`):
119+
120+
```bash
121+
cd tests/k8s
122+
pip install -r requirements.txt
123+
pytest -v -k "not destructive"
124+
```
125+
126+
## Upgrade
127+
128+
```bash
129+
helm upgrade boilstream ./charts/boilstream -f my-values.yaml
130+
```
131+
132+
The StatefulSet selector is intentionally narrow (`app: boilstream`) to keep
133+
upgrade-compatible across chart-version bumps. Standard `app.kubernetes.io/*`
134+
labels are added on top for tooling.

0 commit comments

Comments
 (0)