Skip to content
NewFinOps, Kubernetes and AI, on one platform
Book a demo
UnitVane docs
API referenceReleases

Install the agent with Helm

One release per cluster. The agent reads the cluster every hour, read-only, and sends what it reads to your organization in KubeOn and nowhere else.

On this page

Before you start

  • metrics-server in the cluster. EKS, AKS and GKE include it.
  • Permission to create a namespace, a ClusterRole and a ClusterRoleBinding, once, at install.
  • Helm 3.8 or later, which installs charts from OCI registries. Helm 4 works too.
  • Outbound HTTPS from the cluster to app.kubeon.io. See network requirements.
  • The Admin or Platform engineer role in KubeOn, to create the cluster's token.

1. Create the cluster's token

In KubeOn, open Settings > Integrations > Add cluster, enter the cluster's name as you want it to appear, and choose Create token. KubeOn shows the token once, with the Helm command that installs the agent with it:

From Add cluster
helm upgrade --install kubeon-agent oci://registry.kubeon.io/kubeon/charts/kubeon-agent \  --namespace kubeon --create-namespace \  --set clusterName=prod-use1-a \  --set kubeon.agentToken=<token from KubeOn>

That command is enough for a trial. In production, keep the token in a Secret, created by hand or synced from your secret manager, so it is not in shell history or in Git:

Token Secret
kubectl create namespace kubeonkubectl -n kubeon create secret generic kubeon-agent-token \  --from-literal=agent-token=<token from KubeOn>

2. Install

kubeon-agent.yaml
# kubeon-agent.yamlclusterName: prod-use1-a                  # how the cluster appears in KubeOnkubeon:  existingSecret: kubeon-agent-token      # the Secret holding the cluster's tokenprometheusUrl: http://prometheus-server.monitoring.svc:9090   # optional
Helm
helm upgrade --install kubeon-agent oci://registry.kubeon.io/kubeon/charts/kubeon-agent \  --version 0.6.0 \  --namespace kubeon --create-namespace \  -f kubeon-agent.yaml

3. Check the first run

The CronJob runs at 7 minutes past each hour. To send the first snapshot now:

Verify
kubectl -n kubeon get cronjob,jobkubectl -n kubeon create job --from=cronjob/kubeon-agent kubeon-agent-first-runkubectl -n kubeon logs job/kubeon-agent-first-run# the last line reads: sent snapshot to https://app.kubeon.io (... bytes)

Open Diagnostics in KubeOn. The cluster appears as soon as the job finishes, with a check for each source: metrics-server, Prometheus and volumes. Its nodes, namespaces and workloads appear under Clusters. With Prometheus set, the first run also sends the last 15 days of hourly usage, so cost detail starts before the install.

The first two days

Billing and allocation say Waiting for the first billing data until the cloud bill covers the new cluster, usually within 2 days. That is expected: clouds deliver the bill a day or two late, and allocation runs once it arrives.

To see what the agent collects without sending anything, run it once in dry-run mode:

Dry run
# Collects and prints row counts per kind. Sends nothing.kubectl -n kubeon run kubeon-agent-dry-run --rm -it --restart=Never \  --image=registry.kubeon.io/kubeon/kubeon-agent:0.6.0 \  --overrides='{"spec":{"serviceAccountName":"kubeon-agent"}}' \  --env=K8S_IN_CLUSTER=true --env=CLUSTER_NAME=prod-use1-a \  --env=DATABASE_URL=sqlite:///:memory: --env=JWT_SECRET=unused \  -- python collector.pyc --dry-run

Where the data goes

Each run, the agent asks KubeOn for an upload link, sends the snapshot on it, and KubeOn ingests the snapshot into your organization at once. KubeOn issues the link for your organization's own storage, and it expires after 15 minutes. There is no bucket, region or cloud role to configure, and no second copy: the chart refuses to install, and the agent refuses to run, with an endpoint that is not a kubeon.io address.

What the chart installs

