Configure Kubernetes network policies for enhanced cluster security

Intermediate 35 min May 19, 2026 120 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

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

SymptomCauseFix
All traffic blocked unexpectedlyMissing DNS egress policyAdd policy allowing UDP/TCP 53 to kube-system namespace
Policies not taking effectCNI doesn't support NetworkPolicyInstall Calico or another NetworkPolicy-enabled CNI
Inter-namespace communication failsMissing namespaceSelectorAdd namespaceSelector with proper labels to policy
External traffic cannot reach podsNo ingress policy allows external accessCreate policy with empty 'from' selector for external traffic
Services not resolvingDNS queries blocked by egress policyAllow egress to CoreDNS pods on ports 53/UDP and 53/TCP

Next steps

Running this in production?

Want this handled for you? Setting up network policies once is straightforward. Keeping them tested, updated across environments, and responding to security incidents is the harder part. See how we run infrastructure like this for European SaaS and fintech teams.

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.