Charts
Custom resource definitions
Helm does not install or upgrade CRDs, so the platform CRDs ship as a versionedcrds.yaml
alongside the chart. Apply it server-side before the first install and again before every
upgrade (upgrades need --force-conflicts, because the running controller owns fields on the
installed CRDs):
Install pattern
--timeout bounds
each Kubernetes operation including hooks, and it must not expire before the Job’s own budget or
Helm wedges the release while the Job was still within it.
Use your deployment’s registry, chart version, and values file. For air-gapped deployments, mirror
the chart and image artifacts into your internal registry first. Avoid --atomic on a first
install: hook resources are not release-managed, and a failed hook plus --atomic uninstalls the
fresh release instead of leaving the failed hook Job and its logs in place to diagnose. The
umbrella chart’s own README (shipped in the chart, helm show readme) documents the full hook
semantics, including the reserved hook-weight ranges each subchart owns.
Migrations and upgrades
Database migrations run as a blockingpre-install,pre-upgrade hook Job, always first (it holds
the lowest hook weight in the release), before any workload updates. Migrations are
expand/contract across the whole release: a release’s schema changes stay compatible with the
previous release’s binaries, which is also what makes helm rollback safe (rollback never runs
migrations down). A failed migration Job fails the release and stays in the namespace with its
logs; fix the cause and re-run the same command: the Job is idempotent and re-created on the next
attempt.
If an upgrade wedges in pending-upgrade (for example a hook timeout), recover with
helm rollback; the umbrella chart’s README (shipped in the chart; helm show readme) carries
the exact recovery recipe alongside the full hook semantics.
First API key
Self-hosted installs mint the deployment’s first organization and API key at install time via apost-install hook Job, writing the key material only into the hiloop-first-api-key Secret,
never to logs. helm get notes hiloop -n hiloop prints the retrieval command. Back that Secret up
with your cluster backups: the cluster holds the only copy, and re-running install or upgrade
never re-mints or rotates it. Bring your own key material instead by setting
api.firstKey.existingSecret.
Shared configuration
Keep shared identity and routing values consistent across subcharts:- session issuer;
- session audience;
- API and telemetry hostnames;
- JWKS location;
- image registry and digests;
- database and object storage references.
Telemetry storage and sizing
The telemetry gateway keeps its durable datasets — the canonical event store and the raw-record archive — in an object store, configured withtelemetry-gateway.objectStore.url. This value
is required: an install or upgrade without it fails at render time with
s3://…; S3 credentials come from the pod’s
ambient identity, not the chart), or set file:///data on a single-node install that knowingly
keeps telemetry on the local volume — and size that volume for your full retention if you do.
The gateway runs as a StatefulSet, and each replica keeps its own small persistent volume
(persistence.size, default 5Gi, one claim per pod) holding only the write-ahead log. Its
footprint tracks the un-flushed ingest backlog, not lifetime ingest, so the default is enough
unless you expect sustained multi-gigabyte ingest bursts between flushes. Plan total WAL storage
as persistence.size × replicaCount.
High availability (active/standby)
The sizing presets run one replica — the shape every install can run. Settelemetry-gateway.replicaCount: 2 to run an active/standby pair instead: one active writer
plus one warm standby, coordinated through a Kubernetes lease. Two replicas require a shared
object store (objectStore.url pointing at a bucket, not file://) and the Postgres catalog
(catalog.enabled: true) — the chart refuses to render without them.
What you see with two replicas:
- Two Ready pods, each with its own write-ahead-log volume — per-pod claims named
data-<name>-0anddata-<name>-1, not one shared volume. Each replica also gets the full sizing preset’s CPU and memory. - Clients reach only the active writer, through the Service. The lease holder labels its own
pod and the Service routes on that label —
kubectl get pods -l telemetry.hiloop.ai/writer=activeshows who is serving. A standby addressed directly answers every data request with a retryable “standing by” error (gRPCUNAVAILABLE, HTTP 503); that is by design — retry against the Service. - Rollouts hand off instead of gapping. The outgoing writer releases the lease on shutdown
and keeps serving through a short drain window (
shutdownDrainWindowSeconds) while the standby takes over, so the ingest interruption during an upgrade is on the order of a second rather than a full restart gap. If a node is lost, the standby takes over when the lease expires, within seconds; the dead pod’s un-flushed write-ahead log is replayed from its own volume once that pod is rescheduled, so nothing acknowledged is dropped.
Memory sizing
Two application budgets bound gateway memory, and the sizing preset’s memory limit must clear both with headroom:hotBufferBudgetMiB(default 384) caps in-memory buffered ingest;- each in-flight query runs under a ~256 MiB engine budget.
medium preset (1Gi limit) is sized for exactly that. Raise hotBufferBudgetMiB in
step with a larger preset; small (512Mi) fits light traffic only.
Backpressure
When buffered ingest reaches the budget (for example while the object store is unreachable), the gateway rejects new ingest with a retryable backpressure error — gRPCRESOURCE_EXHAUSTED,
HTTP 429 — instead of growing memory without bound. Nothing already accepted is affected, and a
rejected batch is never half-written. The capture wrapper and SDKs treat this error as retryable
automatically: events are spooled and redelivered with backoff when the gateway recovers, so a
backpressure window shows up as delayed delivery, not data loss. Sustained backpressure is an
operator signal that flushing is stalled (object-store health) or the deployment is undersized.
Request and payload limits
- Ingest and other write requests cap at 32 MiB per message; send larger batches through the streaming ingest RPC, which chunks frames well under the limit.
- Query and read responses cap at 64 MiB per message.
- The run-events read (
GET /v1/telemetry/runs/{run_id}/events) is paginated: each response returns at most one page (limitcapped server-side) plus anextcursor to resume from, so arbitrarily large runs are read page by page.
telemetry-gateway.retentionDays to age out
events older than a cutoff.
Telemetry export sink
Thetelemetry-gateway chart can continuously forward telemetry to an object store you own, as an
opt-in audit/SIEM/warehouse feed. It is off by default; set telemetry-gateway.export.url to an
s3:// (or S3-compatible) bucket to enable it, and optionally point export.credentialsSecretName at
a Secret carrying the sink’s write-only credentials. Events are forwarded as newline-delimited JSON,
partitioned by organization and date; delivery is best-effort and never blocks ingest. See the
export guide for behavior and the
telemetry-gateway Helm reference for the exact export.* values.