Kubernetes 集群默认的“全通”网络模型是很多安全问题的根源。在一个没有策略控制的集群里,前端 Pod 可以直接访问数据库,测试环境可以随意调用生产服务的 API,一旦某个容器被攻破,攻击者就能以它为跳板横扫整个集群。网络策略就是用来解决这个问题的——它像一组 Pod 级别的防火墙规则,精确控制东西向流量。

网络策略不是什么

先厘清一个常见误解:网络策略不是 Kubernetes 自带的“开箱即用”功能。你敲下 kubectl apply -f network-policy.yaml 之后,策略资源确实被创建了,但如果没有 CNI 网络插件去执行它,这条策略就是一张废纸。Calico、Cilium、Weave Net 等插件都支持 NetworkPolicy,但实现细节和性能差异很大。选择插件时,务必确认它支持你需要的策略特性——比如有些插件不支持 namespaceSelector 和 podSelector 的组合,有些对 egress 规则的支持不完整。

一条策略的三个核心要素

网络策略的 YAML 结构并不复杂,但每个字段都有精确含义。一个典型的 NetworkPolicy 包含三部分:podSelector 选定目标 Pod,policyTypes 声明是控制 Ingress 还是 Egress,以及具体的规则列表。podSelector 留空表示选中命名空间内所有 Pod,这个默认行为很容易被忽略,导致策略范围超出预期。规则中的 from 和 to 字段定义了流量来源和去向,可以是 podSelector、namespaceSelector 或 ipBlock 的组合。注意这三者在同一规则内是“或”的关系,不同规则之间是“与”的关系。

默认拒绝:安全基线的第一步

很多团队部署网络策略时,习惯先写允许规则,这其实是本末倒置。正确的做法是先创建一条默认拒绝所有流量的策略,然后在此基础上逐条开放必要的通信。下面这条策略会拒绝命名空间内所有 Pod 的入站流量:

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

podSelector 为空对象,意味着选中该命名空间下的每一个 Pod。没有写任何 ingress 规则,所以没有任何入站流量被允许。同理,把 policyTypes 换成 Egress 就能拒绝所有出站流量。注意执行这条策略后,Pod 连 DNS 查询都发不出去——CoreDNS 通常部署在 kube-system 命名空间,你需要额外创建一条允许向 CoreDNS 发起 UDP 53 端口连接的 egress 规则。

namespaceSelector 和 podSelector 的组合逻辑

这是网络策略中最容易踩坑的地方。当你在一条规则里同时使用 namespaceSelector 和 podSelector 时,它们之间是“与”的关系——只有同时满足命名空间标签和 Pod 标签的源才被允许。但如果你在同一个 from 列表里写了两个元素,一个用 namespaceSelector,一个用 podSelector,它们之间是“或”的关系。举个例子:

ingress:
- from:
  - namespaceSelector:
      matchLabels:
        env: production
    podSelector:
      matchLabels:
        role: frontend
  - podSelector:
      matchLabels:
        role: gateway

第一条规则允许来自标签为 env=production 的命名空间中、且 Pod 标签为 role=frontend 的流量;第二条规则允许当前命名空间内任何标签为 role=gateway 的 Pod 的流量。这两条规则是“或”的关系。如果你想让来自 production 命名空间的所有 Pod 都能访问,只需写 namespaceSelector,不要加 podSelector。

ipBlock 的正确用法和限制

ipBlock 用于放行集群外部的 IP 段,但它有一个关键限制:ipBlock 不能与 podSelector 或 namespaceSelector 写在同一个 from 元素里。这是 API 的硬性约束,因为外部 IP 没有 Pod 或命名空间的概念。如果你需要同时放行集群内部和外部流量,必须把它们拆成两个独立的 from 元素。另外,ipBlock 里的 except 字段可以排除特定 IP,这在需要放行一个大网段但要屏蔽其中某个敏感子网时非常实用。

egress 策略与 DNS 困境

