Skip to main content

k8s-pod-foamtree

k8sfoams is a local, read-only dashboard that answers one question: where is my cluster's requested CPU and memory actually going, and how much room is left on each node?

It visualizes resource requests — what the scheduler reserves — not live usage. That makes it a tool for spotting over-requesting pods and idle headroom, not a performance monitor. It runs on your laptop, reads ~/.kube/config (or $KUBECONFIG) with the standard Kubernetes client, and needs no in-cluster deployment and no metrics-server.

How it works

  1. Lists nodes (status.capacity) and all non-terminated pods. Pods in Succeeded/Failed are excluded — they still report requests via the API but no longer reserve anything.
  2. Normalizes CPU to millicores and memory with bitmath. A pod's effective request is max(sum(regular containers), max(init containers)) — init containers run sequentially, so they are maxed, not summed. This is what the scheduler actually reserves.
  3. Nests the result node → pod → container and adds a synthetic empty child per node for free capacity, then serves it as JSON.
  4. A React single-page app (no build step — React and Babel come from a CDN) fetches CPU and memory in parallel, merges them, and renders. The view auto-refreshes every 60 seconds by default.

2D map

k8sfoams 2D treemap view

A squarified treemap. Each node is a square box, each pod is a foam inside it. A pod with more than one container is split into sub-foams. The empty foam is unused (free) capacity on that node. Pick CPU or Memory with the Resource control.

3D cubes

k8sfoams 3D cube view

An isometric view: one plate per node, one cube per pod. A cube encodes both resources at once:

  • width × depth (footprint) → CPU request
  • height → memory request
  • color → node

Both dimensions are square-root scaled, so a 10× larger pod is not 10× wider. Because a cube already shows both resources, the CPU/Memory picker is disabled in 3D and a Zoom slider takes its place.

Switch views with the sidebar View control or the 2D/3D pill in the header. It is client-side state — no flag, no restart. The scene is pure CSS 3D, not WebGL, so it needs no GPU support.

Controls

  • Memory unit: MiB, GiB (default), or TiB.
  • Context: the sidebar lists every context from your kubeconfig, active one first, tagged by provider. Switching only changes the context inside the k8sfoams web server — your ~/.kube/config file is never modified.
  • Refresh: slider from 5 to 600 seconds, plus a Refresh now button.
  • Filter: the header query bar highlights matching pods and dims the rest — nothing is removed from the view. See Filtering for the full grammar.
  • Focus: click a node to open an overlay listing its pods with per-pod CPU/memory and container breakdown.

Filtering

The query bar in the header is a highlighter, not a filter of last resort: matching pods glow, everything else dims. No pod, node or box ever leaves the layout, so the shape of the cluster stays comparable while you narrow down. Once the query is non-empty and valid, a live counter inside the input reads N / M pods (and turns red at 0).

Type whitespace-separated tokens. All tokens are ANDed — a pod must satisfy every one of them:

ns:kube-system qos:Burstable app=frontend

An empty query matches everything. A query that contains a malformed token is inert: nothing dims, and the offending tokens are listed under the bar with the reason. Half-typing ns: can never blank the view.

Token reference

Token Matches Notes
ns:<name> pod namespace, exact case-insensitive (ns:Kube-System works)
node:<glob> node the pod is scheduled on * is the only wildcard; anchored (whole name must match); case-insensitive
qos:<class> Guaranteed, Burstable, BestEffort case-insensitive; anything else is an error
has:init-containers pods declaring at least one init container currently the only has: field
key=value pod label equals value key and value are case-sensitive (Kubernetes labels are)
key!=value pod label differs from value a missing label counts as unequal, so it matches too
text pod name contains text case-insensitive substring
"quoted text" pod name contains quoted text quotes force literal text — the grammar is skipped

Examples

Every pod named like nginx, anywhere:

nginx

Pods in kube-system that run init containers:

ns:kube-system has:init-containers

Everything the scheduler can evict first, on the worker pool:

qos:BestEffort node:worker-*

Frontend pods that are not in production, named like api:

app=frontend env!=prod api

One specific node — globs are anchored, so dots are literal, not wildcards:

node:ip-10-0-1-5.ec2.internal

All nodes in an AZ suffix, plus a namespace:

