DEV Community

Cover image for Kubernetes NetworkPolicy: isolate and test traffic
Span Observer
Span Observer

Posted on Originally published at span.observer

Kubernetes NetworkPolicy: isolate and test traffic

Picture a Friday evening: a container in your staging namespace starts querying the prod database, for the plain reason that nothing stops it. In Kubernetes that is the default behaviour. A Kubernetes NetworkPolicy is how you put walls where the cluster has none, and this post follows the routine I would suggest: lock a namespace down, reopen only the doors you need, then prove each wall holds.

Well-placed walls pay off a second time: when a call fails, the responsible rule is in a file you can read instead of a puzzle you have to guess at. To watch flows afterwards, see Cilium and Hubble; here we stay on the standard API.

Full disclosure: span.observer did not deploy these policies on a cluster. Manifests and commands are those of the Kubernetes documentation, read on 8 November 2026, and the screenshots reproduce those extracts, test output included, which is what the docs announce rather than something we measured.

What « isolated » means for a pod

Everything hinges on one switch. A pod that no policy selects is open in both directions. As soon as a policy selects it for ingress, only the listed sources get in; the same holds for egress. Four consequences worth knowing before writing any YAML:

  • There is no « deny » rule. You only lengthen the list of permissions, and several policies on one pod simply stack, with no priority.
  • A connection needs both ends to agree: egress allowed at the source, ingress allowed at the target. Replies to an accepted connection come back without any extra rule.
  • The object is merely a declaration. What actually blocks is the network plugin (the CNI); without its support the object exists and does nothing.
  • podSelector: {} picks every pod of the namespace; allowed peers are described by pods, namespaces or IP ranges, with optional ports.

Before you start: where to try this

Pick a disposable cluster. The documentation’s task page asks for a configured kubectl, a server at 1.8 or later and a compatible network provider (it names Antrea, Calico, Cilium and Kube-router), ideally with two worker nodes. Do not make your first attempt on production: a badly thought-out lock also cuts services you had forgotten about.

Kubernetes NetworkPolicy: lock the namespace

The first piece is a policy that selects every pod and grants nothing. After it, the namespace talks to nobody and nobody talks to it.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
Enter fullscreen mode Exit fullscreen mode

Drop Egress from the list and pods keep reaching out freely: a classic way to feel protected when you are only half covered. The docs also offer variants limited to ingress or egress, handy for a gradual migration. Warn the whole team about one side effect: locking egress cuts name resolution, more on that below.

Reopen a single door

For the example, the docs deploy an nginx behind a service, then let in only pods marked access: "true".

kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --port=80
kubectl apply -f https://k8s.io/examples/service/networking/nginx-policy.yaml
Enter fullscreen mode Exit fullscreen mode

The access-nginx policy targets app: nginx pods and accepts ingress from pods in its own namespace carrying that label. Here are both manifests side by side: the lock, then the door ajar.

Two NetworkPolicy manifests from the Kubernetes documentation: default-deny-all with an empty podSelector and policyTypes Ingress and Egress, then access-nginx allowing ingress from pods labelled access true

Prove the wall holds

A rule you have never seen fail is an untested rule. The move is to open a short-lived pod, with and without the label, and attempt the connection:

kubectl run busybox --rm -ti --image=busybox -- /bin/sh
wget --spider --timeout=1 nginx

kubectl run busybox --rm -ti --labels="access=true" --image=busybox -- /bin/sh
wget --spider --timeout=1 nginx
Enter fullscreen mode Exit fullscreen mode

The unlabelled pod should end on « wget: download timed out », the other on « remote file exists ». Note that blocking looks like silence, not a crisp « connection refused ». Without a short --timeout, the test just seems frozen.

Expected output per the Kubernetes documentation: wget from an unlabelled busybox pod ends with download timed out, then with the label access=true it answers remote file exists

Three companion checks: kubectl get networkpolicy -n NAMESPACE for the inventory, kubectl describe networkpolicy NAME to see how the selectors were read, and kubectl get pods --show-labels to confirm the targeted labels really exist.

Do not expect the API to tell you what was refused: the documentation lists logging of network security events among the things NetworkPolicy does not do. You need the CNI’s own tooling, or network observability without sidecars.

Four ways to get it wrong

The silent CNI

kubectl apply answers « created », yet everything still gets through. No controller enforces the object. Right after installing, run a test that must fail; it is the only proof that matters.

One dash too many, one too few

Inside a from block, a single element holding both namespaceSelector and podSelector expresses an AND: those pods, in those namespaces. Two elements split by dashes express an OR, far wider: the named local pods, or every pod in the named namespaces. The YAML stays valid either way, which is the danger.

Two YAML extracts from the Kubernetes documentation: a single from element with namespaceSelector and podSelector (AND), then two separate from elements (OR)

Pods that resolve nothing

A full egress deny blocks DNS queries too. Applications then start failing on names, and the application takes the blame. Add a second policy allowing egress to the cluster DNS; its content depends on your labels, for instance kubernetes.io/metadata.name to name the namespace hosting DNS. I give no copy-ready manifest here because I have not proven one on a cluster.

Asking the API for what it cannot do

No TLS, no common gateway, no targeting a service by name, no explicit deny, no default policy across all namespaces. The docs point to a service mesh or admission controllers. A pod also cannot cut itself off from its own node, and what happens to already-open connections after a change depends on the plugin.

My rollout order

  • One default-deny-all per namespace, applied first outside busy hours.
  • The DNS rule right behind it, before anyone complains.
  • One policy per flow, named after it (allow-frontend-to-api) and versioned in Git.
  • For each rule, one test that passes and one that fails.
  • No ipBlock to target pods: the docs reserve it for external addresses, since pod IPs change.
  • After each change, watch for network errors with your Kubernetes alerts.

Frequently asked questions

Does a NetworkPolicy work without a compatible CNI plugin?

No. The object can be created, but without a plugin that implements it nothing happens, according to the Kubernetes documentation.

What happens when several policies select the same pod?

They stack. No conflict, no order: a connection passes if at least one policy allows it in that direction and the other end accepts it too.

Do replies need an egress rule?

No, replies to an accepted connection return by themselves. Any new outbound connection, however, needs an egress rule once the pod is isolated for egress.

Wrapping up

Lock, reopen one door at a time, verify both ways: that is the whole craft, and the traps fit on one hand (silent CNI, stray dash, forgotten DNS). Try it on a test cluster, then widen namespace by namespace. Reference: the Kubernetes NetworkPolicy documentation.

span.observer is an independent community blog, not affiliated with Kubernetes, the CNCF or Dynatrace LLC. Trademarks belong to their owners. Tools change quickly: check commands and options in their official documentation.


Originally published on span.observer. French version: https://span.observer/fr/networkpolicy-kubernetes-isolation-flux/.

Top comments (0)