Kubernetes Network Policies

Network Policies provide fine-grained control over traffic flow between pods in a Kubernetes cluster. They define how pods can communicate with each other and external services, implementing a zero-trust networking model. This guide covers ingress and egress rules, namespace isolation, and implementations using Calico and Cilium CNI plugins.

Table of Contents

Network Policy Fundamentals

What are Network Policies?

Network Policies are Kubernetes objects that specify how pods communicate with each other and with network endpoints. They work like firewall rules for pod-to-pod traffic.

Prerequisites

Network Policies require a network plugin that supports them. Default network plugins (like Flannel) don't support policies. You need:

  • Calico: Fully-featured network policy support
  • Cilium: Advanced policy with eBPF-based implementation
  • Kube-router: Lightweight option
  • Weave Net: Network policy support available

Policy Evaluation

When a Network Policy is defined, it creates a whitelist of allowed traffic. By default:

  • All traffic is allowed (if no policies exist)
  • Once a policy targets pods, only traffic matching that policy is allowed
  • Multiple policies combine with OR logic (traffic allowed if ANY policy permits it)

Network Policy Syntax

Basic Structure

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
  namespace: production
spec:
  podSelector: {}  # Applies to all pods in namespace
  policyTypes:
  - Ingress
  - Egress
  ingress: []  # No ingress traffic allowed
  egress: []   # No egress traffic allowed

Pod Selection

Target specific pods:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes:
  - Ingress
  - Egress

Policy Types

  • Ingress: Controls incoming traffic to pods
  • Egress: Controls outgoing traffic from pods
  • Both: Restricts both directions

Ingress Rules

Allow All Ingress

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-ingress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress:
  - {}  # Empty rule allows all traffic

Allow from Specific Pods

Allow traffic from pods in the same namespace:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          tier: frontend
    ports:
    - protocol: TCP
      port: 8080

Allow from Namespace

Allow traffic from any pod in another namespace:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-monitoring
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: monitoring
    ports:
    - protocol: TCP
      port: 9090

Allow from External Networks

Allow traffic from specific external networks:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-external-api
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api-gateway
  policyTypes:
  - Ingress
  ingress:
  - from:
    - ipBlock:
        cidr: 203.0.113.0/24  # External network
        except:
        - 203.0.113.5/32  # Exclude specific IP
    ports:
    - protocol: TCP
      port: 443

Multiple Sources

Allow traffic from multiple sources:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-multiple-sources
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: database
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          tier: backend
    - podSelector:
        matchLabels:
          tier: admin
    - namespaceSelector:
        matchLabels:
          name: monitoring
    ports:
    - protocol: TCP
      port: 5432
    - protocol: TCP
      port: 9100  # Prometheus exporter

Egress Rules

Allow All Egress

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-egress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - {}  # Allow all egress

Restrict Egress to Specific Pods

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-egress
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: frontend
  policyTypes:
  - Egress
  egress:
  - to:
    - podSelector:
        matchLabels:
          tier: backend
    ports:
    - protocol: TCP
      port: 8080

Allow DNS and External Access

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-external
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: app
  policyTypes:
  - Egress
  egress:
  # DNS
  - to:
    - namespaceSelector:
        matchLabels:
          name: kube-system
    ports:
    - protocol: UDP
      port: 53
  # External HTTPS
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 169.254.169.254/32  # Block AWS metadata
    ports:
    - protocol: TCP
      port: 443

Namespace Isolation

Deny All Traffic to Namespace

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
  ingress: []
  egress: []

Apply to all namespaces:

# Create the policy in each namespace
for ns in production staging development; do
  kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: $ns
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
EOF
done

Allow Traffic Within Namespace

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-same-namespace
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector: {}  # Any pod in same namespace

Allow Ingress from Specific Namespace

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-ingress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx

Calico Implementation

Installing Calico

Install Calico as your CNI:

# Download Calico manifest
curl https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/tigera-operator.yaml -o tigera-operator.yaml

# Apply operator
kubectl apply -f tigera-operator.yaml

# Wait for operator to be ready
kubectl wait --for=condition=ready pod -l k8s-app=tigera-operator -n tigera-operator --timeout=300s

Install Calico custom resource:

apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  calicoNetwork:
    ipPools:
    - blockSize: 26
      cidr: 10.0.0.0/16
      encapsulation: VXLAN
      natOutgoing: Enabled
      nodeSelector: all()
    nodeAddressAutodetectionV4:
      interface: eth0

Calico NetworkPolicy Extensions

Calico supports additional NetworkPolicy features:

apiVersion: crd.projectcalico.org/v1
kind: NetworkPolicy
metadata:
  name: advanced-policy
  namespace: production
spec:
  selector: tier == "backend"
  types:
  - Ingress
  - Egress
  ingress:
  - action: Allow
    protocol: TCP
    destination:
      ports:
      - 8080
  egress:
  - action: Allow
    protocol: TCP
    destination:
      nets:
      - 10.0.0.0/8
  - action: Allow
    protocol: UDP
    destination:
      ports:
      - 53

Calico Policy Rules

Calico allows more granular control:

# View Calico workload endpoints
calicoctl get workloadendpoints -n production

# View Calico policies
calicoctl get networkpolicies -n production

# Describe a policy
calicoctl get networkpolicies production.allow-frontend -o yaml

Cilium Implementation

Installing Cilium

Install Cilium with Helm:

helm repo add cilium https://helm.cilium.io
helm repo update

helm install cilium cilium/cilium \
  --namespace kube-system \
  --set kubeProxyReplacement=disabled \
  --set k8sServiceHost=10.0.0.10 \
  --set k8sServicePort=6443 \
  --set image.tag=v1.14.0

Wait for Cilium to be ready:

kubectl rollout status -n kube-system ds/cilium

Cilium Network Policies

Cilium supports standard Kubernetes NetworkPolicy plus extended features:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          tier: frontend
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          tier: database
    ports:
    - protocol: TCP
      port: 5432
  - to:
    - namespaceSelector: {}
    ports:
    - protocol: UDP
      port: 53

Cilium CLI for Debugging

# Install cilium CLI
curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz
sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin

# Check Cilium status
cilium status

# Inspect connectivity between pods
cilium connectivity test

# View policy enforcement
cilium policy get

Practical Examples

Example: Three-Tier Application

Frontend, backend, and database isolation:

---
# Deny all by default
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress: []
---
# Allow external traffic to frontend
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-ingress
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: frontend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx
    ports:
    - protocol: TCP
      port: 80
    - protocol: TCP
      port: 443
---
# Allow frontend to backend
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backend
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          tier: frontend
    ports:
    - protocol: TCP
      port: 8080
---
# Allow backend to database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-database
  namespace: production
spec:
  podSelector:
    matchLabels:
      tier: database
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          tier: backend
    ports:
    - protocol: TCP
      port: 5432
---
# Allow DNS for all pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          name: kube-system
    ports:
    - protocol: UDP
      port: 53

Example: Multi-Namespace Communication

Allow specific namespaces to communicate:

---
# Label namespaces
# kubectl label namespace production environment=prod
# kubectl label namespace monitoring environment=monitoring
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-monitoring
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          environment: monitoring
    ports:
    - protocol: TCP
      port: 9090

Example: Microservices Communication

Complex service mesh with specific rules:

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: order-service-policy
  namespace: microservices
spec:
  podSelector:
    matchLabels:
      service: order
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          service: api-gateway
    ports:
    - protocol: TCP
      port: 8080
  egress:
  # To payment service
  - to:
    - podSelector:
        matchLabels:
          service: payment
    ports:
    - protocol: TCP
      port: 8080
  # To inventory service
  - to:
    - podSelector:
        matchLabels:
          service: inventory
    ports:
    - protocol: TCP
      port: 8080
  # To databases
  - to:
    - podSelector:
        matchLabels:
          service: postgres
    ports:
    - protocol: TCP
      port: 5432
  # DNS
  - to:
    - namespaceSelector:
        matchLabels:
          name: kube-system
    ports:
    - protocol: UDP
      port: 53

Conclusion

Network Policies are essential for implementing zero-trust networking in Kubernetes. By carefully planning your communication requirements and implementing deny-all-by-default policies with explicit allow rules, you create a secure network architecture. Whether using Calico for comprehensive network policy support or Cilium for advanced eBPF-based enforcement, proper network segmentation protects your applications on VPS and baremetal infrastructure. Start with liberal policies and gradually restrict traffic as you understand your application requirements, then maintain clear documentation of each policy's purpose and scope.