【K8S 运维实战】28-网络策略NetworkPolicy

📅 2026/8/1 1:27:52
【K8S 运维实战】28-网络策略NetworkPolicy
网络策略:NetworkPolicy 与零信任一句话定位:K8s 默认所有 Pod 全互通,这就好比办公室没墙——NetworkPolicy 就是给 Pod 之间砌墙、开门的施工队。写在前面我处理过一个真实事故:一个业务 Pod 被植入了挖矿程序,然后它用nmap扫了一遍集群内网,发现 Redis 没密码,直接连上去把数据全删了。事后复盘,根因就是没有 NetworkPolicy——所有 Pod 默认全通,一个 Pod 失守等于全集群失守。K8s 的网络模型默认是扁平大二层,任何 Pod 都能直接访问任何 Pod,这对你调试方便,对攻击者更方便。NetworkPolicy 就是 K8s 原生的网络隔离能力,但它能力有限,生产级零信任还得靠 Calico 或 Cilium 的扩展 CRD。这篇我们从原生 NetworkPolicy 讲到 Calico/Cilium 的全局策略,搭一套能落地的零信任分段模型。核心问题Pod 之间默认全通,怎么用 NetworkPolicy 收敛成按需放开的零信任模型?一、原理剖析1.1 NetworkPolicy 的语义模型NetworkPolicy 是 K8s 原生的网络隔离资源,但它的语义有几个反直觉的点,必须先理清楚:NetworkPolicy 作用域podSelector 选中的 Pod策略类型ingress 入站规则egress 出站规则from: 谁能访问我to: 我能访问谁podSelector: 哪些 PodnsSelector: 哪些 namespaceipBlock: 哪些 IP 段port: 哪些端口核心语义:默认允许:如果一个 Pod 没被任何 NetworkPolicy 选中,它的流量是全通的(ingress egress 都允许)选即隔离:一旦某 Pod 被某条 NetworkPolicy 选中,那么未在规则里显式允许的流量就会被拒绝ingress 和 egress 独立:一条策略可以只管 ingress、只管 egress,或都管白名单逻辑:NetworkPolicy 只能允许,不能拒绝——你没写进规则的流量,默认就是拒绝1.2 NetworkPolicy 的三大限制原生 NetworkPolicy 有三个硬伤,踩过坑的人都懂:┌─────────────────────────────────────────────────────────────┐ │ 原生 NetworkPolicy 的三大限制 │ ├──────────┬──────────────────────────────────────────────────┤ │ 限制1 │ 只管控流量,不管入口还是出口都靠规则放行 │ │ 语义不全 │ 无法表达拒绝所有外网这种黑名单语义 │ │ │ 只能写只允许内网白名单 │ ├──────────┼──────────────────────────────────────────────────┤ │ 限制2 │ 无跨 namespace 的全局策略 │ │ 作用域 │ 每个 NetworkPolicy 只作用于一个 namespace │ │ │ 想做集群级默认拒绝,得在每个 ns 里写一遍 │ ├──────────┼──────────────────────────────────────────────────┤ │ 限制3 │ 不支持 L7 协议过滤(HTTP method/路径等) │ │ 仅L3/L4 │ 只能按 IP/端口/标签过滤,做不了按 URL 过滤 │ │ │ 想按 HTTP 路径分权,要用 Cilium/Linkerd 的 L7 策略 │ └──────────┴──────────────────────────────────────────────────┘这三条限制决定了:纯原生 NetworkPolicy 能做基础隔离,但生产级零信任必须上 Calico 或 Cilium 的扩展 CRD。1.3 Calico 与 Cilium 的扩展能力对比主流 CNI 都实现了 NetworkPolicy,但能力差异很大:能力对比矩阵 原生 K8s NP Calico Cilium ───────────────────────────────────────────────────────── L3/L4 过滤 ✓ ✓ ✓ 全局策略(集群级) ✗ ✓ GNP ✓ CCNP IP 池管理 ✗ ✓ IPPool ✓ CiliumPool L7 HTTP 过滤 ✗ ✗(企业版有) ✓(开源版) FQDN 过滤 ✗ ✓(企业版) ✓(开源版) 可视化/审计 ✗ ✗ ✓ Hubble 性能 iptables iptables/ebpf ebpf ─────────────────────────────────────────────────────────选型上,如果你要 L7 能力和可视化,选 Cilium;如果你要成熟的 iptables 兼容性和 Calico 生态,选 Calico。两者都满足零信任分段的基本需求。1.4 零信任分段模型零信任的核心原则是从不信任,始终验证。落到 K8s 网络上,就是默认拒绝,按需放行。我用一个典型业务架构来说明分段模型:┌──────────────────────────────────────────────────────────────┐ │ 零信任网络分段模型 │ ├──────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ingress ┌──────────────┐ │ │ │ 外部流量 │ ────────────── │ Ingress │ │ │ │ (互联网) │ │ Controller │ │ │ └─────────────┘ └──────┬───────┘ │ │ │ 仅允许 │ │ ▼ │ │ ┌──────────────┐ │ │ │ frontend │ │ │ │ (web层) │ │ │ └──────┬───────┘ │ │ │ 仅允许 8080 │ │ ▼ │ │ ┌──────────────┐ │ │ │ backend │ │ │ │ (API层) │ │ │ └──────┬───────┘ │ │ │ 仅允许 6379 │ │ ▼ │ │ ┌──────────────┐ │ │ │ redis │ │ │ │ (数据层) │ │ │ └──────────────┘ │ │ │ │ 规则: │ │ - frontend 只接受 Ingress 的流量 │ │ - backend 只接受 frontend 的流量 │ │ - redis 只接受 backend 的流量 │ │ - 所有 Pod 默认禁止访问外网(egress 全拒) │ │ │ └──────────────────────────────────────────────────────────────┘这个模型的特点:纵向分层:每层只能被上一层访问,跨层访问被拒绝横向隔离:同层不同 app 的 Pod 互相访问被拒绝出站收敛:所有 Pod 默认不能访问外网,只有需要联网的 Pod(如调第三方API)才单独放行数据库最严:数据库 Pod 只接受特定 backend 的特定端口二、实战操作2.1 默认拒绝基线策略零信任的第一步是默认拒绝。先给每个 namespace 建立一个全拒绝基线策略:# default-deny-all.yaml# 作用:选中该 namespace 所有 Pod,拒绝所有 ingress 和 egressapiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:default-deny-allnamespace:productionspec:podSelector:{}# 选中所有 PodpolicyTypes:-Ingress-Egressingress:[]# 空数组 拒绝所有入站egress:[]# 空数组 拒绝所有出站kubectl apply-fdefault-deny-all.yaml# 验证:任何 Pod 都无法访问其他 Podkubectlexec-nproduction deploy/test --curl-m3http://frontend# 应该超时或连接被拒绝注意:这个策略一上,业务立刻就挂——因为连 DNS 都访问不了。所以基线策略要和放行策略一起上。2.2 放行 DNS 和必要的出站默认拒绝后,必须先放行 DNS,否则所有域名解析都挂:# allow-dns-egress.yamlapiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:allow-dns-egressnamespace:productionspec:podSelector:{}policyTypes:-Egressegress:-to:-namespaceSelector:matchLabels:kubernetes.io/metadata.name:kube-systempodSelector:matchLabels:k8s-app:kube-dnsports:-protocol:UDPport:53-protocol:TCPport:53注意namespaceSelector里用的是kubernetes.io/metadata.name,这是 K8s 1.21 自动给 namespace 打的 label,可以直接按名字选 namespace,不用自己手动打。2.3 同 app 互通策略同一个 app 的 Pod 之间需要互通(比如 frontend 的多个副本之间):# allow-same-app.yaml# 作用:允许同一 namespace 内,相同 app 标签的 Pod 互相访问apiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:allow-same-appnamespace:productionspec:podSelector:{}policyTypes:-Ingressingress:-from:-podSelector:{}# 允许同 ns 内所有 Pod# 如果想严格同 app 才互通,用下面这种:# - podSelector:# matchExpressions:# - key: app# operator: Exists更严格的同 app 互通写法:# allow-intra-app.yaml# 用 namespaceSelector podSelector 的组合,精确匹配同 appapiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:allow-frontend-internalnamespace:productionspec:podSelector:matchLabels:app:frontendpolicyTypes:-Ingressingress:-from:-podSelector:matchLabels:app:frontend# 只允许 frontend 访问 frontendports:-protocol:TCPport:80802.4 跨 namespace 白名单典型场景:monitoring namespace 的 Prometheus 要访问所有业务 namespace 的 metrics 端口。# allow-prometheus-scrape.yaml# 放在业务 namespace(如 production)apiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:allow-prometheus-scrapenamespace:productionspec:podSelector:matchLabels:app:backend# backend 暴露 metrics 给 PrometheuspolicyTypes:-Ingressingress:-from:-namespaceSelector:matchLabels:kubernetes.io/metadata.name:monitoringpodSelector:matchLabels:app:prometheusports:-protocol:TCPport:9090# metrics 端口反过来,如果业务 namespace 想放行 monitoring 的所有 Pod(不只是 prometheus),去掉 podSelector:ingress:-from:-namespaceSelector:matchLabels:kubernetes.io/metadata.name:monitoring# 没有 podSelector monitoring namespace 所有 Pod 都能访问ports:-protocol:TCPport:90902.5 Calico GlobalNetworkPolicy:集群级默认拒绝原生 NetworkPolicy 必须 per-namespace,Calico 的 GlobalNetworkPolicy(GNP)可以集群级生效,适合做基线默认拒绝:# default-deny-cluster.yamlapiVersion:crd.projectcalico.org/v1kind:GlobalNetworkPolicymetadata:name:default-denyspec:selector:all()# 选中所有节点上的所有 Podtypes:-Ingress-Egress# 不写 ingress/egress 规则 全部拒绝GNP 还能用 GlobalNetworkSet 做 IP 黑白名单:# global-networkset.yamlapiVersion:crd.projectcalico.org/v1kind:GlobalNetworkSetmetadata:name:corporate-vpn-ipslabels:role:corporate-vpnspec:nets:-10.10.0.0/16-203.0.113.0/24---# 只允许 VPN 网段访问管理接口apiVersion:crd.projectcalico.org/v1kind:GlobalNetworkPolicymetadata:name:allow-admin-from-vpnspec:selector:app admin-paneltypes:-Ingressingress:-action:Allowsource:selector:role corporate-vpn# 引用 GlobalNetworkSet-action:Deny# Calico 支持 Deny,原生不支持source:selector:all()Calico 的action: Deny是原生 NetworkPolicy 没有的能力,能直接写黑名单,这对拒绝外网访问场景特别好用。2.6 Cilium CiliumClusterwideNetworkPolicy如果你用 Cilium,集群级策略用CiliumClusterwideNetworkPolicy(CCNP):# cilium-default-deny.yamlapiVersion:cilium.io/v2kind:CiliumClusterwideNetworkPolicymetadata:name:default-denyspec:description:集群级默认拒绝所有流量endpointSelector:{}# 所有 endpointingress:[]# 拒绝所有入站egress:[]# 拒绝所有出站---# 放行 DNSapiVersion:cilium.io/v2kind:CiliumClusterwideNetworkPolicymetadata:name:allow-dnsspec:endpointSelector:{}egress:-toEndpoints:-matchLabels:k8s:io.kubernetes.pod.namespace:kube-systemk8s:k8s-app:kube-dnstoPorts:-ports:-port:53protocol:UDP-port:53protocol:TCPCilium 的 L7 能力是它最大的亮点,可以直接按 HTTP 路径过滤:# cilium-l7-http.yaml# 只允许 backend 访问 redis 的 GET 命令,禁止 DEL/FLUSHDBapiVersion:cilium.io/v2kind:CiliumNetworkPolicymetadata:name:redis-http-restrictnamespace:productionspec:endpointSelector:matchLabels:app:backendegress:-toEndpoints:-matchLabels:app:redistoPorts:-ports:-port:6379protocol:TCPrules:l7proto:redisl7:-command:[GET]# 只允许 GET2.7 拒绝外网访问生产环境大多 Pod 不需要访问外网,必须收敛。原生 NetworkPolicy 只能写白名单,逻辑是只允许内网 间接拒绝外网:# deny-external-traffic.yaml# 思路:egress 只允许集群内网段,等于拒绝外网apiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:deny-external-egressnamespace:productionspec:podSelector:{}policyTypes:-Egressegress:# 1. 允许集群内 Pod 通信-to:-ipBlock:cidr:10.244.0.0/16# Pod CIDR-to:-ipBlock:cidr:10.96.0.0/12# Service CIDR# 2. 允许 DNS(上面已配,这里可以省略)# 3. 不写公网网段 默认拒绝公网更优雅的写法用 Calico 的 deny 规则:apiVersion:crd.projectcalico.org/v1kind:GlobalNetworkPolicymetadata:name:deny-public-internetspec:selector:all()types:-Egressegress:-action:Allowdestination:nets:-10.0.0.0/8# 内网-172.16.0.0/12-192.168.0.0/16-action:Denydestination:notNets:-10.0.0.0/8-172.16.0.0/12-192.168.0.0/162.8 策略审计与可视化策略写多了,没人知道哪条策略在生效,出了问题排查地狱。Cilium 的 Hubble 是目前最好的可视化工具:# 安装 Hubble(假设已装 Cilium)cilium hubbleenablecilium hubble port-forward# 命令行查看流量hubble observe-nproduction--typeflow# 查看 denied 的流量(排查策略问题)hubble observe-nproduction--verdictDROPPED# 查看特定 Pod 的流量hubble observe--podbackend-xxx# 启动 UIhubble ui# 浏览器访问 http://localhost:12023Hubble 的价值在于能看到流量为什么被丢弃——每条 DROPPED 流量都关联到具体的策略,不用猜。Cilium 还有个杀手锏叫policy mode,可以模拟策略效果而不真正执行:# 切到 audit 模式,只记录不拦截cilium configsetPolicyAuditModetruecilium daemon-restart# 观察一段时间,确认没有误杀hubble observe--verdictDROPPED# 确认无误后切回 enforce 模式cilium configsetPolicyAuditModefalsecilium daemon-restart三、踩坑与排查踩坑 1:NetworkPolicy 不生效现象:写了 default-deny-all,但 Pod 之间还是能 ping 通。原因1:CNI 不支持。NetworkPolicy 必须由 CNI 实现,如果用的是 Flannel(纯 Flannel,不是 Calico on Flannel),它压根不实现 NetworkPolicy,写了等于没写。定位:# 看用的什么 CNIkubectl get pods-nkube-system|grep-Ecalico|cilium|flannel|weave# 如果只有 flannel,NetworkPolicy 不生效解决:换 CNI。生产环境推荐 Calico 或 Cilium。如果不想换,可以装 Calico 的 policy-only 模式,只负责策略不负责 IPAM:# Calico policy-only 模式安装(配合 Flannel 网络)kubectl apply-fhttps://docs.projectcalico.org/manifests/calico-policy-only.yaml踩坑 2:default-deny 一上,DNS 全挂现象:应用 default-deny-all 后,所有 Pod 报 DNS 解析失败。原因:NetworkPolicy 的 egress 把 DNS 的出站流量也拒了。Pod 解析域名要先连 kube-dns 的 ClusterIP,但 ClusterIP 是虚拟 IP,默认拒绝策略把这条流量也拦了。解决:必须先把 allow-dns-egress 策略和 default-deny-all 一起上。顺序上,如果已经上了 deny-all,马上补 DNS 策略:kubectl apply-fdefault-deny-all.yaml kubectl apply-fallow-dns-egress.yaml# 立刻补上更稳妥的做法是把两个策略写在一个文件里一起 apply。踩坑 3:跨 namespace 策略不生效现象:写了namespaceSelector: matchLabels: kubernetes.io/metadata.name: monitoring,但 Prometheus 还是访问不了业务 Pod。原因1:monitoring namespace 是老集群建的,没有kubernetes.io/metadata.name这个自动 label。这个 label 是 K8s 1.21 自动加的,老 namespace 没有。定位:kubectl get ns monitoring --show-labels# 如果没有 kubernetes.io/metadata.namemonitoring,就是这个问题解决:手动补 label,或者用自定义 label:kubectl label ns monitoring kubernetes.io/metadata.namemonitoring# 或者用自定义 label,策略里也对应改kubectl label ns monitoringrolemonitoring原因2:namespaceSelector 和 podSelector 在同一个from项里是AND关系,不是OR。新人经常踩这个坑:# 错误写法:这个表示monitoring namespace 中,appprometheus 的 Pod# 是 AND 关系ingress:-from:-namespaceSelector:matchLabels:kubernetes.io/metadata.name:monitoringpodSelector:matchLabels:app:prometheus# 如果想monitoring namespace 所有 Pod OR 任意 namespace 中 appprometheus 的 Pod# 要写两个 from 项:ingress:-from:-namespaceSelector:matchLabels:kubernetes.io/metadata.name:monitoring-podSelector:matchLabels:app:prometheus踩坑 4:策略太多导致性能下降现象:集群规模上去后(1000 Pod,几百条 NetworkPolicy),Pod 间延迟明显升高,网络吞吐下降。原因:基于 iptables 的实现(原生 NP、Calico iptables 模式),每条策略都会转成 iptables 规则,策略多了 iptables 规则爆炸,每个包都要遍历大量规则。定位:# 看 iptables 规则数量iptables-save|wc-l# 生产环境如果超过 5 万行,性能会明显下降# Calico 看规则数calicoctlnodestatus解决:切到 eBPF 模式。Calico 和 Cilium 都支持 eBPF 数据面,绕开 iptables:# Cilium 安装时指定 datapathciliuminstall--datapath-modetunnel# Calico 开启 ebpf# 修改 calico 配置kubectl patch configmap calico-config-nkube-system\-p{data:{dataplane:ebpf}}合并策略。一条 NetworkPolicy 可以写多个 ingress 规则,比写多条 NetworkPolicy 更高效。四、最佳实践基线策略每个 namespace 一上线就配 default-deny-all,作为基线default-deny-all 必须和 allow-dns-egress 同时上生产集群用 Calico GNP 或 Cilium CCNP 做集群级默认拒绝策略先 audit 模式跑一周,确认无误杀再切 enforce分段模型按业务层分 namespace(web/api/data 隔离)同 namespace 内按 app label 分段数据库 Pod 最严格,只接受特定 backend 的特定端口出站默认全拒,需要联网的 Pod 单独放行策略编写用namespaceSelector跨 ns 放行,别用 IP( Pod IP 会变)用 label 而不是 IP 选 Pod,Pod 重建后策略仍生效端口要写明确,别用port: 0或不写 port(等于全端口放行)一个 app 的策略合并到一条 NetworkPolicy,减少策略数量审计可视化用 Cilium Hubble 做流量可视化,排查神器定期 review denied 流量,看有没有误杀或漏放策略变更走 GitOps,变更可追溯生产环境策略变更必须先在 staging 验证性能大集群(500 节点)用 eBPF 数据面,避开 iptables 性能瓶颈合并相似策略,减少规则总数监控 CNI 组件 CPU/内存,策略多了开销会上去ipBlock 规则尽量用大网段,别写大量小 IP五、小结K8s 默认网络全通,这是最大的攻击面。NetworkPolicy 是收敛的第一步,但原生能力有限——只能做 L3/L4 白名单,没有集群级策略,没有 deny 语义。生产级零信任要靠 Calico 或 Cilium 的扩展 CRD。落地零信任分三步走:基线默认拒绝:每个 namespace 上 default-deny-all,集群级用 GNP/CCNP按需放行:DNS 必放,业务流量按 app 标签和端口精确放行可视化审计:上 Hubble,每条 denied 流量都能关联到策略记住一个原则:NetworkPolicy 是白名单,你写了什么就只允许什么,没写的全是拒绝。所以策略要写全,别漏——漏一条就是漏洞。下一篇我们讲镜像安全,把供应链这块的风险也补上。思考题你的集群现在有没有 NetworkPolicy?如果没有,一个 Pod 被攻破后,攻击者能横向移动到哪些组件?原生 NetworkPolicy 写egress: []拒绝所有出站,DNS 也挂了,为什么?怎么解决?Calico 的action: Deny和原生 NetworkPolicy 的不写就是拒绝有什么本质区别?用 Hubble 看到一个 Pod 被 DROPPED,但不知道是哪条策略导致的,怎么查?延伸阅读K8s NetworkPolicy 官方文档:https://kubernetes.io/docs/concepts/services-networking/network-policies/Calico 策略文档:https://docs.tigera.io/calico/latest/network-policyCilium NetworkPolicy:https://docs.cilium.io/en/latest/network/kubernetes/policy/Hubble 可视化:https://docs.cilium.io/en/latest/observability/hubble/NIST 零信任架构白皮书:https://csrc.nist.gov/publications/detail/sp/800-207/final