Configure Kubernetes network policies with Calico CNI for container security and microsegmentation

Intermediate 35 min Apr 25, 2026 110 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

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

SymptomCauseFix
Pods can't communicate despite policyMissing DNS egress rulesAdd UDP port 53 egress to all policies
Network policies not enforcedCNI doesn't support policiesVerify Calico is installed with kubectl get pods -n kube-system
All traffic blocked unexpectedlyDefault deny applied without allowsCheck policy with kubectl describe networkpolicy
Cross-namespace access failingNamespace labels missingLabel namespaces with kubectl label namespace
Policy changes not taking effectPolicy cache not updatedRestart Calico pods with kubectl rollout restart -n kube-system daemonset/calico-node
Important: Always test network policies thoroughly in a staging environment before applying them to production. Incorrect policies can break application communication and cause service outages.
Performance note: Each network policy adds processing overhead. Monitor cluster performance when implementing extensive microsegmentation, especially in high-traffic environments.

Next steps

Running this in production?

Need help with scale? Setting up network policies is straightforward. Managing policy lifecycles, troubleshooting connectivity issues, and maintaining security compliance across multiple clusters is the harder part. See how we run infrastructure like this for European teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.