NetworkPolicy 是 Kubernetes 里高 ROI 的隔离能力:能显著降低横向移动与爆炸半径。但它也很容易在上线第一天就把业务“全断网”,原因通常是:

  • 没理解 NetworkPolicy 的默认行为(它是 allow-list)
  • egress 没规划好(DNS、监控、追踪、外部依赖全要走)
  • 标签体系不稳定(policy 选不中或选错对象)

下面按上线顺序拆开,重点放在不会把业务一次性打断的做法。

先看上线风险

NetworkPolicy 最大的坑在于:一旦生效,故障会立刻表现成“业务突然不通”。典型场景有四个:

  • 默认拒绝上线后,Ingress/Gateway 到服务的流量被挡住。
  • 开了 egress policy 但忘了 DNS,所有外部域名解析开始超时。
  • label 体系不稳定,policy 没选中目标 Pod,或者选中了不该选的 Pod。
  • 多个 policy 同时选中同一组 Pod,允许规则叠加后和预期不一致。

所以 NetworkPolicy 不适合“一次性全量改”。更稳的做法是先选一个 namespace,先做 ingress 默认拒绝,再逐步补齐入口、DNS、监控、外部 API 等基础依赖。

排障时先收集证据

kubectl get netpol -n <ns>
kubectl describe netpol -n <ns> <policy>
kubectl get pod -n <ns> --show-labels
kubectl get endpointslice -n <ns>
kubectl exec -n <ns> <pod> -- nslookup kubernetes.default

先确认 policy 选中了谁,再确认流量方向,最后再看 CNI 日志。不要一上来就抓包;很多问题在 labels 和 DNS 放行阶段就能解释。

前置条件:你的 CNI 是否真的执行 NetworkPolicy

NetworkPolicy 的执行依赖 CNI 实现:

  • 有的 CNI 默认支持并执行
  • 有的需要显式启用
  • 有的对某些规则存在限制

上线之前先确认平台能力,否则你写了 policy 可能“看起来有”,实际不生效。

NetworkPolicy 的核心语义:选中后“默认拒绝”

很多人误以为 NetworkPolicy 是“加一条 deny 规则”。实际更像这样:

  • 如果某个 Pod 在某个方向(Ingress/Egress)没有被任何 policy 选中,那么该方向 默认允许。
  • 一旦有 policy 选中了该 Pod 且包含该方向,则该方向 默认拒绝,只允许 policy 明确放行的流量。

Pod A 访问 Pod B 时,如果 A 已被 Egress policy 隔离、B 也已被 Ingress policy 隔离,那么源端 Egress 和目标端 Ingress 都必须允许这条连接。只改其中一侧,流量仍会失败;已允许连接的响应流量不需要再写一条反向规则。

这就是为什么“默认拒绝”通常是:

  • podSelector: {}(选中命名空间内所有 Pod)
  • policyTypes: [Ingress] 或 [Egress]

第 1 步:先做 namespace 级别的 Ingress 默认拒绝

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: app
spec:
  podSelector: {}
  policyTypes:
    - Ingress

这会阻止 app 命名空间内 Pod 的所有入站流量,除非后续显式放行。

上线建议:

  • 先在非核心命名空间试点
  • 或只对某个 app label 生效(更小爆炸半径)

第 2 步:放行来自 ingress controller/gateway 的流量

例如允许 ingress-nginx 命名空间过来的流量访问 app=api 的 8080:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-ingress
  namespace: app
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes: [Ingress]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx
      ports:
        - protocol: TCP
          port: 8080

注意:namespaceSelector 依赖命名空间标签,确认你的集群确实有这些 label。

同一个 from/to 条目里同时出现 namespaceSelector 与 podSelector 表示 AND:只匹配该命名空间中的指定 Pod。拆成两个列表条目则表示 OR,会同时允许“整个命名空间”和“其他命名空间中匹配 Pod 标签的对象”。缩进差一层,权限范围可能完全不同。

第 3 步:如果服务之间互调,先放行“同命名空间”

微服务常见需要 namespace 内互通:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-same-namespace
  namespace: app
spec:
  podSelector: {}
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector: {}

这一步的意义是:先让业务恢复互通,再逐步细化到“谁能访问谁”(按 label 收敛)。

第 4 步:Egress 要当成迁移项目来做(DNS 先行)

Egress 是最容易把业务打挂的一环,因为你很可能不知道依赖到底有多少:

  • DNS(必需)
  • 指标/日志/追踪(Prometheus/OTel/日志收集)
  • 外部 API(支付、短信、身份等)
  • 证书、时间同步、镜像仓库(视环境而定)

