Network Policies are the firewall for your Kubernetes pods.
Here’s a Calico-powered AKS cluster, showing how we can isolate pods using Network Policies. Imagine a simple application with a frontend service (frontend) and a backend service (backend). By default, they can talk to each other.
apiVersion: v1
kind: Pod
metadata:
name: frontend
labels:
app: frontend
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
name: backend
labels:
app: backend
spec:
containers:
- name: netcat
image: alpine:latest
command: ["/bin/sh", "-c", "while true; do echo 'Hello from backend' | nc -l -p 8080; sleep 30; done"]
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: frontend-svc
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 80
targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: backend-svc
spec:
selector:
app: backend
ports:
- protocol: TCP
port: 8080
targetPort: 8080
We can test connectivity using kubectl exec.
# From the frontend pod, try to connect to the backend service
kubectl exec -it frontend -- curl http://backend-svc:8080
# Expected output: Hello from backend
Now, let’s enforce isolation. We’ll create a NetworkPolicy that denies all ingress traffic to the backend pods by default.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress-to-backend
namespace: default # Assuming pods are in the 'default' namespace
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
# No ingress rules defined means all ingress is denied
After applying this policy, the frontend can no longer reach the backend.
# From the frontend pod, try to connect to the backend service again
kubectl exec -it frontend -- curl http://backend-svc:8080
# Expected output: curl: (7) Failed to connect to backend-svc port 8080: Connection timed out
This is because the policyTypes: [Ingress] with no ingress rules effectively creates a deny-all for incoming traffic to the backend pods.
To allow the frontend to talk to the backend, we need to explicitly permit that traffic.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: default
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
With this policy applied, connectivity is restored.
# From the frontend pod, try to connect to the backend service
kubectl exec -it frontend -- curl http://backend-svc:8080
# Expected output: Hello from backend
The podSelector in the from section specifies which pods are allowed to send traffic. The ports section restricts which ports on the backend pods can be accessed.
Calico in AKS provides the CNI (Container Network Interface) that implements these Kubernetes NetworkPolicy resources. It translates these abstract policy definitions into concrete network rules enforced at the node level. The key is that NetworkPolicy is a Kubernetes standard, and Calico is one of the popular implementations.
When you define a NetworkPolicy, Calico intercepts the traffic. For ingress traffic to a pod with a NetworkPolicy applied, Calico checks if any ingress rules permit the traffic based on its source (pod selectors, namespace selectors) and destination port. If no rule matches, the traffic is dropped. For egress traffic, a similar process occurs. If policyTypes includes Egress, Calico checks the egress rules.
The most surprising thing is that by default, all pods can talk to all other pods. Kubernetes NetworkPolicy is opt-in. You only get network segmentation if you explicitly define policies.
Consider a scenario where you want to restrict egress traffic from your backend pods to only allow them to talk to a specific external API.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: restrict-backend-egress
namespace: default
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 192.168.1.0/24 # Example: Allow traffic to a specific internal subnet
ports:
- protocol: TCP
port: 80
- to:
- ipBlock:
cidr: 10.0.0.5/32 # Example: Allow traffic to a specific external IP
ports:
- protocol: TCP
port: 443
# If no egress rules are defined, all egress is denied by default if Egress is in policyTypes
This policy allows backend pods to send TCP traffic to port 80 on any IP within the 192.168.1.0/24 subnet and to port 443 on the specific IP 10.0.0.5. Any other egress traffic from these pods would be blocked.
The power of Calico’s implementation is its ability to enforce these policies efficiently, often using eBPF for high performance, directly on the network path. This means you don’t need to rely on sidecar proxies for basic network segmentation.
A common pitfall is forgetting to include policyTypes: [Ingress] or policyTypes: [Egress] when you intend to control that direction of traffic. If policyTypes is omitted, it defaults to Ingress if ingress rules are present, and Egress if egress rules are present. If you want to deny all ingress and then explicitly allow some, you need policyTypes: [Ingress] and an empty ingress list (or no ingress rules). If you want to allow all ingress and then explicitly allow some egress, you’d need policyTypes: [Egress] and your egress rules.
The next step after mastering basic network policies is exploring more advanced features like namespaceSelector to allow traffic from all pods in a specific namespace, or using Calico’s extended NetworkPolicy CRDs for more granular control over Layer 7 attributes.