[{"data":1,"prerenderedAt":425},["ShallowReactive",2],{"blog-en-k3s-networkpolicy-dual-stack-kube-router-ipv6-gap":3,"blog-related-en-k3s-networkpolicy-dual-stack-kube-router-ipv6-gap":366,"blog-en-k3s-networkpolicy-dual-stack-kube-router-ipv6-gap-alt":354},{"id":4,"title":5,"author":6,"body":7,"date":348,"description":349,"extension":350,"image":351,"locale":352,"meta":353,"navigation":354,"path":355,"seo":356,"stem":357,"tags":358,"__hash__":365},"blog\u002Fblog\u002Fen\u002Fk3s-networkpolicy-dual-stack-kube-router-ipv6-gap.md","\"K3s Ignores NetworkPolicy\" Was a Myth. Traffic Still Leaked Through — the ServiceLB Blind Spot","Kubo Team",{"type":8,"value":9,"toc":334},"minimark",[10,15,23,43,50,54,69,82,95,99,105,118,143,160,164,170,181,186,205,209,234,238,244,250,265,301,309,313,316],[11,12,14],"h2",{"id":13},"we-wrote-a-deny-all-networkpolicy-and-external-traffic-still-got-through-normally","We Wrote a deny-all NetworkPolicy, and External Traffic Still Got Through Normally",[16,17,18],"p",{},[19,20],"img",{"alt":21,"src":22},"section01","https:\u002F\u002Fcdn.kubo.hexabase.io\u002Fimages\u002Fblog\u002Fen\u002Fk3s-networkpolicy-dual-stack-kube-router-ipv6-gap\u002Fsection01.webp",[16,24,25,26,30,31,34,35,38,39],{},"To bring a test environment closer to production, we applied a deny-all ingress policy with\n",[27,28,29],"code",{},"podSelector: {}"," to a namespace. Pod-to-pod traffic was blocked exactly as intended — a ",[27,32,33],{},"curl","\nfrom another namespace timed out, as expected. But when we ran the same connectivity check from\noutside the cluster against a Service exposed as ",[27,36,37],{},"type: LoadBalancer",", ",[40,41,42],"strong",{},"the response came back\nnormally, as if the NetworkPolicy didn't exist at all.",[16,44,45,46,49],{},"The moment you realize k3s networkpolicy isn't behaving as expected, the first thing you're\nlikely to suspect is the common explanation: \"K3s's default CNI, Flannel, ignores NetworkPolicy.\"\nIt's true that plain Flannel by itself has no mechanism to enforce NetworkPolicy. But the\nconclusion up front is this: ",[40,47,48],{},"that explanation doesn't apply to K3s."," K3s ships with a\nmechanism that enforces NetworkPolicy from the start. So why did the traffic leak? The cause\nwasn't the NetworkPolicy implementation itself — it was the path taken by ServiceLB, K3s's\nstandard LoadBalancer implementation.",[11,51,53],{"id":52},"correcting-the-myth-that-k3s-ignores-networkpolicy","Correcting the Myth That \"K3s Ignores NetworkPolicy\"",[16,55,56,57,64,65,68],{},"According to ",[58,59,63],"a",{"href":60,"rel":61},"https:\u002F\u002Fdocs.k3s.io\u002Fnetworking\u002Fnetworking-services",[62],"nofollow","K3s's official documentation",",\nK3s bundles ",[40,66,67],{},"kube-router's NetworkPolicy controller as a library, enabled by default",", in\naddition to Flannel. It doesn't use kube-router's other features (such as BGP) — only the\ncontroller component that enforces NetworkPolicy is pulled in. In other words, the moment you\nstand up a K3s cluster, without installing any additional CNI, applying a NetworkPolicy resource\nactually takes effect. The simplification \"K3s = Flannel, therefore NetworkPolicy is ignored\" is\nwrong — and this is in fact an important difference from a plain vanilla Flannel-only setup\n(for example, installing bare Flannel via kubeadm).",[16,70,71,72,75,76,81],{},"If you want to disable this built-in controller, you need to explicitly pass\n",[27,73,74],{},"--disable-network-policy"," at server startup. ",[58,77,80],{"href":78,"rel":79},"https:\u002F\u002Fdocs.k3s.io\u002Fnetworking\u002Fbasic-network-options",[62],"K3s's official networking options\ndocumentation"," recommends combining this\nflag when switching to a CNI with its own policy engine, such as Calico or Cilium, to avoid\nconflicting with the built-in controller.",[16,83,84,85,88,89,94],{},"In other words, the very fact that the deny-all policy in our test environment was working\n",[40,86,87],{},"partially"," is itself evidence that K3s's built-in controller was functioning correctly. The\nproblem wasn't whether the controller existed — it was somewhere else. The work of checking\ndocumentation every time to confirm \"what's enabled by default\" is, on its own, a quiet\noperational cost of running a K3s cluster yourself. With a managed K3s such as ",[58,90,93],{"href":91,"rel":92},"https:\u002F\u002Fkubo.hexabase.io\u002F",[62],"Kubo",",\nthis kind of default-configuration verification can be absorbed as the platform's responsibility.",[11,96,98],{"id":97},"the-real-culprit-is-servicelb-the-networkpolicy-api-cannot-block-traffic-from-a-pods-own-node","The Real Culprit Is ServiceLB. The NetworkPolicy API Cannot Block Traffic From a Pod's Own Node",[16,100,101],{},[19,102],{"alt":103,"src":104},"section02","https:\u002F\u002Fcdn.kubo.hexabase.io\u002Fimages\u002Fblog\u002Fen\u002Fk3s-networkpolicy-dual-stack-kube-router-ipv6-gap\u002Fsection02.webp",[16,106,107,108,113,114,117],{},"The ",[58,109,112],{"href":110,"rel":111},"https:\u002F\u002Fkubernetes.io\u002Fdocs\u002Fconcepts\u002Fservices-networking\u002Fnetwork-policies\u002F",[62],"official Kubernetes NetworkPolicy documentation","\ncontains a line that's easy to miss but important: ",[40,115,116],{},"Pods cannot block access to localhost, and\nthey have no way to block traffic originating from their own resident node."," This isn't a\nK3s-specific constraint — it's a permanent characteristic of the Kubernetes NetworkPolicy API\nitself, and no CNI or policy controller can eliminate it.",[16,119,120,121,124,125,127,128,131,132,137,138,142],{},"The reason this specification tends to surface especially in K3s comes down to how ",[40,122,123],{},"ServiceLB","\n(formerly known as Klipper LB), K3s's standard LoadBalancer implementation, works. When you\ncreate a Service of ",[27,126,37],{},", ServiceLB deploys a DaemonSet Pod on each node, and\nthat Pod ",[40,129,130],{},"writes DNAT rules into the node's own iptables"," to forward traffic bound for the\nhost port to the Service's ClusterIP\n(",[58,133,136],{"href":134,"rel":135},"https:\u002F\u002Fgithub.com\u002Fk3s-io\u002Fk3s\u002Fdiscussions\u002F9927",[62],"What does ServiceLB (Klipper LB) actually do? · Discussion #9927",",\n",[58,139,141],{"href":60,"rel":140},[62],"Networking Services | K3s",").",[16,144,145,146,149,150,155,156,159],{},"In other words, traffic that arrives via a LoadBalancer can be classified, from the Pod's\nperspective, as ",[40,147,148],{},"\"traffic forwarded by the node itself.\""," Combined with the \"node-resident\ntraffic cannot be blocked\" constraint described above, this means ingress deny rules in a\nNetworkPolicy can fail to work as expected against traffic that arrived externally via a\nLoadBalancer. In fact, one K3s user reported exactly this scenario in\n",[58,151,154],{"href":152,"rel":153},"https:\u002F\u002Fgithub.com\u002Fk3s-io\u002Fk3s\u002Fissues\u002F7624",[62],"K3s Issue #7624",": an ",[27,157,158],{},"ipBlock"," deny rule meant to\nblock access to a LoadBalancer Service had no effect. The same setup reportedly did block\ntraffic as intended on AWS EKS, which supports the idea that the LoadBalancer implementation\nitself affects whether NetworkPolicy is actually enforced.",[11,161,163],{"id":162},"steps-to-detect-this","Steps to Detect This",[16,165,166],{},[19,167],{"alt":168,"src":169},"section03","https:\u002F\u002Fcdn.kubo.hexabase.io\u002Fimages\u002Fblog\u002Fen\u002Fk3s-networkpolicy-dual-stack-kube-router-ipv6-gap\u002Fsection03.webp",[16,171,172,173,176,177,180],{},"This kind of problem shows up as perfectly normal in ",[27,174,175],{},"kubectl get networkpolicy",", and ",[27,178,179],{},"apply","\nsucceeds without any error. It's the quiet leaking that makes it tricky. The following steps\nmake it easier to notice.",[182,183,185],"h3",{"id":184},"check-pod-to-pod-traffic-and-loadbalancer-traffic-separately","Check Pod-to-Pod Traffic and LoadBalancer Traffic Separately",[187,188,189,196,202],"ul",{},[190,191,192,195],"li",{},[40,193,194],{},"From another Pod in the same cluster",", access the target Pod directly and confirm it's\nblocked by the NetworkPolicy",[190,197,198,201],{},[40,199,200],{},"From outside the cluster, via the LoadBalancer Service",", access the same Pod and compare\nwhether it's blocked",[190,203,204],{},"If only the latter gets through, the cause is unlikely to be a NetworkPolicy misconfiguration —\nit's more likely that the path itself falls outside the scope of policy evaluation",[182,206,208],{"id":207},"check-the-iptables-rules-on-the-node","Check the iptables Rules on the Node",[16,210,211,212,215,216,219,220,223,224,229,230,233],{},"The NetworkPolicy-related iptables rules written by kube-router and the DNAT rules written by\nServiceLB live in separate chains. Running ",[27,213,214],{},"iptables-save"," on the target node and checking both\nthe ",[27,217,218],{},"KUBE-ROUTER","-prefixed chains and the ServiceLB DNAT rules (managed by the ",[27,221,222],{},"svc-","-prefixed\nPods) lets you trace which path the traffic is actually taking. According to\n",[58,225,228],{"href":226,"rel":227},"https:\u002F\u002Fwww.kube-router.io\u002Fdocs\u002Ftroubleshoot\u002F",[62],"kube-router's official troubleshooting guide",",\ntraffic rejected by NetworkPolicy is logged via ",[40,231,232],{},"iptables NFLOG group 100"," — comparing whether\na rejection is actually being logged, versus traffic getting through with no rejection logged,\nis another way to separate the two paths.",[11,235,237],{"id":236},"fixes-enforce-at-l7-or-switch-to-a-different-cni","Fixes — Enforce at L7, or Switch to a Different CNI",[16,239,240],{},[19,241],{"alt":242,"src":243},"section04","https:\u002F\u002Fcdn.kubo.hexabase.io\u002Fimages\u002Fblog\u002Fen\u002Fk3s-networkpolicy-dual-stack-kube-router-ipv6-gap\u002Fsection04.webp",[16,245,246,249],{},[40,247,248],{},"Option 1: Don't rely on L4 NetworkPolicy — control access at L7","\nUsing an Ingress controller's or a service mesh's access-control features gives you more\nflexible restriction by source IP or path. This lets you enforce access control on\nexternally-exposed endpoints while sidestepping the node-resident traffic limitation.",[16,251,252,255,256,259,260,142],{},[40,253,254],{},"Option 2: Switch to a CNI with more advanced NetworkPolicy capabilities","\nSwitching to a CNI with a more advanced policy engine, such as Calico's GlobalNetworkPolicy, is\nanother option. In that case, explicitly pass ",[27,257,258],{},"--flannel-backend=none --disable-network-policy","\nso it doesn't conflict with the built-in kube-router controller\n(",[58,261,264],{"href":262,"rel":263},"https:\u002F\u002Fgithub.com\u002Fk3s-io\u002Fk3s\u002Fissues\u002F1308",[62],"Issue #1308",[16,266,267,268,271,272,277,278,281,282,284,285,288,289,294,295,300],{},"There's a caveat when switching, though. ",[40,269,270],{},"Disabling the network policy controller does not\nautomatically clean up the iptables rules kube-router had already written","\n(",[58,273,276],{"href":274,"rel":275},"https:\u002F\u002Fgithub.com\u002Fk3s-io\u002Fk3s\u002Fissues\u002F7244",[62],"Issue #7244","). If old rules remain in place while\nthe new CNI's rules are added on top, unintended traffic behavior can result. Running\n",[27,279,280],{},"k3s-killall.sh",", or manually cleaning up with ",[27,283,214],{},"\u002F",[27,286,287],{},"iptables-restore",", must be built\ninto the switchover procedure. Implementation details of kube-router's NetworkPolicy controller\nare documented in the ",[58,290,293],{"href":291,"rel":292},"https:\u002F\u002Fwww.kube-router.io\u002Fdocs\u002Fuser-guide\u002F",[62],"official kube-router user guide",",\nand the basic approach to running NetworkPolicy on K3s is summarized in the\n",[58,296,299],{"href":297,"rel":298},"https:\u002F\u002Fwww.suse.com\u002Fc\u002Francher_blog\u002Fk3s-network-policy\u002F",[62],"official SUSE\u002FRancher blog",".",[16,302,303,304,308],{},"Establishing this kind of CNI switchover procedure from scratch for every individual cluster,\nincluding verifying leftover iptables rules, is operationally costly. ",[58,305,307],{"href":91,"rel":306},[62],"Kubo Cloud","\nprovides CNI configuration and NetworkPolicy enforcement status visualized through Captain UI,\nso you don't have to verify these switchover decisions from zero every time.",[11,310,312],{"id":311},"conclusion-verify-the-assumption-of-secure-by-default-path-by-path","Conclusion — Verify the Assumption of \"Secure by Default,\" Path by Path",[16,314,315],{},"K3s doesn't ignore NetworkPolicy. If anything, thanks to its built-in kube-router controller, it's\none of the few distributions where NetworkPolicy works with no additional setup. But \"NetworkPolicy\nis enabled\" and \"every traffic path is subject to it\" are two different things. In this case, the\nfact that K3s's standard ServiceLB forwards traffic through the node's iptables, combined with the\nNetworkPolicy API's permanent inability to block node-resident traffic, meant that traffic to an\nexternally-exposed service was quietly leaking through.",[16,317,318,319,322,323,328,329,300],{},"This kind of \"gap between paths\" tends to appear more often the more infrastructure you assemble\nyourself. ",[58,320,93],{"href":91,"rel":321},[62]," aims to provide K3s-based managed Kubernetes where\noperations teams don't need to individually verify CNI configuration and NetworkPolicy enforcement\ncoverage. For environments with strict data sovereignty or network isolation requirements,\n",[58,324,327],{"href":325,"rel":326},"https:\u002F\u002Fwww.hexabase.com\u002Fproduct\u002Fkubo\u002Fon-premise",[62],"Kubo On-Premise"," lets you use the same\nverified configuration on your own infrastructure. The habit of questioning \"secure by default\"\nassumptions, path by path, is a useful mindset across Kubernetes operations generally, not just\nK3s. If you'd like to reduce the cost of verifying your network configuration, feel free to reach\nout via ",[58,330,333],{"href":331,"rel":332},"https:\u002F\u002Fwww.hexabase.com\u002Fcontact-us\u002F",[62],"Contact Us",{"title":335,"searchDepth":336,"depth":336,"links":337},"",2,[338,339,340,341,346,347],{"id":13,"depth":336,"text":14},{"id":52,"depth":336,"text":53},{"id":97,"depth":336,"text":98},{"id":162,"depth":336,"text":163,"children":342},[343,345],{"id":184,"depth":344,"text":185},3,{"id":207,"depth":344,"text":208},{"id":236,"depth":336,"text":237},{"id":311,"depth":336,"text":312},"2026-09-24","K3s enforces NetworkPolicy by default via its built-in kube-router controller. Yet traffic via LoadBalancer still leaked through — the cause was ServiceLB's implementation combined with a permanent limitation of the NetworkPolicy API. Here's how to detect and fix it.","md","https:\u002F\u002Fcdn.kubo.hexabase.io\u002Fimages\u002Fblog\u002Fen\u002Fk3s-networkpolicy-dual-stack-kube-router-ipv6-gap\u002Ftitle.webp","en",{},true,"\u002Fblog\u002Fen\u002Fk3s-networkpolicy-dual-stack-kube-router-ipv6-gap",{"title":5,"description":349},"blog\u002Fen\u002Fk3s-networkpolicy-dual-stack-kube-router-ipv6-gap",[359,360,361,362,363,364],"k3s","kubernetes","networkpolicy","servicelb","kube-router","network-security","NqVBu-tqKtOqVfiILiLl1tlDLu_CIbjN4i-1IDg0_7A",[367,378,387,396,405,415],{"path":368,"title":369,"description":370,"date":371,"tags":372},"\u002Fblog\u002Fen\u002Fk3s-vs-kubernetes-cni-flannel-default-comparison","K3s vs Standard Kubernetes: How Different Their CNI Starting Points Really Are","K3s ships with Flannel built in, while standard Kubernetes starts with no CNI selected at all. This comparison, grounded in the official K3s, Kubernetes, Calico, and Cilium docs, explains how that difference affects NetworkPolicy enforcement and network performance.","2026-10-20",[359,360,373,374,375,376,361,377],"cni","flannel","calico","cilium","networking",{"path":379,"title":380,"description":381,"date":382,"tags":383},"\u002Fblog\u002Fen\u002Fk3s-pod-security-admission-restricted-servicelb-outage","We Enabled Restricted and ServiceLB Died: The K3s Pod Security Admission Gap No Generic Guide Mentions","Enabling the restricted Pod Security Admission profile on K3s following standard Kubernetes guides can silently kill ServiceLB. Here's why, backed by K3s's own CIS hardening guide, and how to roll it out safely.","2026-10-16",[359,360,384,385,362,386],"pod-security-admission","pod-security-standards","security",{"path":388,"title":389,"description":390,"date":391,"tags":392},"\u002Fblog\u002Fen\u002Fk3s-cert-manager-servicelb-http01-challenge-renewal-failure","K3s Wouldn't Renew Its cert-manager Certificate. ServiceLB Was Stealing the HTTP-01 Challenge","cert-manager certificates on K3s stopped renewing, and neither Traefik nor cert-manager itself was the cause — ServiceLB was occupying ports 80\u002F443. Here's how the HTTP-01 challenge silently fails, and how to fix it for good.","2026-09-25",[359,360,393,362,394,395],"cert-manager","tls","lets-encrypt",{"path":397,"title":398,"description":399,"date":400,"tags":401},"\u002Fblog\u002Fen\u002Fk3s-traefik-servicelb-ingress-nginx-port-conflict","You Disabled Traefik on K3s, But ingress-nginx Still Can't Grab Port 443. Meet ServiceLB, the Roommate You Forgot About","You ran the K3s disable-traefik flag but ingress-nginx still can't bind port 443. The culprit is ServiceLB (klipper-lb), another default K3s component. Here's how to diagnose it, disable it correctly, and what changes if you're running MetalLB.","2026-09-19",[359,360,402,362,403,404],"traefik","ingress-nginx","load-balancer",{"path":406,"title":407,"description":408,"date":409,"tags":410},"\u002Fblog\u002Fen\u002Fk3s-hpa-containerresource-sidecar-utilization-dilution","HPA Was Scaling on the Sidecar's Share Too. The ContainerResource Blind Spot in Kubernetes","Kubernetes HPA sums resource usage across every container in a Pod, so Istio sidecars and logging agents dilute the scaling decision. Here's how the ContainerResource metric fixes it, in a K3s\u002FKubo context.","2026-10-21",[359,360,411,412,413,414],"hpa","containerresource","autoscaling","service-mesh",{"path":416,"title":417,"description":418,"date":419,"tags":420},"\u002Fblog\u002Fen\u002Fk3s-topology-spread-constraints-poddisruptionbudget-node-drain-pending","We Drained a Node and a Pod Never Came Back. Why Topology Spread Constraints and PodDisruptionBudget Collide on K3s","Draining a node in production K3s left a Pod stuck Pending. The cause: Topology Spread Constraints' maxSkew colliding with PodDisruptionBudget. We verify how draining itself creates a node shortage, based on official documentation.","2026-10-19",[359,360,421,422,423,424],"topology-spread-constraints","poddisruptionbudget","scheduling","node-drain",1792592638029]