ResourceNameNotes
ServiceAccountkubeon-agentNo cloud identity annotations
ClusterRole and bindingkubeon-agent-readerget, list and watch only
Secretkubeon-agent-tokenOnly when the token is passed as kubeon.agentToken
CronJobkubeon-agent7 * * * *, concurrencyPolicy: Forbid, 10-minute deadline, 2 retries
ClusterRole
apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata:  name: kubeon-agent-readerrules:  - apiGroups: [""]    resources: [nodes, pods, namespaces, persistentvolumes,                persistentvolumeclaims, services]    verbs: [get, list, watch]  - apiGroups: [apps]    resources: [deployments, statefulsets, daemonsets, replicasets]    verbs: [get, list, watch]  - apiGroups: [batch]    resources: [jobs, cronjobs]    verbs: [get, list, watch]  - apiGroups: [storage.k8s.io]    resources: [storageclasses]    verbs: [get, list, watch]  - apiGroups: [metrics.k8s.io]    resources: [nodes, pods]    verbs: [get, list]  - apiGroups: [karpenter.sh]            # NodePools, on Karpenter clusters    resources: [nodepools, nodeclaims]    verbs: [get, list, watch]  - apiGroups: [autoscaling]    resources: [horizontalpodautoscalers]    verbs: [get, list, watch]  - apiGroups: [argoproj.io]             # where workloads are deployed from    resources: [applications, rollouts]    verbs: [get, list, watch]  - apiGroups: [kustomize.toolkit.fluxcd.io, helm.toolkit.fluxcd.io,                source.toolkit.fluxcd.io]    resources: [kustomizations, helmreleases, gitrepositories]    verbs: [get, list, watch]

No write verbs, no Secrets, no ConfigMaps. The pod runs as uid 1000 with a read-only root filesystem, no privilege escalation and every capability dropped. The image holds the agent's compiled modules and their dependencies only.

Prometheus

Optional, and recommended. Without it, the agent reads each pod's usage once an hour from metrics-server. With prometheusUrl set, it also sends:

  • Usage averaged over each whole hour instead of one reading, so spiky workloads are sized correctly.
  • The last 15 days of hourly usage, once, when the cluster connects, so savings show from the first day instead of after a week.
  • Disk usage per volume, for over-provisioned volume findings.
  • GPU utilization per pod, where the NVIDIA DCGM exporter runs.
SeriesComes fromUsed for
container_cpu_usage_seconds_total, container_memory_working_set_bytescAdvisor, in the kubeletUsage per pod
kube_pod_container_resource_requests, kube_pod_info, kube_pod_owner, kube_node_status_capacitykube-state-metricsRequests, node and workload in the 15-day history
kubelet_volume_stats_used_bytes, kubelet_volume_stats_capacity_bytesThe kubeletVolume usage
DCGM_FI_DEV_GPU_UTILNVIDIA DCGM exporter (optional)GPU utilization

kube-prometheus-stack collects all of these by default. Set prometheusUrl to the Prometheus service's address inside the cluster; kubectl get svc -A | grep prometheus lists it. The agent queries it without signing in, so a Prometheus that requires authentication, such as Amazon Managed Service for Prometheus, is not supported yet. After the next run, the Prometheus check in Diagnostics shows whether the queries succeed.

Karpenter clusters

Where Karpenter runs, the agent also reads NodePools: their requirements, limits, disruption settings and provisioned capacity. The cluster's Nodes tab lists each pool and flags pools pinned to a single instance type, which Karpenter cannot shrink. Node size proposals for the cluster are written as a change to that NodePool, by name. The ClusterRole already covers nodepools and nodeclaims; nothing else is needed.

Values

ValueDefaultEffect
clusterNamerequiredHow the cluster appears in KubeOn
kubeon.existingSecretemptyA Secret holding the token under the key agent-token
kubeon.agentTokenemptyThe token itself, when there is no Secret
kubeon.directUploadtruefalse sends snapshots through app.kubeon.io, for firewalls that allow only that address
prometheusUrlemptyAdds hourly usage history and volume usage; with the NVIDIA DCGM exporter scraped, GPU utilization per pod
pricing.rateCardemptyClusters with no cloud bill: the rate card that prices their nodes
schedule7 * * * *Hourly is what Diagnostics expects
resources100m CPU, 256Mi; 512Mi limitRaise the memory limit for clusters with tens of thousands of pods
nodeSelector, tolerationsemptyWhere the agent pod runs
Need help with this guide?Contact support