---
# === IDENTITY ===
id: software/devops/k8s-network-policies/2026
canonical_question: "How do Kubernetes Network Policies work and how do I implement them?"
aliases:
  - "How do I restrict pod-to-pod traffic in Kubernetes?"
  - "What is a Kubernetes NetworkPolicy and how do I write one?"
  - "How do I create a default deny network policy in Kubernetes?"
  - "How do I control ingress and egress traffic between Kubernetes pods?"
entity_type: software_reference
domain: software > devops > Kubernetes Network Policies
region: global
jurisdiction: global
temporal_scope: 2019-2026

# === VERIFICATION ===
last_verified: 2026-02-27
confidence: 0.94
version: 1.0
first_published: 2026-02-27

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "v1 GA in Kubernetes 1.7 (2017); no breaking changes since"
  next_review: 2026-08-26
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "NetworkPolicy resources have NO effect without a CNI plugin that implements them (Calico, Cilium, Weave, Antrea, Kube-router)"
  - "Flannel does NOT support NetworkPolicy enforcement natively; requires a secondary CNI"
  - "Policies are namespace-scoped; you must create policies in each namespace separately"
  - "Policies are additive (union); you cannot write a policy that denies a specific flow that another policy allows"
  - "Traffic to/from the node where a pod runs is ALWAYS allowed regardless of NetworkPolicy rules"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "You need Layer 7 (HTTP/gRPC) policy enforcement"
    use_instead: "Cilium Network Policies or a service mesh (Istio/Linkerd)"
  - condition: "You need cluster-wide policies across all namespaces"
    use_instead: "Calico GlobalNetworkPolicy or Cilium ClusterNetworkPolicy"
  - condition: "You need to control external HTTP routing to services"
    use_instead: "Kubernetes Ingress / Gateway API resources"

# === AGENT HINTS ===
inputs_needed:
  - key: "cni_plugin"
    question: "Which CNI plugin is installed in the cluster?"
    type: choice
    options: ["Calico", "Cilium", "Weave", "Antrea", "Flannel", "Unknown"]
  - key: "policy_goal"
    question: "What traffic direction do you want to control?"
    type: choice
    options: ["ingress_only", "egress_only", "both", "default_deny_all"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/devops/k8s-network-policies/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-27)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/security/kubernetes-security/2026"
      label: "Kubernetes Security Checklist"
  solves: []
  alternative_to: []
  often_confused_with: []

# === SOURCES (7 authoritative sources) ===
sources:
  - id: src1
    title: "Network Policies"
    author: Kubernetes Project
    url: https://kubernetes.io/docs/concepts/services-networking/network-policies/
    type: official_docs
    published: 2024-12-15
    reliability: authoritative
  - id: src2
    title: "Kubernetes Network Policy Recipes"
    author: Ahmet Alp Balkan (Google)
    url: https://github.com/ahmetb/kubernetes-network-policy-recipes
    type: community_resource
    published: 2024-06-10
    reliability: high
  - id: src3
    title: "Get started with Kubernetes network policy"
    author: Tigera (Calico)
    url: https://docs.tigera.io/calico/latest/network-policy/get-started/kubernetes-policy/kubernetes-network-policy
    type: official_docs
    published: 2025-01-20
    reliability: high
  - id: src4
    title: "Kubernetes Network Policy - Guide with Examples"
    author: Spacelift
    url: https://spacelift.io/blog/kubernetes-network-policy
    type: technical_blog
    published: 2025-03-15
    reliability: moderate_high
  - id: src5
    title: "Cilium vs Calico vs Flannel CNI Performance Comparison"
    author: sanj.dev
    url: https://sanj.dev/post/cilium-calico-flannel-cni-performance-comparison
    type: technical_blog
    published: 2025-08-20
    reliability: moderate_high
  - id: src6
    title: "Kubernetes Network Policy Best Practices"
    author: Snyk
    url: https://snyk.io/blog/kubernetes-network-policy-best-practices/
    type: technical_blog
    published: 2024-11-05
    reliability: moderate_high
  - id: src7
    title: "Declare Network Policy"
    author: Kubernetes Project
    url: https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy/
    type: official_docs
    published: 2024-12-15
    reliability: authoritative
