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.

Want structured learning?

Take the full Aks course →