How it works¶
flowchart LR
API["Kubernetes API"] -->|"LIST + WATCH"| Cache["Watch cache per context"]
Synth["--synthetic clusters"] --> Extract
Cache --> Extract["Effective requests"]
Extract --> Map["Nodes, pods, containers + health, topology, audit"]
Map -->|"GET /resources"| UI["Dashboard"]
Extract --> Sim["Scheduling simulators"]
Sim -->|"/simulate/fit, /simulate/drain"| UI
Cache -->|"GET /events"| UI
click Map "../node-health/" "Node health"
click Sim "../simulators/" "Scheduling simulators"
click UI "../map-2d/" "2D map"
click Synth "../synthetic-mode/" "Synthetic mode"
Listing the cluster¶
k8sfoams lists nodes and all non-terminated pods. Pods in Succeeded or Failed are left out: they still report
requests through the API but no longer reserve anything.
The first request for a context lists both in pages (--page-size, 500 by default), then keeps the result current in
memory with a WATCH. Refreshes and simulations read that cache, so once it is warm they make no API calls, however large
the cluster. A context's cache stops after 15 minutes without requests; an open tab in Live mode counts as a request.
If the API server becomes unreachable, the dashboard shows the error instead of stale data. --no-watch lists on
every request instead.
Effective requests¶
Quantities are parsed with the Kubernetes client's parse_quantity: CPU to millicores, memory to decimal kB.
A pod's effective request is max(sum(regular containers), max(init containers)). Init containers run one after
another, so they are maxed, not summed. Native sidecars (init containers with restartPolicy: Always) keep running, so
they count as regular containers and add to every init container started after them. This is what the scheduler
actually reserves.
Node sizes come from status.allocatable, not capacity, so kube- and system-reserved capacity is never drawn as free
space.
The payload¶
The result is nested node → pod → container, with CPU, memory and extended requests side by side, and served as JSON
from GET /resources. Each node also carries its health and
topology; each pod its QoS class, labels, owner and audit findings.
The dashboard is a React single-page app. esbuild compiles its .jsx files and bundles React into one bundle.js,
which the Python package ships, so no script is loaded from a CDN. It fetches every resource of the context in one
call and renders.
Live updates¶
By default the view is Live. It keeps a Server-Sent Events stream open (GET /events?context=), and the server sends
a changed event whenever the context's watch cache changes, at most once per second. The browser then fetches again,
so scaling a Deployment shows up within a few seconds.
A change counts only if it alters what the dashboard shows. Status updates that leave the requests, labels and
conditions the same don't trigger it. While the stream is reconnecting, or when there is none (--no-watch), the view
polls every 60 seconds by default instead.
In synthetic mode the generated clusters feed the same stream, so the live view behaves as it does against a watched cluster.