---

# Kubernetes Network Policies

## TL;DR

- **Bottom line**: Kubernetes NetworkPolicy is a namespace-scoped resource (API `networking.k8s.io/v1`) that controls pod-to-pod and pod-to-external traffic at L3/L4 using label selectors, namespace selectors, and CIDR blocks -- but only works if your CNI plugin supports it. [src1]
- **Key tool/command**: `kubectl apply -f network-policy.yaml` and `kubectl get networkpolicy -n <namespace>`
- **Watch out for**: Creating a NetworkPolicy in a cluster with a CNI that does not enforce it (e.g., Flannel alone) -- the resource is accepted silently but has zero effect. [src1]
- **Works with**: Kubernetes 1.7+ (NetworkPolicy v1 GA); requires Calico, Cilium, Weave Net, Antrea, or Kube-router as CNI. [src1]

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- NetworkPolicy has NO effect without a CNI plugin that implements enforcement (Calico, Cilium, Weave, Antrea, Kube-router). Flannel alone does not enforce policies. [src1]
- Policies are **additive** (union of all matching rules). You cannot write a policy that explicitly denies a specific flow allowed by another policy. [src1]
- Policies are **namespace-scoped**. A default-deny policy in namespace `foo` does not affect namespace `bar`. You must create policies per namespace. [src1]
- Traffic to/from the node where a pod runs is **always allowed**, regardless of NetworkPolicy rules. [src1]
- `ipBlock` rules apply to external (non-pod) traffic only. Pod IPs are ephemeral; do not use `ipBlock` to target pods. [src1]
- Egress policies that block port 53 (UDP/TCP) will break DNS resolution for affected pods. Always allow DNS egress when restricting outbound traffic. [src3]

## Quick Reference

| Pattern | podSelector | policyTypes | Ingress Rules | Egress Rules | Effect |
|---|---|---|---|---|---|
| Default deny all ingress | `{}` (all pods) | `[Ingress]` | `[]` (empty) | -- | Block all inbound traffic namespace-wide |
| Default deny all egress | `{}` (all pods) | `[Egress]` | -- | `[]` (empty) | Block all outbound traffic namespace-wide |
| Default deny both | `{}` (all pods) | `[Ingress, Egress]` | `[]` (empty) | `[]` (empty) | Block all traffic in both directions |
| Allow all ingress | `{}` (all pods) | `[Ingress]` | `[{from: [podSelector: {}]}]` | -- | Override deny: allow all inbound |
| Allow specific pod ingress | `matchLabels: {app: db}` | `[Ingress]` | `from: [{podSelector: {app: api}}]` | -- | Only pods labeled `app=api` can reach `app=db` |
| Allow cross-namespace | `matchLabels: {app: db}` | `[Ingress]` | `from: [{namespaceSelector: {team: frontend}}]` | -- | Pods in namespaces labeled `team=frontend` can reach db |
| Allow specific port | `matchLabels: {app: db}` | `[Ingress]` | `from: [...] ports: [{port: 5432, protocol: TCP}]` | -- | Only TCP/5432 allowed |
| Allow egress to CIDR | `matchLabels: {app: api}` | `[Egress]` | -- | `to: [{ipBlock: {cidr: 10.0.0.0/8}}]` | Outbound only to 10.0.0.0/8 |
| Allow DNS egress | `{}` (all pods) | `[Egress]` | -- | `to: [] ports: [{port: 53, protocol: UDP}, {port: 53, protocol: TCP}]` | Allow DNS lookups for all pods |
| Deny external egress | `{}` (all pods) | `[Egress]` | -- | `to: [{podSelector: {}}, {namespaceSelector: {}}]` | Only cluster-internal egress allowed |

[src1] [src2]

## Decision Tree

```
START: What traffic control do you need?
|
+-- Want to block ALL traffic by default?
|   +-- YES --> Apply "default deny all" policy to namespace (see Step 1)
|   |           Then add specific allow policies per service
|   +-- NO  |
|           v
+-- Want to restrict INBOUND traffic to specific pods?
|   +-- YES --> Use podSelector + ingress rules (see Code Example 1)
|   |   +-- From same namespace only? --> Use podSelector in "from"
|   |   +-- From another namespace?   --> Use namespaceSelector in "from"
|   |   +-- From external IPs?        --> Use ipBlock in "from"
|   +-- NO  |
|           v
+-- Want to restrict OUTBOUND traffic from specific pods?
|   +-- YES --> Use podSelector + egress rules (see Code Example 3)
|   |   +-- IMPORTANT: Always allow DNS (port 53 UDP/TCP) in egress
|   +-- NO  |
|           v
+-- Want to combine ingress + egress?
    +-- YES --> Set policyTypes: [Ingress, Egress] with both rule sets
    +-- NO  --> No NetworkPolicy needed (default: all traffic allowed)
```

## Step-by-Step Guide

### 1. Verify your CNI supports NetworkPolicy

Before writing any policy, confirm your cluster's CNI plugin enforces NetworkPolicy. Applying policies to a cluster without enforcement gives a false sense of security. [src1]

```bash
# Check which CNI is installed
kubectl get pods -n kube-system -l k8s-app -o wide | grep -E 'calico|cilium|weave|antrea'

# Alternative: check CNI config directory
kubectl get nodes -o jsonpath='{.items[0].metadata.annotations}' | grep -o 'cni[^"]*'
```

**Verify**: If you see `calico-node`, `cilium`, `weave-net`, or `antrea-agent` pods running in `kube-system`, NetworkPolicy enforcement is active.

### 2. Apply a default-deny policy to your namespace

Start with a deny-all baseline. This ensures no pod can communicate unless explicitly permitted. [src2] [src6]

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

```bash
kubectl apply -f default-deny-all.yaml
```

**Verify**: `kubectl get networkpolicy -n production` --> should show `default-deny-all` with `POD-SELECTOR: <none>` (matches all pods).

### 3. Allow DNS egress for all pods

After applying default-deny egress, pods cannot resolve DNS. This breaks virtually everything. Add a DNS allow policy immediately. [src3]

```yaml
# allow-dns.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to: []
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
```

```bash
kubectl apply -f allow-dns.yaml
```

**Verify**: `kubectl exec -n production <pod> -- nslookup kubernetes.default` --> should resolve successfully.

### 4. Create allow policies for your services

Now explicitly open the traffic paths your application requires. [src1] [src4]

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

```bash
kubectl apply -f allow-frontend-to-api.yaml
```

**Verify**: `kubectl exec -n production <frontend-pod> -- curl -s -o /dev/null -w "%{http_code}" http://api:8080/health` --> should return `200`.

### 5. Test blocked traffic

Confirm that traffic NOT explicitly allowed is blocked. [src4]

```bash
# From a pod that should NOT have access:
kubectl exec -n production <unauthorized-pod> -- curl --connect-timeout 3 http://api:8080/health
# Expected: connection timeout (exit code 28)
```

**Verify**: The command should time out after 3 seconds, confirming the policy blocks unauthorized traffic.

### 6. Label your namespaces for cross-namespace policies

Cross-namespace policies require labeled namespaces. Kubernetes 1.21+ automatically labels namespaces with `kubernetes.io/metadata.name`. For older clusters, add labels manually. [src1]

```bash
# Label namespaces for policy selectors
kubectl label namespace monitoring team=monitoring
kubectl label namespace logging team=logging

# Verify labels
kubectl get namespace --show-labels | grep -E 'monitoring|logging'
```

**Verify**: `kubectl get namespace monitoring -o jsonpath='{.metadata.labels}'` --> should include `team: monitoring`.

## Code Examples

### YAML: Default deny all traffic in a namespace

