Sign in
← Kubernetes: networking
6 min · medium · Lesson 6 of 8

NetworkPolicy: who can talk to whom

How to lock a Pod down from unwanted traffic with label-based rules, and where the one-dash mistake hides.

What a NetworkPolicy is

A NetworkPolicy is a Kubernetes object that sets rules for network traffic between Pods. A Pod is the smallest unit Kubernetes runs; one or more containers run inside a Pod. NetworkPolicy works like a firewall, but it selects Pods by their labels, not by IP addresses.

The NetworkPolicy object itself blocks nothing. The rules are enforced by the CNI plugin (Container Network Interface, the cluster component responsible for Pod networking). Calico, Cilium and Kube-router support NetworkPolicy, but simpler plugins like flannel don't: the rules get created in the API and are silently ignored. You need to check a policy's effect on the exact cluster where it will run.

kind's default network plugin is kindnet, and starting with kind v0.24.0 it enforces NetworkPolicy. This lesson's labs run on it.

Everything is allowed by default

no NetworkPolicy client-a client-b client-c Pod app: web traffic is open with a NetworkPolicy on app: web role: allowed role: blocked Pod in other ns ✕ ✕ Pod app: web only what's listed is allowed
The first policy on a Pod flips the default: only what it lists gets through.

If a namespace has no NetworkPolicy at all, traffic is open. Any Pod can reach anywhere. A namespace is an area inside the cluster that separates one set of objects from another.

As soon as a NetworkPolicy is attached to a Pod, a rule kicks in for it: everything not explicitly allowed is denied. After that, the policy lists which traffic gets through.

If a Pod is selected by several policies, their permissions add up. There's no order among policies, and one policy can't revoke what another allowed.

Anatomy of a manifest

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: web-only-from-allowed
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              role: allowed
      ports:
        - protocol: TCP
          port: 8080

podSelector is which Pods the rules apply to. Here it's Pods with the label app: web. An empty {} means every Pod in the namespace.

policyTypes is which traffic the policy governs. Ingress is incoming traffic, Egress is outgoing. You can list one type or both.

ingress.from is where incoming traffic is allowed from. A source is set through podSelector (Pods with these labels), namespaceSelector (Pods from a namespace with these labels) or ipBlock (an IP range).

A podSelector inside from with no namespaceSelector next to it only looks for Pods in the policy's own namespace. To select a namespace by name, the label kubernetes.io/metadata.name is handy: Kubernetes sets it on every namespace itself, with the value equal to the namespace's name.

ports is which ports are open. Without this block, all ports are open.

AND and OR in selectors

Inside one from list element, selectors are combined with AND: a Pod with this label AND in a namespace with that label. Different list elements are combined with OR.

AND, one element, two selectors (a Pod with label role: allowed AND in a namespace with label team: a):

ingress:
  - from:
      - podSelector:
          matchLabels:
            role: allowed
        namespaceSelector:
          matchLabels:
            team: a

OR, two elements (a Pod with label role: allowed OR any Pod from a namespace with label team: a):

ingress:
  - from:
      - podSelector:
          matchLabels:
            role: allowed
      - namespaceSelector:
          matchLabels:
            team: a

The difference is one dash. It's the single most common mistake in NetworkPolicy.

What's in fromLogicWho gets through
two selectors in one list elementANDa Pod with label role: allowed from namespace team: a
two separate list elementsORa Pod with label role: allowed, or any Pod from team: a

Checking the effect

The most reliable way to check a policy is to try connecting from both sides. For example, next to Pod web (fronted by Service web) there are two clients on image busybox:1.36.1: client-allowed with label role: allowed, and client-blocked with label role: blocked.

From the allowed client:

kubectl exec client-allowed -- nc -zv -w 2 web 8080
web (10.96.12.34:8080) open

From the blocked one:

kubectl exec client-blocked -- nc -zv -w 2 web 8080
nc: web (10.96.12.34:8080): Connection timed out
command terminated with exit code 1

The allowed client connects right away, the blocked one waits 2 seconds (-w 2) and gets a timeout: packets are silently dropped, and no "connection refused" comes back. Notice the port: the policy opens 8080, so that's what gets checked, not 80. A new policy doesn't apply instantly, it takes a few seconds, so a probe right after apply is worth repeating.

Three commands help figure out what actually applied:

# List of policies in the namespace
kubectl get networkpolicy

# How the policy breaks down into selectors and ports
kubectl describe networkpolicy web-only-from-allowed

# Pod labels, to see what matchLabels catches
kubectl get pods --show-labels

Common mistakes

Homework

Practice the theory on your own cluster: 2 steps with a check. Set up the cluster with the Kubernetes locally track, the tasks live in the k8s-practice repository.

./run.sh start k8s-network-policy     # set up the starting state
./run.sh verify k8s-network-policy    # check the current step

In short

What you'll need for the tasks

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson k8s-network-06-network-policy