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)
----------------------------------------Features
- Suspicion scoring — each pod is scored against 9 independent signals; only pods meeting the threshold are shown
- Explainable results —
--explainshows exactly which signals fired and why - Pre-scoring filters — DaemonSet and StatefulSet pods are always skipped; pods younger than
--age-thresholdand 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), or2(error); use--quietfor exit-code-only mode - Multiple output formats —
text(tabular) andjson
Installation
Homebrew (macOS / Linux)
brew tap k-krew/tap brew install 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) |
Scoring
Columbo scores every pod that passes the pre-filtering stage. Signals are independent and additive.
| 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 |
Debug-like substrings (case-insensitive): debug, test, shell, ubuntu, busybox, curl.
Owned pods (Deployment, Job, …) are still scored on all other signals — the +50 only fires for truly bare pods with no ownerReferences.
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 |
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
[
{
"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
getonnamespacesis 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.