```yaml
# Input:  A namespace where you want zero-trust networking
# Output: All pod traffic blocked; only explicitly allowed flows work

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production        # Change to your namespace
spec:
  podSelector: {}              # {} = select ALL pods in this namespace
  policyTypes:
    - Ingress                  # Block all inbound
    - Egress                   # Block all outbound
  # No ingress/egress rules = deny everything
```

### YAML: Allow ingress from specific pods on specific ports

```yaml
# Input:  Backend pods that should only accept traffic from frontend on port 8080
# Output: Only frontend pods can reach backend on TCP/8080

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-allow-frontend
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: backend
      tier: api
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend    # Only pods with this label
      ports:
        - protocol: TCP
          port: 8080           # Only this port
```

### YAML: Allow cross-namespace monitoring access

```yaml
# Input:  Prometheus in 'monitoring' namespace needs to scrape all pods
# Output: Pods in production allow ingress from monitoring namespace on metrics port

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-monitoring-scrape
  namespace: production
spec:
  podSelector: {}              # All pods in production
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: monitoring  # Auto-label (K8s 1.21+)
      ports:
        - protocol: TCP
          port: 9090           # Prometheus metrics port
```

### YAML: Restrict egress to specific CIDRs and DNS

> Full script: [yaml-restrict-egress-to-specific-cidrs-and-dns.yml](scripts/yaml-restrict-egress-to-specific-cidrs-and-dns.yml) (31 lines)

```yaml
# Input:  API pods that should only reach a specific external service + DNS
# Output: Egress limited to 10.0.0.0/8 (cluster), external API, and DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
# ... (see full script)
```

### YAML: AND vs OR selector logic (critical distinction)

> Full script: [yaml-and-vs-or-selector-logic-critical-distinction.yml](scripts/yaml-and-vs-or-selector-logic-critical-distinction.yml) (47 lines)

```yaml
# Input:  Understanding the difference between AND and OR selectors
# Output: Two different policies demonstrating the YAML indentation trap
# --- OR logic: two SEPARATE list items ---
# Allows traffic from: (any pod in namespace labeled team=frontend)
#                  OR  (any pod labeled role=client in THIS namespace)
# ... (see full script)
```

## Anti-Patterns

### Wrong: Applying NetworkPolicy without a supporting CNI

```yaml
# BAD -- Policy is accepted but NEVER enforced (Flannel alone)
# Gives a dangerous false sense of security
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
spec:
  podSelector: {}
  policyTypes:
    - Ingress
# Result: All traffic still flows freely. Zero security benefit.
```

### Correct: Verify CNI enforcement before relying on policies

```bash
# GOOD -- Verify CNI first, then apply
# Check for a policy-enforcing CNI
kubectl get ds -n kube-system | grep -E 'calico|cilium|weave|antrea'
# If no result: install one before deploying NetworkPolicies
# Example: install Calico
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml
```

### Wrong: Blocking egress without allowing DNS

```yaml
# BAD -- Blocks ALL egress including DNS
# Every pod loses name resolution; all service discovery breaks
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: restrict-egress
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Egress
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: database
      ports:
        - protocol: TCP
          port: 5432
# Result: api pod can reach database IP but cannot resolve 'database' hostname
```

### Correct: Always include DNS in egress policies

> Full script: [correct-always-include-dns-in-egress-policies.yml](scripts/correct-always-include-dns-in-egress-policies.yml) (25 lines)

```yaml
# GOOD -- Allow DNS + specific service egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: restrict-egress-with-dns
# ... (see full script)
```

### Wrong: Confusing AND vs OR selectors (YAML indentation trap)

```yaml
# BAD -- Intended: allow traffic from frontend pods in the staging namespace
# Actual: allows ALL pods in staging namespace OR ALL frontend pods in ANY namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: overly-permissive
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:         # List item 1 (OR)
            matchLabels:
              env: staging
        - podSelector:               # List item 2 (OR) -- NOT AND!
            matchLabels:
              app: frontend
# Result: Opens db to ALL pods in staging AND all frontend pods everywhere
```

### Correct: Use single list item for AND logic

