Warrior, you've reached a very important production-security chapter.
We learned in Chapter 11 that Kubernetes networking generally allows Pods to communicate across the cluster.
That's powerfulโbut it can also be dangerous.
Imagine:
Frontend Pod
Backend Pod
Worker Pod
Redis Pod
PostgreSQL Pod
Monitoring PodIf networking is completely unrestricted, a compromised Pod may be able to attempt connections to many other workloads.
We want:
Frontend โ Backend โ
Backend โ PostgreSQL โ
Backend โ Redis โ
Worker โ PostgreSQL โ
Frontend โ PostgreSQL โ
Frontend โ Redis โ
Random Pod โ PostgreSQL โThis is what NetworkPolicy helps us achieve.
A Kubernetes NetworkPolicy is an API object used to control network traffic to and/or from Pods.
In simple terms:
NetworkPolicy acts like a firewall for Pod network traffic.
Mental model:
NetworkPolicy
โ
โโโโโโโโโโโดโโโโโโโโโโ
โผ โผ
Ingress Egress
โ โ
Incoming Outgoing
traffic trafficThese two words are extremely important.
Traffic coming into a Pod.
Client
โ
โ incoming
โผ
Backend PodThat's ingress.
Traffic leaving a Pod.
Backend Pod
โ
โ outgoing
โผ
PostgreSQLThat's egress.
So:
Ingress = IN
Egress = OUTSuppose:
Frontend
โ
โผ
Backend
โ
โโโโโโโโบ PostgreSQL
โ
โโโโโโโโบ RedisWe can define:
Frontend
โ
โ allowed
โผ
Backend
Backend
โ
โโโ allowed โ PostgreSQL
โโโ allowed โ Redis
Frontend
โ
โโโ blocked โ PostgreSQLThis creates network segmentation.
This is a very important Kubernetes concept.
Creating:
kind: NetworkPolicydoesn't magically create firewall rules unless your cluster's networking implementation supports and enforces NetworkPolicy.
The actual enforcement is provided by the cluster's network plugin/CNI.
Examples include:
So:
NetworkPolicy object
โ
CNI / networking implementation
โ
Actual traffic enforcementLet's start with a simple policy.
Suppose:
app=backendWe want to control incoming traffic to those Pods.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontendLet's understand this carefully.
podSelector#podSelector:
matchLabels:
app: backendThis selects the Pods that the policy protects.
So:
app=backendmeans:
Backend Pod A
Backend Pod B
Backend Pod Care affected.
policyTypes#policyTypes:
- IngressThis means we're defining ingress behavior.
We're controlling:
Incoming trafficto the selected Pods.
from#from:
- podSelector:
matchLabels:
app: frontendThis says:
Allow traffic from Pods labeled
app=frontend.
So:
Frontend
โ
โ โ
โผ
BackendBut:
Random Pod
โ
โ โ
โผ
Backendassuming the policy is otherwise isolating ingress to the selected backend Pods.
We can make the policy more specific.
Suppose Node.js backend listens on:
3000Then:
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 3000Now:
Frontend โ Backend:3000 โ
but a different port isn't allowed by this rule.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-ingress
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 3000Architecture:
production
โ
โโโโโโโโโโโโดโโโโโโโโโโโ
โผ โผ
Frontend Backend
app=frontend app=backend
โ โฒ
โ TCP:3000 โ
โโโโโโโโโโโโโโโโโโโโโโโ
โ
NetworkPolicies are generally additive.
Suppose you have:
Policy A โ allow Frontend
Policy B โ allow WorkerThen the resulting allowed traffic is effectively:
Frontend โ
Worker โ
not:
Only Worker โAdding another policy doesn't automatically override the previous one.
Think:
Policy A
+
Policy B
+
Policy C
=
Combined allowed rulesNow we reach one of the most important production patterns:
Default deny
Instead of saying:
Allow everything
except bad trafficyou can use:
Deny by default
and explicitly allow required trafficThis is much closer to a least-privilege networking model.
Suppose we want to deny incoming traffic to all Pods in production unless another policy explicitly allows it.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- IngressNotice:
podSelector: {}Empty selector means:
Select all Pods in the namespace.
So:
All production Pods
โ
โผ
Default deny ingresspodSelector: {}?#This:
podSelector: {}means:
All Pods in this namespaceWhereas:
podSelector:
matchLabels:
app: backendmeans:
Only backend PodsYou can also deny outgoing traffic by default.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- EgressNow:
Production Pods
โ
โผ
Outgoing traffic blocked
unless explicitly allowedFor strong segmentation:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- EgressThis gives you:
Ingress โ denied by default
Egress โ denied by defaultThen you explicitly allow what is needed.
A very common conceptual model is:
Step 1
โโโโโโ
Deny everything
โ
Step 2
โโโโโโ
Allow frontend โ backend
โ
Step 3
โโโโโโ
Allow backend โ database
โ
Step 4
โโโโโโ
Allow backend โ Redis
โ
Step 5
โโโโโโ
Allow required DNS / external servicesThis is much safer than:
Everything โ EverythingSo far we've used:
podSelector:But what if the source Pod is in another namespace?
You can use:
namespaceSelector:Example:
from:
- namespaceSelector:
matchLabels:
team: frontendMeaning:
Allow traffic from Pods in namespaces labeled
team=frontend.
You can combine:
from:
- namespaceSelector:
matchLabels:
environment: production
podSelector:
matchLabels:
app: frontendThis means:
Allow Pods matching
app=frontendfrom namespaces matchingenvironment=production.
This is useful for larger clusters.
This can be confusing:
from:
- namespaceSelector:
matchLabels:
environment: production
podSelector:
matchLabels:
app: frontendThe two selectors under the same list item are combined.
Think:
Namespace must match
AND
Pod must matchfrom Entries#Consider:
from:
- podSelector:
matchLabels:
app: frontend
- podSelector:
matchLabels:
app: workerThis means:
Frontend OR Workercan access the selected destination, subject to the specified ports.
So:
Frontend โโโ
โโโโบ Backend
Worker โโโโโLet's create a realistic application.
production
โ
โโโ frontend
โ
โโโ backend
โ
โโโ postgres
โ
โโโ redisDesired network:
Frontend
โ
โผ
Backend
โ
โโโโบ PostgreSQL
โ
โโโโบ RedisBlocked:
Frontend โโXโโโบ PostgreSQL
Frontend โโXโโโบ Redis
Random Pod โโXโโโบ Backend
Random Pod โโXโโโบ PostgreSQLSuppose PostgreSQL listens on:
5432Policy:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: postgres-policy
namespace: production
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 5432Result:
Backend
โ
โ TCP 5432
โผ
PostgreSQL
โ
Redis normally listens on:
6379Policy:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: redis-policy
namespace: production
spec:
podSelector:
matchLabels:
app: redis
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 6379Now:
Backend โโTCP 6379โโโบ Redis
โ
Imagine an attacker compromises:
Frontend PodWithout network restrictions:
Frontend
โ
โโโโบ Backend
โโโโบ Redis
โโโโบ PostgreSQLWith NetworkPolicy:
Frontend
โ
โโโโบ Backend
โ
โโโโบ Redis
โโโโบ PostgreSQLThis limits lateral movement.
Very important:
NetworkPolicy doesn't replace:
Database passwords
TLS
Authentication
AuthorizationFor example:
NetworkPolicy
โ Can this Pod establish network traffic?
Database authentication
โ Does this client have valid credentials?You should use both.
These solve different problems.
Controls:
Who can interact with Kubernetes API?Controls:
Which Pods can communicate over the network?So:
RBAC
โ Kubernetes API permissions
NetworkPolicy
โ Network traffic permissionsIngress protects incoming connections.
But you also need to consider outgoing traffic.
Imagine:
Backend Pod
โ
โโโโบ PostgreSQL
โโโโบ Redis
โโโโบ External API
โโโโบ InternetDo you really want the backend to talk to everything?
Maybe not.
You could restrict egress.
Suppose backend should access PostgreSQL:
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432And:
policyTypes:
- EgressNow:
Backend
โ
โโโโบ PostgreSQL:5432
โ
Other outbound traffic may be denied if the Pod is isolated for egress and no other policy allows it.
Suppose your Node.js backend needs:
PostgreSQL
Redis
OpenAI API
Google API
DNS
MonitoringIf you enable strict default-deny egress:
Backend
โ
โโโ PostgreSQL โ
โโโ Redis โ
โโโ OpenAI โ
โโโ Google โ
โโโ DNS โuntil you explicitly allow required destinations.
This is why NetworkPolicy rollout should be tested carefully.
Kubernetes applications commonly use DNS:
backend-service
postgres-service
redis-serviceIf you block all egress and forget DNS, your application may stop resolving service names.
Typical cluster DNS traffic uses:
UDP 53
TCP 53depending on the DNS path and implementation.
A policy may need to allow traffic to the cluster DNS service.
A common conceptual rule looks like:
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53Exact labels and DNS architecture should be verified for your cluster.
The important idea is:
Application
โ
โผ
DNS
โ
โผ
Service name โ Service IPYour backend might use:
postgres-serviceinstead of:
10.42.1.27If DNS is blocked:
postgres-service
โ
โ Can't resolveApplication fails.
A Service doesn't bypass NetworkPolicy.
Remember:
Client
โ
Service
โ
PodNetworkPolicy applies to Pod traffic.
For example:
Frontend
โ
backend-service
โ
Backend PodThe policy needs to allow the actual traffic reaching the backend Pods.
Suppose:
Internet
โ
Ingress Controller
โ
frontend-service
โ
Frontend PodYour NetworkPolicy needs to account for the actual source Pods involved in the traffic path.
You cannot simply assume:
"Internet"is represented as a normal Kubernetes Pod selector.
Ingress controllers often run as Pods themselves, so policies may need to permit traffic from the controller's namespace/labels.
A production design might start with:
All ingress โ DENY
All egress โ DENYThen:
Ingress Controller โ Frontend
Frontend โ Backend
Backend โ PostgreSQL
Backend โ Redis
Backend โ DNS
Backend โ required external services
Monitoring โ required endpointsEverything else:
โThis is a powerful security architecture.
INTERNET
โ
โผ
Ingress Controller
โ
โผ
Frontend Pods
โ
TCP 3000
โ
โผ
Backend Pods
/ \
/ \
TCP 5432 TCP 6379
โ โ
โผ โผ
PostgreSQL RedisNetwork rules:
Ingress Controller โ Frontend โ
Frontend โ Backend โ
Backend โ PostgreSQL โ
Backend โ Redis โ
Frontend โ PostgreSQL โ
Frontend โ Redis โ
Random Pod โ PostgreSQL โ
Random Pod โ Redis โapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 3000This protects backend ingress.
Then PostgreSQL has its own policy:
backend โ PostgreSQLRedis has:
backend โ RedisThis is much easier to reason about than one gigantic policy.
Another common misconception.
Creating:
kind: NetworkPolicydoesn't mean all traffic in the cluster is suddenly blocked.
Isolation happens for Pods selected by policies and for the relevant direction.
For example:
podSelector:
matchLabels:
app: backendmeans:
Backend Pods are the selected targetsOther Pods aren't automatically selected by that policy.
Remember Chapter 8:
Namespaceisn't automatically a network security boundary.
Without NetworkPolicy:
Namespace A
โ
โโโโโโบ Namespace Bmay be able to communicate.
With NetworkPolicy:
Namespace A
โ
โ policy
โผ
Namespace Byou can explicitly control communication.
A useful security philosophy is:
Don't trust network location by default.
Instead of:
Inside cluster = trustedthink:
Every communication path
must be explicitly justified.For example:
Frontend โ Backend
allowed
Frontend โ DB
not allowedThis is network-level least privilege.
These can work together for defense in depth.
For example:
Backend Pod
โ
โโโ ServiceAccount
โ โ
โ RBAC
โ
โโโ NetworkPolicy
โ
Allowed trafficEven if one layer is misconfigured, another layer may reduce exposure.
But neither replaces the other.
ingress:
- {}This can effectively allow all ingress to selected Pods.
Use carefully.
Result:
Service name
โ
DNS fails
โ
Application failsPolicy:
app: frontendActual Pod:
app: webResult:
Policy doesn't select intended Pods.Labels are critical.
You may write:
namespaceSelector:but the namespace doesn't have the expected label.
Traffic gets blocked.
Backend:
3000Policy allows:
8080Result:
Connection blockedThe cluster's networking implementation must support NetworkPolicy enforcement.
Suppose:
Frontend โ Backendis failing.
Check:
Pods:
kubectl get pods -n production --show-labelsPolicies:
kubectl get networkpolicy -n productionInspect:
kubectl describe networkpolicy backend-policy \
-n productionCheck Services:
kubectl get svc -n productionCheck DNS:
nslookup backend-serviceTest connectivity from a Pod:
curl http://backend-service:3000If:
Frontend โ Backenddoesn't work:
Frontend Pod
โ
โผ
DNS resolution?
โ
โผ
Service exists?
โ
โผ
Service has endpoints?
โ
โผ
Backend Pod ready?
โ
โผ
NetworkPolicy allows?
โ
โผ
Correct port?Don't immediately blame NetworkPolicy.
A Kubernetes API object used to define allowed network traffic to and/or from selected Pods, enforced by a compatible networking implementation.
Ingress = incoming traffic
Egress = outgoing trafficpodSelector do?#Selects Pods to which the policy applies.
podSelector: {} mean?#Select all Pods in the policy's namespace.
Yes, using namespace selectors and appropriate policy rules.
No.
RBAC โ Kubernetes API permissions
NetworkPolicy โ Network trafficNo.
Database authentication is separate.
Deny traffic by default for selected Pods/directions, then explicitly allow required communication.
Because applications may need DNS and external/internal services that have not yet been explicitly allowed.
A compatible cluster networking implementation/CNI.
NetworkPolicy
โ
โโโ Ingress
โ โ
โ Incoming
โ
โโโ Egress
โ
OutgoingpodSelector:namespaceSelector:ports:
- protocol: TCP
port: 3000podSelector: {}
policyTypes:
- Ingressor:
podSelector: {}
policyTypes:
- Egressor both.
For your Node.js/Next.js architecture:
INTERNET
โ
โผ
Ingress Controller
โ
โผ
Frontend
โ
โ allowed
โผ
Backend
/ \
/ \
allowed allowed
โผ โผ
PostgreSQL RedisNetworkPolicy:
Internet โ Frontend controlled
Frontend โ Backend โ
Backend โ PostgreSQL โ
Backend โ Redis โ
Frontend โ PostgreSQL โ
Frontend โ Redis โ
Random Pod โ Backend โ
Random Pod โ PostgreSQL โAnd security overall:
Kubernetes Security
โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโ
โผ โผ โผ
RBAC NetworkPolicy Container Security
โ โ โ
โผ โผ โผ
API access Network access Runtime access
โ โ โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโ
โผ
Least PrivilegeRBAC controls "who can use the Kubernetes API"; NetworkPolicy controls "who can talk to whom over the network."
We'll move into Observability and learn how to answer:
"My Pod is running, but something is wrong. How do I find out what happened?"
We'll cover:
kubectl logs
Container logs
Multi-container logs
Previous/crashed container logs
Logging architecture
Centralized logging
ELK / EFK
Fluent Bit
Loki
Structured JSON logs
Correlation IDs
Log levels
Production Node.js loggingAnd we'll build a practical logging architecture for a Node.js + Next.js + Kubernetes application.
Mark it as complete to track your mastery.