Kubernetes Network Policies Explained (and How I Actually Made Them Work)

Kubernetes Network Policies Explained (and How I Actually Made Them Work)

1 3 13
calendar_today agoschedule4 min read

If you've ever wondered, "How do I stop my pods from randomly talking to everything in my cluster?" – this is the post for you.

Today, I dove into Kubernetes Network Policies – the way you control who can talk to whom inside your cluster. I started with weavenet, then switched to Calico, and I'm excited to walk you through how it all came together.


What Even Is a Network Policy?

A NetworkPolicy in Kubernetes is like a firewall rule for your pods. It lets you:

  • Control inbound traffic (who can talk to a pod)
  • Control outbound traffic (where a pod can talk to)

For example:

  • You can create a deny-all policy that blocks all incoming traffic to a pod.
  • Or an allow-only policy that says: "Only the backend pods can talk to the mysql pod on port 3306."

Without network policies, by default, everything can talk to everything in your cluster. That's fine for demos, terrible for real security.


Why I Needed a Different CNI

Kubernetes itself doesn't implement network policies. It just defines them. The actual work is done by your CNI plugin (Container Network Interface).

I started with weavenet, which supports basic network policies. But as I dug deeper, I realized I wanted something more powerful and production-friendly. That's when I switched to Calico, which is:

  • Fast
  • Scalable
  • Great for complex network policies
  • Works well with kind clusters (as long as you configure it correctly)

If you're just experimenting, weavenet is fine. If you want to level up your cluster networking, Calico is a great next step.


My Test Cluster: A Kind Setup with Calico

To test network policies, I created a simple kind cluster with:

  • 1 control-plane node
  • 2 worker nodes
  • Disabled default CNI (disableDefaultCNI: true)
  • Custom pod subnet: 192.168.0.0/16

kind Cluster YAML

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 30001
        hostPort: 30001
  - role: worker
  - role: worker
networking:
  disableDefaultCNI: true
  podSubnet: "192.168.0.0/16"

Creating the Cluster

With the YAML saved as kind-config.yaml, I created the cluster like this:

kind create cluster --name day26 --config kind-config.yaml

Then I checked the nodes:

kubectl get nodes

And here's the part that was a bit confusing at first:

Until I installed a CNI (like Calico), the worker nodes stayed in a NotReady state.

That's because Kubernetes needs a CNI to:

  • Assign IPs to pods
  • Set up networking between nodes
  • Enable traffic to flow across the cluster

Without a CNI, the control-plane node can be Ready, but the worker nodes will remain NotReady until networking is configured.

Only after installing Calico did all nodes show as Ready:

kubectl get nodes
# NAME           STATUS   ROLES           AGE   VERSION
# day26-control-plane   Ready    control-plane   ...
# day26-worker          Ready    worker          ...
# day26-worker2         Ready    worker          ...

Once Calico was running, my cluster was ready for real network policy experiments.


The App I Built: Frontend → Backend → MySQL

To make network policies meaningful, I created a tiny 3-tier app:

  • frontend pod + service
  • backend pod + service
  • mysql pod + service (db service)

Pods & Services

apiVersion: v1
kind: Pod
metadata:
  name: frontend
  labels:
    role: frontend
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
        - name: http
          containerPort: 80
          protocol: TCP
***
apiVersion: v1
kind: Service
metadata:
  name: frontend
  labels:
    role: frontend
spec:
  selector:
    role: frontend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
***
apiVersion: v1
kind: Pod
metadata:
  name: backend
  labels:
    role: backend
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
        - name: http
          containerPort: 80
          protocol: TCP
***
apiVersion: v1
kind: Service
metadata:
  name: backend
  labels:
    role: backend
spec:
  selector:
    role: backend
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
***
apiVersion: v1
kind: Service
metadata:
  name: db
  labels:
    name: mysql
spec:
  selector:
    name: mysql
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306
***
apiVersion: v1
kind: Pod
metadata:
  name: mysql
  labels:
    name: mysql
spec:
  containers:
    - name: mysql
      image: mysql:latest
      env:
        - name: "MYSQL_USER"
          value: "mysql"
        - name: "MYSQL_PASSWORD"
          value: "mysql"
        - name: "MYSQL_DATABASE"
          value: "testdb"
        - name: "MYSQL_ROOT_PASSWORD"
          value: "verysecure"
      ports:
        - name: http
          containerPort: 3306
          protocol: TCP

At this point, everything could talk to everything:

  • frontend could reach backend
  • backend could reach mysql
  • frontend could also reach mysql directly (which we probably don't want)

Now it was time to lock it down.


My First Real Network Policy: Only Backend → MySQL

The goal:

  • mysql should only accept traffic from pods with label role: backend
  • On port 3306
  • No one else (including frontend) should touch the database

NetworkPolicy Manifest

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-test
spec:
  podSelector:
    matchLabels:
      name: mysql
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              role: backend
      ports:
        - port: 3306

Key parts:

  • podSelector: targets the mysql pod (name: mysql)
  • policyTypes: [Ingress]: we're controlling inbound traffic
  • ingress.from.podSelector: only pods with role: backend are allowed
  • ports: only port 3306

After applying this:

  • backendmysql works
  • frontendmysql blocked
  • mysql → anywhere (outbound) still allowed (we didn't restrict Egress)

How I Tested It

I used a simple approach:

  1. Run a temporary pod with curl or mysql client.
  2. Try to reach services from different pods.
  3. Observe which connections succeed or fail.

Example:

# From frontend pod – should fail
kubectl exec -it frontend -- curl -s http://db:3306

# From backend pod – should succeed
kubectl exec -it backend -- curl -s http://db:3306

Seeing the first one fail and the second one succeed was the "aha!" moment: the policy is actually working.


Wrapping Up

Network Policies are one of those things that feel abstract until you actually apply them and see connections blocked in real time. Once that happens, you suddenly understand:

  • Why security matters
  • Why "everything can talk to everything" is not a strategy
  • How Kubernetes gives you granular control over your cluster's traffic
Part 7 of 7 in My Kubernetes Journey
🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work

Dharanidharan - Feb 9

Understanding Kubernetes ClusterRoles and ClusterRoleBindings

AYANFE - Jul 7

Understanding Kubernetes RBAC: Roles, RoleBindings, and and Client Certificates

AYANFE - Jul 6

What Is SARIF and How Does It Help Security Tools Work Together?

Ganesh Kumar - Jul 4

Understanding Kubernetes Service Accounts (The Identity Every Pod Uses)

AYANFE - Jul 7
chevron_left
328 Points17 Badges
Abuja,Nigeria.
9Posts
3Comments
6Connections
A gentleman with a rough edge.

Related Jobs

View all jobs →

Commenters (This Week)

1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!