My First Attempt at Kubernetes Network Policies
So I’ve been learning Kubernetes for a while now, and one thing that kept confusing me was network security. Like, how do you actually control which pods can talk to each other? Today I decided to dive into Network Policies.
Here’s what I learned.
Wait, Why Do We Even Need Network Policies?
Here’s something that surprised me: by default, every pod in Kubernetes can talk to every other pod. No restrictions. Nothing.
When I first read that, I was like… really? That seems like a security nightmare. And it kind of is! Imagine if a malicious container got into your cluster — it could potentially access everything.
So Network Policies are basically how you set up rules about which pods can communicate with each other. Think of it like setting up firewall rules, but for your Kubernetes cluster.
What I’m Working With
For this experiment, I’m using:
- A Kubernetes cluster (set up with kubeadm on Ubuntu)
- kubectl command-line tool
- A network plugin that supports Network Policies (important: not all of them do!)
One thing I learned the hard way: Network Policies only work if your network plugin supports them. Calico and Romana do, but you’ll want to check your setup before spending time on policies that won’t actually work.
Getting Started
First things first, I needed to make sure my cluster was actually running properly:
watch kubectl get nodesThis command watches your nodes come online. Once I saw my worker node (the one with ROLES showing <none>), I pressed Ctrl+C to stop watching.
Experiment 1: Blocking That Scary Metadata Endpoint
Okay, so here’s something that freaked me out a bit. If you’re running Kubernetes on AWS (which I am), there’s this special IP address — 169.254.169.254—that AWS uses for something called the "metadata endpoint."
Any container can access it and grab AWS credentials. Yeah. Not great.
So I started by looking at a Network Policy that was already set up to block this:
kubectl get networkpolicy deny-metadata -o yamlThe important part looks like this:
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 169.254.169.254/32Let me explain what I understood from this (it took me a while):
podSelector: {}means this applies to ALL pods in the namespacepolicyTypes: - Egressmeans we're controlling OUTGOING trafficipBlockis saying: allow connections to all IPs EXCEPT that metadata endpoint
Basically, pods can reach the internet, but not that one specific scary IP address.
Quick Tip: Understanding the YAML
If you ever want to know what fields you can use in Network Policies, there’s a super helpful command:
kubectl explain networkpolicy.spec.egressYou can dig deeper into specific fields too:
kubectl explain networkpolicy.spec.egress.to
kubectl explain networkpolicy.spec.egress.to.ipBlockThese commands saved me so much time instead of googling documentation!
Testing If It Actually Works
Time to see if this policy actually does anything. I created a simple test pod:
kubectl run busybox --image=busybox --rm -it /bin/shThis spins up a temporary pod and drops me into a shell. First, I tried reaching Google:
wget https://google.comThat worked fine! Now let’s try the metadata endpoint:
wget 169.254.169.254Nothing. It just hung there. The policy is blocking it! I pressed Ctrl+C to cancel and typed exit to leave the pod.
Get Pooja’s stories in your inbox
Join Medium for free to get updates from this writer.
Okay, cool. The policy works… but wait.
Here’s Where I Got Confused
Network Policies are “namespaced” — meaning they only apply to pods in the same namespace. I didn’t fully understand this at first, so I tested it:
kubectl create namespace test
kubectl run busybox --image=busybox --rm -it -n test /bin/shNow from this NEW namespace, I tried the metadata endpoint again:
wget 169.254.169.254That’s when it clicked — the deny-metadata policy was only protecting the default namespace. Any other namespace could still access the metadata endpoint.
Why This Matters (The Scary Part)
Just to see how bad this could be, I tried grabbing actual AWS credentials:
role=$(wget -qO- 169.254.169.254/latest/meta-data/iam/security-credentials)
wget -qO- 169.254.169.254/latest/meta-data/iam/security-credentials/$roleAnd yep, there they were. Real AWS credentials, just sitting there for the taking.
Lesson learned: You need to apply Network Policies to EVERY namespace, not just the default one. Also, only use trusted container images from private registries.
Experiment 2: Controlling Traffic Between Pods
Okay, so blocking external IPs is one thing. But what about controlling which pods can talk to each other inside your cluster?
Let’s say I have a simple app with two parts:
- A web server (running Nginx)
- A cache layer (we’ll pretend with busybox)
I only want the cache to be able to talk to the web server. Nothing else should get through.
Creating My First Network Policy
Here’s the policy I wrote (honestly, I looked at some examples first):
cat > app-policy.yaml <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-tiers
namespace: test
spec:
podSelector:
matchLabels:
app-tier: web
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app-tier: cache
ports:
- port: 80
EOFBreaking this down in plain English:
- This applies to pods with the label
app-tier: web - We’re controlling INCOMING traffic (
Ingress) - Only allow traffic FROM pods labeled
app-tier: cache - And only on port 80
Let me apply it:
kubectl create -f app-policy.yamlSetting Up the Test Environment
Now I need some actual pods to test with. First, the web server:
kubectl run web-server -n test -l app-tier=web --image=nginx:1.15.1 --port 80That -l app-tier=web part is important—it's the label that the Network Policy is looking for.
Test 1: The Pod That Should Work
Time to test! I’ll create a pod with the cache label and see if it can reach the web server:
# First, grab the web server's IP address
web_ip=$(kubectl get pod -n test -o jsonpath='{.items[0].status.podIP}')# Create a cache-tier pod
kubectl run busybox -n test -l app-tier=cache --image=busybox --env="web_ip=$web_ip" --rm -it /bin/shInside the pod, I tried connecting:
wget $web_ip
exitIt worked! The cache pod can talk to the web server. That’s exactly what we wanted.
Test 2: The Pod That Should Fail
Now let’s try from a pod WITHOUT the cache label:
kubectl run busybox -n test --image=busybox --env="web_ip=$web_ip" --rm -it /bin/shInside this pod:
wget $web_ipAnd… it just hangs. Forever. The Network Policy is blocking it.
I pressed Ctrl+C and typed exit.
This is when it really clicked for me — labels are SUPER important in Kubernetes. That one label difference (app-tier: cache vs no label) is the difference between access granted and access denied.
What I Learned Today
After spending a few hours with Network Policies, here are my main takeaways:
Network Policies are namespaced
This one caught me off guard. A policy in the default namespace won't protect pods in other namespaces. You need to apply them everywhere.
Labels are everything
Seriously. The whole system relies on labels. If you mess up a label on a pod, your entire security model falls apart.
Default = Allow Everything
Without Network Policies, any pod can talk to any other pod. Once you add a policy, it switches to “deny by default” for that specific pod. Only what you explicitly allow gets through.
Check your network plugin first
Network Policies literally don’t work unless your CNI supports them. I’m using Calico, which does, but not all plugins support this feature.
The metadata endpoint is actually dangerous
Before today, I didn’t realize how easy it was to grab AWS credentials from inside a pod. Definitely blocking that in production.
I’ll probably write more as I learn. For now, I’m just happy I got Network Policies working without breaking everything.
If you’re also learning Kubernetes and found this helpful, let me know! And if I got something wrong (totally possible), please leave a comment so I can learn.
Follow me for more Kubernetes learning adventures!