```yaml
# GOOD -- Single list item = AND logic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: precise-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:         # Same list item = AND
            matchLabels:
              env: staging
          podSelector:               # Both must match
            matchLabels:
              app: frontend
# Result: Only frontend pods in the staging namespace can reach db
```

### Wrong: Using ipBlock to target pods

```yaml
# BAD -- Pod IPs are ephemeral; this breaks on reschedule
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: fragile-policy
spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes:
    - Ingress
  ingress:
    - from:
        - ipBlock:
            cidr: 10.244.1.5/32     # This pod IP will change!
```

### Correct: Use label selectors for pod-to-pod policies

```yaml
# GOOD -- Labels are stable across pod restarts
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: stable-policy
spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: api              # Labels survive reschedules
```

## Common Pitfalls

- **Silent no-op with unsupported CNI**: NetworkPolicy resources are accepted by the API server even if no CNI enforces them. Flannel alone ignores policies entirely. Fix: verify CNI with `kubectl get ds -n kube-system | grep -E 'calico|cilium|weave|antrea'`. [src1]
- **DNS breaks after egress deny**: Default-deny egress blocks UDP/TCP port 53, breaking all DNS resolution. Services become unreachable by name. Fix: always create a companion policy allowing egress on port 53. [src3]
- **AND vs OR YAML indentation**: A single dash (`-`) difference in YAML indentation changes selector logic from AND to OR, potentially opening traffic to unintended sources. Fix: use a YAML linter and test with `kubectl describe networkpolicy`. [src4]
- **Namespace label mismatch**: Cross-namespace policies using `namespaceSelector` fail silently if the target namespace lacks the expected label. Fix: verify with `kubectl get namespace --show-labels` and use `kubernetes.io/metadata.name` (auto-applied in K8s 1.21+). [src1]
- **Forgetting policyTypes field**: Omitting `policyTypes` when you only have egress rules causes Kubernetes to infer `[Ingress]` only, not `[Egress]`. Your egress rules are silently ignored. Fix: always explicitly set `policyTypes`. [src1]
- **Policies don't apply to HostNetwork pods**: Pods with `hostNetwork: true` bypass NetworkPolicy entirely because they use the node's network stack, not the pod network. Fix: use node-level firewall rules (iptables, nftables) for host-networked pods. [src1]
- **No pod-level deny rules**: You cannot deny specific traffic that another policy allows. Policies are purely additive. Fix: restructure policies so the unwanted flow is never explicitly allowed. [src1]
- **Empty selector matches everything**: `podSelector: {}` selects ALL pods; `namespaceSelector: {}` selects ALL namespaces. An accidental empty selector creates an overly permissive policy. Fix: always verify selector specificity with `kubectl describe networkpolicy`. [src6]

## Diagnostic Commands

```bash
# List all network policies in a namespace
kubectl get networkpolicy -n <namespace>

# Describe a specific policy (shows parsed selectors and rules)
kubectl describe networkpolicy <policy-name> -n <namespace>

# Check which policies affect a specific pod
kubectl get networkpolicy -n <namespace> -o json | \
  jq '.items[] | select(.spec.podSelector.matchLabels as $sel |
  all(keys_unsorted[]; . as $k | env[$k] == $sel[$k])) | .metadata.name'

# Test connectivity between pods (quick check)
kubectl exec -n <namespace> <source-pod> -- \
  curl --connect-timeout 3 -s -o /dev/null -w "%{http_code}" http://<target-service>:<port>/

# Test DNS resolution (verify DNS egress works)
kubectl exec -n <namespace> <pod> -- nslookup kubernetes.default.svc.cluster.local

# Debug with Calico: check Felix logs for policy decisions
kubectl logs -n kube-system -l k8s-app=calico-node --tail=50 | grep -i policy

# Debug with Cilium: use Hubble for real-time flow observation
kubectl exec -n kube-system <cilium-pod> -- hubble observe --namespace <namespace> --verdict DROPPED

# Verify CNI plugin is running and healthy
kubectl get pods -n kube-system -l k8s-app --field-selector=status.phase!=Running

# Export all policies in a namespace as YAML (for review)
kubectl get networkpolicy -n <namespace> -o yaml

# Test with a temporary debug pod
kubectl run nettest --image=nicolaka/netshoot --rm -it --restart=Never -n <namespace> -- \
  curl --connect-timeout 3 <target-service>:<port>
```

