Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

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

Sponsored

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 typesIngress, Egress, or both (default: Ingress only)
  • 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

  1. Enable a CNI plug-in that supports NetworkPolicies — not all do. Calico, Cilium, Weave Net, and Canal support them. Flannel alone does not.

  2. Design your isolation model — choose between: - Default deny all ingress (most secure) - Default deny all egress (prevents data exfiltration) - Allow only specific sources/destinations

  3. Label your pods consistently — NetworkPolicies rely on labels. Without them, you can't target pods.

  4. Write the policy YAML — define podSelector, policyTypes, ingress/egress rules.

  5. Apply the policykubectl apply -f policy.yaml. It takes effect immediately.

  6. 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 podSelector fields in a single from block when you meant to OR them (use separate from blocks 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 podSelector never matches, and the policy has no effect.

Variations

  1. CiliumNetworkPolicy — a custom resource from Cilium that offers more features like HTTP-aware rules, encryption, and cluster mesh policies.
  2. CalicoNetworkPolicy — Calico's extended policy CRD that supports global network policies and namespace-level selectors.
  3. 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).

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.