Implement granular network security controls in Kubernetes using Calico CNI. Learn to create ingress and egress policies, namespace isolation, and label-based microsegmentation for production clusters.
Prerequisites
- Kubernetes cluster with admin access
- kubectl configured
- Basic Kubernetes knowledge
What this solves
Kubernetes network policies provide granular control over how pods communicate with each other and external endpoints. Without policies, all pods can communicate freely, creating security risks in production environments. This tutorial shows you how to implement network policies with Calico CNI to enforce microsegmentation, isolate namespaces, and control traffic flow based on labels and selectors.
Prerequisites
- Working Kubernetes cluster with administrative access
- kubectl configured and connected to your cluster
- Basic understanding of Kubernetes pods, services, and namespaces
Install and configure Kubernetes cluster with Calico CNI
Install Calico CNI on existing cluster
If your cluster doesn't have Calico installed, deploy it using the official manifest. Calico is required for network policy enforcement.
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.4/manifests/tigera-operator.yaml
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.4/manifests/custom-resources.yaml
Verify Calico installation
Check that all Calico pods are running and the installation completed successfully.
kubectl get pods -n calico-system
kubectl get nodes -o wide
Enable network policy support
Verify that your cluster supports network policies by checking the CNI configuration.
kubectl get calicoctl
kubectl api-versions | grep networking.k8s.io
Understand network policy fundamentals
Network policy components
Network policies consist of selectors (which pods to apply to), ingress rules (incoming traffic), and egress rules (outgoing traffic). Each rule can specify allowed sources, destinations, ports, and protocols.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: example-policy
namespace: default
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 80
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
Create test environment
Deploy sample applications to test network policies. We'll create frontend, backend, and database pods with different labels.
kubectl create namespace policy-demo
Create frontend deployment
kubectl create deployment frontend --image=nginx:alpine -n policy-demo
kubectl label deployment frontend app=frontend -n policy-demo
kubectl expose deployment frontend --port=80 -n policy-demo
Create backend deployment
kubectl create deployment backend --image=nginx:alpine -n policy-demo
kubectl label deployment backend app=backend -n policy-demo
kubectl expose deployment backend --port=80 -n policy-demo
Create database deployment
kubectl create deployment database --image=postgres:13-alpine -n policy-demo
kubectl label deployment database app=database -n policy-demo
kubectl expose deployment database --port=5432 -n policy-demo
Create ingress network policies
Deny all ingress traffic
Start with a default deny policy that blocks all incoming traffic to pods in the namespace. This follows the principle of least privilege.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: policy-demo
spec:
podSelector: {}
policyTypes:
- Ingress
kubectl apply -f deny-all-ingress.yaml
Allow frontend to backend communication
Create a policy allowing frontend pods to communicate with backend pods on port 80. This demonstrates label-based selection.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: policy-demo
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 80
kubectl apply -f allow-frontend-to-backend.yaml
Allow external traffic to frontend
Create a policy allowing external traffic (from outside the cluster) to reach frontend pods. This enables ingress controllers to route traffic.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-external-to-frontend
namespace: policy-demo
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Ingress
ingress:
- from: []
ports:
- protocol: TCP
port: 80
kubectl apply -f allow-external-to-frontend.yaml
Create egress network policies
Deny all egress traffic
Create a default deny policy for outgoing traffic. This prevents pods from making unauthorized external connections.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-egress
namespace: policy-demo
spec:
podSelector: {}
policyTypes:
- Egress
kubectl apply -f deny-all-egress.yaml
Allow backend to database communication
Permit backend pods to connect to database pods on PostgreSQL port 5432. This demonstrates secure database access patterns.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-to-database
namespace: policy-demo
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
kubectl apply -f allow-backend-to-database.yaml
Allow DNS resolution
Enable DNS lookups for all pods by allowing connections to CoreDNS. Without this, pods cannot resolve service names.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: policy-demo
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: kube-system
- podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
kubectl apply -f allow-dns.yaml
Implement namespace isolation
Create production and staging namespaces
Set up separate namespaces with labels to demonstrate isolation between environments.
kubectl create namespace production
kubectl create namespace staging
kubectl label namespace production env=production
kubectl label namespace staging env=staging
Isolate production namespace
Create a policy that prevents any traffic from reaching production pods unless it originates from the same namespace.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: isolate-production
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
env: production
egress:
- to:
- namespaceSelector:
matchLabels:
env: production
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
kubectl apply -f isolate-production.yaml
Allow staging to access shared services
Enable staging namespace to communicate with shared services while maintaining production isolation.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-staging-shared
namespace: staging
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
env: staging
- namespaceSelector:
matchLabels:
name: shared-services
egress:
- to:
- namespaceSelector:
matchLabels:
env: staging
- namespaceSelector:
matchLabels:
name: shared-services
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
kubectl apply -f allow-staging-shared.yaml
Advanced policies with label selectors
Multi-label selection
Create policies using multiple labels for fine-grained control. This allows complex selection criteria for enterprise environments.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: multi-label-policy
namespace: policy-demo
spec:
podSelector:
matchLabels:
app: web
tier: frontend
version: v2
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: loadbalancer
- namespaceSelector:
matchLabels:
purpose: monitoring
podSelector:
matchLabels:
app: prometheus
ports:
- protocol: TCP
port: 8080
- protocol: TCP
port: 9090
kubectl apply -f multi-label-policy.yaml
IP block restrictions
Use IP blocks to restrict access to specific network ranges. This is useful for allowing only certain external networks or blocking known bad actors.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ip-block-policy
namespace: policy-demo
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/8
except:
- 10.0.1.0/24
- ipBlock:
cidr: 192.168.1.0/24
ports:
- protocol: TCP
port: 443
kubectl apply -f ip-block-policy.yaml
Test and troubleshoot network policies
Test connectivity between pods
Use kubectl exec to test network connectivity and verify policies are working correctly.
# Get pod names
kubectl get pods -n policy-demo
Test allowed connection (frontend to backend)
kubectl exec -n policy-demo deployment/frontend -- wget --timeout=5 --spider backend.policy-demo.svc.cluster.local
Test blocked connection (should fail)
kubectl exec -n policy-demo deployment/database -- wget --timeout=5 --spider frontend.policy-demo.svc.cluster.local
Debug with netshoot
Deploy a network troubleshooting pod to test connectivity from different contexts.
kubectl run netshoot --rm -i --tty --image nicolaka/netshoot -n policy-demo -- /bin/bash
Monitor policy logs
Check Calico logs for policy violations and blocked connections.
kubectl logs -n calico-system -l k8s-app=calico-node --tail=100
kubectl logs -n calico-system -l k8s-app=calico-kube-controllers --tail=100
Verify your setup
# List all network policies
kubectl get networkpolicies --all-namespaces
Check policy details
kubectl describe networkpolicy deny-all-ingress -n policy-demo
Verify Calico is enforcing policies
kubectl get pods -n calico-system
Test connectivity between specific pods
kubectl exec -n policy-demo deployment/frontend -- nc -zv backend.policy-demo.svc.cluster.local 80
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| All traffic blocked unexpectedly | Missing DNS egress policy | Add policy allowing UDP/TCP 53 to kube-system namespace |
| Policies not taking effect | CNI doesn't support NetworkPolicy | Install Calico or another NetworkPolicy-enabled CNI |
| Inter-namespace communication fails | Missing namespaceSelector | Add namespaceSelector with proper labels to policy |
| External traffic cannot reach pods | No ingress policy allows external access | Create policy with empty 'from' selector for external traffic |
| Services not resolving | DNS queries blocked by egress policy | Allow egress to CoreDNS pods on ports 53/UDP and 53/TCP |
Next steps
- Configure Pod Security Standards for admission control
- Implement advanced microsegmentation with Calico
- Set up ingress controllers with SSL certificates
- Monitor your cluster with Prometheus
- Configure RBAC policies for user access control