Kubernetes Network Policies Guide — Traffic Isolation
In this tutorial, you'll learn about Kubernetes Network Policies Guide. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Kubernetes Network Policies define how pods communicate with each other and with external endpoints, providing micro-segmentation and zero-trust network security at the pod level.
What You'll Learn
You'll master Network Policies — podSelector and namespaceSelector, ingress and egress rules, policy types (default deny, allow specific), CIDR-based external access, and CNI plugin requirements for policy enforcement.
Why This Problem Matters
By default, every pod can communicate with every other pod in Kubernetes. In a multi-tenant cluster, a compromised pod in one namespace can attack pods in another namespace. Network policies isolate workloads, preventing lateral movement in case of a breach.
Real-World Use
Durga Antivirus Pro uses Network Policies to isolate its scanning pipeline: frontend pods can only talk to backend pods, backend pods can only talk to database pods, and database pods accept traffic only from backend pods on port 5432.
Network Policy Architecture
flowchart TB
subgraph Namespace: Frontend
Web[web:80]
end
subgraph Namespace: Backend
API[api:8080]
Worker[worker:9000]
end
subgraph Namespace: Database
PG[postgres:5432]
Redis[redis:6379]
end
subgraph External
Internet[Internet]
end
Web -->|allow| API
API -->|allow tcp:5432| PG
API -->|allow tcp:6379| Redis
Worker -->|deny all| API
Web -->|deny| PG
Internet -->|allow tcp:443| Web
style PG fill:#f90,color:#fff
style Web fill:#090,color:#fff
style API fill:#09f,color:#fff
Default Deny All
# default-deny-all.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # Selects all pods in namespace
policyTypes:
- Ingress
- Egress
kubectl apply -f default-deny-all.yaml
# After applying, no pod in 'production' can send or receive traffic
Allow Specific Ingress
# allow-api-access.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-access
namespace: production
spec:
podSelector:
matchLabels:
app: api-server
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
tier: frontend
podSelector:
matchLabels:
app: web-server
ports:
- protocol: TCP
port: 8080
Allow Database Access
# allow-postgres-access.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-postgres
namespace: production
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: api-server
ports:
- protocol: TCP
port: 5432
- from:
- namespaceSelector:
matchLabels:
name: monitoring
- podSelector:
matchLabels:
app: prometheus
ports:
- protocol: TCP
port: 9187 # PostgreSQL exporter
Allow Egress to Specific External
# allow-egress-external.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-external
namespace: production
spec:
podSelector:
matchLabels:
app: api-server
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
ports:
- protocol: TCP
port: 443
- to:
- namespaceSelector:
matchLabels:
name: database
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
Network Policy Simulator
class Pod:
def __init__(self, name: str, namespace: str, labels: dict):
self.name = name
self.namespace = namespace
self.labels = labels
self.namespace_labels = {}
class NetworkPolicy:
def __init__(self, name: str, namespace: str,
pod_selector: dict,
ingress: list = None,
egress: list = None):
self.name = name
self.namespace = namespace
self.pod_selector = pod_selector
self.ingress = ingress or []
self.egress = egress or []
class PolicyEngine:
def __init__(self):
self.policies = []
self.namespace_labels = {}
def add_policy(self, policy: NetworkPolicy):
self.policies.append(policy)
def set_namespace_labels(self, ns: str, labels: dict):
self.namespace_labels[ns] = labels
def match_pod(self, pod: Pod, selector: dict) -> bool:
if not selector:
return True
return all(pod.labels.get(k) == v for k, v in selector.items())
def match_namespace(self, pod: Pod, ns_selector: dict) -> bool:
if not ns_selector:
return False
ns_labels = self.namespace_labels.get(pod.namespace, {})
return all(ns_labels.get(k) == v for k, v in ns_selector.items())
def check_ingress(self, source: Pod, target: Pod) -> bool:
relevant = [
p for p in self.policies
if p.namespace == target.namespace
and self.match_pod(target, p.pod_selector)
]
if not relevant:
return True # No policies = allow all
for policy in relevant:
for rule in policy.ingress:
for src_selector in rule.get("from", []):
namespace_match = True
pod_match = True
if "namespaceSelector" in src_selector:
namespace_match = self.match_namespace(
source, src_selector["namespaceSelector"]
)
if "podSelector" in src_selector:
pod_match = self.match_pod(
source, src_selector["podSelector"]
)
if namespace_match and pod_match:
return True
return False
engine = PolicyEngine()
engine.set_namespace_labels("frontend", {"tier": "frontend"})
engine.set_namespace_labels("backend", {"tier": "backend"})
engine.set_namespace_labels("database", {"tier": "database"})
engine.add_policy(NetworkPolicy(
"default-deny", "database", {},
ingress=[], egress=[]
))
engine.add_policy(NetworkPolicy(
"allow-api", "database",
{"app": "postgres"},
ingress=[
{"from": [{"podSelector": {"app": "api-server"},
"namespaceSelector": {"tier": "backend"}}],
"ports": [{"protocol": "TCP", "port": 5432}]}
]
))
web = Pod("web", "frontend", {"app": "web-server"})
api = Pod("api", "backend", {"app": "api-server"})
pg = Pod("pg", "database", {"app": "postgres"})
print(f"web -> postgres: {engine.check_ingress(web, pg)}")
print(f"api -> postgres: {engine.check_ingress(api, pg)}")
Expected output:
web -> postgres: False
api -> postgres: True
Deny by Namespace
# deny-cross-namespace.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-cross-namespace
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: production
ports:
- protocol: TCP
port: 8080
Label-Based Namespace Selection
# Label namespaces for policy matching
kubectl label namespace production tier=production
kubectl label namespace staging tier=staging
kubectl label namespace monitoring name=monitoring
# Verify labels
kubectl get ns --show-labels
Network Policy with CIDR
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ci-cd
spec:
podSelector:
matchLabels:
app: api-server
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 203.0.113.0/24 # CI/CD runner IPs
ports:
- protocol: TCP
port: 8080
Common Mistakes
1. Assuming Network Policies Work Without a CNI Plugin
Network policies require a CNI plugin that supports them (Calico, Cilium, Weave, Antrea). Flannel doesn't support network policies. If no policy-enabled CNI is installed, all traffic passes regardless of policies.
2. Forgetting Default Deny
An allow-only policy without a default deny is ineffective. Traffic from non-matching pods is still allowed. Always start with a default-deny policy, then add specific allow rules.
3. Using namespaceSelector Without Namespace Labels
namespaceSelector matches labels on the namespace, not the namespace name. If namespaces don't have relevant labels, the selector matches nothing. Label your namespaces: kubectl label ns <name> tier=<value>.
4. Not Testing Policy Impact
A restrictive policy can silently block traffic, causing hours of debugging. Test policies in a non-production environment first. Use kubectl run tmp --image=alpine -it -- sh to manually test connectivity.
5. Overly Broad Egress Rules
Allowing egress to 0.0.0.0/0 defeats the purpose of egress policies. If a pod only needs to reach a specific external API, allow only that IP: ipBlock: { cidr: 203.0.113.50/32 }.
6. Confusing podSelector and namespaceSelector Scope
Within a from block, podSelector selects pods in the SAME namespace as the policy unless combined with namespaceSelector. To select pods in a different namespace, always pair with namespaceSelector.
7. Ignoring Port Restrictions
A policy that allows traffic from a pod selector on all ports (ports: []) is effectively the same as no port restriction. Always specify the exact ports needed.
Practice Questions
1. How do Network Policies differ from Cloud Security groups?
Network Policies are Kubernetes-native, pod-level firewalls. Security groups are VM/instance-level firewalls at the cloud provider level. Network Policies are enforced by the CNI plugin within the cluster; security groups protect the cluster boundary.
2. What happens when multiple Network Policies apply to the same pod?
All policies are evaluated. If ANY policy allows traffic, it's allowed, provided no policy explicitly denies it (Kubernetes policies are additive, not subtractive). Default-deny policies work by having no allow rules.
3. How do you allow traffic from all pods in the cluster?
Use an ingress rule with only namespaceSelector: {} (empty selector = all namespaces) and no podSelector. This allows all pods in all namespaces to reach the target pod.
4. Can Network Policies restrict traffic to a specific pod port?
Yes. The ports field in ingress/egress rules specifies protocol and port. Only traffic to that port is allowed. If ports are omitted, all ports are allowed.
5. Challenge: Design a zero-trust network for a microservice architecture.
A system has 15 Microservices across 4 namespaces (frontend, backend, data, monitoring). Each microservice should only communicate with its dependencies, and only on specific ports. External traffic enters through an ingress gateway. Monitoring tools (Prometheus, Grafana) need access to metrics endpoints on all pods. Design the complete set of Network Policies.
Mini Project: Policy Tester Script
import yaml
import subprocess
import sys
class NetworkPolicyTester:
def __init__(self, namespace: str):
self.namespace = namespace
def test_connection(self, from_pod: str, to_ip: str,
port: int, proto: str = "tcp") -> bool:
cmd = [
"kubectl", "exec", from_pod, "-n", self.namespace, "--",
"sh", "-c",
f"timeout 3 nc -zv {to_ip} {port} 2>&1]
]
result = subprocess.run(cmd, capture_output=True, text=True)
return result.returncode == 0
def test_all_connections(self, matrix: list):
print(f"{'From':>15} {'To':>15} {'Port':>6} {'Result':>8}")
print("-" * 46)
for src, dst, port in matrix:
ok = self.test_connection(src, dst, port)
status = "ALLOW" if ok else "DENY"
print(f"{src:>15} {dst:>15} {port:>6} {status:>8}")
# Simulated test (no kubectl required)
def simulate_policy_test(policies: list, test_cases: list):
print(f"{'From':>15} {'To':>15} {'Port':>6} {'Result':>8}")
print("-" * 46)
for src_namespace, src_app, dst_namespace, dst_app, port in test_cases:
allowed = True
for policy in policies:
ns_match = policy.get("namespace") == dst_namespace
app_match = (
not policy.get("app") or policy["app"] == dst_app
)
if ns_match and app_match:
src_allowed = any(
r.get("src_ns") == src_namespace
and (not r.get("src_app") or r["src_app"] == src_app)
for r in policy.get("allowed", [])
)
if not src_allowed:
allowed = False
status = "ALLOW" if allowed else "DENY"
print(f"{src_app:>15} {dst_app:>15} {port:>6} {status:>8}")
policies = [
{"namespace": "database", "app": "postgres",
"allowed": [{"src_ns": "backend", "src_app": "api"}]},
{"namespace": "database", "app": "redis",
"allowed": [{"src_ns": "backend", "src_app": "api"}]},
]
tests = [
("frontend", "web", "backend", "api", 8080),
("frontend", "web", "database", "postgres", 5432),
("backend", "api", "database", "postgres", 5432),
]
simulate_policy_test(policies, tests)
Expected output:
From To Port Result
----------------------------------------------
web api 8080 ALLOW
web postgres 5432 DENY
api postgres 5432 ALLOW
FAQ
What's Next
Congratulations on completing this Network Policies guide! Here's where to go from here:
- Practice daily — Apply a default-deny policy to a test namespace
- Build a project — Design and implement a full micro-segmentation strategy
- Explore related topics — Calico network policies, Cilium NetworkPolicy, egress gateways, service mesh security
- Join the community — Share your network policy configurations and get feedback
Remember: every expert was once a beginner. Keep isolating!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro