Implement Kubernetes network policies for pod-to-pod security and traffic isolation

Intermediate 45 min May 22, 2026 33 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

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/
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

SymptomCauseFix
Pods can't resolve DNSDNS egress not allowedApply DNS egress policies to all namespaces
Network policies not enforcingCNI doesn't support policiesEnsure Calico is properly installed and running
All traffic blocked unexpectedlyDefault deny without allow rulesCreate specific allow policies for required communication
Cross-namespace communication failsMissing namespace labelskubectl label namespace name=namespace-name
External traffic cannot reach servicesIngress policy too restrictiveAdd external ingress policy or whitelist specific sources
Calico pods not startingResource constraints or CNI conflictsCheck node resources and remove conflicting CNI configurations

Next steps

Running this in production?

Want this handled for you? Setting up network policies once is straightforward. Keeping them updated, monitored, and tuned as your cluster grows is the harder part. See how we run infrastructure like this for European teams with compliance requirements.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure security hardening for businesses that depend on uptime. From initial setup to ongoing operations.