HOWTOIntermediate

Kubernetes Network Policies: Microsegmentation Guide

Implement network microsegmentation in Kubernetes with Network Policies. Covers default deny, allow rules, namespace isolation, egress policies, and debugging.

Dylan H.

Platform Engineering

February 3, 2026
7 min read
Kubernetes Network Policies: Microsegmentation Guide

Prerequisites

  • Kubernetes cluster with network policy support (Calico, Cilium, etc.)
  • kubectl access with admin privileges
  • Basic understanding of Kubernetes networking
  • Familiarity with YAML manifests

Overview

Kubernetes Network Policies enable microsegmentation by controlling traffic flow between pods. By default, pods can communicate with any other pod - Network Policies allow you to restrict this and implement a Zero Trust network model within your cluster.

Who Should Use This Guide:

  • Platform engineers securing Kubernetes workloads
  • Security teams implementing Zero Trust in Kubernetes
  • DevOps engineers hardening production clusters
  • Architects designing secure multi-tenant environments

Network Policy Capabilities:

FeatureDescription
Pod SelectorSelect pods by labels to apply policies
Namespace SelectorSelect pods in specific namespaces
Ingress RulesControl incoming traffic to pods
Egress RulesControl outgoing traffic from pods
IP BlocksAllow/deny traffic from CIDR ranges
PortsSpecify allowed ports and protocols

Network Policy CNI Support:

CNI PluginNetwork Policy SupportAdvanced Features
CalicoFullGlobalNetworkPolicy, DNS policies
CiliumFullL7 policies, DNS-aware, Hubble
Weave NetFull-
FlannelNone (requires Calico)-
Azure CNIFull (with Azure NPM)-
AWS VPC CNIPartial (with Calico)-

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│              Kubernetes Network Policy Architecture                 │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Namespace: production                Namespace: database           │
│  ┌─────────────────────────────┐     ┌─────────────────────────────┐│
│  │    ┌─────────┐              │     │         ┌─────────┐         ││
│  │    │ Web App │              │     │         │  MySQL  │         ││
│  │    │ Pod     │──────────────┼─────┼────────▶│  Pod    │         ││
│  │    └─────────┘     Allow    │     │  Allow  └─────────┘         ││
│  │         │          3306     │     │  from                        ││
│  │         │                   │     │  production                  ││
│  │         │                   │     │                              ││
│  │         ▼                   │     └─────────────────────────────┘│
│  │    ┌─────────┐              │                                    │
│  │    │ API     │              │     Namespace: monitoring          │
│  │    │ Pod     │              │     ┌─────────────────────────────┐│
│  │    └─────────┘              │     │         ┌─────────┐         ││
│  │         │                   │     │         │Prometheus│         ││
│  │         │ Allow 9090        │     │         │  Pod    │◀────────┼│
│  │         └───────────────────┼─────┼─────────└─────────┘  Scrape ││
│  │                             │     │          from all           ││
│  └─────────────────────────────┘     └─────────────────────────────┘│
│                                                                     │
│  Default: DENY all traffic (with default deny policies)            │
│  Explicit: ALLOW only defined paths                                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Prerequisites Check

Verify Network Policy Support

# Check if CNI supports network policies
kubectl get pods -n kube-system -l k8s-app=calico-node
kubectl get pods -n kube-system -l k8s-app=cilium
 
# For AKS, check Azure NPM
kubectl get pods -n kube-system -l k8s-app=azure-npm
 
# Create test policy to verify support
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Ingress
EOF
 
# Verify policy was created
kubectl get networkpolicy test-network-policy
 
# Cleanup test
kubectl delete networkpolicy test-network-policy

Install Calico (If Needed)

# For clusters without network policy support
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml
 
# Verify installation
kubectl get pods -n calico-system
kubectl wait --for=condition=ready pod -l k8s-app=calico-node -n calico-system --timeout=300s

Step 1: Implement Default Deny Policies

Start with a default deny posture, then explicitly allow required traffic.

Default Deny All Ingress (Per Namespace)

# default-deny-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: production
spec:
  podSelector: {}  # Applies to all pods in namespace
  policyTypes:
  - Ingress
  # No ingress rules = deny all ingress
kubectl apply -f default-deny-ingress.yaml

Default Deny All Egress (Per Namespace)

# default-deny-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Egress
  # No egress rules = deny all egress
# default-deny-all.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

Apply to All Namespaces Script

#!/bin/bash
# apply-default-deny.sh
 
# Namespaces to protect (exclude system namespaces)
NAMESPACES=$(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}' | tr ' ' '\n' | grep -v -E '^(kube-system|kube-public|kube-node-lease|calico-system)$')
 
for ns in $NAMESPACES; do
  echo "Applying default deny to namespace: $ns"
  kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: $ns
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
EOF
done

Step 2: Allow DNS Traffic

After denying all egress, pods can't resolve DNS. Allow DNS queries.

# allow-dns.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: production
spec:
  podSelector: {}  # All pods in namespace
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53

Alternative: Allow DNS by IP (CoreDNS ClusterIP)

