skip to content

In a Kubernetes NetworkPolicy, explain the difference between listing podSelector and namespaceSelector as two separate items in a from list versus combining them in a single item, and when you would reach for ipBlock instead.

level: middleimportance: must knowfreq 56%

answer

  1. Separate dashes = OR, same dash = AND
  2. Bare podSelector = policy's own namespace
  3. namespaceSelector: {} = all namespaces
  4. ipBlock cannot mix with label selectors
  5. Ports match the pod's container port, not the Service port

basics

~20 s

Separate list items are OR'd - each is an independent peer. Selectors inside one item are AND'd, so podSelector plus namespaceSelector in the same item means pods with those labels only in namespaces with those labels. ipBlock matches CIDR ranges and is for non-pod peers such as external services; it cannot be combined with the label selectors in one item.

solid answer

~60 s

The `from` (and `to`) field is a **list of peers, OR'd together**. Within a single list item, the fields present are **AND'd**. So this allows traffic from pods labelled `app: api` **in any namespace labelled `env: prod`**: ```yaml from: - namespaceSelector: {matchLabels: {env: prod}} podSelector: {matchLabels: {app: api}} ``` Whereas splitting them across two items allows traffic from **any pod in prod namespaces** *or* **any pod labelled app=api in the policy's own namespace** - dramatically wider, and the single most common NetworkPolicy bug. The dash placement is the whole difference, and YAML makes it easy to get wrong silently. Other rules: a bare `podSelector` peer implicitly means "in this policy's namespace"; a bare `namespaceSelector` means all pods in the matching namespaces. Kubernetes automatically labels namespaces with `kubernetes.io/metadata.name`, so you can select a specific namespace without adding labels. **`ipBlock`** matches a CIDR with optional `except` ranges. It is for peers that are not pods - external APIs, on-prem ranges, a cloud database - and it must be the only peer type in its list item.

code

yaml · 15 lines
yaml
# AND: prometheus pods, only from the monitoring namespace
ingress:
  - from:
      - namespaceSelector:
          matchLabels: {kubernetes.io/metadata.name: monitoring}
        podSelector:
          matchLabels: {app: prometheus}

# OR: every pod in monitoring, PLUS every local pod labelled prometheus
ingress:
  - from:
      - namespaceSelector:
          matchLabels: {kubernetes.io/metadata.name: monitoring}
      - podSelector:
          matchLabels: {app: prometheus}

go deeper

for a junior

Know that from is a list of allowed peers and that pod and namespace selectors exist; recall that separate entries mean OR.

for a middle

Explain AND-within-a-peer versus OR-across-peers precisely, the implicit same-namespace default, and when ipBlock applies.

for a senior

Treat the OR mistake as a security defect: review dash placement, prefer kubernetes.io/metadata.name, and test denied sources rather than only allowed ones.

for a principal

Push for guardrails - policy linting or admission checks in CI that flag over-broad peers - since the failure is silent, valid, and easy for any team to reintroduce.

