Kubernetes Network Policies
Introduction¶
By default, every pod can reach every other pod in the cluster, across
namespaces. NetworkPolicy objects restrict that. They're namespaced, additive
(union of all policies selecting a pod), and — critically — only enforced if
your CNI supports them (Calico, Cilium, Antrea, Weave; not Flannel alone).
The model¶
- A pod is non-isolated until some NetworkPolicy selects it. Then it becomes isolated for the direction(s) that policy specifies, and only traffic matching an allow rule gets through.
- Rules are allow-only. Isolation + no matching rule = denied.
policyTypes: [Ingress],[Egress], or both.
Default deny (the baseline)¶
Apply this per namespace, then open holes:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: team-a
spec:
podSelector: {} # all pods in the namespace
policyTypes: [Ingress, Egress]
Now nothing in team-a can send or receive — including DNS. So immediately add:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: team-a
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- namespaceSelector:
matchLabels: { kubernetes.io/metadata.name: kube-system }
ports:
- { protocol: UDP, port: 53 }
- { protocol: TCP, port: 53 }
Allow specific traffic¶
# api pods accept ingress from web pods (same ns) and from the ingress controller
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-ingress
namespace: team-a
spec:
podSelector:
matchLabels: { app: api }
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels: { app: web }
- namespaceSelector:
matchLabels: { kubernetes.io/metadata.name: ingress-nginx }
ports:
- { protocol: TCP, port: 8080 }
# api pods may egress to the database namespace and to the internet on 443
egress:
- to:
- namespaceSelector:
matchLabels: { kubernetes.io/metadata.name: data }
podSelector:
matchLabels: { app: postgres }
ports: [{ protocol: TCP, port: 5432 }]
- to:
- ipBlock:
cidr: 0.0.0.0/0
except: [10.0.0.0/8, 169.254.169.254/32] # no cluster-internal, no metadata
ports: [{ protocol: TCP, port: 443 }]
Selector gotcha: namespaceSelector + podSelector in the same from
entry (no - between them) means "pods matching X in namespaces matching Y".
Separate list items (- namespaceSelector then - podSelector) means "OR".
Beyond standard NetworkPolicy¶
Standard policy is L3/L4 only, namespace-scoped, no ordering, no deny rules, no logging. For more, use your CNI's CRDs:
- Cilium
CiliumNetworkPolicy/CiliumClusterwideNetworkPolicy— L7 (HTTP paths/methods, DNS names, Kafka), cluster-wide, deny rules. - Calico
GlobalNetworkPolicy— ordered, deny, logging, host endpoints.
Verification and troubleshooting¶
kubectl get networkpolicy -A
kubectl describe networkpolicy api-ingress -n team-a
# Test from inside a pod
kubectl run t --rm -it --image=nicolaka/netshoot -n team-a -- bash
nc -zv api.team-a 8080 # should work from an allowed source
nc -zv postgres.data 5432
curl -m3 https://example.com
nslookup kubernetes.default
- Applied a default-deny and everything broke — DNS. Add the
allow-dnsegress policy. Also health probes: the kubelet probes from the node IP — ingress from the node CIDR may need allowing (some CNIs exempt probes, some don't). - Policy has no effect — the CNI doesn't enforce NetworkPolicy (plain
Flannel), or it's not in "policy" mode.
kubectl get pods -n kube-systemfor calico/cilium. namespaceSelectornot matching — pre-1.22 namespaces lacked the automatickubernetes.io/metadata.namelabel; label them, or use your own.- Egress to a Service IP blocked despite allowing the pod — kube-proxy DNATs the ClusterIP to a pod IP; the policy is evaluated against the pod IP after DNAT, so allow the backend pods, not the Service. Some CNIs handle Service IPs directly.
- Cross-namespace allow not working — both a
namespaceSelector(to pick the namespace) AND the traffic must match; and the target pod's namespace is where the policy lives. - Can't reach the API server / node-local DNS — those may be on the host
network;
ipBlockthe node CIDR or use the CNI's host-endpoint handling. - Intermittent drops after a deploy — new pods get IPs before the policy's ipBlock/selectors converge; brief. Persistent = a selector typo.
Related tools and reading¶
- On-site: Subnet Calculator, Port Checker.
- Related posts: Kubernetes Services and Endpoints, CoreDNS in Kubernetes.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Docker & Kubernetes
StatefulSets Explained
StatefulSets give pods sticky names, sticky storage, and ordered operations — what databases and clustered apps need. Here's how each guarantee works.
October 24, 2026Persistent Volumes and Claims, Explained
PVC is a request, PV is the fulfilled storage, StorageClass is the factory. Most 'Pending PVC' problems are an access-mode or provisioner mismatch.
October 24, 2026Kubernetes Jobs and CronJobs
A Job runs pods until they succeed; a CronJob creates Jobs on a schedule. Here's parallelism, retry limits, the missed-schedule trap, and history cleanup.
October 24, 2026