Deploy to Amazon EKS
This is the end-to-end runbook for standing up the core on a real EKS cluster with a production license. It targets a self-operated pilot.
Two things are deliberately staged:
- Core platform first (ingest → ClickHouse → query-api → portal). This is well-exercised and installs cleanly.
- eBPF sensor second, only after node validation. The sensor is the data source on a real cluster; validate it on a throwaway cluster on your exact node AMI before enabling it anywhere real.
Prerequisites
Section titled “Prerequisites”- An EKS cluster (1.29+) and
kubectl/helmpointed at it, with cluster-admin (the bundled ClickHouse operator installs CRDs plus a ClusterRole/Binding). - EBS CSI driver installed with a gp3 StorageClass (ClickHouse needs a
default or named StorageClass for its PVC). Confirm with
kubectl get storageclass. - AWS Load Balancer Controller installed (for the ALB Ingress).
- An ACM certificate for the hostname you’ll serve the portal on, in the cluster’s region. Note its ARN.
- The
.liclicense file issued to you. (The core boots without one — see Licensing — so you can install first and add it later.) - Pullable images: either the
ghcr.io/ultrvlt/*packages are published and public for the chart’simageTag, or you’ve mirrored them to a private registry and will setregistry+imagePullSecrets.
1. Create the license Secret
Section titled “1. Create the license Secret”Using the .lic file issued to you:
kubectl create namespace ultrvltkubectl -n ultrvlt create secret generic ultrvlt-license \ --from-file=valid.lic=./customer.licThe chart’s queryApi.license.existingSecret points at this. Leaving the
license unset boots the core UI-locked but still ingesting — a safe default,
not a working demo.
2. Create the admin credential
Section titled “2. Create the admin credential”The portal ships with auth.mode=local and is fail-closed — the install
errors if no admin credential is provided. Prefer a pre-computed argon2id hash
so no plaintext is stored:
# Generate an argon2id PHC hash however you like; the core also accepts a# plaintext password (hashed at boot) via queryApi.auth.admin.password.export ADMIN_HASH='$argon2id$v=19$m=65536,t=3,p=2$...'Also generate a stable session secret (required at more than one query-api replica):
export SESSION_SECRET=$(openssl rand -hex 32)3. Install
Section titled “3. Install”helm dependency build deploy/helm/ultrvlt # vendors the CH operator subchart
helm install ultrvlt deploy/helm/ultrvlt \ -n ultrvlt \ -f deploy/helm/ultrvlt/values-prod.yaml \ --set queryApi.license.existingSecret=ultrvlt-license \ --set-string queryApi.auth.admin.passwordHash="$ADMIN_HASH" \ --set queryApi.auth.sessionSecret="$SESSION_SECRET" \ --set portalUi.ingress.host=ultrvlt.example.com \ --set-string portalUi.ingress.annotations.'alb\.ingress\.kubernetes\.io/certificate-arn'=<ACM_ARN>The prod overlay (values-prod.yaml) sets ALB+ACM ingress, gp3 storage, and
larger resource requests. Adjust the ClickHouse PVC size for your retention.
4. Reach the portal (HTTPS is mandatory)
Section titled “4. Reach the portal (HTTPS is mandatory)”kubectl -n ultrvlt get ingressPoint your DNS (Route 53) at the ALB hostname. Browse to
https://ultrvlt.example.com and log in as admin.
5. Smoke test the core
Section titled “5. Smoke test the core”# license is valid/unlocked:kubectl -n ultrvlt exec deploy/ultrvlt-query-api -- \ wget -qO- localhost:8080/api/v1/license# clickhouse reachable:kubectl -n ultrvlt exec deploy/ultrvlt-query-api -- \ wget -qO- localhost:8080/api/v1/pingAt this point the platform is up. With no sensor yet, the pages are empty until telemetry arrives — either enable the sensor (below) or point OTLP emitters at the ingest-gateway.
6. Enable the eBPF sensor (only after node validation)
Section titled “6. Enable the eBPF sensor (only after node validation)”Validate first, on a throwaway cluster on your real node AMI. L4 and L7 capture have been validated on EKS (kernel 6.12), but kernels and AMIs vary. Before enabling it anywhere real:
- Confirm the node kernel is BTF-enabled (
/sys/kernel/btf/vmlinuxexists) and ≥ 5.8 (ring buffer). AL2023 and Bottlerocket ship compatible kernels; confirm for your specific AMI. - Architecture: the BPF objects are built per-arch (amd64 + arm64), so the L7 kprobes read the correct registers on both. The L4 service map uses an arch-independent tracepoint.
- Kernel range: the L7 programs relocate across kernels via CO-RE (validated
through 6.12; the
iov_iterfield rename anditer_typeenum reorder are handled). Much older or newer kernels may warrant a spot-check; L4 is unaffected. - The DaemonSet runs
privileged+hostPIDwithtolerations: Exists(every node, including control-plane). Review that against your security posture.
Then:
helm upgrade ultrvlt deploy/helm/ultrvlt -n ultrvlt \ -f deploy/helm/ultrvlt/values-prod.yaml \ --reuse-values \ --set sensor.enabled=true \ --set sensor.siteId=prod --set sensor.clusterId=eksWatch the DaemonSet. On startup each pod runs an eBPF preflight (memlock
rlimit, kernel BTF, kprobe + ring-buffer support) and logs one clear diagnostic.
On an incompatible kernel the pod does not crash-loop: it logs
eBPF unavailable on this node; running in degraded mode and keeps running the
non-eBPF paths (node heartbeat/metering, resource sampler, k8s enrichment), so
the node still meters and appears in the fleet — just without L4/L7 capture.
Check for that log line to tell “degraded” apart from “capturing”; if you see
it, grab the err=... reason (usually missing BTF) before deciding whether the
AMI is usable.
Optionally enable the cluster-state collector (k8s object health for the Infrastructure pages; no eBPF, just the k8s API):
--set clusterState.enabled=trueKnown limitations for a pilot
Section titled “Known limitations for a pilot”- Single ClickHouse replica (no HA/DR). Their storage, their hardware — size the PVC and back it up per your retention needs.
- Sensor on an unsupported kernel degrades to node metering + resource + k8s enrichment (no L4/L7) instead of crash-looping — watch the pod logs to tell the two states apart.