Skip to content
C Codeloom
Kubernetes

Kubernetes Network Policies: A Practical Tutorial

Learn how Kubernetes NetworkPolicies work, how to lock down pod-to-pod traffic with selectors, and how to roll out a default-deny posture without breaking your cluster.

·4 min read · By Codeloom
Intermediate 10 min read

What you'll learn

  • What NetworkPolicies actually control
  • How ingress and egress rules are evaluated
  • How to apply a default-deny posture safely
  • Common selector mistakes that cause outages

Prerequisites

  • Basic Kubernetes pod and namespace knowledge

What and Why

By default, every pod in a Kubernetes cluster can talk to every other pod across every namespace. That is fine for a demo, but it is a disaster for compliance and blast radius. A compromised frontend should not be able to query the payments database directly. NetworkPolicies are namespaced resources that tell the CNI plugin which traffic is allowed in and out of pods that match a label selector.

NetworkPolicies are enforced by the CNI (Calico, Cilium, Antrea). If your CNI does not support them, the resource is silently ignored, which is an unpleasant surprise to discover during an audit.

Mental Model

Think of NetworkPolicies as a stateful allow-list firewall attached to pods, not to namespaces. As soon as a pod is matched by at least one policy on a given direction (ingress or egress), all traffic in that direction is denied except what the policies explicitly allow. If no policy selects the pod for that direction, the pod is wide open.

That asymmetry trips people up. Creating an ingress policy does not affect egress, and vice versa.

Hands-on Example

Let us protect a payments API so only the checkout service in the web namespace can reach it.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: payments-allow-checkout
  namespace: payments
spec:
  podSelector:
    matchLabels:
      app: payments-api
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: web
          podSelector:
            matchLabels:
              app: checkout
      ports:
        - protocol: TCP
          port: 8080

The combined namespaceSelector and podSelector under the same from entry is an AND. If you wrote them as two list items, it would be an OR, which is much more permissive than most people intend.

namespace: web                namespace: payments
[checkout pod] ---allowed---> [payments-api:8080]
[other pod]    ---denied----X [payments-api:8080]

namespace: analytics
[any pod]      ---denied----X [payments-api:8080]
Traffic flow after policy applied

Next, add a default-deny baseline in the payments namespace:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: payments
spec:
  podSelector: {}
  policyTypes:
    - Ingress

An empty podSelector selects every pod in the namespace. With no ingress rules listed, all inbound traffic is dropped except what other policies explicitly permit.

Common Pitfalls

The biggest mistake is forgetting that DNS is egress traffic too. If you apply a default-deny egress policy, pods cannot resolve kubernetes.default or any external host until you allow egress to kube-dns on UDP and TCP 53.

egress:
  - to:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: kube-system
        podSelector:
          matchLabels:
            k8s-app: kube-dns
    ports:
      - protocol: UDP
        port: 53
      - protocol: TCP
        port: 53

Other gotchas: NetworkPolicies do not apply to host-networked pods, they cannot match by IP for in-cluster traffic reliably across CNIs, and the kubernetes.io/metadata.name label only exists on namespaces in 1.22 and later.

Production Tips

Roll out policies in three phases. First, deploy them in audit or log-only mode if your CNI supports it (Calico has Log, Cilium has Hubble). Second, apply targeted allow rules for known flows. Third, add the default-deny last, after you have watched logs for at least one full business cycle.

Label every namespace with name: <namespace> at creation time so cross-namespace selectors are easy. Keep one policy per logical flow rather than one giant policy with many rules; small policies are easier to review and delete.

Pair NetworkPolicies with PodSecurity admission and a service mesh for mTLS. NetworkPolicies answer “who can connect”, not “is the connection encrypted and authenticated”.

Wrap-up

NetworkPolicies are the cheapest, most effective microsegmentation tool in Kubernetes. Start by labeling everything, add a default-deny per namespace, then carve allow-lists for real flows. The result is a cluster where a single compromised pod cannot pivot freely, and your auditors stop asking uncomfortable questions.