Connect Kubernetes to Xplorr: OpenCost Cost Allocation for Any Cluster
Connect Kubernetes to Xplorr
Section titled “Connect Kubernetes to Xplorr”Xplorr reads Kubernetes cost data from OpenCost, the open source Kubernetes cost monitor. Once a cluster is connected, Xplorr splits its cost by namespace, controller, pod and pod label, and reports idle capacity separately from workload cost.
Xplorr never talks to your Kubernetes API server and never holds cluster credentials. It only reads what OpenCost has already computed.
Prerequisite: OpenCost in the cluster
Section titled “Prerequisite: OpenCost in the cluster”OpenCost must be running in the cluster before you connect it. Xplorr does not install it for you and does not duplicate its setup here.
Follow the OpenCost installation guide. A default install exposes the OpenCost API on port 9003, as the service opencost in the opencost namespace.
Check that the API answers from inside the cluster before going further:
kubectl -n opencost port-forward service/opencost 9003:9003Then, in another terminal:
curl -sS "http://localhost:9003/allocation?window=1d&aggregate=namespace&step=1d"A healthy OpenCost returns JSON with "code": 200 and a data array. If that call fails, fix OpenCost first. Everything below depends on it.
What the data gives you
Section titled “What the data gives you”| Dimension | What you see |
|---|---|
| Namespace | Total cost per namespace |
| Controller | Cost per Deployment, StatefulSet, DaemonSet, Job and so on, with the controller kind shown beside it |
| Pod | Cost per pod, with its namespace |
| Label | Cost grouped by any pod label key OpenCost reported, for example team or app |
Every row is broken into CPU, RAM, GPU, storage (persistent volumes), network (data transfer plus load balancers), shared cost and a total.
Idle capacity is reported separately. Provisioned node capacity that no workload requested is never spread across namespaces. It appears as its own row so you can see how much of the cluster bill nobody asked for.
Two ways to connect
Section titled “Two ways to connect”Pick the one that matches your network.
| Method | Best for | What it needs |
|---|---|---|
| Push, recommended | Private clusters, and most clusters generally | Outbound HTTPS only. Nothing exposed to the internet. |
| Pull | Clusters whose OpenCost endpoint Xplorr can already reach | An ingress or load balancer in front of OpenCost |
Push mode is the default in the console, and the one that works for a cluster sitting behind a firewall or on a private network. Pull mode exists for clusters that already publish an OpenCost endpoint.
The ingest mode is fixed when the cluster is created. To change it, delete the cluster and add it again.
Both paths end up writing the same daily cost rows through the same upsert, so a day that arrives twice is replaced, never counted twice.
Method 1: Push mode (recommended)
Section titled “Method 1: Push mode (recommended)”A CronJob runs inside your cluster. It reads OpenCost over cluster internal DNS and POSTs the response to Xplorr over outbound HTTPS, carrying a bearer token that can write cost data for that one cluster and nothing else.
Step 1: Add the cluster in Xplorr
Section titled “Step 1: Add the cluster in Xplorr”Log in to console.xplorr.io and open Kubernetes from the left sidebar. Click Connect cluster. You need the admin role.
Fill in:
- Cluster name (required, up to 120 characters), for example
prod-eks - How cost data reaches Xplorr: choose Private cluster, which is selected by default and marked Recommended
- Linked cloud account (optional): pick the cloud account that pays for the cluster nodes. See Link a cloud account below.
- Accept cost data from the collector: leave checked
Click Connect. Xplorr opens the collector dialog straight away, because a push cluster does nothing until its collector is installed.
Step 2: Generate the ingest token
Section titled “Step 2: Generate the ingest token”In the collector dialog, click Generate token.
The token appears once. It starts with xpk8s_ and is 32 bytes of random data behind that prefix.
Copy it now if you want it outside the manifest. Xplorr stores only a SHA-256 hash of it, so nobody, including Xplorr support, can show it to you again. If you lose it, generate a new one and revoke the old one.
What a token can do:
- Write Kubernetes cost data for exactly one cluster in one organization
- Nothing else. It cannot read cost data, cannot touch another cluster, and is not an API token.
Step 3: Apply the manifest
Section titled “Step 3: Apply the manifest”The dialog shows a manifest with the token already injected. Use Copy or Download to save it as xplorr-collector.yaml, then apply it:
kubectl apply -f xplorr-collector.yamlThe manifest contains three objects:
| Object | Name | Purpose |
|---|---|---|
Namespace | xplorr-collector | Holds the collector, separate from your workloads |
Secret | xplorr-ingest-token | Holds the ingest token, read by the CronJob as an environment variable |
CronJob | xplorr-cost-collector | Runs hourly, reads OpenCost, POSTs the result to Xplorr |
Defaults in the generated manifest:
| Setting | Default | Notes |
|---|---|---|
| Schedule | 0 * * * * (hourly, UTC) | concurrencyPolicy: Forbid, so runs never overlap |
| OpenCost URL | http://opencost.opencost.svc.cluster.local:9003 | Set through the OPENCOST_URL environment variable |
| Window | 3d | Each run re-sends the last three days, so a missed run heals itself |
| Image | curlimages/curl, pinned to an exact tag | A stock upstream image, not an Xplorr binary, so you can review what runs |
| Resources | 10m CPU and 32Mi memory requested, 200m and 128Mi limits |
The collector posts to https://ingest.xplorr.io/api/v1/kubernetes/ingest/<clusterId>, already filled in as XPLORR_INGEST_URL in the generated manifest, sending the ingest token as a bearer header. That is the only outbound destination it needs, so an egress allowlist only has to permit that host over HTTPS.
If OpenCost does not run in the opencost namespace, edit OPENCOST_URL in the CronJob before applying.
The collector pod is deliberately unprivileged: no ServiceAccount token is mounted, the root filesystem is read only, every Linux capability is dropped, it runs as a non root user, and the only secret it holds is the ingest token.
Step 4: Confirm data is arriving
Section titled “Step 4: Confirm data is arriving”Force a first run rather than waiting for the hour:
kubectl -n xplorr-collector create job xplorr-first-run --from=cronjob/xplorr-cost-collectorkubectl -n xplorr-collector logs job/xplorr-first-runA successful run logs how many bytes it collected from OpenCost, then sent to Xplorr. The token is never printed.
In the console, the cluster’s status moves from Waiting for collector to Receiving, and Last data shows when the collector last posted.
Managing tokens
Section titled “Managing tokens”The collector dialog lists every token for the cluster, showing its prefix and last four characters, who created it, and when it was last used. You can:
- Generate token to issue another one, for example to rotate without downtime. Issue the new token, apply the updated manifest, then revoke the old one.
- Revoke a token. Any collector still using it stops being able to send cost data immediately. Cost data already collected is kept, and the revoked token stays in the list with its last used timestamp as an audit trail.
Creating and revoking tokens are both recorded in the audit log. Only the display prefix is recorded, never the token.
Method 2: Pull mode
Section titled “Method 2: Pull mode”Use this only when Xplorr can reach your OpenCost endpoint from outside the cluster.
Step 1: Expose OpenCost
Section titled “Step 1: Expose OpenCost”Put an ingress or load balancer in front of the OpenCost API (port 9003 by default) and protect it. OpenCost has no authentication of its own, so an endpoint exposed without protection lets anyone read your cluster cost data.
Protect it with a header your ingress checks, for example a bearer token or a shared secret header.
Step 2: Add the cluster in Xplorr
Section titled “Step 2: Add the cluster in Xplorr”Open Kubernetes in the console, click Connect cluster, and choose Reachable from Xplorr.
Fill in:
- Cluster name
- OpenCost API URL, for example
https://opencost.example.com. It must behttporhttps. Credentials in the URL are rejected; put them in the auth header instead. Cloud instance metadata addresses are refused. - Auth header name (optional, defaults to
Authorization). Letters, digits and hyphens only. - Auth header value (optional, up to 4096 characters). Stored encrypted and never shown again. Xplorr sends it on every call.
- Linked cloud account (optional)
- Include in the daily sync: leave checked
Click Test connection before saving. Xplorr calls your endpoint for a one day window and reports how many allocations came back.
Step 3: Run the first sync
Section titled “Step 3: Run the first sync”Save the cluster, then click Sync now on its row. Xplorr fetches recent days and loads them. After that the cluster refreshes on the daily schedule.
The Test button on the cluster row re-runs the connection check at any time without loading data.
How often data refreshes
Section titled “How often data refreshes”| Mode | Cadence | Window |
|---|---|---|
| Push | Whatever the CronJob schedule says, hourly by default | The last 3 days on every run |
| Pull | A daily job, 03:30 UTC by default | The last 3 days, or 30 days on a cluster’s first ever sync |
Re-sending a day replaces it rather than adding to it, so overlapping windows are safe.
A collector may heal a gap but not rewrite arbitrary history: the ingest endpoint accepts allocation days within the last 120 days and ignores anything older or dated in the future.
Link a cloud account (optional)
Section titled “Link a cloud account (optional)”Linking a cluster to the cloud account that pays for its nodes turns on allocation coverage: Xplorr compares the cluster cost OpenCost reported against the billed compute and managed Kubernetes cost of that account over the same period.
Coverage near 100% means the cluster explains its node spend. A low number usually means OpenCost is missing part of the cluster, or that the account pays for a lot besides this cluster.
Linking also decides who sees the cluster. Members and viewers see clusters linked to cloud accounts they have been granted access to. A cluster with no linked account is visible to admins only.
Reading the data
Section titled “Reading the data”The Kubernetes page shows, for the selected range (7, 30 or 90 days) and optionally a single cluster:
- Cluster cost, Idle cost with idle as a percentage of cluster cost, number of Clusters, and Allocation coverage
- Daily cost, a stacked chart of allocated workload cost against idle capacity per day
- Cost breakdown, with tabs for Namespace, Controller, Pod and Label, showing CPU, RAM, GPU, Storage, Network, Shared, Total and each row’s share. Idle capacity sits in its own row at the bottom.
- Allocation coverage by cluster, comparing each cluster’s cost with the compute bill of its linked account
- Connected clusters, with source, linked account, status and last data
On the Label tab, pick a label key from the dropdown. Xplorr lists the label keys OpenCost actually reported for workloads in the range, so an empty dropdown means OpenCost sent no pod labels.
Cluster status
Section titled “Cluster status”| Badge | Meaning |
|---|---|
| Receiving | Push cluster, a collector has posted data |
| Waiting for collector | Push cluster with a token issued, nothing posted yet |
| No token issued | Push cluster with no active token. Open Collector and generate one. |
| Synced | Pull cluster, last sync succeeded |
| Sync failed | Pull cluster, last sync failed. The error is shown on the page. |
| Not synced yet | Pull cluster that has never synced. Click Sync now. |
| Disabled | The cluster is off. Push clusters reject ingest, pull clusters are skipped by the daily job. |
Common mistakes
Section titled “Common mistakes”- Connecting the cluster before installing OpenCost. There is nothing to read. Install OpenCost first and confirm its API answers.
- Picking pull mode for a private cluster. If Xplorr cannot reach the endpoint, every sync fails. Use push mode; it is the default for a reason.
- Leaving the OpenCost URL pointing at the wrong namespace. The generated manifest assumes OpenCost runs in the
opencostnamespace. EditOPENCOST_URLif yours does not. - Losing the token and re-applying the old manifest. The token is shown once. Generate a new one, apply the new manifest, revoke the old token.
- Exposing OpenCost without protection to make pull mode work. OpenCost has no authentication of its own. Always put an auth header in front of it.
- Expecting a partial day. Xplorr stores whole UTC days. Today’s cost appears once the day is complete and the next run picks it up.
Troubleshooting
Section titled “Troubleshooting”Status stays at “Waiting for collector”
The CronJob has not completed a successful run. Check it:
kubectl -n xplorr-collector get cronjob,job,podkubectl -n xplorr-collector logs job/<job-name>Common causes: the CronJob was never created because the manifest was applied to the wrong cluster or context, egress to Xplorr is blocked, or OPENCOST_URL points somewhere OpenCost is not.
Invalid ingest token
The token in the Secret does not match any token for that cluster. Generate a new token, apply the refreshed manifest, and confirm the Secret really updated:
kubectl -n xplorr-collector get secret xplorr-ingest-token -o jsonpath='{.data.token}' | base64 -d | head -c 14That prints only the recognisable prefix, which is enough to compare with the prefix shown in the console.
This ingest token has been revoked
Someone revoked it. Generate a new one and re-apply.
This cluster is configured for pull ingest
You applied a collector manifest to a cluster that was created in pull mode. Ingest mode cannot be changed after creation, so delete the cluster and add it again as a private cluster.
Payload too large or Too many allocation rows
The window is too wide for one request. The ingest endpoint caps the body size, the number of allocation sets per request and the number of rows. Reduce COST_WINDOW in the CronJob, for example from 3d to 1d, and let the hourly schedule keep the data current.
Too many ingest requests for this cluster
The collector is posting faster than the per cluster rate limit allows. An hourly schedule is far below it; a schedule of every minute is not.
OpenCost API not found
The URL does not serve the OpenCost allocation API. Xplorr tries /allocation and falls back to /allocation/compute for older releases. Give the base URL of the API, not a path below it.
OpenCost rejected the credentials
The auth header name or value is wrong for your ingress. Re-enter the value in the cluster dialog; a stored value is never shown again, so leaving it blank keeps the old one.
This address is not allowed
The URL points at a cloud instance metadata address. Those are refused deliberately. Use the real OpenCost endpoint.
Cluster cost looks far lower than the cloud bill
Check allocation coverage. OpenCost prices the nodes it can see, so a cluster spanning node groups OpenCost is not configured for, or an account paying for much more than this cluster, both show up as low coverage.
Setup checklist
Section titled “Setup checklist”- OpenCost installed and its API answering inside the cluster
- Cluster added in Xplorr with the right ingest mode
- Push mode: token generated and manifest applied with
kubectl apply - Push mode: a collector run completed and the status shows Receiving
- Pull mode: connection test passed and the first sync ran
- Cloud account linked, if you want allocation coverage
- Namespace costs visible on the Kubernetes page
Does Xplorr need access to my Kubernetes API? No. In push mode the collector reads only the OpenCost HTTP API and mounts no ServiceAccount token. In pull mode Xplorr calls the OpenCost endpoint you give it. Neither path uses cluster credentials.
Do I have to open a port for push mode? No. The collector makes an outbound HTTPS request. Nothing in your cluster needs to be reachable from the internet.
Can one token write to several clusters? No. A token is scoped to one cluster in one organization. A token for cluster A cannot write cluster B.
Can I run the collector on a different schedule?
Yes. Edit the schedule field in the CronJob. Each run re-sends a rolling window, so a slower schedule still produces complete days, just later.
Does Xplorr support clusters outside AWS, Azure and GCP? Yes for push mode. The collector only needs OpenCost and outbound HTTPS, so on premises and other hosted clusters work. Allocation coverage needs a linked cloud account, so it stays empty for a cluster with no cloud bill behind it.
Who can connect or delete a cluster? Admins. Members and viewers can see clusters linked to cloud accounts they have access to, and read cost data, but cannot change the connection.
What happens to cost history if I delete a cluster? It is removed with the cluster. Export what you need first from Reports and Export.
Related guides
Section titled “Related guides”- Cost Allocation and Tags for allocating cloud cost outside Kubernetes
- Connect AWS, Connect Azure, Connect GCP to link the account paying for your nodes
- Resource Inventory to see the nodes themselves