## Version History & Compatibility

| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| networking.k8s.io/v1 | Stable (GA) since K8s 1.7 | None | Current API; use this version |
| extensions/v1beta1 | Removed in K8s 1.16 | API group changed | Change `apiVersion` to `networking.k8s.io/v1` |
| K8s 1.21+ | Feature | `kubernetes.io/metadata.name` auto-label on namespaces | Simplifies cross-namespace `namespaceSelector` |
| K8s 1.25+ | Feature | `endPort` field GA | Allows port ranges: `port: 32000, endPort: 32100` |
| AdminNetworkPolicy | Alpha (K8s 1.27+) | New cluster-scoped API | Future: admin-enforced policies that override namespace policies |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| You need L3/L4 pod traffic isolation | You need L7 (HTTP path/header) filtering | Cilium L7 Policy, Istio AuthorizationPolicy |
| Implementing zero-trust within a cluster | You need cluster-wide default policies | Calico GlobalNetworkPolicy, Cilium ClusterNetworkPolicy |
| Restricting pod egress to specific CIDRs | You need to block traffic by DNS name | Cilium DNS-aware policies |
| Multi-tenant namespace isolation | You need to rate-limit traffic | Service mesh or CNI-specific rate limiting |
| Compliance requires microsegmentation | You only need external ingress routing | Kubernetes Ingress / Gateway API |
| Your CNI supports NetworkPolicy (Calico, Cilium, Weave, Antrea) | Your cluster uses Flannel without a policy plugin | Install Calico or Cilium alongside Flannel |

## Important Caveats

- **CNI-dependent behavior**: Different CNI plugins may have subtle differences in how they interpret edge cases (e.g., `ipBlock` with pod CIDRs, `except` ranges). Always test policies with your specific CNI. [src1]
- **No audit logging in vanilla Kubernetes**: Standard NetworkPolicy provides no logging of allowed/denied connections. For audit trails, use Cilium Hubble, Calico Felix logging, or GKE network policy logging. [src3]
- **Performance at scale**: Each NetworkPolicy translates to iptables rules (Calico legacy mode) or eBPF maps (Cilium, Calico eBPF mode). Clusters with thousands of policies may see increased latency with iptables-based enforcement. Cilium's eBPF has O(1) lookup time regardless of rule count. [src5]
- **Stateful connection tracking**: NetworkPolicy is stateful -- reply traffic for allowed connections is implicitly permitted. You do not need separate rules for return traffic. [src1]
- **Node traffic exception**: Traffic between a pod and its host node is always allowed. This means node-local processes (kubelet, node exporters) can always reach pods on that node regardless of policies. [src1]

## CNI Plugin Comparison for NetworkPolicy

| Feature | Calico | Cilium | Weave Net | Antrea |
|---|---|---|---|---|
| L3/L4 NetworkPolicy | Yes | Yes | Yes | Yes |
| L7 Policy (HTTP/gRPC) | No (OSS) / Yes (Enterprise) | Yes (native) | No | No |
| DNS-aware policies | No (OSS) | Yes | No | No |
| Global/Cluster policies | GlobalNetworkPolicy (CRD) | CiliumClusterwideNetworkPolicy | No | ClusterNetworkPolicy |
| Data plane | iptables or eBPF | eBPF | iptables | OVS or eBPF |
| Policy logging | Felix logs | Hubble | No | AntreaProxy logs |
| Memory per node | 120-180 MB | 180-250 MB | 50-80 MB | 100-150 MB |
| Throughput (cross-node) | 9.4 Gbps | 9.8 Gbps | 8.2 Gbps* | ~9.0 Gbps |

*Weave throughput measured with encryption disabled. [src5]

## Related Units

- [Kubernetes Security Checklist](/software/security/kubernetes-security/2026)