# Get CoreDNS ClusterIP
kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}'
# allow-dns-by-ip.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 10.96.0.10/32  # Replace with your CoreDNS IP
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53

Step 3: Create Application-Specific Policies

Web Application Ingress Policy

Allow traffic from ingress controller to web pods.

# web-app-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-ingress-to-web
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: web-frontend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: ingress-nginx
      podSelector:
        matchLabels:
          app.kubernetes.io/name: ingress-nginx
    ports:
    - protocol: TCP
      port: 8080

Allow Web to API Communication

# web-to-api.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-web-to-api
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api-backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: web-frontend
    ports:
    - protocol: TCP
      port: 3000

Allow API to Database Communication

# api-to-database.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-api-to-database
  namespace: database
spec:
  podSelector:
    matchLabels:
      app: mysql
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: production
      podSelector:
        matchLabels:
          app: api-backend
    ports:
    - protocol: TCP
      port: 3306

Egress policy for API pods:

# api-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-egress
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api-backend
  policyTypes:
  - Egress
  egress:
  # Allow DNS
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53
  # Allow database access
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: database
      podSelector:
        matchLabels:
          app: mysql
    ports:
    - protocol: TCP
      port: 3306
  # Allow external API calls (optional)
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 10.0.0.0/8      # Block internal
        - 172.16.0.0/12   # Block internal
        - 192.168.0.0/16  # Block internal
    ports:
    - protocol: TCP
      port: 443

Step 4: Namespace Isolation

Isolate namespaces from each other by default.

Deny Cross-Namespace Traffic

# namespace-isolation.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-from-other-namespaces
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector: {}  # Only same namespace

Allow Specific Cross-Namespace Communication

# allow-monitoring-scrape.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-prometheus-scrape
  namespace: production
spec:
  podSelector:
    matchLabels:
      prometheus.io/scrape: "true"
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: monitoring
      podSelector:
        matchLabels:
          app: prometheus
    ports:
    - protocol: TCP
      port: 9090

Step 5: External Traffic Control

Allow External Ingress via LoadBalancer

# allow-external-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-external-web
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: web-frontend
      expose: external
  policyTypes:
  - Ingress
  ingress:
  - from:
    - ipBlock:
        cidr: 0.0.0.0/0
    ports:
    - protocol: TCP
      port: 443

Restrict Egress to Specific External IPs

# restrict-external-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-specific-external
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: payment-service
  policyTypes:
  - Egress
  egress:
  # DNS
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
    ports:
    - protocol: UDP
      port: 53
  # Payment processor IPs only
  - to:
    - ipBlock:
        cidr: 203.0.113.0/24  # Payment provider range
    ports:
    - protocol: TCP
      port: 443

Step 6: Complete Application Example

Full network policy set for a three-tier application.

# complete-app-policies.yaml
---
# Default deny for production namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
---
# Allow DNS for all pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53
---
# Frontend: Allow from ingress, allow to API
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: frontend
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: ingress-nginx
    ports:
    - protocol: TCP
      port: 80
  egress:
  - to:
    - podSelector:
        matchLabels:
          tier: backend
    ports:
    - protocol: TCP
      port: 8080
---
# Backend: Allow from frontend, allow to database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          tier: frontend
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          tier: database
    ports:
    - protocol: TCP
      port: 5432
---
# Database: Allow from backend only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: database-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: database
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          tier: backend
    ports:
    - protocol: TCP
      port: 5432

Step 7: Testing and Validation

Deploy Test Pods

# test-pods.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-client
  namespace: production
  labels:
    role: test
spec:
  containers:
  - name: curl
    image: curlimages/curl:latest
    command: ["sleep", "infinity"]
---
apiVersion: v1
kind: Pod
metadata:
  name: test-server
  namespace: production
  labels:
    role: test
    app: web-frontend
spec:
  containers:
  - name: nginx
    image: nginx:alpine
    ports:
    - containerPort: 80

Test Connectivity

# Apply test pods
kubectl apply -f test-pods.yaml
 
# Wait for pods to be ready
kubectl wait --for=condition=ready pod/test-client -n production --timeout=60s
kubectl wait --for=condition=ready pod/test-server -n production --timeout=60s
 
# Test allowed traffic (should work if policy allows)
kubectl exec -n production test-client -- curl -s --connect-timeout 5 test-server
 
# Test blocked traffic (should timeout)
kubectl exec -n production test-client -- curl -s --connect-timeout 5 http://10.96.0.1
 
# Test DNS resolution
kubectl exec -n production test-client -- nslookup kubernetes.default
 
# Test external connectivity (if allowed)
kubectl exec -n production test-client -- curl -s --connect-timeout 5 https://google.com

Network Policy Testing Script

#!/bin/bash
# test-network-policies.sh
 
NAMESPACE="production"
TEST_POD="test-client"
 
echo "=== Network Policy Testing ==="
 
# Test 1: DNS Resolution
echo -n "DNS Resolution: "
if kubectl exec -n $NAMESPACE $TEST_POD -- nslookup kubernetes.default > /dev/null 2>&1; then
    echo "PASS"
else
    echo "FAIL"
fi
 
