Troubleshooting
Known failure modes and their fixes — install, sign-in, demo data, collector, eBPF, logs, profiles, network, rightsizing, alerts, Grafana, MCP and the advisor.
If your problem isn't here, check GitHub issues or ask in Discussions.
Install
Pods stay Pending or the control plane restarts
The control plane waits for its stores for controlPlane.storeWait (2 minutes) and exits if they aren't reachable, so it restarts until they are. Check the embedded stores first:
kubectl -n kubehero-system get pods,pvc
kubectl -n kubehero-system logs deploy/kubehero-control-plane --tail=50Pending stores usually mean no default StorageClass — set postgresql.storage.storageClass / clickhouse.storage.storageClass.
I can't sign in to the dashboard
Sign-in takes a token. Read the admin token from the Secret:
kubectl -n kubehero-system get secret kubehero-control-plane \
-o jsonpath='{.data.admin-token}' | base64 -d; echoPages show a "demo" badge
A signal whose store isn't configured serves labelled demo data. On a default install this clears once the stores are up. If you disabled the embedded stores, configure postgresql.external / clickhouse.external. In production set controlPlane.demoMode: "false" so a misconfiguration fails loudly instead.
Collector
kubectl -n kubehero-system get pods -l app.kubernetes.io/component=collector -o wide
kubectl -n kubehero-system logs ds/kubehero-collector --tail=50Costs look N× too high (pre-0.3 collectors)
Before v0.3 every collector reported every pod in the cluster. Upgrade the chart: v0.3 collectors report only their own node (NODE_NAME from the downward API). A collector without NODE_NAME logs a loud warning because it would double-count.
Node prices look wrong
Each node sample records where its price came from: the kubehero.io/node-hourly-usd annotation, the pricing engine, or an estimate. Set the annotation for negotiated prices, and make sure the pricing engine is running (pricingEngine.enabled). GCP's catalog needs an API key (pricingEngine.gcpBillingSecret).
eBPF didn't attach
kubehero_collector_ebpf_enabled == 1 and kubehero_collector_ebpf_attached == 0The collector logs one line and keeps collecting everything else. Common causes:
- kernel older than 5.8 or cgroup v1 — check
uname -randstat -fc %T /sys/fs/cgroup(should printcgroup2fs) on the node; - the platform forbids privileged pods (e.g. GKE Autopilot) — set
collector.ebpf.enabled: false; - a security module (SELinux, AppArmor, locked-down kernels) blocks BPF.
Logs
- Nothing appears for old pods.
collector.logs.startFrom: end(default) skips what was already on disk at the collector's first start; new lines flow. Usestartto backfill. - Lines are missing under load. The per-container rate limit dropped them — see
kubehero_collector_log_lines_dropped_total; raisecollector.logs.rateLimit. - A namespace is missing. Check
collector.logs.excludeNamespaces; the collector's own namespace is always excluded. - A LogQL feature errors. See the support matrix —
unwrap,quantile_over_timeand thepatternparser aren't supported in v0.3.
Profiles
- No eBPF profiles. See eBPF didn't attach. Annotated pods are still scraped over pprof.
- Stacks full of
[unknown]or truncated. User-space stacks need frame pointers: fine for Go and Rust; add-XX:+PreserveFramePointerfor the JVM; rebuild C/C++ with frame pointers. JIT frames need perf maps — or use pprof / a Pyroscope SDK. - A pod isn't scraped. Check the annotations (
profiles.grafana.com/cpu.scrape: "true"plus the port) and thatcollector.profiling.scrapeis on.
Network
- The service map is empty. Flows need eBPF (above) and
collector.ebpf.netflow: true; they appear after the first flush. - Everything goes to one external address. A NAT gateway or proxy hides destinations; traffic is attributed to its address.
- Egress prices are off. Set
controlPlane.pricing.netEgressUSDPerGB/netCrossZoneUSDPerGBto your rates.
Rightsizing doesn't apply
Read the policy's status — every blocked change says why:
kubectl -n kubehero-system get rightsizingpolicy <name> -o jsonpath='{range .status.plannedChanges[*]}{.workload}{"\t"}{.outcome}{"\t"}{.reason}{"\n"}{end}'Usual reasons: the policy isn't armed (Armed condition false), confidence is below minConfidence (less than a day or five days of data), maxChangePerDay reached, a rollout is in progress, a VPA owns the container, fewer than minReplicas replicas, or the workload is annotated kubehero.io/rightsizing: "disabled".
Alerts don't notify
- The five default rules are seeded without channels — add channels to them.
- Check the rule's
pendingFor: pending alerts don't notify. - Check silences:
kubehero alerts silence list. - Use Test in the rule editor, or
kubehero alerts test, to see the evaluated series.
Grafana's Loki data source errors
401— set theAuthorization: Bearer <token>header on the data source.- "multi-tenant queries are not supported" —
X-Scope-OrgIDcontains|; use one cluster per data source.
MCP and the CLI
unauthenticated— setKUBEHERO_ENDPOINTandKUBEHERO_TOKEN(or--endpoint/--token, orkubehero auth login).- An HTTP MCP server isn't reachable from another machine —
--http :8765binds127.0.0.1only; pass an explicit host if you really mean to expose it.
The advisor uses the rules brain
- Check the key:
advisor.anthropic.existingSecretmust name a Secret with the key inadvisor.anthropic.secretKey(defaultapi-key). - Network policies may block egress to the Anthropic API.
- The advisor falls back to the rules brain on any API error, refusal or timeout — its logs say why.
Prometheus doesn't scrape KubeHero
The ServiceMonitor and PrometheusRule are matched by the prometheus.release label (default kube-prometheus-stack). Set it to your Prometheus release name, or disable label selection in kube-prometheus-stack (serviceMonitorSelectorNilUsesHelmValues: false, ruleSelectorNilUsesHelmValues: false).