Skip to content

k-krew/columbo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

columbo

Claude Assisted CI Release Go License

columbo is a read-only Kubernetes CLI that finds pods that are likely forgotten or abandoned. Instead of guessing, it calculates a suspicion score - the higher the score, the more likely the pod is unused, manually created, or left behind.

Example

$ columbo

NAMESPACE     POD                         SCORE   AGE
default       nginx-debug                 125     12d
kube-system   nsenter-75l64h              75      419d
default       network-multitool           70      483d
$ columbo --explain

NAMESPACE     POD                         SCORE   AGE
default       nginx-debug                 125     12d

default/nginx-debug   score: 125
  - no ownerReferences (+50)
  - no Service match (+10)
  - no labels (+10)
  - namespace=default (+5)
  - debug-like name ("debug") (+15)
  - serviceAccountName=default (+5)
  - phase=Failed (+20)
  - no CPU/memory requests or limits (+10)
----------------------------------------

Why Columbo?

Identifying abandoned resources in a Kubernetes cluster is hard. Labels rot, owners leave, and standard dashboards show you what is running, but not why. Columbo automates the detective work by looking for classic signs of neglect (no owners, missing services, default namespaces, old age, and failure states). By mathematically scoring these signals, Columbo helps platform teams reclaim wasted resources and reduce clutter without blindly deleting workloads.

Features

  • Suspicion scoring — each pod is scored against 10 independent signals; only pods meeting the threshold are shown
  • Explainable results--explain shows exactly which signals fired and why
  • Pre-scoring filters — DaemonSet and StatefulSet pods are always skipped; pods younger than --age-threshold and pods with an ignore annotation are excluded before scoring
  • Read-only — columbo never modifies or deletes anything in the cluster
  • CI-friendly — exits 0 (clean), 1 (suspicious pods found), or 2 (error); use --quiet for exit-code-only mode
  • Multiple output formatstext (tabular) and json

Installation

Go Install

go install github.com/k-krew/columbo@latest

Homebrew (macOS / Linux)

brew tap k-krew/tap
brew trust k-krew/tap
brew install --cask columbo

From source

git clone https://github.com/k-krew/columbo.git && cd columbo
go build -o columbo .

Binary download

Grab the latest binary from GitHub Releases and place it in your $PATH.

Quick Start

# Scan all namespaces with defaults (threshold 70, age > 7d)
columbo

# Limit to one namespace
columbo -n default

# Lower the threshold to catch more pods
columbo --threshold 30

# See why each pod scored the way it did
columbo --explain

# Machine-readable output
columbo -o json

# JSON with scoring breakdown
columbo -o json --explain

# CI mode — no output, exit code only
columbo -q
echo $?   # 0 = clean, 1 = suspicious pods found, 2 = error

Flags

Flag Short Default Description
--kubeconfig ~/.kube/config Path to kubeconfig file
--context current context Kubernetes context to use
--namespace -n all namespaces Limit to a specific namespace
--threshold 70 Minimum score to include in output
--age-threshold 7d Minimum pod age to consider (1h, 24h, 7d, …)
--ignore-annotation orphan-check/ignore Exclude pods carrying this annotation with value "true"
--output -o text Output format: text or json
--explain false Show per-pod scoring breakdown
--quiet -q false Suppress all output (exit code only)
--config Path to a .columbo.yaml configuration file

Scoring

Columbo scores every pod that passes the pre-filtering stage. Signals are independent and additive.

The table below shows the default point values. Every value can be overridden via a configuration file.

Signal Points
No ownerReferences (bare / manually created pod) +50
No Service selector matches the pod's labels +10
No labels +10
Namespace is default or test +5
Phase is Failed or Succeeded +20
Any container (or init container) in CrashLoopBackOff +15
No CPU/memory requests or limits on any container +10
Pod name contains a debug-like substring +15
serviceAccountName is default (or empty) +5
Any container image uses :latest or has no tag +10

Debug-like substrings (case-insensitive): debug, test, shell, ubuntu, busybox, curl.

Owned pods (Deployments, Jobs, …) are still scored on all other signals — the +50 only fires for truly bare pods with no ownerReferences.

Jobs and CronJobs: Pods owned by Jobs are fully scored. Because old, forgotten Jobs often linger in Succeeded or Failed states, they naturally rack up points (e.g., +20 for Terminal Phase, +5 for default namespace). If an abandoned Job accumulates enough points, Columbo will flag it for cleanup.

DaemonSet and StatefulSet pods are skipped before scoring entirely.

Pre-scoring Filters

Pods are filtered before scoring — they never receive a score and never appear in output:

Filter Condition
Too young Pod age < --age-threshold
Ignored annotation Pod has --ignore-annotation key with value "true" (case-insensitive)
Managed controller Pod is owned by a DaemonSet or StatefulSet

Configuration

Signal weights and the score threshold can be overridden with a YAML file. Pass it via --config:

columbo --config .columbo.yaml

Priority for threshold: --threshold flag > threshold in config file > built-in default (70).

All fields are optional. Omitted fields keep their default values.

# .columbo.yaml
threshold: 80

weights:
  no_owner_refs: 60         # default: 50
  no_service_match: 10
  no_labels: 10
  suspicious_namespace: 5
  terminal_phase: 20
  crash_loop_backoff: 15
  no_resources: 10
  debug_name: 15
  default_service_account: 5
  latest_image_tag: 10

Setting a weight to 0 effectively disables that signal.

Output Formats

text — tabular (default)

NAMESPACE     POD                         SCORE   AGE
default       nginx-debug                 125     12d
kube-system   nsenter-75l64h              75      419d

With --explain:

default/nginx-debug   score: 125
  - no ownerReferences (+50)
  - no Service match (+10)
  - debug-like name ("debug") (+15)
  - ...
----------------------------------------

json — machine-readable

columbo -o json
[
  {
    "namespace": "default",
    "pod": "nginx-debug",
    "score": 125,
    "age": "12d"
  }
]

With --explain the "reasons" array is included:

[
  {
    "namespace": "default",
    "pod": "nginx-debug",
    "score": 125,
    "age": "12d",
    "reasons": [
      "no ownerReferences (+50)",
      "no Service match (+10)",
      "debug-like name (\"debug\") (+15)"
    ]
  }
]

Exit Codes

Code Meaning
0 No suspicious pods found
1 One or more suspicious pods detected
2 Execution error (API unreachable, bad flags, …)

Safety

columbo is strictly read-only. It only calls LIST and GET on pods, services, and namespaces. It never creates, updates, or deletes any resource.

Minimum required RBAC:

rules:
  - apiGroups: [""]
    resources: ["pods", "services", "namespaces"]
    verbs: ["get", "list"]

If get on namespaces is not permitted, columbo assumes the namespace exists and continues — it will not fail on limited RBAC.

Contributing

Contributions are welcome! Feel free to open issues and pull requests. Whether it's a bug fix, new feature, documentation improvement, or just a question - all input is appreciated.

License

Apache License 2.0 — see LICENSE.

About

A minimal CLI to detect potentially forgotten Kubernetes pods using an explainable suspicion score.

Topics

Resources

License

Stars

6 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages