Label Selector Tester · Kubernetes
See which pods your selector matches — and why.
Paste the pods and the selector — a kubectl -l string or a matchLabels / matchExpressions block — and get a per-resource verdict with the exact clause that decided it. Including the rule almost everyone answers backwards: NotIn and != match a resource that has no such label at all.
Runs in your browser — nothing you paste leaves this page. How we prove that
Kubernetes Label Selector Tester playground
Equality-based clauses (=, !=) and set-based ones (in, notin, a bare key, !key) can be mixed freely in one -l string — commas mean AND. A Deployment's spec.selector accepts both forms but is immutable after creation; a Service's spec.selector is a plain label map and supports equality only.
Results update as you type — press Enter to run now.
Paste one manifest, a --- stream, a kind: List or a YAML list. Press Esc to release keyboard focus from the editor; ⌘/Ctrl + Enter runs and leaves it. Nothing you paste is uploaded.
Paste the resources above — or tap an example — to see, per resource, whether the selector matches it and which clause decided.
The Gap
A selector that matches nothing is not an error anywhere in Kubernetes.
kubectl apply accepts it. The API server accepts it. No controller logs a warning, no event is emitted, no field is marked invalid. The only symptom of a Service whose selector misses its pods by one character is an Endpoints object with no addresses — and by the time you are looking at that, you are already debugging in the wrong place. The same silence covers a NetworkPolicy whose podSelector selects nothing, which fails open in exactly the way a security control should not.
The rules themselves are the second half of the problem, because two of them read backwards. env!=prod matches a pod that has no env label at all — NotIn is satisfied by an absent key, by design, in one branch of Requirement.Matches. And an empty selector matches everything, while a nil selector matches nothing. Both facts are one line of Go each, and both are routinely remembered the other way round.
Which is why this is a tool and not a blog post. Ask an assistant "does env!=prod match a pod with no env label" and you will get a fluent answer with a fifty-fifty chance of being wrong — and generated manifests routinely put matchExpressions inside a Service's spec.selector, a field that only accepts a plain label map. Paste the claim in here and the answer comes from the same semantics apimachinery implements, with the reason attached, in a tab that sends nothing anywhere.
Sizing the workload rather than selecting it? Kubernetes Resource Calculator totals requests and limits across replicas, and Env Example Checker catches the variables a container expects but never gets.
The Pipeline
How it works.
Four steps, all inside your browser tab, re-run as you type.
-
Parse the selector the way apimachinery does.
The `-l` grammar goes through a port of its own lexer and parser — context-sensitive, so `op in (in,notin)` is legal, and whitespace-tolerant, so `env in ( prod , dev )` is the same selector. Structured selectors go through `LabelSelectorAsSelector`'s rules instead.
-
Read whatever is in the clipboard.
One manifest, a `---` stream, a `kind: List`, or a YAML list. A workload's `spec.template.metadata.labels` becomes its own row, because a Service selects pod labels and not the Deployment's own.
-
Evaluate clause by clause.
`Requirement.Matches`, once per resource per clause: `In` needs the key present, `NotIn` is satisfied by an absent key, `Exists` accepts an empty value, `DoesNotExist` does not.
-
Show the reason, not just the verdict.
Every clause carries the sentence that decided it, naming the actual label value. The absent-key matches get an amber annotation, because those are the ones people and language models answer backwards.
Reference
Every operator, and what an absent key does.
Seven ways to write a clause, four operators behind them, and the column that is almost never printed next to the syntax: what happens when the resource does not carry the key at all. Three of the seven match in that case.
| Written as | Operator | Structured form | Key absent |
|---|---|---|---|
| key=value | In | matchLabels: {key: value} One value. `=` and `==` are the same operator. | no match |
| key==value | In | matchLabels: {key: value} Identical to `=`; only the spelling is preserved. | no match |
| key!=value | NotIn | operator: NotIn, values: [value] The one people get backwards. An absent key satisfies it. | MATCHES |
| key in (a,b) | In | operator: In, values: [a, b] At least one value is required; the set is de-duplicated and sorted. | no match |
| key notin (a,b) | NotIn | operator: NotIn, values: [a, b] Same absent-key rule as `!=`, because it is the same operator. | MATCHES |
| key | Exists | operator: Exists Any value, including the empty string. Values are forbidden. | no match |
| !key | DoesNotExist | operator: DoesNotExist Fails if the key is present with an empty value — it is still present. | MATCHES |
"Has the label, and it is not prod"
One clause cannot say that. env!=prod also picks up every resource with no env label. Add Exists next to it — commas mean AND.
# matches unlabelled pods too
kubectl get pods -l 'env!=prod'
# has an env label, and it is not prod
kubectl get pods -l 'env,env!=prod' The empty selector, three ways
Empty means everything; nil means nothing; and a Service omits the field when it is empty, which leaves it with no managed endpoints at all.
# every pod in the namespace
podSelector: {}
# every resource, again
selector:
matchLabels: {}
# a Service with NO selector: endpoints
# are yours to manage by hand
spec:
ports: [{ port: 80 }] The Fence
What it deliberately does not do.
Each of these was considered and refused, because the alternative was a plausible answer that could be wrong. A tester you cannot audit is a tester you have to trust.
Field selectors
A different mechanism with a per-kind, per-version allow-list of indexed fields, mostly without set-based operators. Simulating it offline would mean guessing which fields your cluster indexes.
Node affinity Gt and Lt
They belong to NodeSelectorRequirement, not LabelSelectorRequirement. Typing replicas>2 gets a message that says so instead of a comparison a label selector cannot express.
namespaceSelector semantics
A NetworkPolicy peer combines a namespaceSelector with a podSelector across namespaces. Without the namespace objects, the honest answer is the podSelector on its own.
Anything that needs the cluster
No API server is contacted, so no live pod list, no admission result and no endpoints object. Everything here is derived from the text you pasted.
Limits, stated rather than hidden: up to 500 resource documents from a paste of at most 500,000 characters, and at most 100 clauses in one selector. A label key name is validated at 63 characters, its optional DNS-subdomain prefix at 253, and a label value at 63 — the same limits the API server enforces.
Next Step
Paste the trace into the incident channel.
"Copy report" gives you the whole run as plain text: the canonical selector, then one block per resource with its verdict and every clause that decided it. Then keep going — total what the workload actually reserves, or check the variables its containers expect.
Kubernetes Label Selector Tester — 3 of 5 resources match
selector: app=web,tier=frontend
NO MATCH — Pod/web-d
metadata.labels: app=web, tier=frontnd
[ok] app=web — label app="web" = "web"
[no] tier=frontend — label tier="frontnd" ≠ "frontend" FAQ
Questions, answered.
Tap a question to expand the answer.
Why does env!=prod match a pod that has no env label at all?
Because that is what the rule says, and it is the single most surprising line in the whole label-selector API. In k8s.io/apimachinery, Requirement.Matches handles NotIn and != with one branch: if the label set does not have the key, return true. In and == take the opposite branch: if the label set does not have the key, return false. So env!=prod means "no env label saying prod", which a pod with no env label satisfies perfectly. If you meant "has an env label, and it is not prod", you need two clauses: env,env!=prod — Exists AND NotIn. This tester annotates every clause that holds because the key is absent, so you never have to remember which way round it goes.
What is the difference between matchLabels and matchExpressions?
matchLabels is a plain map, and every pair in it becomes one equality requirement: {app: web, tier: frontend} is exactly app=web,tier=frontend. matchExpressions is a list of {key, operator, values} entries and is the only way to write the set-based operators In, NotIn, Exists and DoesNotExist. When a selector has both, apimachinery ANDs everything together — LabelSelectorAsSelector adds each matchLabels pair and each expression to the same requirement list. There is no OR anywhere in a label selector, at any level.
Can a Service selector use matchExpressions?
No, and this is the mistake generated manifests make most often. A Service's spec.selector is typed map[string]string, not a LabelSelector: it takes a plain label map and supports equality only. Write matchLabels or matchExpressions there and the API server rejects the field outright. Deployments, StatefulSets, DaemonSets, Jobs and NetworkPolicy podSelector all take a real LabelSelector and accept both forms. Paste a Service manifest with matchExpressions into this tester and it warns you, then shows what a Deployment WOULD have matched — so you can see both the error and the intent.
Does an empty selector match everything or nothing?
It depends on whether the field is empty or absent, and the two are not the same. An empty LabelSelector — {}, or matchLabels: {} — is labels.Everything(): it matches every object, which is exactly why a NetworkPolicy with podSelector: {} applies to every pod in the namespace. A selector that is nil, on the other hand, matches nothing: LabelSelectorAsSelector(nil) returns labels.Nothing(). And a Service is a third case again: its selector field is omitted when empty, so a Service with selector: {} is a Service with no selector, and the endpoints controller does not manage its endpoints at all.
Does anything I paste leave my browser?
No. Both parsers, the validator and the matcher are JavaScript running in your tab — there is no server, no API call and no logging, so 0 bytes are uploaded. That matters here because a pod dump is not neutral text: it carries internal image references, node names, annotations, sometimes a token mounted as an env var. This is the tool you can use on production output without a conversation about data handling.
My Service has zero endpoints. What do I check first?
The pod labels, not the Service. Paste the Service manifest as the selector and the actual pods as the resources: the tester reads spec.selector for you and shows a per-clause trace on each pod, so a one-character label typo shows up as label tier="frontnd" ≠ "frontend" instead of as an empty endpoint list. Two structural traps to rule out as well. First, a Service selects POD labels, not the Deployment's own metadata.labels — this tester lists a workload's spec.template.metadata.labels as its own "Pod template" row for exactly that reason. Second, a selector that matches nothing is never an error anywhere in Kubernetes: kubectl accepts it, the API accepts it, and the only symptom is an empty endpoints object.
Does it support field selectors like --field-selector status.phase=Running?
No, and that is a deliberate fence rather than a missing feature. Field selectors are a different mechanism: they filter on an object's own fields, they are evaluated by the API server against a small allow-list that varies per resource type, and almost none of them support set-based operators. Simulating them offline would mean encoding which fields each Kubernetes version indexes for each kind, and getting it subtly wrong. This tool covers label selectors, which are uniform across every resource type and fully defined by the object you already have in your clipboard.
What about the Gt and Lt operators I have seen in node affinity?
They exist, but not on a label selector. Node affinity uses NodeSelectorRequirement, a separate type whose operator set is In, NotIn, Exists, DoesNotExist, Gt and Lt. A LabelSelectorRequirement — the one inside matchExpressions, spec.selector and podSelector — has only the first four. So Gt and Lt are valid in nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution and invalid everywhere this tester looks. Type replicas>2 into the selector field and it says so rather than inventing a comparison a label selector does not have.
How much can it handle, and what are the limits?
It evaluates the first 500 resource documents from a paste of up to 500,000 characters, and a selector of at most 100 clauses — every cap says so on screen with the real count, so a truncated run never looks like a complete one. Keys and values are validated against the real limits: a label key name is at most 63 characters, an optional DNS-subdomain prefix before the / at most 253, and a label value at most 63 (an empty value is legal). An invalid key or value in the SELECTOR is a hard error, because the API server would reject it; an invalid label on a RESOURCE is only a note, because the question you asked was whether the selector matches it.
More free, private DevOps tools.
The Label Selector Tester is one tool in OpsCanopy — a growing canopy of browser-based validators, converters and testers that never touch a server.
39 free tools, every one offline-capable — opscanopy.com works with no signup and nothing uploaded.
Related: Kubernetes Resource Calculator for what a workload reserves, Env Example Checker for the variables a container expects, the Alertmanager Route Tester for the other label-matching tree that silently drops things, and the JSON ↔ YAML Converter when a manifest needs reshaping for kubectl patch — or browse the full tools directory.
Provided as-is for convenience; always confirm critical configuration against the cluster that will consume it. OpsCanopy is free and open.