node:*-eu-west-1a ns:payments

Guaranteed pods carrying a label value with a space:

qos:Guaranteed app="my app"

Match a pod name that looks like a filter token — leading quotes make the whole token literal text:

"web:1"

Without the quotes, web:1 is read as an unknown filter prefix and reported as an error.

Sharp edges

  • != wins over =. env!=prod is one inequality, never env! equals prod.
  • A filter prefix must be a bare word before :. app=ns:x is a label selector for key app, value ns:x — not a namespace filter.
  • Only node: can dim a node. Node plates and boxes stay in the layout either way; pod-level terms dim pods, never their node.
  • A missing label matches !=. env!=prod highlights pods with env: staging and pods with no env label at all — the Kubernetes selector semantics.
  • Every problem is reported at once. The parser never stops on the first bad token, so a three-error query lists three errors.

Errors you can hit

Query Message
ns: ns: needs a value
qos:Cheap unknown QoS class — use Guaranteed, Burstable or BestEffort
has:sidecars unknown has: field — use init-containers
zone:eu unknown filter — use ns:, node:, qos:, has:
=frontend label selector needs a key
app= label selector needs a value
"" empty quoted value
app="my app unterminated quoted value

Focusing the input opens a popover with the same token list; it is replaced by the error list while a token is malformed. The × on the right clears the query.

HTTP API

Route Returns
GET / the dashboard
GET /healthcheck {"status": "ok"}
GET /resources/cpu, GET /resources/memory treemap JSON; optional ?context=<name>. CPU in millicores, memory in decimal kB
GET /contexts [{"context": "...", "active": true}]

Installation

Prerequisites

Install uv package manager:

curl -LsSf https://astral.sh/uv/install.sh | sh

Install from source

# Install dependencies and the package in development mode
make restore_dev

# Or install without dev dependencies
make restore

Install via PyPi

pip install k8sfoams
# or with uv
uv pip install k8sfoams

Run k8s-pod-foamtree

After installation, run the application:

# Using make
make run

# Or directly
k8sfoams

# Or with uv run
uv run k8sfoams

Command lines arguments

  • host: host IP address on which server listen, default is 127.0.0.1
  • port: port number on which server listen, default is 8080
  • d: turn on debug mode when server starts

Example:

k8sfoams --host 0.0.0.0 --port 8080 -d

Development

Running tests

# Run all tests (type checking, linting, security, unit tests)
make tests

# Run individual test suites
make unit_tests
make static_code_analysis
make check_types
make bandit

CI/CD targets

For CI/CD environments (GitHub Actions, etc.), use these targets that work with system Python:

make restore_ci   # Install dependencies with --system flag
make tests_ci     # Run all tests without uv run prefix

Building the package

make build

Clean build artifacts

make clean

Release files for k8sfoams 1.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for k8sfoams 1.5.0
File Size Uploaded
k8sfoams-1.5.0.tar.gz 50.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for k8sfoams 1.5.0
File Interpreter ABI Platform
k8sfoams-1.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 102.4 kB

Release files / k8sfoams-1.5.0.tar.gz

Download URL k8sfoams-1.5.0.tar.gz
Size 50.5 kB
Tags Source
SHA-256 checksum
How to use checksums
5653ee440aa6df867797645f77e9ec65be41ae997f12add241d2c489a02a345d
BLAKE2b-256 checksum
How to use checksums
4a26923c70b847ff87fc0b8190b4d899e018b2e3151c1e3c6b4d24084efc051b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.21

Release files / k8sfoams-1.5.0-py3-none-any.whl

Download URL k8sfoams-1.5.0-py3-none-any.whl
Size 51.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
427c12e19ce83567fda3c814f60a6a2c566b47af5a64629530fd1327cfb19fab
BLAKE2b-256 checksum
How to use checksums
ba9e149fe6cc35c8aae76e2d2685f35bcc0e0d99c615f4bdd686c0b8921d8032
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.21

Release history Release notifications | RSS feed

1.7.0

2 release files

1.6.0

2 release files

This release

1.5.0 This release

2 release files

1.4.1

2 release files

1.4.0

2 release files

1.3.2

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.5

2 release files

1.1.4

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.0.3

1 release file

1.0.2

1 release file

1.0.1

1 release file

1.0.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page