Egress 控制往往被忽视,但它的重要性不亚于 Ingress。一个被攻破的 Pod 如果不能向外发起连接,攻击者就无法下载恶意工具、无法反弹 Shell、无法访问元数据服务。实施 egress 策略时,第一个要解决的就是 DNS。如果你的 CoreDNS 部署在 kube-system,需要创建这样一条策略:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
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 向 kube-system 命名空间中标签为 k8s-app=kube-dns 的 Pod 发起 UDP 53 端口连接。如果你的集群使用其他 DNS 方案,需要相应调整标签和端口。另外,很多应用还需要访问外部的 API 或数据库,这些都需要在 egress 规则中显式放行对应的 IP 或域名对应的 Pod。

命名空间隔离的多层设计

单靠一条默认拒绝策略远远不够。生产环境应该采用分层隔离:第一层是命名空间级别的粗粒度隔离,比如只允许 frontend 命名空间访问 backend 命名空间的特定端口;第二层是应用级别的细粒度隔离,比如 backend 命名空间内只有 API Pod 能访问数据库 Pod 的 5432 端口;第三层是外部访问控制,通过 ipBlock 限制哪些外部 IP 可以访问 Ingress Controller。这种分层设计让安全策略清晰可维护,出问题时也容易排查。

标签规范决定策略成败

网络策略完全依赖标签选择器,如果标签体系混乱,策略要么漏掉关键流量,要么过度放行。建议团队在项目初期就约定好核心标签:app 标识应用名,tier 标识层级(frontend/backend/database),env 标识环境(staging/production)。这些标签要写入 Deployment 的 spec.template.metadata.labels 中,确保新创建的 Pod 自动继承。修改已有 Pod 的标签不会触发策略更新,但新策略会立即生效于符合标签的 Pod——这意味着你改一个 Pod 的标签,可能瞬间让它失去数据库的访问权限。

策略测试与排错方法

网络策略排错是公认的痛点。kubectl describe networkpolicy 只能看到策略定义,看不到实际生效情况。实用的排错手段包括:在源 Pod 里用 nc -zv 目标IP 端口 测试连通性;用 tcpdump 抓包确认流量是否到达目标 Pod 的网络接口;检查 CNI 插件的日志,Calico 的 calico-node 和 Cilium 的 cilium-agent 都会记录策略执行细节。还有一个容易忽略的点:网络策略是命名空间资源,但它的作用范围可以跨命名空间。如果你在 default 命名空间创建了一条策略,它可能影响 kube-system 的 Pod 对 default 命名空间 Pod 的访问——排查问题时要把视野扩大到整个集群。

策略治理与 GitOps

当集群中网络策略数量超过几十条时,人工管理会变得不可持续。把网络策略纳入 GitOps 流程是成熟的实践:策略文件和应用代码放在同一个仓库,通过 CI/CD 自动部署。这样每次应用变更时,网络策略也同步更新,避免“代码上线了但策略没跟上”的尴尬。更进一步,可以使用 OPA 或 Kyverno 这类策略引擎,在准入控制阶段强制要求每个命名空间必须有默认拒绝策略,从源头杜绝裸奔的命名空间。

性能考量与插件差异

网络策略的数量和复杂度直接影响数据平面的性能。每条策略最终会被 CNI 插件翻译成 iptables 规则或 eBPF 程序。iptables 模式下,规则是线性匹配的,几百条策略可能导致明显的延迟增加。eBPF 模式的 Cilium 在这方面优势明显,它用哈希表做查找,性能几乎不受策略数量影响。如果你的集群规模较大,选择 CNI 插件时要把这个因素考虑进去。另外,尽量避免在单条策略里写过于复杂的标签组合,拆分成多条简洁的策略不仅性能更好,可读性也更强。

网络策略隔离不是一次性配置,而是一个持续演进的工程实践。它需要和应用的架构设计同步推进,需要团队对流量拓扑有清晰认知,更需要把策略当作代码一样对待——有版本控制、有测试、有审计。做到这些,Kubernetes 网络才能真正从“全通”走向“零信任”。