# Test 2: Same namespace communication
echo -n "Same Namespace (test-server): "
if kubectl exec -n $NAMESPACE $TEST_POD -- curl -s --connect-timeout 3 test-server > /dev/null 2>&1; then
    echo "PASS (allowed)"
else
    echo "BLOCKED"
fi
 
# Test 3: Cross namespace (kube-system)
echo -n "Cross Namespace (kube-system): "
if kubectl exec -n $NAMESPACE $TEST_POD -- curl -s --connect-timeout 3 http://metrics-server.kube-system > /dev/null 2>&1; then
    echo "ALLOWED (check policy)"
else
    echo "BLOCKED (expected)"
fi
 
# Test 4: External internet
echo -n "External Internet: "
if kubectl exec -n $NAMESPACE $TEST_POD -- curl -s --connect-timeout 3 https://google.com > /dev/null 2>&1; then
    echo "ALLOWED"
else
    echo "BLOCKED"
fi
 
echo "=== Testing Complete ==="

Step 8: Debugging Network Policies

View Applied Policies

# List all network policies in namespace
kubectl get networkpolicies -n production
 
# Describe specific policy
kubectl describe networkpolicy allow-web-to-api -n production
 
# Get policy YAML
kubectl get networkpolicy allow-web-to-api -n production -o yaml

Check Pod Labels

# Verify pod labels match policy selectors
kubectl get pods -n production --show-labels
 
# Check if pod matches a selector
kubectl get pods -n production -l app=api-backend

Calico-Specific Debugging

# View Calico policies
kubectl get networkpolicies.crd.projectcalico.org -A
 
# Check Calico node status
kubectl exec -n calico-system -it $(kubectl get pod -n calico-system -l k8s-app=calico-node -o name | head -1) -- calico-node -bird-live
 
# View Felix logs
kubectl logs -n calico-system -l k8s-app=calico-node -c calico-node | grep -i policy

Cilium-Specific Debugging

# Check Cilium status
kubectl exec -n kube-system -it $(kubectl get pod -n kube-system -l k8s-app=cilium -o name | head -1) -- cilium status
 
# Monitor policy drops
kubectl exec -n kube-system -it $(kubectl get pod -n kube-system -l k8s-app=cilium -o name | head -1) -- cilium monitor --type drop
 
# View endpoint policies
kubectl exec -n kube-system -it $(kubectl get pod -n kube-system -l k8s-app=cilium -o name | head -1) -- cilium endpoint list

Packet Capture

# Capture traffic on a pod (requires debug privileges)
kubectl debug -n production pod/api-pod -it --image=nicolaka/netshoot -- tcpdump -i any -n
 
# Capture specific traffic
kubectl debug -n production pod/api-pod -it --image=nicolaka/netshoot -- tcpdump -i any port 3306 -n

Common Patterns

Allow Internal Load Balancer

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-internal-lb
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: internal-api
  policyTypes:
  - Ingress
  ingress:
  - from:
    - ipBlock:
        cidr: 10.0.0.0/8  # Internal network range
    ports:
    - protocol: TCP
      port: 8080

Allow Health Checks

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-health-checks
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress:
  # Allow kubelet health checks
  - from:
    - ipBlock:
        cidr: 0.0.0.0/0  # Node network
    ports:
    - protocol: TCP
      port: 8080  # Health check port
      endPort: 8081

Allow Pod-to-Pod Same Label

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-same-app
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: microservice-a
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: microservice-a

Troubleshooting

Common Issues:

SymptomPossible CauseSolution
All traffic blockedDefault deny without allow rulesAdd required allow policies
DNS not workingForgot DNS egress policyAdd allow-dns policy
Cross-namespace blockedNamespace labels missingAdd kubernetes.io/metadata.name label
Ingress not workingWrong ingress namespace selectorVerify ingress controller namespace
Pods can't reach externalEgress to 0.0.0.0/0 missingAdd external egress rule

Verification Commands:

# Check pod can reach service
kubectl exec -n production test-pod -- nc -zv service-name 80
 
# Check DNS resolution
kubectl exec -n production test-pod -- nslookup kubernetes.default
 
# Check external connectivity
kubectl exec -n production test-pod -- curl -I https://google.com
 
# List all policies affecting a pod
kubectl get networkpolicy -n production -o wide

Verification Checklist

Policy Implementation:

  • Default deny policies applied to sensitive namespaces
  • DNS egress allowed for all pods
  • Application-specific ingress rules created
  • Application-specific egress rules created
  • Cross-namespace policies for allowed communication

Testing:

  • Allowed traffic flows correctly
  • Blocked traffic is denied
  • DNS resolution works
  • External access controlled as expected

Operations:

  • Policy documentation updated
  • GitOps workflow for policy changes
  • Monitoring for policy violations
  • Runbook for debugging connectivity issues

Next Steps

After implementing Network Policies:

  1. Implement Cilium L7 Policies - HTTP/gRPC-aware filtering
  2. Enable Network Policy Logging - Audit denied connections
  3. Integrate with Service Mesh - Combine with Istio/Linkerd
  4. Automate Policy Generation - Use tools like Cilium Editor

References


Last Updated: February 2026