Kubernetes NetworkPolicy 实战:默认全通的坑与用标签做零信任隔离
2026/8/8 12:57:29 网站建设 项目流程

Kubernetes NetworkPolicy 实战:默认全通的坑与用标签做零信任隔离

很多人第一次意识到 K8s 网络的默认行为时都会愣一下:同一个集群里,任意 Pod 都能访问任意其他 Pod。你的前端 Pod 能直连数据库 Pod,一个被攻破的边缘服务能横向扫遍整个命名空间。生产环境里这几乎等于没有网络隔离。

NetworkPolicy 就是给 Pod 之间的流量加防火墙规则的。这篇从「默认全通」的现场讲起,一步步收敛到「默认拒绝 + 白名单放行」的零信任模型。

前提:你的 CNI 得支持 NetworkPolicy

先泼一盆冷水:NetworkPolicy 是个「声明」,真正执行它的是 CNI 插件。如果你的集群用的是不支持NetworkPolicy 的 CNI(比如默认配置的 flannel),你写的策略会被 API Server 乖乖收下,然后完全不生效——这是最坑的地方,你以为隔离了,其实没有。

支持的 CNI:Calico、Cilium、Weave Net 等。快速自查:

# 看用的什么 CNIkubectl get pods-nkube-system|grep-E'calico|cilium|weave|flannel'

如果是 flannel,要么换 CNI,要么叠加 Calico 的 policy-only 模式。下面的例子假设你用的是 Calico/Cilium。

先看默认全通的现场

建两个 Pod,一个当「数据库」,一个当「攻击者」,验证默认能不能通:

kubectl create ns demo kubectl run db--image=nginx--labels="app=db"-ndemo kubectl run attacker--image=busybox-ndemo--command--sleep3600

拿到 db 的 IP,从 attacker 去连:

DB_IP=$(kubectl get pod db-ndemo-ojsonpath='{.status.podIP}')kubectlexec-ndemo attacker --wget-qO---timeout=3http://$DB_IP

会正常返回 nginx 首页 HTML。attacker 和 db 毫无关系,却能直连——这就是默认全通。

第一步:命名空间级默认拒绝

零信任的第一原则是「先关死,再开口子」。给整个命名空间加一条「拒绝所有入站流量」的兜底策略:

apiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:default-deny-ingressnamespace:demospec:# podSelector 为空 = 选中命名空间内所有 PodpodSelector:{}policyTypes:-Ingress# 只管入站;不写 Ingress 规则 = 拒绝所有入站

关键理解 NetworkPolicy 的语义:

  • 一旦有任何策略选中了某个 Pod,该 Pod 就从「默认全通」切换到「默认拒绝,只放行被明确允许的」。
  • podSelector: {}匹配命名空间内所有 Pod。
  • policyTypes: [Ingress]且没有ingress:规则 → 所有入站被拒。

应用后再连一次:

kubectl apply-fdefault-deny.yaml kubectlexec-ndemo attacker --wget-qO---timeout=3http://$DB_IP# wget: download timed out —— 通了,现在被拒了

第二步:只放行该放行的

现在 db 谁都连不上,包括合法的后端服务。我们只想让app=backend标签的 Pod访问 db 的 80 端口。用标签精确开口子:

apiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:allow-backend-to-dbnamespace:demospec:podSelector:matchLabels:app:db# 这条策略作用在 db 上policyTypes:-Ingressingress:-from:-podSelector:matchLabels:app:backend# 只允许 app=backend 的 Podports:-protocol:TCPport:80

验证:attacker(没有 backend 标签)仍然连不上,而新建一个带app=backend的 Pod 就能连:

kubectl run backend--image=busybox-ndemo--labels="app=backend"\--command--sleep3600kubectlexec-ndemo backend --wget-qO---timeout=3http://$DB_IP# 正常返回 nginx 首页 —— 白名单放行成功

这就是零信任:基于身份(标签)而非 IP 放行。Pod IP 会随重建变化,但标签是稳定的语义身份。

坑一:from 里 namespaceSelector 和 podSelector 的「与/或」

跨命名空间放行时最容易踩的坑——下面这两种写法含义完全不同:

# 写法 A:一个 from 元素里同时有两个 selector = 「与」ingress:-from:-namespaceSelector:matchLabels:env:prodpodSelector:matchLabels:app:backend# 含义:必须是 env=prod 命名空间里、且 app=backend 的 Pod# 写法 B:两个 from 元素 = 「或」ingress:-from:-namespaceSelector:matchLabels:env:prod-podSelector:matchLabels:app:backend# 含义:env=prod 命名空间的任意 Pod,或 本命名空间里 app=backend 的 Pod

写法 B 里第二个podSelector只作用于本命名空间,不会扩到 prod 命名空间。想「prod 命名空间里的 backend」必须用写法 A 那种同一元素内的组合。这个「短横线位置决定与/或」的坑,配错了要么放行过宽、要么该通的不通。

坑二:别忘了出站(Egress)和 DNS

上面只管了 Ingress。如果你还要加default-deny-egress锁死出站,会立刻发现一个惊喜:Pod 连 DNS 都解析不了了,因为 DNS 查询(到 kube-dns/CoreDNS 的 53 端口)也是出站流量,被一起拒了。

加 egress 默认拒绝时,记得放行 DNS:

apiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:allow-dns-egressnamespace:demospec:podSelector:{}policyTypes:-Egressegress:-to:-namespaceSelector:{}# 允许到 kube-system 里的 CoreDNSports:-protocol:UDPport:53-protocol:TCPport:53

不放行 53 端口的话,应用里所有http://service-name这种域名访问都会失败,而且报错常常是「connection timeout」而非「DNS 错误」,排查起来很误导。

小结

  • K8s 默认全通,同集群任意 Pod 互访;NetworkPolicy 是给 Pod 间流量加防火墙,但必须 CNI 支持(Calico/Cilium 可,默认 flannel 不可,且不生效还不报错)。
  • 零信任套路:先default-deny-ingress关死,再用podSelector按标签开白名单;基于标签身份放行,而非易变的 Pod IP。
  • 记牢「from 里短横线决定与/或」:同一元素内多个 selector 是「与」,多个元素是「或」,配错直接导致放行范围错。
  • 加 egress 默认拒绝时,第一件事是放行 53 端口的 DNS,否则所有域名访问静默超时。
  • 一句话记忆:NetworkPolicy 是白名单模型——只要有一条策略选中了 Pod,没被明确允许的流量就一律拒绝。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询