This guide gets kubernetes-ontology running in its recommended MVP mode:
kubernetes-ontologydruns as the ontology server.kubernetes-ontologyruns as the CLI client.- The server reads Kubernetes objects and maintains an in-memory graph.
- The client queries status, entities, relations, neighbors, and diagnostic subgraphs through the server.
The daemon is read-only with respect to the Kubernetes resources it observes: it does not create, update, patch, delete, or annotate workloads and related objects. Helm installation does create this project's own Deployment, Service, ServiceAccount, ConfigMap, and read-only RBAC. The MVP stores graph state in memory only: restarting the daemon rebuilds the graph from the Kubernetes API.
| Path | Best for | What runs where |
|---|---|---|
| Release binary server + client | Private or offline clusters whose nodes cannot pull public images, or users who want no in-cluster install | Server, CLI, and optional viewer run on your workstation or a bastion host. |
| Helm + release CLI | Users who want to try the project without compiling Go code | Server and viewer run in Kubernetes. CLI runs on your workstation. |
| Source build | Contributors and local development | Server, CLI, and viewer run from this repository. |
If cluster nodes can pull ghcr.io images, Helm is the most Kubernetes-native
path. If the cluster is private, air-gapped, or cannot pull public images,
use the release binary path from a workstation or bastion that can reach the
Kubernetes API server. Use the source path when changing code or testing local
patches.
- Prerequisites
- No-Compile Path: Release Binary Server + Client
- No-Compile Path: Helm + Release CLI
- Source Path
- Query Examples
- Topology Viewer
- Verification Flow For Changes
- Troubleshooting
- A kubeconfig with read access to the target cluster.
- Network access from your machine to the Kubernetes API server.
- For the release binary path: a downloaded GitHub Release archive. It includes
kubernetes-ontologyd(server),kubernetes-ontology(client), andkubernetes-ontology-viewer(optional viewer). - For the Helm path:
kubectl,helm, a downloadedkubernetes-ontologyCLI binary, and cluster-node access to the configured container registry. - For local development from source: Go installed locally.
Use this path when you want to avoid installing anything in the cluster, or when cluster nodes cannot pull the published GHCR image. It runs the read-only server on your workstation or a bastion host using your kubeconfig, then points the CLI at that local server.
Set the version and choose the release archive for your machine:
export KO_VERSION=v0.1.8
curl -LO "https://github.com/Colvin-Y/kubernetes-ontology/releases/download/${KO_VERSION}/kubernetes-ontology_${KO_VERSION}_linux_amd64.tar.gz"
tar -xzf "kubernetes-ontology_${KO_VERSION}_linux_amd64.tar.gz"
cd "kubernetes-ontology_${KO_VERSION}_linux_amd64"Use linux_amd64, linux_arm64, darwin_amd64, darwin_arm64, or
windows_amd64.zip for other machines. If the environment cannot access
GitHub, download the archive from a connected network and transfer it through
your approved internal channel.
Release builds include version metadata. The CLI automatically performs a short, best-effort GitHub Release check during normal use, and you can run the same flow explicitly:
./kubernetes-ontology --version
./kubernetes-ontology --check-update
./kubernetes-ontology --updateSet KUBERNETES_ONTOLOGY_SKIP_UPDATE_CHECK=1 or pass --no-update-check for
offline scripts that should not attempt outbound network calls.
Create a local config file. The release archive includes
local/kubernetes-ontology.yaml.example:
cp local/kubernetes-ontology.yaml.example kubernetes-ontology.yamlEdit it for your kubeconfig path and collection scope, or create a minimal config:
kubeconfig: /absolute/path/to/kubeconfig.yaml
cluster: your-logical-cluster
contextNamespaces:
- default
- kube-system
server:
addr: 127.0.0.1:18080
bootstrapTimeout: 2m
streamMode: informerStart the server in the foreground:
./kubernetes-ontologyd --config ./kubernetes-ontology.yamlOr keep it in the background for a short diagnostic session:
nohup ./kubernetes-ontologyd --config ./kubernetes-ontology.yaml > kubernetes-ontologyd.log 2>&1 &
echo $! > kubernetes-ontologyd.pidQuery it from another terminal:
./kubernetes-ontology --server "http://127.0.0.1:18080" --statusOpen the optional local viewer:
./kubernetes-ontology-viewer --server "http://127.0.0.1:18080"The binary path creates no Kubernetes Deployment, Service, ServiceAccount,
ConfigMap, or RBAC. It creates only host-local processes:
kubernetes-ontologyd for collection/query serving and, if used,
kubernetes-ontology-viewer for the local web UI. Stop foreground processes
with Ctrl-C. Stop background processes when you are done:
kill "$(cat kubernetes-ontologyd.pid)"If you also background the viewer, store and kill its PID the same way. Remove any temporary logs, pid files, and copied kubeconfigs according to your local security policy.
This path runs the server in Kubernetes from a published container image and
uses kubectl port-forward plus the release CLI binary from your machine.
In private environments, mirror ghcr.io/colvin-y/kubernetes-ontology to an
internal registry and set KO_IMAGE to that mirror, or use the release binary
path above.
Set the version and image namespace you want to use:
export KO_VERSION=v0.1.8
export KO_IMAGE=ghcr.io/colvin-y/kubernetes-ontology
curl -LO "https://github.com/Colvin-Y/kubernetes-ontology/releases/download/${KO_VERSION}/kubernetes-ontology-0.1.8.tgz"Use the KO_VERSION value for the release tag you want to install. If you
publish a fork or a different package namespace, replace KO_IMAGE with your
image reference.
Install the Helm chart:
helm upgrade --install kubernetes-ontology kubernetes-ontology-0.1.8.tgz \
--namespace kubernetes-ontology \
--create-namespace \
--set image.repository="${KO_IMAGE}" \
--set image.tag="${KO_VERSION}" \
--set cluster="your-logical-cluster" \
--set contextNamespaces='{default,kube-system}'The chart installs the project server, viewer, ServiceAccount, ConfigMap, and
read-only RBAC. The daemon uses those in-cluster credentials only for
get/list/watch collection. Inside the pod, the server listens on :18080
rather than 0.0.0.0:18080 so Kubernetes IPv4, IPv6, and dual-stack networking
can use the wildcard listener supported by the runtime. By default the chart
does not grant Secret reads. To include Secret nodes and uses_secret edges,
opt in explicitly:
helm upgrade --install kubernetes-ontology kubernetes-ontology-0.1.8.tgz \
--namespace kubernetes-ontology \
--reuse-values \
--set rbac.readSecrets=trueWait for the server:
kubectl -n kubernetes-ontology rollout status deploy/kubernetes-ontologyExpose the in-cluster server on your workstation:
kubectl -n kubernetes-ontology port-forward svc/kubernetes-ontology 18080:18080Download the CLI from GitHub Releases in another terminal. Example for macOS Apple Silicon:
curl -LO "https://github.com/Colvin-Y/kubernetes-ontology/releases/download/${KO_VERSION}/kubernetes-ontology_${KO_VERSION}_darwin_arm64.tar.gz"
tar -xzf "kubernetes-ontology_${KO_VERSION}_darwin_arm64.tar.gz"
sudo install "kubernetes-ontology_${KO_VERSION}_darwin_arm64/kubernetes-ontology" /usr/local/bin/kubernetes-ontologyUse linux_amd64, linux_arm64, darwin_amd64, darwin_arm64, or
windows_amd64.zip for other machines.
Query the port-forwarded server:
kubernetes-ontology --server "http://127.0.0.1:18080" --statuskubernetes-ontology \
--server "http://127.0.0.1:18080" \
--list-entities \
--entity-kind Pod \
--namespace default \
--limit 20Open the dependency-free viewer. The Helm chart deploys it by default:
kubectl -n kubernetes-ontology port-forward svc/kubernetes-ontology-viewer 8765:8765Then open:
http://127.0.0.1:8765
You can also run the viewer locally from the GitHub Release archive without Python:
kubernetes-ontology-viewer --server "http://127.0.0.1:18080"When you are done with a short Helm trial, stop any kubectl port-forward
processes with Ctrl-C. To remove the in-cluster footprint:
helm uninstall kubernetes-ontology --namespace kubernetes-ontologyDelete the namespace only if it was created just for this trial and contains no resources you want to keep:
kubectl delete namespace kubernetes-ontologyUse this path when you want to run from a local checkout.
make build build-daemonCreate a machine-local YAML config:
cp local/kubernetes-ontology.yaml.example local/kubernetes-ontology.yamlEdit local/kubernetes-ontology.yaml:
kubeconfig: /absolute/path/to/kubeconfig.yaml
cluster: your-logical-cluster
namespace: default
contextNamespaces:
- default
- kube-system
workloadResources:
- group: apps.kruise.io
version: v1beta1
resource: statefulsets
kind: StatefulSet
namespaced: true
- group: redis.io
version: v1beta1
resource: clusters
kind: Cluster
namespaced: true
controllerRules:
- apiVersion: apps.kruise.io/*
kind: "*"
namespace: kruise-system
controllerPodPrefixes:
- kruise-controller-manager
nodeDaemonPodPrefixes:
- kruise-daemon
csiComponentRules:
- driver: diskplugin.csi.alibabacloud.com
namespace: kube-system
controllerPodPrefixes:
- csi-provisioner-
nodeAgentPodPrefixes:
- csi-plugin-
server:
addr: 127.0.0.1:18080
url: http://127.0.0.1:18080
bootstrapTimeout: 2m
streamMode: informer
pollInterval: 5sserver.addr is used by kubernetes-ontologyd. server.url is documentation
for local tooling; the CLI only queries a daemon when --server or a
*-server make target is used.
bootstrapTimeout bounds the initial full snapshot sync. Large clusters or
slow API servers usually need more than the old 30 second default.
local/kubernetes-ontology.yaml is ignored by git. Put local kubeconfig paths,
private cluster names, namespaces, collection resources, display rules, and
scratch query defaults there. Make targets automatically use this file when it
exists. Use CONFIG=other.yaml for a different config file.
NAMESPACE is the default namespace used by client queries and diagnostic
entrypoints. contextNamespaces is the server collection scope.
The context namespace list is only a collection scope. It does not mark pods as infrastructure, business, system, or any other ontology role.
workloadResources tells the collector which CRD-like workload resources should
be collected so ownerReference chains can resolve through them. controllerRules
adds display-only controller ownership, such as Kruise workloads being served by
kruise-controller-manager and node-local kruise-daemon pods.
csiComponentRules links a StorageClass provisioner / CSI driver name to the
real CSI controller and node-agent Pods by namespace and name prefix. No
driver-specific component inference runs unless a matching rule is configured.
For workloadResources.kind, use the actual Kubernetes ownerReference kind
such as StatefulSet for Kruise ASTS, not a local nickname.
The example OpenKruise and Redis resources are optional. On a clean kind
cluster without those CRDs installed, the server logs the unavailable custom
resources and skips their informers; remove those entries or install the CRDs
when you want them collected.
The equivalent CLI flags still exist for one-off overrides:
--context-namespaces "default,kube-system"
--workload-resources "apps.kruise.io/v1beta1/statefulsets/StatefulSet,redis.io/v1beta1/clusters/Cluster"
--controller-rules "apiVersion=apps.kruise.io/*;kind=*;namespace=kruise-system;controller=kruise-controller-manager;daemon=kruise-daemon"
--csi-component-rules "driver=diskplugin.csi.alibabacloud.com;namespace=kube-system;controller=csi-provisioner-;agent=csi-plugin-"The Makefile still accepts KUBECONFIG=..., CLUSTER=..., and
CONTEXT_NAMESPACES=... for one-off runs when CONFIG is not used.
In terminal 1:
make serveThe daemon performs an initial full snapshot sync, then refreshes the in-memory
ontology graph with Kubernetes informers. If informer startup fails, it logs the
failure and falls back to polling. Set streamMode: polling or pass
--stream-mode polling to force polling. Stop it with Ctrl+C.
Equivalent direct command:
go run ./cmd/kubernetes-ontologyd --config local/kubernetes-ontology.yamlFor a one-shot bootstrap API without continuous refresh:
go run ./cmd/kubernetes-ontologyd \
--config local/kubernetes-ontology.yaml \
--disable-pollingIn terminal 2:
curl -s http://127.0.0.1:18080/healthzExpected shape:
{"cluster":"your-logical-cluster","ok":true,"phase":"ready"}Read full runtime status through the CLI client:
make status-serverDirect CLI:
./bin/kubernetes-ontology --server "http://127.0.0.1:18080" --statusList pods:
make list-entities-server ENTITY_KIND=Pod NAMESPACE=default LIMIT=20Get one entity by Kubernetes identity:
make get-entity-server ENTITY_KIND=Pod NAMESPACE=default NAME=my-podThe response contains entity.entityGlobalId. Use that ID for relation and
neighbor queries.
Example direct CLI command:
go run ./cmd/kubernetes-ontology \
--server "http://127.0.0.1:18080" \
--list-entities \
--entity-kind Pod \
--namespace default \
--limit 20Resolve one entity with the agent-friendly alias:
./bin/kubernetes-ontology \
--server "http://127.0.0.1:18080" \
--resolve-entity \
--entity-kind Pod \
--namespace default \
--name my-podList outgoing neighbors for an entity:
make neighbors-server \
ENTITY_ID='your/entityGlobalId' \
DIRECTION=out \
LIMIT=50Filter by relation kind:
make neighbors-server \
ENTITY_ID='your/entityGlobalId' \
RELATION_KIND=scheduled_on \
DIRECTION=outList relations from one entity:
make list-relations-server \
FROM_ID='your/entityGlobalId' \
RELATION_KIND=scheduled_onThe direct CLI alias for filtered relation listing is:
./bin/kubernetes-ontology \
--server "http://127.0.0.1:18080" \
--list-filtered-relations \
--from 'your/entityGlobalId' \
--relation-kind scheduled_on \
--limit 50Common relation kinds include:
controlled_byowns_podscheduled_onselects_poduses_config_mapuses_secretuses_service_accountbound_by_role_bindingmounts_pvcbound_to_pvreported_by_eventaffected_by_webhookmanaged_by_csi_controllerserved_by_csi_node_agent
Diagnose a pod:
make diagnose-pod-server NAMESPACE=default NAME=my-podDiagnose a workload:
make diagnose-workload-server NAMESPACE=default NAME=my-deploymentDirect CLI:
go run ./cmd/kubernetes-ontology \
--server "http://127.0.0.1:18080" \
--diagnose-pod \
--namespace default \
--name my-pod \
--max-nodes 200 \
--max-edges 400For workloads:
./bin/kubernetes-ontology \
--server "http://127.0.0.1:18080" \
--diagnose-workload \
--namespace default \
--name my-deploymentThe diagnostic response is a focused subgraph intended for the MVP
fault-diagnosis workflow and downstream AI-agent consumption.
It includes additive schemaVersion, recipe, lanes, partial,
warnings, budgets, rankedEvidence, degradedSources, and conflicts
fields so agents can tell bounded evidence from complete cluster truth.
Canonical recipe hints are pod-incident, workload-incident, storage-csi,
service-routing, identity, node-context, helm-ownership, and
helm-upgrade-runtime-failure.
When resources carry standard Helm metadata, diagnostic graphs can also include
HelmRelease and HelmChart nodes connected by managed_by_helm_release and
installs_chart edges. These edges are label evidence with confidence scores,
not exact Helm manifest membership.
For a failed Helm upgrade where the user does not have CLI output, start from the release and inspect current cluster evidence:
This Incident Context Pack flow requires v0.1.6 or newer.
kubernetes-ontology \
--server "http://127.0.0.1:18080" \
--diagnose-helm-release \
--namespace default \
--name my-release \
--recipe helm-upgrade-runtime-failureThe response can identify release-owned resources, rollout blockers, Events,
and probable chart ownership. It cannot observe Helm template, values,
repository, client, hook, or --atomic rollback errors unless the user provides
the Helm output.
An offline reference fixture for this story is available at
samples/helm-upgrade-failure/diagnostic-graph.json.
Additional failure-mode fixtures live under samples/failure-modes/.
Pod-centered diagnostic queries keep shared nodes bounded by default. For
example, a pod's ServiceAccount is shown, but the traversal does not continue
through that ServiceAccount to every other pod using it. Use
terminalKinds=... or expandTerminalNodes=true on HTTP queries when you need
that deeper fan-out. Use maxNodes and maxEdges when you need an explicit
response budget.
The CLI is a convenience wrapper over the HTTP API:
curl -s 'http://127.0.0.1:18080/status'
curl -s 'http://127.0.0.1:18080/entities?kind=Pod&namespace=default&limit=20'
curl -s 'http://127.0.0.1:18080/entity?kind=Pod&namespace=default&name=my-pod'
curl -s 'http://127.0.0.1:18080/neighbors?entityGlobalId=your/entityGlobalId&direction=out'
curl -s 'http://127.0.0.1:18080/expand?entityGlobalId=your/entityGlobalId&depth=1'
curl -s 'http://127.0.0.1:18080/diagnostic/pod?namespace=default&name=my-pod&maxNodes=200&maxEdges=400'
curl -s 'http://127.0.0.1:18080/diagnostic/pod?namespace=default&name=my-pod&expandTerminalNodes=true'Graph and list responses include the original fields plus additive freshness
metadata from the daemon runtime status. Error responses include the historical
error string plus code, message, status, retryable, and source.
Diagnostic responses additionally include budget and ranked-evidence metadata.
For agent workflows that need machine-readable stderr on failures:
./bin/kubernetes-ontology \
--server "http://127.0.0.1:18080" \
--machine-errors \
--resolve-entity \
--entity-kind Pod \
--namespace default \
--name missing-podWith make serve still running, start the local viewer in another terminal:
make visualizeOpen:
http://127.0.0.1:8765
Click Load topology to read live entities and relations from SERVER_URL.
Use Auto refresh for continuous polling, or load a focused pod/workload
diagnostic graph from the same page. The Diagnostic Signals panel surfaces
recipe metadata, freshness, budget truncation, warnings, conflicts, degraded
sources, and ranked evidence. Evidence and conflict entries focus the related
node or edge when the fixture or daemon response includes IDs.
Viewer URLs can carry state for handoff between docs, issues, and agents:
http://127.0.0.1:8765/?diagnostic=1&kind=Pod&namespace=default&name=my-pod&recipe=pod-incident
http://127.0.0.1:8765/?file=samples/failure-modes/crashloopbackoff/diagnostic-graph.json&focusNode=demo-cluster/core/Pod/default/api-7c9d/pod-crash/_
Select a node and use Expand 1 hop to fetch the next layer from the daemon.
The CLI equivalent is:
make expand-node-server ENTITY_ID='your/entityGlobalId' EXPAND_DEPTH=1Direct CLI:
./bin/kubernetes-ontology \
--server "http://127.0.0.1:18080" \
--expand-entity \
--entity-id 'your/entityGlobalId' \
--expand-depth 1After expanding, select the same node and use Collapse 1 hop to remove that
expansion layer. Double-click toggles the same expand/collapse behavior. For an
agent workflow, export the viewer state and collapse the same node locally:
make -s collapse-node-graph GRAPH_FILE=/tmp/kubernetes-ontology-visible-topology.json ENTITY_ID='your/entityGlobalId'After every code change, run the fixed local verification flow:
make verify
make serve
make visualize
make live-check NAMESPACE=default NAME=my-podRun make serve and make visualize in separate terminals. make live-check
then exercises the daemon, viewer topology proxy, and viewer diagnostic proxy.
That makes the timeout-prone visualization path part of the normal check, not a
manual afterthought.
If make serve fails with KUBECONFIG is required, check
local/kubernetes-ontology.yaml, or pass CONFIG=... / KUBECONFIG=... on
the command line.
If /healthz returns ok: false, inspect:
make status-serverIf entity lists are empty, check that NAMESPACE matches the target workload.
Also check that contextNamespaces includes the namespace you want the server
to collect. An empty contextNamespaces list means collect all namespaces.
If a diagnostic query returns entry not found, verify the exact pod or
workload name:
make list-entities-server ENTITY_KIND=Pod NAMESPACE=default
make list-entities-server ENTITY_KIND=Workload NAMESPACE=defaultThe default backend is in-memory. Restarting kubernetes-ontologyd rebuilds
the graph from Kubernetes.