Kubernetes Network Policies
Declarative firewall rules for Pod-to-Pod traffic. Kubernetes ships with an “allow-all” networking model — every Pod can reach every other Pod by default. NetworkPolicies flip this to an explicit whitelist model, enabling zero-trust east-west segmentation inside the cluster. Source: CKA Day 26
The Problem: Flat Open Networking
By default, a Kubernetes cluster has no internal firewall. The CNI plugin (e.g., Flannel, Calico, Cilium) creates overlay routes that let any Pod talk to any other Pod on any port, across all Namespaces. This means:
- A compromised frontend Pod can directly scan or connect to a database Pod
- A misconfigured workload can accidentally leak data to unrelated services
- There is no network-level defense-in-depth beyond application authentication
Ingress = traffic flowing into a Pod from an external source (user, another Pod, the internet).
Egress = traffic flowing out of a Pod toward an external destination.
NetworkPolicies are the Kubernetes-native answer: they let you declare which connections are legitimate and silently deny everything else.
What Is a NetworkPolicy?
A NetworkPolicy is a namespace-scoped API object (networking.k8s.io/v1) that:
- Selects a set of Pods using a label query (
podSelector) - Defines allowed traffic directions (
Ingress,Egress, or both) - Whitelists specific sources/destinations and ports
Key principle: NetworkPolicies are whitelist-only. They describe what is permitted, not what is forbidden. Once any NetworkPolicy selects a Pod, that Pod flips from “allow all” to “deny all except whitelisted”.
CNI Plugin Support Matrix
NetworkPolicies are not enforced by the API server — they are enforced by the CNI plugin running on each node. If your CNI does not implement the policy controller, NetworkPolicy objects will exist but have zero effect.
| CNI Plugin | Policy Support | Production Notes |
|---|---|---|
| Flannel | ❌ No | Simple overlay; no built-in policy engine |
| kindnet | ❌ No | Default in Kind clusters; fine for local tests without policies |
| Weave Net | ⚠️ Deprecated | Last significant update ~2 years ago; unreliable on Kubernetes 1.30+ |
| Calico | ✅ Yes | Industry standard; default on GKE; supports L3/L4 policies and advanced features |
| Cilium | ✅ Yes | eBPF-based; supports L3/L4/L7 policies, DNS-aware rules, and observability |
| AWS VPC CNI | ✅ Yes (with Network Policy Agent) | Required add-on for EKS |
| Azure CNI | ✅ Yes | Supported on AKS with Azure Network Policy or Calico |
CKA exam implication: You must know that Flannel and kindnet do NOT support NetworkPolicies, and that Calico is the go-to choice for policy-enabled clusters.
YAML Anatomy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-allow-backend
namespace: default
spec:
podSelector:
matchLabels:
app: mysql # Target: Pods with label app=mysql
policyTypes:
- Ingress # Direction(s) this policy controls
ingress:
- from:
- podSelector:
matchLabels:
role: backend # Source: only Pods labeled role=backend
ports:
- protocol: TCP
port: 3306 # Destination port on the target PodField Reference
| Field | Scope | Description |
|---|---|---|
podSelector | Target | Label query that decides which Pods this policy protects. Empty {} = all Pods in the namespace. |
policyTypes | Behavior | List containing Ingress and/or Egress. Required to activate that direction. |
ingress | Whitelist | List of from blocks (sources) and ports (destination ports) allowed into target Pods. |
egress | Whitelist | List of to blocks (destinations) and `ports** allowed out of target Pods. |
from / to | Matchers | Can contain podSelector, namespaceSelector, ipBlock, or a combination. |
Selector Combinations in from / to
# Allow from any Pod in the 'monitoring' Namespace
- from:
- namespaceSelector:
matchLabels:
env: monitoring
# Allow from a specific Pod in ANY Namespace (less common)
- from:
- podSelector:
matchLabels:
app: frontend
# Allow from a specific Pod in a specific Namespace (AND logic)
- from:
- podSelector:
matchLabels:
app: frontend
namespaceSelector:
matchLabels:
env: staging
# Allow from an external IP range (e.g., on-prem bastion)
- from:
- ipBlock:
cidr: 10.0.0.0/8
except:
- 10.1.0.0/16AND vs OR: When
podSelectorandnamespaceSelectorappear in the same array element, they are ANDed (Pod must match labels AND be in a Namespace matching labels). When they appear in separate array elements, they are ORed.
Default Deny Pattern
Production best practice is to start with default-deny policies per Namespace, then open explicit paths:
Deny All Ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {} # All Pods in this namespace
policyTypes:
- Ingress
# No ingress rules = deny everything incomingDeny All Egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
spec:
podSelector: {}
policyTypes:
- EgressCombined Deny All
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- EgressAfter default-deny, add granular allow policies:
frontend→backendon TCP 80backend→databaseon TCP 3306- All Pods → CoreDNS on UDP 53 (required for DNS resolution)
- Monitoring Pods → all workloads on TCP 9090 (metrics scraping)
Practical Example: Three-Tier Application
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ frontend │───▶ │ backend │───▶ │ database │
│ (nginx:80) │ │ (nginx:80) │ │ (mysql:3306)│
└─────────────┘ └─────────────┘ └─────────────┘
Goal: Only backend may reach database on port 3306. frontend (and any other Pod) must be blocked.
Step 1 — Default Deny Ingress in Namespace
Apply the default-deny-ingress manifest above.
Step 2 — Allow Backend → Database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-allow-backend
spec:
podSelector:
matchLabels:
app: mysql
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
role: backend
ports:
- protocol: TCP
port: 3306Result:
frontendPod:telnet db 3306→ ❌ hangs / times outbackendPod:telnet db 3306→ ✅ connects immediately- Any new unlabeled Pod:
telnet db 3306→ ❌ denied
Calico on Kind: Lab Setup
The default Kind CNI (kindnet) does not enforce NetworkPolicies. To practice with Calico:
kind-calico.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
disableDefaultCNI: true
podSubnet: "192.168.0.0/16"
nodes:
- role: control-plane
- role: worker
- role: workerCommands
# 1. Create cluster (nodes will show NotReady)
kind create cluster --name cka --config kind-calico.yaml
# 2. Install Calico
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml
# 3. Wait for CNI pods
kubectl get pods -n kube-system -l k8s-app=calico-node -w
# 4. Verify nodes are Ready
kubectl get nodesCalico deploys as a DaemonSet (calico-node), placing one enforcement agent per node. It uses Linux iptables or eBPF (depending on configuration) to insert allow/deny rules based on every NetworkPolicy object.
Production clusters: The same Calico installation applies to kubeadm-provisioned VMs. After
kubeadm init, install Calico before joining worker nodes. Ensure--pod-network-cidrmatches Calico’s default pool (192.168.0.0/16). See Kubeadm Cluster Setup for the full installation workflow. Source: CKA Day 27
Imperative Commands
# List all NetworkPolicies in a namespace
kubectl get networkpolicy
kubectl get netpol
# Describe a specific policy (shows effective selectors and ports)
kubectl describe netpol db-allow-backend
# Test connectivity from inside a Pod
kubectl exec -it frontend-pod -- bash
telnet db 3306 # or curl backend:80
# Verify DNS still works after default-deny (egress to CoreDNS must be allowed)
nslookup kubernetes.defaultCommon Pitfalls
| Pitfall | Explanation |
|---|---|
| CNI doesn’t support policies | Policies exist but are ignored. Always verify your CNI before relying on NetworkPolicies for security. |
| Forgetting CoreDNS egress | Default-deny egress breaks DNS resolution. Explicitly allow UDP 53 to the kube-system Namespace. |
| Label mismatch | podSelector uses labels, not Pod names or Service names. A typo in a label means zero Pods are selected. |
| Empty selector = all Pods | podSelector: {} selects every Pod in the namespace — powerful but dangerous if unintended. |
| Policies are namespace-scoped | A NetworkPolicy in namespace-a cannot directly select Pods in namespace-b without a namespaceSelector. |
| Ingress vs Egress confusion | ingress rules describe who can reach into the target Pod. egress rules describe what the target Pod can reach out to. |
CKA Exam Patterns
- Create a NetworkPolicy from scratch that restricts a sensitive workload (e.g., database) to only its legitimate consumer tier
- Know the
networking.k8s.io/v1API group and the four top-level fields:podSelector,policyTypes,ingress,egress - Remember that policies are whitelist-only: once a Pod is selected, unlisted traffic is denied
- Be comfortable with
telnetorcurlfrom inside Pods to verify policy enforcement - Know that Flannel and kindnet are common trap answers for “which CNI does NOT support NetworkPolicies?”
See Also
- Kubernetes Services — How cluster-internal DNS and load balancing work; the paths that NetworkPolicies then restrict
- Kubernetes Labels and Selectors — The query engine that drives
podSelectorandnamespaceSelector - Kubernetes Namespaces — Namespace boundaries and cross-namespace NetworkPolicy patterns
- Kubernetes DaemonSet — How CNI agents like Calico are deployed
- Kind Cluster Setup — Local multi-node cluster configuration
- Zero-Trust Access — The security model behind default-deny and least-privilege networking
- TLS Fundamentals — Transport-layer security that complements (but does not replace) NetworkPolicies
- Kubernetes RBAC — Identity and authorization at the API layer; NetworkPolicies are the network-layer counterpart
Tags: kubernetes networkpolicy cni calico security zero-trust cka networking egress ingress