Network policies are how you control traffic flow between pods in your Kubernetes cluster.

Let’s see this in action. Imagine we have two deployments: frontend and backend.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: httpd
        image: httpd:latest
        ports:
        - containerPort: 80

By default, pods can talk to each other freely. If we kubectl exec into a frontend pod and try to curl http://backend:80, it will succeed.

Now, let’s enforce a network policy. We’ll create a NetworkPolicy resource that denies all ingress traffic to the backend pods by default, and then explicitly allows ingress from frontend pods.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-allow-frontend
  namespace: default # Make sure this matches your namespace
spec:
  podSelector:
    matchLabels:
      app: backend # This policy applies to pods with the label app=backend
  policyTypes:
  - Ingress # We are defining rules for incoming traffic
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend # Allow traffic only from pods with the label app=frontend
    ports:
    - protocol: TCP
      port: 80 # Allow traffic on TCP port 80

After applying this policy with kubectl apply -f your-policy.yaml, if we try to kubectl exec into the frontend pod and curl http://backend:80 again, it will fail with a connection refused error. The backend pod is now isolated.

If we wanted to allow traffic from any pod in the default namespace to the backend pod, we’d modify the from section:

ingress:
- from:
  - namespaceSelector: {} # Allows from any pod in any namespace

Or, to allow traffic from a specific namespace named monitoring:

ingress:
- from:
  - namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: monitoring # Allow from pods in the 'monitoring' namespace

Network policies are implemented by a network plugin that supports them. On GKE, this is typically Calico or GKE Dataplane V2 (which uses Cilium). When you create a network policy, the control plane translates this into rules that the network plugin enforces at the node level, often using iptables or eBPF.

The core concept is "default deny, explicit allow." When no network policies apply to a pod, all traffic is allowed. The moment any network policy selects a pod (either via podSelector or namespaceSelector in an ingress or egress rule), that pod becomes subject to the policy. If a pod is selected by a policy, all traffic to or from that pod is denied by default, unless explicitly allowed by a rule within that policy.

What most people don’t realize is that a pod can be selected by multiple network policies. In such cases, the pod must satisfy the rules of all the policies that select it. If a pod is selected by a policy that allows ingress from frontend and another policy that allows ingress from api-gateway, and the traffic is coming from frontend, it will be allowed. If the traffic is coming from a pod not matching either, it will be denied. This is sometimes called "union of rules".

The next step is to understand egress policies, which control traffic leaving a pod.

Want structured learning?

Take the full Gke course →