3. 默认行为与隔离模型

理解 NetworkPolicy,最关键的一步不是记住 YAML 字段,而是先建立一个清晰的心智模型:Kubernetes 网络默认是“全通”的,而 NetworkPolicy 是叠加在 Pod 之上的“选择性隔离”机制。只有先弄清“没有策略时会发生什么”“策略一旦选中 Pod 会怎样”“如何构造默认拒绝”,才能正确设计后续的允许规则。本节围绕这三个问题展开,并给出可直接套用的默认拒绝模板。

一、未应用任何策略时的全通行为

在标准 Kubernetes 集群中,只要 CNI 插件支持 NetworkPolicy(如 Calico、Cilium、Antrea、Weave Net 等),并且命名空间内没有任何 NetworkPolicy 选中某个 Pod,那么这个 Pod 的网络行为就是完全开放的:

  • 入向(Ingress)全通:集群内任意命名空间的任意 Pod、任意节点,都可以访问该 Pod 暴露的端口;如果 Service 类型为 NodePort/LoadBalancer,集群外部流量也能进入。
  • 出向(Egress)全通:该 Pod 可以访问任意目标 IP、任意端口,包括其他命名空间的 Pod、集群 Service、节点网络,以及集群外部的互联网地址。
  • 无状态、无日志:默认情况下不会因为“没有策略”而产生拒绝日志,流量被静默放行。

这种“全通”是 Kubernetes 的默认约定,也是很多安全事件的根源:一个被攻陷的前端 Pod 可以横向扫描并访问数据库、缓存、消息队列等后端服务。因此,生产环境通常不会停留在“无策略”状态,而是先建立隔离基线,再按需放行。

判断一个 Pod 是否被隔离,看的是“是否有策略的 podSelector 选中它”,而不是“命名空间里有没有策略”。一个命名空间里存在策略,但某 Pod 未被任何策略选中,它依然是全通的。

二、隔离模型:策略选中即“默认拒绝”

NetworkPolicy 的隔离语义可以概括为一句话:一旦某个 Pod 在某个方向上被至少一条策略选中,该方向就进入“默认拒绝”状态,只有策略中显式允许的流量才能通过。这里有两个容易混淆的要点:

  • 方向独立:Ingress 和 Egress 分别判断。一条只写了 policyTypes: [Ingress] 的策略,只隔离入向,出向仍然全通。
  • 策略叠加是“并集”:如果多个策略同时选中同一个 Pod,允许的流量是所有策略允许规则的并集,不会互相覆盖或“取交集”。

因此,实现“默认拒绝”并不是靠某个特殊字段,而是靠用一条策略选中命名空间内所有 Pod,并且不写任何 allow 规则。这条策略本身不包含任何 ingress 或 egress 规则,于是所有方向都被拒绝,形成隔离基线。

三、创建命名空间级默认拒绝策略

下面给出一个可直接使用的“默认拒绝全部入向与出向”的模板。它通过 podSelector: {} 选中命名空间内所有 Pod,并通过 policyTypes 同时声明 Ingress 和 Egress:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: prod
spec:
  podSelector: {}          # 空选择器 = 选中本命名空间所有 Pod
  policyTypes:
    - Ingress
    - Egress
  # 不写 ingress / egress 规则 = 不显式允许任何流量

应用方式:

kubectl apply -f default-deny-all.yaml
kubectl get networkpolicy -n prod
kubectl describe networkpolicy default-deny-all -n prod

如果只想先隔离入向、保留出向(例如让 Pod 仍能访问外部依赖),可以把 policyTypes 改为只保留 Ingress,得到“默认拒绝入向”策略:

spec:
  podSelector: {}
  policyTypes:
    - Ingress

反过来,只写 Egress 则只隔离出向。实际落地时,建议先上“默认拒绝入向”,观察业务是否受影响,再逐步收紧出向,避免一次性切断 DNS、镜像拉取、日志上报等必要流量。

四、默认拒绝生效后的影响与验证

