Set up Calico CNI in Kubernetes to implement network policies for pod-to-pod traffic control, create ingress and egress rules for microsegmentation, and test policy enforcement to secure container communication at the network layer.
Prerequisites
- Kubernetes cluster with admin access
- kubectl configured
- Basic understanding of Kubernetes networking
- Test applications for policy validation
What this solves
Kubernetes network policies with Calico CNI provide fine-grained control over pod-to-pod communication within your cluster. This enables microsegmentation by defining which pods can communicate with each other, blocking unwanted traffic flows, and implementing zero-trust networking principles for container security.
Step-by-step installation
Install Calico CNI in your cluster
Apply the Calico manifest to install the CNI plugin and enable network policy support in your Kubernetes cluster.
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.4/manifests/calico.yaml
Wait for all Calico pods to be ready across all nodes:
kubectl wait --for=condition=ready pod -l k8s-app=calico-node -n kube-system --timeout=300s
kubectl wait --for=condition=ready pod -l k8s-app=calico-kube-controllers -n kube-system --timeout=300s
Verify Calico installation
Check that Calico components are running and network policy enforcement is enabled.
kubectl get pods -n kube-system -l k8s-app=calico-node
kubectl get pods -n kube-system -l k8s-app=calico-kube-controllers
Verify that the Calico CNI binary is installed on worker nodes:
kubectl get nodes -o wide
kubectl describe node | grep -A5 "System Info"
Create test applications
Deploy sample applications to test network policy enforcement between different namespaces and pods.
kubectl create namespace frontend
kubectl create namespace backend
kubectl create namespace database
Deploy a web frontend application:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: frontend
labels:
app: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
role: web
spec:
containers:
- name: frontend
image: nginx:1.25
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: frontend-svc
namespace: frontend
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 80
type: ClusterIP
kubectl apply -f frontend-app.yaml
Deploy backend and database services
Create backend API and database services to test multi-tier application communication.
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: backend
labels:
app: backend
spec:
replicas: 2
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
role: api
spec:
containers:
- name: backend
image: httpd:2.4
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: backend-svc
namespace: backend
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 80
type: ClusterIP
kubectl apply -f backend-app.yaml
Deploy a database service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: database
namespace: database
labels:
app: database
spec:
replicas: 1
selector:
matchLabels:
app: database
template:
metadata:
labels:
app: database
role: db
spec:
containers:
- name: database
image: postgres:15
env:
- name: POSTGRES_PASSWORD
value: "testpass123"
ports:
- containerPort: 5432
---
apiVersion: v1
kind: Service
metadata:
name: database-svc
namespace: database
spec:
selector:
app: database
ports:
- port: 5432
targetPort: 5432
type: ClusterIP
kubectl apply -f database-app.yaml
Configure network policies
Create default deny policy
Start with a default deny policy that blocks all traffic, then explicitly allow required connections. This implements a zero-trust approach.
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
kubectl apply -f default-deny.yaml
Allow frontend to backend communication
Create an egress policy for frontend pods to communicate with backend services and an ingress policy for backend to receive traffic.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-egress
namespace: frontend
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: backend
ports:
- protocol: TCP
port: 80
- to: []
ports:
- protocol: UDP
port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-ingress
namespace: backend
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: frontend
ports:
- protocol: TCP
port: 80
Label the namespaces to enable policy selection:
kubectl label namespace frontend name=frontend
kubectl label namespace backend name=backend
kubectl label namespace database name=database
kubectl apply -f frontend-to-backend.yaml
Configure backend to database access
Allow backend services to connect to the database while blocking direct frontend access to the database layer.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-egress
namespace: backend
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: database
ports:
- protocol: TCP
port: 5432
- to: []
ports:
- protocol: UDP
port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database-ingress
namespace: database
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: backend
podSelector:
matchLabels:
role: api
ports:
- protocol: TCP
port: 5432
kubectl apply -f backend-to-database.yaml
Create ingress access policy
Allow external traffic to reach frontend services while maintaining internal microsegmentation.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-ingress
namespace: frontend
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Ingress
ingress:
- from: []
ports:
- protocol: TCP
port: 80
kubectl apply -f ingress-access.yaml
Test network policy enforcement
Test allowed connections
Verify that legitimate traffic flows work according to your network policies.
kubectl run test-pod --image=busybox --rm -it --restart=Never -- sh
From inside the test pod, try connecting to services:
wget -qO- --timeout=5 frontend-svc.frontend.svc.cluster.local
wget -qO- --timeout=5 backend-svc.backend.svc.cluster.local
Test from frontend to backend (should work):
kubectl exec -n frontend deployment/frontend -- wget -qO- --timeout=5 backend-svc.backend.svc.cluster.local
Test blocked connections
Verify that unauthorized traffic is blocked by your network policies.
Test direct frontend to database (should fail):
kubectl exec -n frontend deployment/frontend -- nc -z -w5 database-svc.database.svc.cluster.local 5432
Test backend to database (should work):
kubectl exec -n backend deployment/backend -- nc -z -w5 database-svc.database.svc.cluster.local 5432
Monitor policy violations
Check Calico logs for network policy enforcement events and denied connections.
kubectl logs -n kube-system -l k8s-app=calico-node | grep -i "denied\|dropped"
kubectl get networkpolicy --all-namespaces
View detailed policy status:
kubectl describe networkpolicy -n frontend
kubectl describe networkpolicy -n backend
kubectl describe networkpolicy -n database
Advanced policy configurations
Implement time-based policies
Create policies that allow connections only during specific time windows using Calico's advanced features.
kubectl apply -f - <
Create service-specific policies
Implement granular policies based on service types and application roles.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-service-policy
namespace: backend
spec:
podSelector:
matchLabels:
role: api
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: frontend
podSelector:
matchLabels:
role: web
ports:
- protocol: TCP
port: 80
egress:
- to:
- namespaceSelector:
matchLabels:
name: database
podSelector:
matchLabels:
role: db
ports:
- protocol: TCP
port: 5432
- to: []
ports:
- protocol: UDP
port: 53
kubectl apply -f service-policies.yaml
Verify your setup
kubectl get networkpolicy --all-namespaces
kubectl get pods -n kube-system -l k8s-app=calico-node
kubectl exec -n frontend deployment/frontend -- wget -qO- --timeout=5 backend-svc.backend.svc.cluster.local
kubectl exec -n frontend deployment/frontend -- nc -z -w5 database-svc.database.svc.cluster.local 5432 || echo "Database access blocked (expected)"
kubectl logs -n kube-system -l k8s-app=calico-node --tail=10
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Pods can't communicate despite policy | Missing DNS egress rules | Add UDP port 53 egress to all policies |
| Network policies not enforced | CNI doesn't support policies | Verify Calico is installed with kubectl get pods -n kube-system |
| All traffic blocked unexpectedly | Default deny applied without allows | Check policy with kubectl describe networkpolicy |
| Cross-namespace access failing | Namespace labels missing | Label namespaces with kubectl label namespace |
| Policy changes not taking effect | Policy cache not updated | Restart Calico pods with kubectl rollout restart -n kube-system daemonset/calico-node |
Next steps
- Configure Falco runtime security for Kubernetes threat detection to add behavioral monitoring alongside network policies
- Configure Kubernetes Pod Security Standards with admission controllers for comprehensive security policy enforcement
- Implement advanced network policies with OPA Gatekeeper for policy-as-code management
- Configure Istio security policies with mTLS for service mesh-level security
- Monitor network policy effectiveness with Prometheus metrics
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' # No Color
# Script configuration
SCRIPT_NAME="$(basename "$0")"
CALICO_VERSION="${1:-v3.26.4}"
KUBECTL_VERSION="${2:-v1.28.0}"
# Function to print colored output
print_status() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
print_step() {
echo -e "${BLUE}[$1]${NC} $2"
}
# Usage message
usage() {
cat << EOF
Usage: $SCRIPT_NAME [CALICO_VERSION] [KUBECTL_VERSION]
Configure Kubernetes network policies with Calico CNI for container security and microsegmentation
Arguments:
CALICO_VERSION Calico version to install (default: v3.26.4)
KUBECTL_VERSION kubectl version to install (default: v1.28.0)
Examples:
$SCRIPT_NAME
$SCRIPT_NAME v3.26.4 v1.28.0
Requirements:
- Root or sudo privileges
- Kubernetes cluster access
- Internet connectivity
EOF
exit 1
}
# Cleanup function
cleanup() {
print_error "Script failed. Cleaning up..."
# Remove temporary files
rm -f /tmp/calico.yaml /tmp/frontend-app.yaml /tmp/backend-app.yaml /tmp/database-app.yaml /tmp/network-policies.yaml 2>/dev/null || true
}
# Set up error handling
trap cleanup ERR
# Check arguments
if [[ "${1:-}" == "-h" ]] || [[ "${1:-}" == "--help" ]]; then
usage
fi
# Check if running as root or with sudo
if [[ $EUID -ne 0 ]]; then
print_error "This script must be run as root or with sudo"
exit 1
fi
print_step "1/8" "Detecting operating system and package manager..."
# Auto-detect distro and set package manager
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 check-update || true"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum check-update || true"
;;
*)
print_error "Unsupported distribution: $ID"
exit 1
;;
esac
print_status "Detected: $PRETTY_NAME using $PKG_MGR"
else
print_error "Cannot detect operating system"
exit 1
fi
print_step "2/8" "Installing prerequisites..."
# Update package lists
$PKG_UPDATE
# Install required packages
case "$PKG_MGR" in
apt)
$PKG_INSTALL curl wget gnupg2 software-properties-common apt-transport-https ca-certificates
;;
dnf|yum)
$PKG_INSTALL curl wget gnupg2 ca-certificates
;;
esac
print_step "3/8" "Installing kubectl..."
# Check if kubectl is already installed and correct version
if command -v kubectl &> /dev/null; then
CURRENT_VERSION=$(kubectl version --client -o json 2>/dev/null | grep -o '"gitVersion":"[^"]*' | cut -d'"' -f4 || echo "unknown")
if [[ "$CURRENT_VERSION" == "$KUBECTL_VERSION" ]]; then
print_status "kubectl $KUBECTL_VERSION already installed"
else
print_warning "kubectl version mismatch. Installing $KUBECTL_VERSION"
fi
else
# Install kubectl
curl -LO "https://dl.k8s.io/release/$KUBECTL_VERSION/bin/linux/amd64/kubectl"
curl -LO "https://dl.k8s.io/$KUBECTL_VERSION/bin/linux/amd64/kubectl.sha256"
echo "$(cat kubectl.sha256) kubectl" | sha256sum --check
chmod 755 kubectl
mv kubectl /usr/local/bin/
rm -f kubectl.sha256
print_status "kubectl installed successfully"
fi
print_step "4/8" "Verifying Kubernetes cluster access..."
# Test kubectl connectivity
if ! kubectl cluster-info &> /dev/null; then
print_error "Cannot connect to Kubernetes cluster. Please ensure kubeconfig is properly configured."
exit 1
fi
print_status "Kubernetes cluster connection verified"
print_step "5/8" "Installing Calico CNI..."
# Download and apply Calico manifest
curl -o /tmp/calico.yaml "https://raw.githubusercontent.com/projectcalico/calico/$CALICO_VERSION/manifests/calico.yaml"
kubectl apply -f /tmp/calico.yaml
# Wait for Calico pods to be ready
print_status "Waiting for Calico pods to become ready..."
kubectl wait --for=condition=ready pod -l k8s-app=calico-node -n kube-system --timeout=300s
kubectl wait --for=condition=ready pod -l k8s-app=calico-kube-controllers -n kube-system --timeout=300s
print_step "6/8" "Verifying Calico installation..."
# Verify Calico components
kubectl get pods -n kube-system -l k8s-app=calico-node
kubectl get pods -n kube-system -l k8s-app=calico-kube-controllers
print_step "7/8" "Creating test applications..."
# Create namespaces
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 -
# Create frontend application manifest
cat > /tmp/frontend-app.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: frontend
labels:
app: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
role: web
spec:
containers:
- name: frontend
image: nginx:1.25
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: frontend-svc
namespace: frontend
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 80
type: ClusterIP
EOF
# Create backend application manifest
cat > /tmp/backend-app.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: backend
labels:
app: backend
spec:
replicas: 2
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
role: api
spec:
containers:
- name: backend
image: httpd:2.4
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: backend-svc
namespace: backend
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 80
type: ClusterIP
EOF
# Create database application manifest
cat > /tmp/database-app.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: database
namespace: database
labels:
app: database
spec:
replicas: 1
selector:
matchLabels:
app: database
template:
metadata:
labels:
app: database
role: db
spec:
containers:
- name: database
image: postgres:15
env:
- name: POSTGRES_PASSWORD
value: "testpass123"
ports:
- containerPort: 5432
---
apiVersion: v1
kind: Service
metadata:
name: database-svc
namespace: database
spec:
selector:
app: database
ports:
- port: 5432
targetPort: 5432
type: ClusterIP
EOF
# Apply application manifests
kubectl apply -f /tmp/frontend-app.yaml
kubectl apply -f /tmp/backend-app.yaml
kubectl apply -f /tmp/database-app.yaml
# Wait for deployments to be ready
kubectl wait --for=condition=available deployment/frontend -n frontend --timeout=120s
kubectl wait --for=condition=available deployment/backend -n backend --timeout=120s
kubectl wait --for=condition=available deployment/database -n database --timeout=120s
print_step "8/8" "Creating sample network policies..."
# Create network policies manifest
cat > /tmp/network-policies.yaml << 'EOF'
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
---
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
ports:
- protocol: TCP
port: 80
EOF
# Apply network policies
kubectl apply -f /tmp/network-policies.yaml
# Add namespace labels for network policies
kubectl label namespace frontend name=frontend --overwrite
kubectl label namespace backend name=backend --overwrite
kubectl label namespace database name=database --overwrite
print_status "Calico CNI installation and configuration completed successfully!"
# Verification
print_status "Verification Results:"
echo "- Calico pods status:"
kubectl get pods -n kube-system -l k8s-app=calico-node --no-headers | wc -l | xargs echo " Calico nodes running:"
kubectl get pods -n kube-system -l k8s-app=calico-kube-controllers --no-headers | wc -l | xargs echo " Calico controllers running:"
echo "- Test applications status:"
kubectl get pods -n frontend --no-headers | grep Running | wc -l | xargs echo " Frontend pods running:"
kubectl get pods -n backend --no-headers | grep Running | wc -l | xargs echo " Backend pods running:"
kubectl get pods -n database --no-headers | grep Running | wc -l | xargs echo " Database pods running:"
echo "- Network policies:"
kubectl get networkpolicy --all-namespaces --no-headers | wc -l | xargs echo " Network policies created:"
# Cleanup temporary files
rm -f /tmp/calico.yaml /tmp/frontend-app.yaml /tmp/backend-app.yaml /tmp/database-app.yaml /tmp/network-policies.yaml
print_status "Setup complete! You can now test network policies and implement microsegmentation."
Review the script before running. Execute with: bash install.sh