Set up granular network security in Kubernetes using Calico CNI with default-deny policies and pod-to-pod communication rules for microsegmentation.
Prerequisites
- Kubernetes cluster with admin access
- kubectl configured
- Basic understanding of Kubernetes networking
- Cluster nodes with sufficient resources
What this solves
Kubernetes network policies provide microsegmentation and traffic isolation between pods, namespaces, and external services. By default, Kubernetes allows all pod-to-pod communication, which creates security risks in production environments. This tutorial implements Calico CNI with network policies to create secure, controlled communication paths.
Prerequisites and cluster preparation
Verify Kubernetes cluster status
Ensure your Kubernetes cluster is running and you have cluster-admin privileges.
kubectl cluster-info
kubectl get nodes
kubectl auth can-i "" "" --all-namespaces
Check existing CNI configuration
Identify the current CNI plugin and remove conflicting network configurations.
kubectl get pods -n kube-system | grep -E "(calico|flannel|weave|cilium)"
ls /etc/cni/net.d/
Remove existing CNI if needed
If you have a different CNI installed, remove it before installing Calico. Skip this step if you're setting up a new cluster.
# For flannel
kubectl delete -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
Clean up CNI configuration
sudo rm -rf /etc/cni/net.d/*
sudo rm -rf /var/lib/cni/*
Install and configure Calico CNI with network policy support
Install Calico operator
Deploy the Calico operator which manages the Calico installation and configuration lifecycle.
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/tigera-operator.yaml
Configure Calico custom resource
Create the installation configuration for Calico with network policy enforcement enabled.
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
name: default
spec:
calicoNetwork:
ipPools:
- blockSize: 26
cidr: 192.168.0.0/16
encapsulation: VXLANCrossSubnet
natOutgoing: Enabled
nodeSelector: all()
nodeMetricsPort: 9091
---
apiVersion: operator.tigera.io/v1
kind: APIServer
metadata:
name: default
spec: {}
Apply Calico configuration
Deploy Calico using the custom resource configuration.
kubectl apply -f calico-installation.yaml
Wait for Calico deployment
Monitor the Calico pods until they reach running state across all nodes.
kubectl get pods -n calico-system --watch
kubectl wait --for=condition=Ready pods --all -n calico-system --timeout=300s
Install calicoctl CLI tool
Install the Calico command-line tool for advanced network policy management.
curl -L https://github.com/projectcalico/calico/releases/download/v3.27.0/calicoctl-linux-amd64 -o calicoctl
sudo chmod +x calicoctl
sudo mv calicoctl /usr/local/bin/
Create default deny-all network policies for namespace isolation
Create test namespaces
Set up isolated namespaces to demonstrate network policy enforcement.
kubectl create namespace frontend
kubectl create namespace backend
kubectl create namespace database
Deploy test applications
Create sample applications in each namespace for testing network policies.
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-app
namespace: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
tier: web
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: frontend-service
namespace: frontend
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-app
namespace: backend
spec:
replicas: 2
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
tier: api
spec:
containers:
- name: httpd
image: httpd:2.4
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: backend-service
namespace: backend
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: database-app
namespace: database
spec:
replicas: 1
selector:
matchLabels:
app: database
template:
metadata:
labels:
app: database
tier: data
spec:
containers:
- name: postgres
image: postgres:16
env:
- name: POSTGRES_PASSWORD
value: "securepassword123"
ports:
- containerPort: 5432
---
apiVersion: v1
kind: Service
metadata:
name: database-service
namespace: database
spec:
selector:
app: database
ports:
- port: 5432
targetPort: 5432
Apply test applications
Deploy the test workloads across the three namespaces.
kubectl apply -f test-apps.yaml
Create default deny-all policies
Implement namespace-level default deny policies that block all ingress and egress traffic.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: frontend
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: backend
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: database
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Apply default deny policies
Deploy the network policies to enforce default-deny behavior across all namespaces.
kubectl apply -f default-deny-policies.yaml
Implement granular pod-to-pod communication rules with ingress and egress policies
Allow DNS resolution
Create policies to permit DNS queries, which are essential for service discovery.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-access
namespace: frontend
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-access
namespace: backend
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-access
namespace: database
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Create frontend-to-backend communication policy
Allow frontend pods to communicate with backend services on specific ports.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: backend
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: frontend
podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 80
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-egress-to-backend
namespace: frontend
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: backend
podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 80
Create backend-to-database communication policy
Allow backend pods to connect to database services while restricting other access.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-to-database
namespace: database
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: backend
podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 5432
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-egress-to-database
namespace: backend
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: database
podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
Label namespaces for policy targeting
Add labels to namespaces so network policies can reference them in selectors.
kubectl label namespace frontend name=frontend
kubectl label namespace backend name=backend
kubectl label namespace database name=database
Apply communication policies
Deploy the DNS and inter-namespace communication policies.
kubectl apply -f allow-dns-policies.yaml
kubectl apply -f frontend-to-backend-policy.yaml
kubectl apply -f backend-to-database-policy.yaml
Create external traffic ingress policy
Allow external traffic to reach frontend applications through ingress controllers.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-external-to-frontend
namespace: frontend
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Ingress
ingress:
- from: []
ports:
- protocol: TCP
port: 80
Apply external access policy
Enable external traffic to reach your frontend services.
kubectl apply -f allow-external-ingress.yaml
Verify your setup
Check network policy enforcement
Verify that all network policies are created and active.
kubectl get networkpolicy --all-namespaces
calicoctl get networkpolicy --all-namespaces
Test blocked communication
Verify that unauthorized connections are blocked by network policies.
# This should fail - frontend cannot directly access database
kubectl exec -n frontend deployment/frontend-app -- curl --connect-timeout 5 database-service.database:5432
This should fail - no namespace can access backend without proper labels
kubectl run test-pod --image=busybox --rm -it --restart=Never -- wget --timeout=5 backend-service.backend
Test allowed communication
Confirm that authorized connections work through the defined policies.
# This should work - frontend can access backend
kubectl exec -n frontend deployment/frontend-app -- curl --connect-timeout 5 backend-service.backend
Test DNS resolution works
kubectl exec -n frontend deployment/frontend-app -- nslookup backend-service.backend
Monitor policy enforcement
Check Calico logs for policy decisions and blocked connections.
kubectl logs -n calico-system -l k8s-app=calico-node --tail=50 | grep -i policy
calicoctl get globalnetworkpolicy -o yaml
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Pods can't resolve DNS | DNS egress not allowed | Apply DNS egress policies to all namespaces |
| Network policies not enforcing | CNI doesn't support policies | Ensure Calico is properly installed and running |
| All traffic blocked unexpectedly | Default deny without allow rules | Create specific allow policies for required communication |
| Cross-namespace communication fails | Missing namespace labels | kubectl label namespace name=namespace-name |
| External traffic cannot reach services | Ingress policy too restrictive | Add external ingress policy or whitelist specific sources |
| Calico pods not starting | Resource constraints or CNI conflicts | Check node resources and remove conflicting CNI configurations |
Next steps
- Set up Kubernetes monitoring with Prometheus Operator to track network policy metrics
- Configure Kubernetes ingress controller with NGINX and SSL certificates for secure external access
- Implement advanced network policies with Cilium and eBPF monitoring
- Configure Kubernetes Pod Security Standards for comprehensive security enforcement
- Set up automated SSL certificate management for your applications
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Default values
CALICO_VERSION="v3.27.0"
CIDR_RANGE="${1:-192.168.0.0/16}"
# Usage function
usage() {
echo "Usage: $0 [CIDR_RANGE]"
echo "Example: $0 192.168.0.0/16"
echo "Default CIDR: 192.168.0.0/16"
exit 1
}
# Error handling
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Cleaning up...${NC}"
kubectl delete -f /tmp/calico-installation.yaml --ignore-not-found=true 2>/dev/null || true
rm -f /tmp/calico-installation.yaml /tmp/test-apps.yaml 2>/dev/null || true
}
trap cleanup ERR
# Detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian) PKG_MGR="apt"; PKG_INSTALL="apt install -y"; PKG_UPDATE="apt update" ;;
almalinux|rocky|centos|rhel|ol|fedora) PKG_MGR="dnf"; PKG_INSTALL="dnf install -y"; PKG_UPDATE="dnf update -y" ;;
amzn) PKG_MGR="yum"; PKG_INSTALL="yum install -y"; PKG_UPDATE="yum update -y" ;;
*) echo -e "${RED}Unsupported distro: $ID${NC}"; exit 1 ;;
esac
else
echo -e "${RED}/etc/os-release not found. Cannot detect distribution.${NC}"
exit 1
fi
# Check prerequisites
echo -e "${BLUE}[1/12] Checking prerequisites...${NC}"
if [ "$EUID" -eq 0 ]; then
echo -e "${YELLOW}Running as root. Consider using a user with sudo privileges.${NC}"
fi
if ! command -v kubectl &> /dev/null; then
echo -e "${RED}kubectl not found. Please install kubectl first.${NC}"
exit 1
fi
if ! command -v curl &> /dev/null; then
echo -e "${YELLOW}Installing curl...${NC}"
$PKG_UPDATE
$PKG_INSTALL curl
fi
# Verify Kubernetes cluster
echo -e "${BLUE}[2/12] Verifying Kubernetes cluster...${NC}"
if ! kubectl cluster-info &> /dev/null; then
echo -e "${RED}Kubernetes cluster not accessible. Please check your kubeconfig.${NC}"
exit 1
fi
if ! kubectl auth can-i "*" "*" --all-namespaces &> /dev/null; then
echo -e "${RED}Insufficient permissions. Cluster-admin privileges required.${NC}"
exit 1
fi
echo -e "${GREEN}Cluster verification successful${NC}"
# Check existing CNI
echo -e "${BLUE}[3/12] Checking existing CNI configuration...${NC}"
EXISTING_CNI=$(kubectl get pods -n kube-system 2>/dev/null | grep -E "(calico|flannel|weave|cilium)" | head -1 | awk '{print $1}' || true)
if [ -n "$EXISTING_CNI" ]; then
echo -e "${YELLOW}Found existing CNI: $EXISTING_CNI${NC}"
echo -e "${YELLOW}Warning: This script will install Calico. Ensure compatibility with your setup.${NC}"
fi
# Install Calico operator
echo -e "${BLUE}[4/12] Installing Calico operator...${NC}"
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VERSION}/manifests/tigera-operator.yaml
# Wait for operator to be ready
echo -e "${BLUE}[5/12] Waiting for Tigera operator to be ready...${NC}"
kubectl wait --for=condition=Ready pods --all -n tigera-operator --timeout=300s
# Create Calico installation configuration
echo -e "${BLUE}[6/12] Creating Calico installation configuration...${NC}"
cat > /tmp/calico-installation.yaml << EOF
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
name: default
spec:
calicoNetwork:
ipPools:
- blockSize: 26
cidr: ${CIDR_RANGE}
encapsulation: VXLANCrossSubnet
natOutgoing: Enabled
nodeSelector: all()
nodeMetricsPort: 9091
---
apiVersion: operator.tigera.io/v1
kind: APIServer
metadata:
name: default
spec: {}
EOF
chmod 644 /tmp/calico-installation.yaml
# Apply Calico configuration
echo -e "${BLUE}[7/12] Applying Calico configuration...${NC}"
kubectl apply -f /tmp/calico-installation.yaml
# Wait for Calico deployment
echo -e "${BLUE}[8/12] Waiting for Calico pods to be ready...${NC}"
kubectl wait --for=condition=Ready pods --all -n calico-system --timeout=600s
kubectl wait --for=condition=Ready pods --all -n calico-apiserver --timeout=300s
# Install calicoctl CLI tool
echo -e "${BLUE}[9/12] Installing calicoctl CLI tool...${NC}"
CALICOCTL_URL="https://github.com/projectcalico/calico/releases/download/${CALICO_VERSION}/calicoctl-linux-amd64"
curl -L "$CALICOCTL_URL" -o /tmp/calicoctl
chmod 755 /tmp/calicoctl
sudo mv /tmp/calicoctl /usr/local/bin/calicoctl
# Create test namespaces
echo -e "${BLUE}[10/12] Creating test namespaces...${NC}"
kubectl create namespace frontend --dry-run=client -o yaml | kubectl apply -f -
kubectl create namespace backend --dry-run=client -o yaml | kubectl apply -f -
kubectl create namespace database --dry-run=client -o yaml | kubectl apply -f -
# Deploy test applications
echo -e "${BLUE}[11/12] Deploying test applications...${NC}"
cat > /tmp/test-apps.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-app
namespace: frontend
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
tier: web
spec:
containers:
- name: nginx
image: nginx:1.25-alpine
ports:
- containerPort: 80
resources:
limits:
memory: "64Mi"
cpu: "50m"
---
apiVersion: v1
kind: Service
metadata:
name: frontend-service
namespace: frontend
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-app
namespace: backend
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
tier: api
spec:
containers:
- name: httpd
image: httpd:2.4-alpine
ports:
- containerPort: 80
resources:
limits:
memory: "64Mi"
cpu: "50m"
EOF
chmod 644 /tmp/test-apps.yaml
kubectl apply -f /tmp/test-apps.yaml
# Wait for test apps to be ready
kubectl wait --for=condition=Available deployment/frontend-app -n frontend --timeout=300s
kubectl wait --for=condition=Available deployment/backend-app -n backend --timeout=300s
# Verification
echo -e "${BLUE}[12/12] Running verification checks...${NC}"
# Check Calico pods
if kubectl get pods -n calico-system | grep -q "Running"; then
echo -e "${GREEN}✓ Calico pods are running${NC}"
else
echo -e "${RED}✗ Calico pods not running properly${NC}"
exit 1
fi
# Check calicoctl
if calicoctl version &> /dev/null; then
echo -e "${GREEN}✓ calicoctl installed successfully${NC}"
else
echo -e "${RED}✗ calicoctl installation failed${NC}"
exit 1
fi
# Check test applications
if kubectl get pods -n frontend | grep -q "Running" && kubectl get pods -n backend | grep -q "Running"; then
echo -e "${GREEN}✓ Test applications deployed successfully${NC}"
else
echo -e "${RED}✗ Test applications not running properly${NC}"
exit 1
fi
# Cleanup temporary files
rm -f /tmp/calico-installation.yaml /tmp/test-apps.yaml
echo -e "${GREEN}"
echo "========================================"
echo "Calico CNI Installation Complete!"
echo "========================================"
echo -e "${NC}"
echo "Next steps:"
echo "1. Create network policies to control traffic between namespaces"
echo "2. Test connectivity between pods"
echo "3. Monitor network policy effectiveness"
echo ""
echo "Example commands:"
echo " kubectl get pods -A"
echo " calicoctl get nodes"
echo " kubectl get networkpolicies -A"
echo ""
echo "CIDR Range configured: ${CIDR_RANGE}"
Review the script before running. Execute with: bash install.sh