This episode explains why Flannel does not enforce NetworkPolicy and how to complete it: adding a policy engine such as Calico policy-only or Cilium on top of Flannel without replacing the CNI, then applying the first NetworkPolicy and verifying it.

Kubernetes provides NetworkPolicy as the standard for restricting traffic between Pods. But that standard only works if the CNI enforces it. This is where Flannel honestly admits its limits.
Episode 14 explains why Flannel does not enforce NetworkPolicy, then guides you through adding a policy engine on top of Flannel without replacing the CNI. You will install Calico policy-only, apply your first NetworkPolicy, and verify it.
NetworkPolicy needs an engine that filters packets based on Pod labels and keeps connection state. Flannel is designed as a simple data plane that only builds routes and overlays. Adding policy enforcement would break the simplicity that is the main reason people choose Flannel.
Instead of replacing the CNI, the common pattern is to add a separate policy engine. This engine reads NetworkPolicy from the API server, converts it into rules on the nodes, and filters traffic without interfering with Pod network creation. Flannel stays the data plane; the engine becomes the policy plane.
Calico provides a dedicated manifest that only deploys the policy components, without replacing the Flannel CNI:
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico-policy-only.yamlThe kubectl apply -f calico-policy-only.yaml command deploys the Calico daemons and components that enforce policy, while Pod network creation stays with Flannel.
Cilium can also act as a policy engine on top of Flannel in certain modes. The choice comes down to team preference and the features you need. What is certain is that both approaches avoid the far larger risk of a full CNI migration.
The most common example: a namespace may only receive traffic from a specific namespace:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-kube-system-only
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-systemThe policy above makes all Pods in that namespace only able to receive traffic from the kube-system namespace. All other traffic will be blocked by the policy engine.
kubectl apply -f allow-kube-system-only.yamlThe kubectl apply -f allow-kube-system-only.yaml command registers the policy with the API server. The policy engine then enforces it on every node.
Deploy two Pods in different namespaces and test connectivity. Pods in the namespace covered by the policy must not be reachable from other namespaces:
kubectl run nettest-a --image=busybox --restart=Never -- sleep 3600
kubectl exec -it nettest-a -- ping -c 2 <ip-pod-namespace-lain>If ping fails after the policy is applied, enforcement is working. Without a policy engine, ping would succeed because Flannel does not filter.
Adding a policy engine adds new components you must maintain: its daemon, RBAC, and metrics. Start with a loose policy and test carefully before tightening. A policy mistake is easier to prove than it is to fix in the middle of an incident.
Tip
Before enabling a default deny policy in production, make sure cluster monitoring, ingress, and DNS are on the allowlist. Otherwise, the whole cluster could lose connectivity in an instant.
After installing a policy engine, make sure it is actually enforcing rules. There are many cases where a policy is applied but has no effect because the engine is not running or the namespace labels do not match. Check the policy engine's Pods in your cluster:
kubectl get pods -n calico-system 2>/dev/null || kubectl get pods -n kube-system | grep -i calicoThe output of kubectl get pods -n calico-system shows the Calico components that enforce policy. Make sure they are all in the Running state.
Do not settle for a single test. Create additional scenarios: allow traffic from one specific namespace, then block another namespace. Every policy change should be tested in staging before being used in production, just like the GitOps practices in episode 18.
Episode 14 completes Flannel's security with a policy plane: understanding Flannel's limits with NetworkPolicy, adding Calico policy-only or Cilium on top of it, and applying a first policy that actually works.
Key takeaways:
In the next episode, episode 15, we will secure the host layer: firewall and host networking — the ports that must be opened for VXLAN, WireGuard, and IPsec in both the host firewall and the cloud, as well as how to pick the right host interface with --iface and avoid IP conflicts.