Skip to content

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:

  1. Core platform first (ingest → ClickHouse → query-api → portal). This is well-exercised and installs cleanly.
  2. 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.
  • An EKS cluster (1.29+) and kubectl/helm pointed 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 .lic license 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’s imageTag, or you’ve mirrored them to a private registry and will set registry + imagePullSecrets.

Using the .lic file issued to you:

Terminal window
kubectl create namespace ultrvlt
kubectl -n ultrvlt create secret generic ultrvlt-license \
--from-file=valid.lic=./customer.lic

The 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.

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:

Terminal window
# 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):

Terminal window
export SESSION_SECRET=$(openssl rand -hex 32)
Terminal window
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.

Terminal window
kubectl -n ultrvlt get ingress

Point your DNS (Route 53) at the ALB hostname. Browse to https://ultrvlt.example.com and log in as admin.

Terminal window
# 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/ping

At 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/vmlinux exists) 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_iter field rename and iter_type enum reorder are handled). Much older or newer kernels may warrant a spot-check; L4 is unaffected.
  • The DaemonSet runs privileged + hostPID with tolerations: Exists (every node, including control-plane). Review that against your security posture.

Then:

Terminal window
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=eks

Watch 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):

Terminal window
--set clusterState.enabled=true
  • 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.