## The grammar A NetworkPolicy ingress rule looks like: ```yaml ingress: - from: [ peer, peer, ... ] # OR across peers ports: [ port, port, ... ] # OR across ports ``` And traffic is allowed if it matches **some peer AND some port** in **some rule** of **some policy** selecting the pod. Three levels of OR, one AND between peer and port. Inside a single peer, whatever fields are set must **all** match. That is where the AND lives, and it is expressed purely by YAML indentation - whether a `podSelector` key sits under the same dash as `namespaceSelector` or under its own. ## The classic bug ```yaml # INTENDED: monitoring namespace's prometheus pods only from: - namespaceSelector: matchLabels: {kubernetes.io/metadata.name: monitoring} - podSelector: matchLabels: {app: prometheus} ``` This actually allows **every pod in the monitoring namespace**, plus **every pod labelled `app: prometheus` in the policy's own namespace**. Someone who can create a pod in `monitoring`, or label a local pod `app: prometheus`, reaches the protected workload. The correct version merges them under one dash: ```yaml from: - namespaceSelector: matchLabels: {kubernetes.io/metadata.name: monitoring} podSelector: matchLabels: {app: prometheus} ``` Because both forms are valid YAML and valid API objects, nothing warns you. This is the single highest-value detail to know about the API, and a favourite interview probe. ## Defaults inside peers - **`podSelector` alone**: matches pods with those labels *in the policy's own namespace only*. There is no way to say "this pod label in any namespace" without also giving a `namespaceSelector` (use an empty `namespaceSelector: {}` to mean all namespaces). - **`namespaceSelector` alone**: matches *all* pods in namespaces carrying those labels. - **`namespaceSelector: {}`**: all namespaces - that is, the entire cluster's pods. - **`podSelector: {}` inside a peer**: all pods in the relevant namespaces. Note the same empty-selector-means-everything rule as in `spec.podSelector`, which is why an accidental `{}` is dangerous. - Since v1.21 the API server labels every namespace with `kubernetes.io/metadata.name: <name>`, so targeting one namespace by name needs no extra labelling. Relying on hand-applied namespace labels is fragile because whoever can edit a namespace's labels can grant themselves access. ## ipBlock and non-pod peers `ipBlock` selects by CIDR, with an optional `except` list of narrower CIDRs to exclude: ```yaml to: - ipBlock: cidr: 10.0.0.0/8 except: ["10.0.5.0/24"] ``` Use it for peers with no pod identity: an external payment API, an on-prem network, a managed database endpoint. Constraints and gotchas: - It **cannot appear alongside** `podSelector` or `namespaceSelector` in the same peer item - IP matching and label matching are different worlds. - `except` is the *only* subtractive construct in the whole API, and it only narrows within its own CIDR. - Matching in-cluster pods by IP is a trap: pod IPs are ephemeral, and depending on the CNI, traffic that has been SNATed or DNATed may present a node IP rather than the pod IP, so IP-based rules behave inconsistently. Use label selectors for anything inside the cluster. - Egress to an external service that resolves to changing IPs (most SaaS endpoints) is painful with `ipBlock`. DNS-name-based egress rules exist only as CNI-specific CRDs, not in the upstream API. ## Ports The `ports` list ANDs with the peer set. A rule with `from` but no `ports` allows all ports for those peers; a rule with `ports` but no `from` allows those ports from **all** sources. Newer clusters also support `endPort` for ranges. Ports refer to the **target pod's container port**, not the Service port, which surprises people using a Service whose port differs from `targetPort` - the policy must name the pod-side port. ## Practical advice - Review the dash placement of every peer list in code review; it is the field most often wrong. - Prefer `kubernetes.io/metadata.name` for namespace targeting over custom labels that anyone can add. - Keep in-cluster rules label-based and reserve `ipBlock` for genuinely external ranges. - Write a test that asserts a *disallowed* source is blocked; an over-broad OR usually still passes the allow test, so only the deny test catches the bug.

  • Why is selecting in-cluster peers by ipBlock a bad idea?
    Pod IPs are ephemeral and reassigned, so a CIDR rule silently grants access to whatever pod later holds that address. Worse, depending on the CNI and the path taken, in-cluster traffic may arrive SNATed to a node IP, so IP-based matching behaves inconsistently between direct pod-to-pod and Service-routed traffic. Label selectors express identity that survives rescheduling and are the intended mechanism.
  • A Service exposes port 80 mapping to targetPort 8080 on the pods. Which port does the NetworkPolicy rule need?
    8080, the container port on the destination pod. Policy enforcement happens at the pod, after kube-proxy has already DNATed the Service port to the target port, so the packet the CNI evaluates is addressed to 8080. Writing port 80 in the policy produces a rule that never matches, which is a frequent cause of unexpectedly blocked traffic.

saying these in an interview costs you the question

  • Putting podSelector and namespaceSelector on separate list items when an AND was intended
  • Assuming a bare podSelector peer matches pods in every namespace
  • Trying to combine ipBlock with label selectors in the same peer entry
  • Writing the Service port instead of the pod's container port in the ports list
  • Relying on hand-applied namespace labels that any namespace editor could add to themselves

context