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,Egressor 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.
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 trafficAn 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 trafficApply 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.
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.