首页 / 资讯中心 / 文章详情

Kubernetes网络策略落地指南:CNI选型、故障排查与设计套路

Kubernetes网络策略落地指南:CNI选型、故障排查与设计套路 ★ FEATURED ARTICLE
一开始接触 Kubernetes 网络策略Network Policies的时候我其实挺不屑的——不就是给 Pod 之间加白名单嘛写几个 yaml 规则罢了能有多复杂直到我在某公司接手一个多服务集群测试环境里一切正常一上生产就发现服务之间疯狂互相访问安全评审被画了一堆红叉我才意识到这东西远没有看起来那么简单。网络策略的难点不在怎么写规则而在于怎么判断一个规则真实生效了怎么不让策略误伤正常流量以及怎么和你的 CNI 插件、服务网格好好相处。这篇内容我会把实际落地过程中反复踩过的坑、验证过的方法和最终沉淀下来的设计套路一次性都说清楚。我默认看这篇东西的你是已经能熟练操作 kubectl、看得懂基础 yaml 的运维或开发如果还是新手也别慌碰到基础概念我会顺带解释。这篇内容不会只停留在NetworkPolicy 是什么的层面重点会放在选型、调试、案例和兼容性这些真正影响你落地的环节。1. 先想清楚一件事网络策略到底在网络的哪一层干活很多人把 NetworkPolicy 和传统防火墙搞混以为它是类似 iptables 那种包过滤配了就能挡住一切。这个认知偏差是后续所有问题的根源。实际上 NetworkPolicy 是一个API 层面的准入声明它本身不干活真正干活的是你集群里的 CNI 插件——比如 Calico、Cilium、Weave Net 这些。也就是说你写的那一堆 yaml 规则本质上是给 CNI 插件看的意图由插件翻译成具体的底层规则比如 iptables 或 eBPF 程序去执行。同一个 NetworkPolicy 规则在不同 CNI 插件下的执行机制、性能表现和 debug 手段完全不同。比如 Calico 默认走 iptables规则多了之后节点上的 iptables 链条会膨胀得吓人Cilium 走 eBPF性能好但你要排查问题就得会看 eBPF 的 metricsWeave Net 则对 Netpol 的支持在一定版本里都不完整。所以你在选型 CNI 的时候其实就已经变相决定了你未来要面对的网络策略体验。1.1 一个最容易忽略的前提CNI 必须支持 NetworkPolicy这句话听起来像废话但真的有一大批人栽在这。某次我去帮一个朋友排查问题他用的 CNI 是 Flannel配了 NetworkPolicy 之后发现完全不生效搞了整整两天最后发现 Flannel 官方压根不支持 NetworkPolicy原生的 Flannel 是不支持的某些分支或者组合方案才支持。这不是个例很多入门者默认Kubernetes 自带网络策略功能却不知道它依赖底层实现。所以第一个实战经验就是在部署集群之前先确认你的 CNI 支不支持 NetworkPolicy以及支持到什么程度。Calico、Cilium、Weave Net 这些主流的都支持但支持的规则细节有差异。比如有些 CNI 对 NetworkPolicy 里的 ipBlock 段处理不好有些对端口范围的 support 不完整。你在测试环境验证通过、上生产却失效很多时候不是写错了而是 CNI 的解析能力差异。1.2 网络策略和 Service、DNS 之间容易打架的关系这个坑更隐蔽。NetworkPolicy 默认拦截的是 Pod IP 之间的流量但你在集群里访问服务的时候走的往往是 Service 的 ClusterIP流量会先打到 kube-proxy 那一层再做 DNAT 转发到后端 Pod。问题来了如果 NetworkPolicy 只放行了到某个 Service 的 ClusterIP 的流量后端的 Pod 可能收不到因为实际流量到达 Pod 时源 IP 和目标 IP 可能已经变了取决于你的 externalTrafficPolicy 和 kube-proxy 的模式。还有一种常见情况你给某个命名空间下的 Pod 应用了只允许来自特定来源的策略却忘了放行 DNS 流量结果所有 Pod 域名解析全部失败。很多服务表现为网络不通排查半天发现是 CoreDNS 被策略误杀了。这类问题完全不是策略写法问题而是策略范围的边界没想清楚。我在后面从实际故障反推策略盲区那一章会专门展开。2. 动手之前必须搞清楚的关键参数selector、namespace 和 ipBlock 各自的脾性先说一个不少人搞混的点NetworkPolicy 里的 podSelector 和 namespaceSelector 都是基于 label 的匹配不是名字匹配。很多人习惯按名字指定允许访问的 Pod发现写成了又踩坑。比如你写podSelector: matchLabels: { app: my-app }但别的命名空间里也有一个app: my-app的 Pod那一瞬间策略的作用范围可能超出你的预期。2.1 podSelector 用得最多也最容易写错它的规则很直接在同一个 NetworkPolicy 所在的命名空间里选出一批 Pod 作为策略作用对象。如果你写的是一个空的{}那就表示匹配命名空间内所有 Pod。这一点很简单但带来的连锁反应不简单——很多人为了省事写了一个空 podSelector 的默认拒绝策略结果把整个命名空间所有 Pod 的入站流量全挡了连健康检查都过不去。我的建议是每一条策略先明确作用对象是谁不要试图先把默认拒绝做出来再慢慢放行。虽然 default-deny 是一个经典的安全实践但你要意识到它是一把双刃剑。在生产集群里用 default-deny 之前一定要在你自己的环境里完整跑一遍所有服务的连通性验证。2.2 namespaceSelector 可以跨命名空间但有 version 差异在 NetworkPolicy 里面你可以用 namespaceSelector 去匹配来自哪个命名空间的流量。这里有一个坑是很多从旧版本升上来的集群会碰到的旧版本 Kubernetes 里的 namespaceSelector 只支持matchLabels不支持matchExpressions更早的版本里还出现过对namespace 的 name 作为 label的支持不稳定的现象。kubernetes.io/metadata.name这个 label 是后来才普及的如果你在新的集群里用namespaceSelector: matchLabels: { kubernetes.io/metadata.name: your-ns }大概率没问题但在某些比较老的版本或某些 CNI 的实现里它就是不识别。所以如果你想按命名空间名字来匹配更稳妥的做法是给命名空间手动打一个固定的 label比如teampayments然后按这个 label 去做 namespaceSelector。这样迁移到任何版本都不会出幺蛾子。2.3 ipBlock 最大的问题不是写法而是绕不开的节点流量ipBlock 是用来匹配特定 IP 段的比如允许来自某个运维跳板机的 IP 访问。但你要小心即便你写了 ipBlock 只允许某个来源 IP集群节点的 IP 地址和 kube-proxy 所在节点的 IP 也可能出现在源 IP 段中。服务之间的流量经过 NAT 后可能表现为来自节点 IP而不是真正的 Pod IP。比如你用 ipBlock 限制只允许 10.10.0.0/16 访问你的数据库 Pod但你的节点地址恰好也在某个网段内或者你的负载均衡器健康检查请求来自 probe IP 网段那流量来源就会被干扰。这种问题非常隐蔽且跟 CNI 的 NAT 行为强相关。遇到策略配了但流量还是通/不通的时候第一时间要看的不是策略本身而是数据包到达 Pod 时真正的源 IP 是什么。2.4 一个实用的三段式策略设计思路鉴于上面这些坑我后来总结了一套相对稳妥的三段式策略设计思路在这里分享给你先写默认拒绝default-deny隔离所有未经允许的入向/出向流量这个作为基线再写明确放行按服务依赖关系用 podSelector namespaceSelector 精确放行目标流量最后放行基础设施流量专门为 DNS、监控、健康检查这类系统关键路径开一条策略。这个顺序看起来简单但实际上很多人做反了——先放行一堆业务流量然后在最后才想起来加默认拒绝结果默认拒绝一加上所有因为之前策略掩盖的网络依赖问题全部暴露线上必炸。所以不管你多着急default-deny 先上精确放行随后顺序反了会出事。3. 从零到一写一份安全的 NetworkPolicy规则结构拆解了解了各种 selector 的特性我来拆一份完整可用的 yaml。很多文档喜欢把 ingress 和 egress 分开讲但真实场景里它们是一体的你必须同时考虑数据从哪进、到哪出。下面我以自己的一个模拟项目 X 为例——一个典型的三层架构前端frontend、后端backend、数据库database全部在命名空间app-ns里。apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-all namespace: app-ns spec: podSelector: {} policyTypes: - Ingress - Egress第一份策略是 default-deny。它把app-ns下所有 Pod 的入向、出向流量全部拦了。你可能觉得这步子迈得太大但别忘了这只是基底后面全部要靠精确放行来开门。只要后面的规则你写得完整这个 default-deny 就不会误伤任何正常流量。apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-frontend-to-backend namespace: app-ns spec: podSelector: matchLabels: app: backend policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: frontend ports: - protocol: TCP port: 8080第二条策略允许app: frontend的 Pod 访问app: backend的 8080 端口。这里要注意端口必须对应 backend 实际监听的端口而不是 Service 暴露的端口。很多人习惯写 Service 的 Port结果发现策略不生效底层原因就在这——NetworkPolicy 只认 Pod 层面的端口。apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-dns-egress namespace: app-ns spec: podSelector: {} policyTypes: - Egress egress: - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system podSelector: matchLabels: k8s-app: kube-dns ports: - protocol: UDP port: 53第三条是给所有 Pod 放行 DNS 出向流量。这里也展示了一个细节同时用 namespaceSelector 和 podSelector 筛选时两者的交集就是最终匹配的 Pod 集合。目标 DNS Pod 在 kube-system 命名空间所以两层匹配把它们勾出来。允许 DNS 几乎是所有 egress 策略的必配项不然任何域名解析都会失败。3.1 标记不清晰的 Pod 会直接让策略失效写策略时你依赖的是 label如果 label 本身混乱策略就是个摆设。我在项目里踩过一次某后端服务的 Pod 同时带上了app: backend和app: server两个 label而其他团队的策略只匹配app: server我的策略匹配app: backend双层叠加之后互不干扰看起来很美好但一旦有人误更新 label两个策略的匹配范围同时改变问题就来了。所以团队的 label 规范必须前置并且要有 review——尤其是在多团队共享集群的情况下。3.2 端口段和协议匹配另一个容易虽通犹错的地方除了单端口匹配NetworkPolicy 也支持端口段比如port: 8000-9000。但要注意这种写法在某些 CNI 插件上可能出现性能下降因为底层规则会被展开成多个独立规则。如果你的策略数量本来就很大端口段可能成为性能隐患。性能之外协议类型也很重要很多服务走的是 UDP比如 DNS、部分服务发现协议如果你只写了 TCPUDP 流量就直接被默认拒绝拦了。写规则的时候协议、端口必须和实际通信方式一一对应宁可多列几种也别漏掉。4. 从实际故障反推策略盲区我遇到过的三个典型翻车现场这一章我专门把真实环境里遇到过的故障复盘一遍。说实话这些故障排查过程远比策略本身复杂但每个都值得你记下来因为它们在别人集群里大概率也会发生。4.1 案例一加了 default-deny 后整个集群的监控全部熄火某次上线了一套 default-deny 策略之后第二天监控大盘数据全停了。排查链路是这样的先看 Prometheus 的 Pod 日志发现抓取目标全部超时再看被监控的 Pod发现业务一切正常最后看 NetworkPolicy才反应过来——监控组件的抓取行为是从监控 Pod 发起到业务 Pod 的入向连接而 default-deny 把所有入向流量全拦了监控流量不是业务流量我根本没想到要单独给它开一条策略。修复方式很简单增加一条允许来自监控命名空间的、目标端口为业务 metrics 端口的入向策略。但这件事给我一个很大的教训——压测环境里要模拟完整的监控链路不然默认拒绝一开监控系统首当其冲瘫痪故障定位会变得更难。4.2 案例二跨命名空间调用完全不通问题却在另一条策略有一次两个服务跨命名空间调用A 命名空间的服务怎么都连不上 B 命名空间的服务。检查 A 的 egress 策略放行的源和目标写得很清楚检查 B 的 ingress 策略也明确允许来自 A 的流量。看起来没问题但就是不通。后来我把 B 的 ingress 策略全部列出来才发现 B 命名空间里还有另一条严格只允许来自某个固定 IP 段的策略而 A 服务的 Pod 经过节点 NAT 后的源 IP 不在那个 IP 段里。策略之间不是合并关系是叠加关系任何一条不匹配都会让流量被默认拒绝掉。所以排查的时候不能只看看起来相关的那一条要把命名空间内所有策略的命中情况整体盘一遍。4.3 案例三kube-proxy 的 masquerade 把源 IP 整没了这个和 ipBlock 的坑紧密相关。某次我们这边有个服务需要拿到真实客户端 IP 去做业务逻辑NetworkPolicy 里也是按真实客户端 IP 放行的测试环境没问题一到生产环境中就不通。原因就是生产环境的 kube-proxy 模式或 CNI 的 NAT 配置会把源 IP 改掉导致策略里 ipBlock 匹配不到真实来源。遇到这种情况一个办法是调整外部流量策略让流量不要经过容易引发 SNAT 的链路另一个办法是直接放宽策略用 namespaceSelector 或 podSelector 代替 ipBlock 做来源限制。如果你的业务对真实源 IP 有强依赖在写网络策略之前就应该和网络团队确认整条链路的 NAT 行为。4.4 从这些故障里提炼出的一套通用排查顺序整理一下我遇到网络策略相关问题时通用的排查顺序先看事件kubectl describe networkpolicy和kubectl get networkpolicy确认策略确实存在且被 K8s API 接受再看日志进入 Pod 抓包或看 CNI 的日志确认数据包是否真的到达了目标 Pod检查命中用一些专门工具做流量模拟确认策略的匹配情况逐条审视把所有相关命名空间的策略列出来逐条模拟流量行为最后看 CNI 机制确认是不是 NAT、隧道、多网卡这些底层机制在干扰。这套顺序帮我在绝大多数情况下都能快速定位问题不会一上来就翻策略配置浪费时间。5. 日志和监控没有这个策略 debug 约等于盲人摸象网络策略的规则不像应用代码你觉得它生效但它到底有没有让某个数据包通过很多时候是看不见的。所以我强烈建议在接网络策略这种项目之前先把监控和日志链路搭好否则一切所谓的排查都是在猜。5.1 最基础的指标必须监控拒绝事件和吞吐量通用的做法是给 CNI 组件比如 Calico 或 Cilium配置 Prometheus 监控把拒绝事件和吞吐量指标拉出来。这里我给一份简单的 ServiceMonitor 参考针对 Cilium 的apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: cilium-monitor namespace: monitoring spec: selector: matchLabels: k8s-app: cilium namespaceSelector: matchNames: - kube-system endpoints: - interval: 30s port: metrics如果你用的是 Calico对应的 metrics 端口可能是 9091felix metrics。拉好指标之后你至少能观察到数据包被拒绝的次数是否在上升这个信号可以帮你快速判断是不是策略拦截了流量。5.2 从日志里定位被拒绝的流量Cilium 提供 hubble 组件可以直接观察到被网络策略拒绝的流量的 source、destination 和 verdict。这比去节点上抓包高效得多。在故障处理时我一般会这么做确认 hubble 已经安装并采集流日志用hubble observe --verdict DROPPED或过滤特定 Pod 命名的命令定位是谁在丢包对照丢弃流量的方向ingress 或 egress和四元组信息精准找到是哪条策略误伤。如果你用的是 Calico可以用calicoctl或felix的日志但更多时候我倾向直接用节点上的tcpdump抓包来确认包有没有到达主机。日志和抓包结合着看判断速度会快很多。6. 从入站限制到精细化流量管控网络策略进阶玩法当你已经能熟练写基础策略之后有几个进阶方向我认为很有价值限制跳板机访问、按命名空间强制隔离、以及结合服务网格做微隔离。这些玩法能覆盖更多真实场景。6.1 对运维入口做严格的来源限制很多团队的集群运维会有一台跳板机用于应急连接和 kubectl 操作。你要做的网络策略不只是限制普通服务还要限制这种人的入口。比如你会写一条策略只允许来自跳板机所在网段的流量访问某个运维管理端 Pod。ipBlock 写法示例 ingress: - from: - ipBlock: cidr: 192.168.100.0/24 except: - 192.168.100.20/32这个例子里我特意把某个 IP 从允许列表里剔除。这类策略在安全审计里很常见但实际使用时要反复确认跳板机的出口 IP 是固定的不然一跳变就全连不上了。6.2 命名空间间的强制隔离让应用团队自己管理授权租户隔离是多团队集群的刚需。你不用每加一个应用就去写一堆白名单而是把命名空间设计成默认互相不可见然后为每组明确有依赖关系的团队开一条互访策略。这样安全团队和业务团队各司其职安全团队负责建隔离底座业务团队负责申请策略。实际落地时我推荐给每个命名空间都打上归属团队的 label比如teampayments、teamcheckout。然后网络策略的 namespaceSelector 都基于这个 label 来写而不是写死命名空间名字。这样团队调整架构时策略还能继续生效。6.3 结合服务网格实现更细的微隔离严格来说NetworkPolicy 是 L3/L4 层的网络策略服务网格比如 Istio则可以做 L7 层HTTP 方法、路径等的细粒度控制。这两者不冲突可以搭配使用NetworkPolicy 作为第一道粗粒度防线服务网格做应用层细粒度管控。我的经验是先让 NetworkPolicy 把大的访问边界画好再在边界内部用服务网格做精细化授权这样的组合既避免了所有流量都由网格转发带来的性能损耗又能保证安全边界足够清晰。7. 把策略下发做成自动化流程而不是手工敲 yaml当集群规模上去之后手工敲 yaml 再 apply 的方式一定会出问题。没有评审、没有版本控制、没有自动化测试策略就是你集群里的一颗定时炸弹。所以我花了不少精力在自动化上这里分享几个关键方向。7.1 用 GitOps 管策略Policy as Code做法很简单把所有 NetworkPolicy 的 yaml 放到 Git 仓库里通过 CI/CD 流水线做自动化的格式校验、dry-run 和 apply。这样做的好处是每一次策略变更都有迹可循团队 review 也方便。这里我习惯用 kubeconform 这类工具先做 schema 校验kubeconform -summary -strict -ignore-missing-schemas -schema-location default networkpolicy/*.yaml有些更进一步的团队会直接在 CI 里跑一些集成测试比如准备一套临时集群、自动部署一个模拟环境、应用策略、跑连通性测试全过了再合入主分支。这能有效防止改个 label 参数结果全打错的低级错误。7.2 定期做策略清理与评审策略写多了之后会出现僵尸策略——看起来还在但已经没有人引用了。这些策略拦不到什么真实流量却白白增加 CNI 的规则数量影响性能。我的习惯是每迭代一个大版本之后梳理所有 NetworkPolicy把引用到已下线服务或已废弃 label 的策略清理掉同时做一次全量连通性测试确保安全基线没有因为清理而失效。这项工作看起来不起眼但对集群长期健康运行非常重要。8. 调优与容量规划当策略规则超过千条时的性能隐患很多人忽略一个问题网络策略不仅是正确性问题还是性能问题。当策略数量增长到一定规模底层的 iptables 规则链会越来越长每个数据包命中链路要匹配的次数变多延迟和 CPU 消耗都会上升。我在一个高流量集群里实测过当节点上的 felix 规则条数破万之后新建连接出现肉眼可见的延迟。8.1 少用大范围 selector多用精确匹配在设计上更推荐把策略尽量贴近业务 Pod 的粒度缩小。比如podSelector: { app: backend }会比空{}更高效。策略一旦用空 selector 命中大量 PodCNI 在每个节点上要考虑的规则就更多。而且匹配越宽泛就越容易造成误伤。8.2 考虑 eBPF 方案Cilium 的 Netpol 性能优势如果你的集群规模很大、性能敏感我建议考虑 Cilium 这类 eBPF 方案。我自己在某公司做 k8s 网络时对比过同样是数千条 NetpoleBPF 数据路径在延迟和 PPS 上的表现比传统的 iptables 方案好不少。当然这并不是说 Calico 不行——Calico 在运维工具链成熟度上有优势——只是在极限性能场景下eBPF 这个方向更值得投入。你评估的时候要把团队对底层机制的理解程度也算进成本里否则排障会很痛苦。8.3 定期做压力和扩展性测试网络策略上线之后不要以为就万事大吉了。定期用压测工具模拟大流量和大量连接数观察 CNI 组件的 CPU 和内存变化以及数据包延迟是否劣化是必要的体检。我就遇到过策略一直没问题但某次业务大促流量上来之后CNI 组件的 CPU 飙到 90%最后靠调整策略粒度才救回来。9. 最后补一刀真实生产环境中我总结的七条实践到这里核心内容已经讲完。我把平时反复使用、反复验证的七条实战经验集中列在这里方便你保存下来对照检查。第一条默认拒绝永远先做精确放行随后再做。顺序反了线上必炸。第二条所有策略先分级命名。比如default-deny、allow-frotnend-to-backend、allow-dns-egress命名一旦混乱后期排查简直要命。第三条DNS 流量永远要放行。CoreDNS 在 kube-system 里不放行它所有服务都废了。第四条端口写 Pod 实际监听端口不是 Service 端口。这是入门者最容易犯的错。第五条label 必须先行于策略。没有清晰稳定的 label 体系策略就是空中楼阁。第六条永远不要只靠肉眼看 yaml 判断策略是否生效。用监控、日志和连通性测试说话。第七条策略的变更也要走评审和回归测试。策略相当于网络的交通规则随意改动就是一场灾难。以上这七条几乎每一句背后都是我踩过的坑。你如果在一开始就建立好这些习惯后面的维护成本会低很多。10. 我个人的选择谁适合哪种 CNI 方案最后聊聊选型这事。这可能是你开始用 NetworkPolicy 之前第一步就要做出的决定。我这边给三种典型场景配了三种思路场景推荐方案理由中小集群、团队运维经验一般Calico稳定工具链成熟资料多出问题好排查大规模集群、性能敏感CiliumeBPF 数据路径高效可观测性强Netpol 性能好已有服务网格、追求统一管控Cilium Istio用 NetworkPolicy 做 L3/L4 粗粒度服务网格做 L7 细粒度当然你也可以只用 Flannel 这种轻量方案但前提是你确认自己不需要 Netpol或者愿意接受改造方案。选型这事没有标准答案更多是取舍。但我的经验是一旦涉及多团队共享集群Netpol 几乎必选越早落地越主动。我折腾网络策略最大的一个感受是这东西很像是把一把网络侧的安全剪刀交到了应用团队手里你用得好它就是治理利器用不好它就是故障温床。我自己其实更偏爱先建立清晰的安全基线再用策略精确开孔而不是毫无目的地堆规则。你在实际项目中如果有类似的坑不妨按我这套流程重新梳理一遍应该能省下不少事后补救的时间。
阅读完成 · 觉得有帮助?
咨询建站