All writing
Updated 3 min readNaren

Controlling Pod traffic with network policies

Every Pod can reach every other Pod by default. Network policies cut that down to what you allow, starting from a default deny.

The last post exposed applications with Services and Ingress. Now that they can be reached, the next job is securing how they communicate.

Network policies control how Pods talk to each other and to other network endpoints. They work like a firewall for your Pods, with fine-grained rules for the traffic inside your cluster.

Why you need them

By default, every Pod in a Kubernetes cluster can talk to every other Pod. That flat network is convenient in development and a serious risk in production. If one Pod is compromised, an attacker can move sideways to any other Pod in the cluster.

Network policies let you apply micro-segmentation: traffic between Pods is restricted to what you explicitly allow. That shrinks the attack surface and helps contain a breach.

A policy is built from four parts:

  • A Pod selector decides which Pods the policy applies to, usually by their labels.
  • Ingress rules decide which incoming traffic the selected Pods accept.
  • Egress rules decide which outgoing traffic the selected Pods may send.
  • Policy types say whether the policy covers Ingress, Egress or both.

Your Container Network Interface (CNI) plugin enforces network policies. Calico, Cilium and Weave Net all do. If your cluster's CNI plugin does not support network policies, they are not enforced.

Start from a default deny

Network policies are additive. If no policy applies to a Pod, all traffic to and from that Pod is allowed. As soon as any policy selects a Pod for ingress or for egress, only the traffic some policy explicitly allows in that direction is permitted. Everything else in that direction is denied.

That leads to the most important practice: start with a default deny policy.

Namespace Internet / Other Namespaces Allow Policy Blocked by Default Deny Blocked by Default Deny Allowed by Policy Pod A Pod B External Traffic Allow Ingress from Pod C

1. Deny all ingress traffic in a namespace

This policy blocks all incoming traffic to every Pod in a namespace, unless another policy allows it. It is the basic security measure the rest build on.

# default-deny-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: my-app-namespace # Apply this to your application's namespace
spec:
  podSelector: {} # Selects ALL Pods in 'my-app-namespace'
  policyTypes:
  - Ingress # This policy only applies to incoming traffic

An empty podSelector selects every Pod in the namespace. policyTypes lists only Ingress, so the policy concerns incoming traffic alone. Because it defines no ingress rules, it denies all incoming traffic to every Pod in my-app-namespace.

Apply it with kubectl apply -f default-deny-ingress.yaml.

2. Deny all egress traffic in a namespace

The same pattern denies all outgoing traffic from the Pods in a namespace:

# default-deny-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: my-app-namespace
spec:
  podSelector: {} # Selects ALL Pods in 'my-app-namespace'
  policyTypes:
  - Egress # This policy only applies to outgoing traffic

Apply it with kubectl apply -f default-deny-egress.yaml.

For production applications, apply both a default deny ingress and a default deny egress policy to your application namespaces. Then define the traffic you allow, explicitly.

Allowing specific traffic

With a default deny in place, you add allow rules on top of it.

User Namespace External Allowed Denied Denied Allowed Allowed Denied Other Pods Frontend Pod Backend Service Database Service User

3. Allow ingress from specific Pods

Say a frontend application needs to talk to a backend application, both in the default namespace:

# allow-frontend-to-backend.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: backend # This policy applies to Pods with label 'app: backend'
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend # Allow traffic from Pods with label 'app: frontend'
    ports:
    - protocol: TCP
      port: 8080 # Allow on TCP port 8080 (where backend listens)

The policy targets Pods labeled app: backend. Its ingress section allows incoming traffic only from Pods labeled app: frontend, and only on TCP port 8080.

Apply it with kubectl apply -f allow-frontend-to-backend.yaml.

4. Allow egress to an IP range

An application might need to connect to a database or an API outside the cluster:

# allow-egress-to-external-db.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-egress-to-external-db
  namespace: my-app-namespace
spec:
  podSelector:
    matchLabels:
      app: my-app # This policy applies to Pods with label 'app: my-app'
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 192.168.1.0/24 # Allow egress only to this CIDR range
        except: # Optional: exclude specific IPs within the CIDR
        - 192.168.1.5/32
    ports:
    - protocol: TCP
      port: 5432 # Allow on TCP port 5432 (e.g., PostgreSQL)

The policy targets Pods labeled app: my-app and covers outgoing traffic only. Its egress section allows connections to addresses in the 192.168.1.0/24 block, except the ones listed under except, and only on TCP port 5432, the PostgreSQL port.

Apply it with kubectl apply -f allow-egress-to-external-db.yaml.

Health checks need no policy

Liveness and readiness probes keep working under a default deny, with no extra policy. The kubelet sends them from the Pod's own node, and traffic between a Pod and the node it runs on is always allowed.

Good habits

Deny first: start by denying all ingress and egress traffic, then allow only what is needed.

Network policies rely on labels, so give your Pods and namespaces consistent, meaningful ones.

Policies get complicated quickly. Test them in a non-production environment first, so you know they behave as expected and don't block legitimate traffic by accident.

Write down what each policy is for, and monitor the traffic inside your cluster so unexpected blocked or allowed connections show up.

The next post moves on to storage, and how Kubernetes keeps data that must outlive a Pod: Persistent storage.