最推荐的 egress 第一步:先放行 DNS。

一个更“紧”的 DNS 放行方式是只放行 kube-system 里 CoreDNS:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: app
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
        - protocol: TCP
          port: 53

不同集群 CoreDNS 的 label 可能不同,按实际改。启用 NodeLocal DNSCache 时,Pod 可能访问节点上的本地 DNS 地址,而不是直接访问 kube-system 中的 CoreDNS Pod;应从 Pod 的 /etc/resolv.conf、DNS Service/DaemonSet 和 CNI 文档确认实际目标。

外部访问:ipBlock 很实用,但要认清局限

NetworkPolicy 支持 ipBlock:

egress:
  - to:
      - ipBlock:
          cidr: 203.0.113.0/24
    ports:
      - protocol: TCP
        port: 443

注意点:

  • 它是 IP 维度,不是 DNS 维度;外部 SaaS IP 可能变动
  • 很多团队会引入统一 egress proxy/NAT,维持可控出口
  • Service、Ingress 或云负载均衡器可能在 NetworkPolicy 生效前后做 SNAT/DNAT,不同网络实现看到的源/目标 IP 可能不同;不要用 ipBlock 选择 Pod IP

调试与排障:先确认“哪些 policy 选中了我”

当你发现“突然不通”:

kubectl get netpol -n app
kubectl describe netpol -n app <name>
kubectl exec -n app -it <pod> -- sh

如果 Pod 内没有工具,建议用 ephemeral container 来跑 curl/dig/nc,这样不污染生产镜像且符合真实网络命名空间。

很多时候你会发现:

  • 常见原因:policy 本身可用,但目标 Pod 没有匹配的 label
  • 或者某个“宽松 allow” policy 让流量仍然放行(policy 的效果是 union)

Label 策略:policy 的上限就是 label 的质量

NetworkPolicy 完全依赖 label 选择器,所以 label 不只是普通元数据,它还是安全边界的一部分:

  • 保持 team、app、role、env 这类标签稳定。
  • 禁止 team-dev2、test-final 这类临时命名进入生产。
  • label 变更要像安全配置一样 review。
  • 排障时先列出选中 Pod 的所有 policy,再判断是拒绝还是宽松放行。

为什么“该挡住的流量还能进来”

多个 NetworkPolicy 可以同时选中同一个 Pod。同一方向的生效规则是所有允许规则的并集。

所以你要做精细隔离时,先检查有没有历史策略还在放行:

kubectl get netpol -n app --show-labels
kubectl describe netpol -n app <name>

一条“允许 namespace X 全部流量”的旧规则,足以扩大最终允许集合,让新策略看起来没有收紧。

平台常见的“基础放行集”

很多平台最终都会沉淀一组“每个 namespace 都需要”的 policy:

  1. 允许 ingress/gateway 进来
  2. 允许 monitoring 抓 /metrics
  3. 允许 egress DNS

把这些做成模板/基线,会让运营成本大幅下降。

Day-2 运营清单

  • 一次只改一个 namespace 或 workload,观察流量后再推广。
  • 用 staging 跑真实流量模式,不要只测健康检查。
  • 维护 DNS、metrics、tracing、日志收集这些平台依赖的放行模板。
  • NetworkPolicy 的目标是从第一天开始逐步减少不必要的连接,同时保持业务可运维。

上线前自检

  • 确认 CNI 真正执行 NetworkPolicy
  • 先做 ingress 默认拒绝,再逐步放行入口
  • egress 从 DNS 开始,逐步补齐依赖白名单
  • 维持稳定的 label 体系(policy 依赖 labels)
  • 试点/渐进上线,避免“一刀切”

启用隔离前的四个判断

Q: 为什么策略不生效? A: 需要支持 NetworkPolicy 的 CNI 插件;没有 provider 时策略会被忽略。

Q: DNS 怎么放行? A: 先从 Pod 的 /etc/resolv.conf 确认实际 DNS 地址,再放行对应 CoreDNS Pod 或 NodeLocal DNS 路径的 TCP/UDP 53。

Q: 默认就是隔离吗? A: 只有被策略选择的 Pod 才会被隔离;没有被选择的 Pod 仍然是默认放行。

Q: 为什么一加 egress 策略业务就不通了? A: 因为 DNS、监控、追踪和外部 API 往往都依赖 egress;如果没有先补齐白名单,业务会立即出现解析失败或调用超时。

参考链接