Pod isolation via NetworkPolicies
Kubernetes NetworkPolicies control pod-to-pod traffic. Learn how to define and apply rules to isolate workloads for security.
Focus: use NetworkPolicies for pod isolation
Inside a Kubernetes cluster, every pod can talk to every other pod by default — no firewall, no security group, no questions asked. That flat network model is great for development but dangerous in production, where a compromised frontend pod could reach your database directly. NetworkPolicies are your first line of defense: they act as an in-cluster firewall that lets you define which pods can communicate with each other, turning your cluster from a free-for-all into a secure, segmented environment.
The problem this lesson solves
Without NetworkPolicies, any pod can send traffic to any other pod on any port. This means a simple bug in a frontend service could accidentally write to the database, or a malicious container could exfiltrate data from a backend pod. Traditional firewalls operate at the cluster boundary, but they can't inspect east-west traffic between pods. This gap is a major security risk, especially in multi-tenant clusters or production environments handling sensitive data.
The pain is real: - No visibility or control over pod-to-pod communication - Accidental data leaks between microservices - Compliance requirements (PCI-DSS, HIPAA) demand network segmentation - A compromised container has free reign to attack other services
NetworkPolicies solve this by giving you declarative, Kubernetes-native rules that control ingress (incoming) and egress (outgoing) traffic at the pod level.
Core concept / mental model
Think of NetworkPolicies as a firewall rule set attached to one or more pods. Each policy selects pods using label selectors (like the selector in Services), then defines allowed traffic using from and to blocks with optional port restrictions.
Key mental model elements:
- Pod selector — which pods does this policy apply to?
- Ingress rules — who can send traffic to the selected pods?
- Egress rules — which destinations can the selected pods reach?
- Policy types —
Ingress,Egress, or both (default:Ingressonly) - Default behavior — if no policy matches a pod, all traffic is allowed. If any policy matches, all traffic not explicitly allowed is denied.
Pro tip: NetworkPolicies are additive — they only allow traffic. Denial is implicit when a policy exists. This means you must craft policies that explicitly allow the traffic you need.
How it works step by step
-
Enable a CNI plug-in that supports NetworkPolicies — not all do. Calico, Cilium, Weave Net, and Canal support them. Flannel alone does not.
-
Design your isolation model — choose between: - Default deny all ingress (most secure) - Default deny all egress (prevents data exfiltration) - Allow only specific sources/destinations
-
Label your pods consistently — NetworkPolicies rely on labels. Without them, you can't target pods.
-
Write the policy YAML — define
podSelector,policyTypes,ingress/egressrules. -
Apply the policy —
kubectl apply -f policy.yaml. It takes effect immediately. -
Test connectivity — use a temporary pod (
kubectl run test --image=busybox --rm -it -- wget --timeout=2 <target-ip>) to verify rules.
Hands-on walkthrough
Let's create a secure namespace with two deployments: frontend and backend (simulating a database). We'll isolate backend so only frontend can reach it.
Step 1: Create namespace with default deny
kubectl create ns secure-app
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: secure-app
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
EOF
This denies all ingress and egress traffic for all pods in secure-app. Nothing in or out until we add explicit rules.
Step 2: Deploy frontend and backend
kubectl run backend --image=nginx -n secure-app --labels=app=backend,role=db --expose --port=80
kubectl run frontend --image=nginx -n secure-app --labels=app=frontend,role=web --expose --port=80
Both pods and Services exist, but due to default-deny-all, no traffic flows.
Step 3: Allow ingress to backend only from frontend
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: secure-app
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- port: 80
EOF
Now only pods with label app: frontend can reach the backend on port 80.
Step 4: Test connectivity
kubectl run test-frontend --image=busybox -n secure-app --rm -it --labels=app=frontend -- wget -qO- http://backend
# Output: <!DOCTYPE html> ... (NGINX page)
kubectl run test-other --image=busybox -n secure-app --rm -it --labels=app=other -- wget -qO- --timeout=2 http://backend
# Output: wget: download timed out (connection blocked)
The policy works — only the frontend can access the backend.
Step 5: Allow egress for frontend
Frontend can't make outbound calls (like DNS) because we denied all egress. Let's allow DNS and traffic to backend:
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-egress
namespace: secure-app
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: backend
ports:
- port: 80
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
EOF
Now frontend can talk to backend and resolve DNS, but not reach the internet or other pods.
Compare options / when to choose what
| Approach | Pros | Cons | When to use |
|---|---|---|---|
| Default deny all then open specific paths | Maximum security, minimal attack surface | Complex to set up initially; must explicitly allow DNS, monitoring, etc. | Production, multi-tenant, or compliance-heavy environments |
| Allow all, then restrict | Easy to start, policies add safety gradually | Insecure until policies are applied — easy to forget | Development, small clusters, initial rollout |
| Namespace isolation via Namespace selectors | Simple, broad rules; policy covers entire namespace | Less granular — can't distinguish pods within the same namespace | Teams sharing a namespace, defense in depth |
| Third-party solutions (Calico, Cilium) | Advanced features: global network policies, service graph, encryption | Extra operational overhead, licensing costs (some) | Large clusters, zero-trust, advanced monitoring needs |
When to use each: - Default deny all is best for security-critical workloads (GDPR, PCI). - Namespace isolation works well when you trust everything inside a namespace but want to separate concerns between teams. - Allow-specific paths is the most common pattern for microservices — lock down each service to only its necessary connections.
Troubleshooting & edge cases
Problem 1: NetworkPolicy not taking effect
kubectl describe networkpolicy allow-frontend-to-backend -n secure-app
Check if your CNI plug-in supports NetworkPolicies. Flannel does not; Calico does. Also verify the pod labels match the podSelector exactly.
Problem 2: DNS resolution broken after applying Egress policy
# Common symptom: curl fails with "name or service not known"
# Fix: Add explicit egress rule for kube-dns
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
Problem 3: Policy overlap — two policies affect the same pod
# If policy A allows traffic from X, and policy B allows from Y, the pod accepts traffic from both.
# There is no "deny" in a policy. To block, you must not have any allow rule covering that source.
Edge case 1: Multiple namespaces in ingress/egress
# Allow ingress from any pod in namespace 'monitoring'
ingress:
- from:
- namespaceSelector:
matchLabels:
name: monitoring
- podSelector: {} # all pods in matching namespace
Note the namespaceSelector and podSelector are ANDed within a single from block. To OR them, add a second from block.
Edge case 2: IP-based rules (for external traffic)
# Allow traffic from specific external CIDR
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/24
except:
- 10.0.0.5/32
IP blocks are useful for allowing traffic from load balancers or VPNs.
Edge case 3: Policy with no podSelector matches no pods, causing no effect.
# Wrong: leaves blank selector, applies to nothing
podSelector: {}
# Correct: applies to all pods in namespace
podSelector: {}
# Wait — that IS correct. {} means all pods. If you match nothing, use
podSelector:
matchLabels:
nonexistent: true
But you'd rarely want that.
What you learned & what's next
You now understand how to use NetworkPolicies for pod isolation in Kubernetes. You learned to: - Explain the security gap that NetworkPolicies fill. - Define the mental model of NetworkPolicies as a declarative, label-based firewall. - Create a default deny policy and then allow specific traffic paths. - Compare approaches like default-deny vs. namespace isolation vs. third-party solutions. - Troubleshoot common issues like broken DNS and policy overlap.
Next lesson: Learn about Pod Disruption Budgets to ensure high availability during voluntary disruptions (updates, node maintenance) — another critical safety layer for production clusters.
Key takeaway: NetworkPolicies are not optional in production. They are the simplest way to enforce the principle of least privilege on your cluster's network. Start with default deny all ingress, then open only what you need.
Pro tip: Automate your NetworkPolicy creation using a label-based framework like "Polaris" or include it in your CI/CD pipeline to ensure every new deployment gets a baseline policy.
Practice recap
Mini exercise: Create a new namespace practice-isolation. Deploy two deployments: app (nginx) and db (redis). Apply a NetworkPolicy that allows only app to talk to db on port 6379, and denies all other traffic. Verify with kubectl run test inside the namespace.
Common mistakes
- Assuming Flannel supports NetworkPolicies — it does not. Use Calico, Cilium, Weave Net, or another CNI with policy support.
- Forgetting to add egress rules for DNS (port 53 UDP to kube-dns), causing name resolution to fail.
- Using multiple
podSelectorfields in a singlefromblock when you meant to OR them (use separatefromblocks instead). - Applying a policy with
podSelector: {}thinking it matches no pods — it matches all pods, potentially blocking all traffic unexpectedly. - Not labeling pods consistently, so the
podSelectornever matches, and the policy has no effect.
Variations
- CiliumNetworkPolicy — a custom resource from Cilium that offers more features like HTTP-aware rules, encryption, and cluster mesh policies.
- CalicoNetworkPolicy — Calico's extended policy CRD that supports global network policies and namespace-level selectors.
- Using namespace selectors instead of pod selectors for simpler policies that isolate entire namespaces from each other.
Real-world use cases
- Secure a payment service namespace so that only the order service can reach it, blocking all other pods in the cluster.
- Isolate a staging environment from production by applying a default-deny policy on the staging namespace, then allowing only egress to a shared monitoring stack.
- Comply with PCI-DSS by implementing network segmentation between cardholder data environments and public-facing web services using NetworkPolicies.
Key takeaways
- NetworkPolicies are a Kubernetes-native firewall that controls pod-to-pod traffic using label selectors.
- Without a NetworkPolicy, all pods can communicate freely — you must explicitly lock down traffic in production.
- Policies are additive: they only allow traffic; implicit denial applies once any policy targets a pod.
- Always allow DNS egress (port 53 UDP) or your pods won't resolve service names.
- Test your policies with a temporary busybox pod to confirm the desired isolation.
- Start with a default deny-all policy, then open specific paths (least privilege).
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.