Skip to content

Kubernetes Network Policies Guide — Traffic Isolation

DodaTech Updated 2026-06-24 9 min read

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

Does Kubernetes have a deny-all default Network Policy?

No. By default, all traffic is allowed between pods. You must explicitly create a default-deny Network Policy to block traffic. This is intentional — Kubernetes defaults to permissive to avoid breaking existing workloads during policy migration.

How do Network Policies work with Services?

Network Policies control traffic at the pod level, not the Service level. Traffic sent to a ClusterIP is redirected to a pod, and the policy is evaluated against the destination pod's labels and namespace, not the Service's.

Can I use Network Policies to block traffic to specific external domains?

No. Network Policies only support IP-based (CIDR) egress rules, not DNS names. To block traffic to specific domains, use a proxy or egress gateway that filters by domain, or use Cilium's DNS-based network policies (a Cilium-specific feature).

What's Next

Kubernetes Security Contexts Guide
Kubernetes Service Accounts Guide
Kubernetes Namespaces & RBAC

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