策略生效后,命名空间内所有 Pod 的对应方向流量都会被拒绝,典型现象包括:

  • 其他命名空间的 Pod 无法再访问本命名空间的服务端口,连接超时或被重置。
  • 本命名空间 Pod 无法访问集群 DNS(kube-dns/CoreDNS),域名解析失败,表现为 no such host 或解析超时。
  • 本命名空间 Pod 无法访问外部 API、数据库、对象存储等,出现连接超时。
  • 健康检查、探针如果来自 kubelet 所在节点,通常不受 Pod 网络策略影响,但跨节点访问可能受影响,需要结合 CNI 实现确认。

验证时不要只看“服务是否报错”,而应做方向明确的连通性测试。例如在 prod 命名空间启动一个临时 Pod,分别测试 DNS、同命名空间服务、跨命名空间服务:

kubectl run nettest -n prod --rm -it --image=busybox:1.36 -- sh
# 容器内执行:
nslookup kubernetes.default.svc.cluster.local
wget -qO- --timeout=3 http://backend.prod.svc.cluster.local:8080/healthz
wget -qO- --timeout=3 http://api.other.svc.cluster.local:8080/healthz

如果 DNS 解析失败、同命名空间服务也超时,说明默认拒绝已生效;此时需要按业务依赖逐条补充允许规则,例如先放行 DNS 出向,再放行必要的服务端口。

默认拒绝策略会同时影响新创建的 Pod 和已存在的 Pod,因为 podSelector 是动态匹配的。上线前务必确认关键依赖(DNS、镜像仓库、监控采集、日志上报)已有对应放行策略,否则可能造成大面积故障。

五、隔离模型的可视化理解

下图用状态对比的方式呈现“无策略全通”与“默认拒绝隔离”两种状态,并标出策略选中 Pod 后各方向的默认行为,帮助建立方向独立的判断直觉。

NetworkPolicy 隔离模型:全通 vs 默认拒绝 状态 A:无策略选中 → 全通 其他命名空间 Pod / Service 集群外部 互联网 / DB 目标 Pod 未被任何策略选中 Ingress 全通 · Egress 全通 无拒绝日志,流量静默放行 状态 B:默认拒绝策略选中 → 隔离 其他命名空间 Pod / Service 集群外部 互联网 / DB 目标 Pod 被 default-deny-all 选中 Ingress 默认拒绝 · Egress 默认拒绝 仅显式 allow 规则可放行 判断依据:是否有策略的 podSelector 选中该 Pod;Ingress 与 Egress 方向独立判定

六、默认拒绝策略的落地建议

把默认拒绝作为基线,而不是一次性“封死”。推荐按以下顺序推进:

  1. 先观察:在非生产命名空间应用 default-deny-all,用连通性测试确认哪些依赖被切断。
  2. 补 DNS:几乎所有 Pod 都需要访问 CoreDNS,先放行到 kube-system 命名空间的 UDP/TCP 53。
  3. 补必要服务:按“谁访问谁、哪个端口”逐条添加 allow 规则,例如前端到后端 8080、后端到数据库 5432。
  4. 再收紧出向:如果一开始只做了入向默认拒绝,确认业务稳定后再补出向默认拒绝。
  5. 持续验证:每次新增策略后,用临时 Pod 做正反两向测试,确保“该通的通、不该通的不通”。

需要特别强调的是:默认拒绝策略本身不区分“合法流量”和“恶意流量”,它只是把默认放行改为默认拒绝。真正的安全能力来自“默认拒绝 + 最小化显式允许”的组合。因此,default-deny-all 是起点,而不是终点;后续每一条 allow 规则,都应当能回答“哪个 Pod、访问哪个 Pod、哪个端口、为什么必须允许”这四个问题。

最后给出一个可直接复用的检查清单,用于确认默认拒绝是否按预期落地:

检查项预期结果判断方式
策略是否选中全部 PodpodSelector 为空对象kubectl describe networkpolicy
隔离方向是否正确policyTypes 含 Ingress/Egress查看 spec.policyTypes
DNS 是否可用能解析集群域名临时 Pod 执行 nslookup
跨命名空间访问未放行则超时/拒绝wget 目标 Service 端口
同命名空间访问未放行则超时/拒绝同命名空间临时 Pod 互测
出向外部访问未放行则超时访问外部 IP 或域名

掌握默认行为与隔离模型后,后续的允许规则设计就有了明确的参照系:先默认拒绝,再按业务依赖逐条放行,最终形成可审计、可维护的网络治理基线。