4. 标签选择器与命名空间选择器

在 NetworkPolicy 中,真正决定“谁能访问谁”的并不是 IP 地址,而是选择器。NetworkPolicy 通过 podSelector、namespaceSelector 以及它们内部的 matchLabels 与 matchExpressions 来圈定一组 Pod,再配合 ingress/egress 规则决定放行还是拒绝。理解选择器的匹配语义,是写出正确策略的前提;一旦语义理解偏差,策略可能过宽(形同虚设)或过窄(误伤正常流量)。

一、matchLabels 与 matchExpressions 的匹配语义

选择器由两部分组成:matchLabels 是键值相等的精确匹配,多个键之间是“与”关系;matchExpressions 支持 In、NotIn、Exists、DoesNotExist 四种运算符,用于表达集合与存在性判断。两者同时出现时,同样按“与”合并,即所有条件都必须满足。

需要特别注意两个边界:其一,matchLabels 与 matchExpressions 之间是逻辑与,不是或;其二,NotIn 与 DoesNotExist 对“标签不存在”的处理不同——NotIn 要求键存在且值不在集合内,而 DoesNotExist 只要求键不存在。下面用一段策略片段说明组合方式:

podSelector:
  matchLabels:
    app: web
  matchExpressions:
    - key: tier
      operator: In
      values: ["frontend", "edge"]
    - key: env
      operator: NotIn
      values: ["test"]

该选择器选中的 Pod 必须同时满足:app=web、tier 的值为 frontend 或 edge、且 env 存在且不等于 test。若某个 Pod 没有 env 标签,则因 NotIn 要求键存在而被排除。

二、namespaceSelector 与 podSelector 的组合规则

跨命名空间通信控制的关键,在于 ingress.from 或 egress.to 中 namespaceSelector 与 podSelector 的书写位置。它们有两种截然不同的语义:

  • 并列在同一列表项内(同一 from 元素下同时写 namespaceSelector 和 podSelector):表示“在匹配的命名空间中,再匹配其中的 Pod”,两者是“与”关系。
  • 分别写在不同的列表项中:表示“或”关系,即满足任一来源即可放行。

此外,namespaceSelector 为空 {} 时表示“所有命名空间”,而完全省略 namespaceSelector 只写 podSelector 时,默认只作用于策略所在命名空间内的 Pod。这一区别是跨命名空间策略中最容易出错的地方。

常见误区:把 namespaceSelector 与 podSelector 写成两个并列的 from 项,本意是“A 命名空间的 X 应用”,结果却变成“A 命名空间全部 Pod 或本命名空间 X 应用”,放行了远超预期的流量。

三、跨命名空间通信的典型场景

假设集群中有 frontend、backend、monitoring 三个命名空间。backend 中的 api 服务只允许 frontend 命名空间内带 app=web 标签的 Pod 访问 8080 端口,同时允许 monitoring 命名空间内所有 Pod 抓取指标端口 9090。策略可写为:

ingress:
  - from:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: frontend
        podSelector:
          matchLabels:
            app: web
    ports:
      - protocol: TCP
        port: 8080
  - from:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: monitoring
    ports:
      - protocol: TCP
        port: 9090

这里第一条规则中两个选择器处于同一列表项,是“与”;第二条规则只写 namespaceSelector,表示该命名空间下所有 Pod。Kubernetes 1.21 起会自动为每个命名空间打上 kubernetes.io/metadata.name 标签,因此可以直接用它做命名空间匹配,无需手动打标。

四、匹配关系可视化

下图把“策略选择器 → 命名空间 → Pod 标签 → 端口”的匹配链路画出来,帮助对照理解“与/或”在结构上的位置差异。

NetworkPolicy 选择器匹配链路 NetworkPolicy backend/api-policy podSelector: app=api ingress 规则 ×2 规则 1(与关系) namespaceSelector: frontend podSelector: app=web port: 8080 同一列表项 → 同时满足 规则 2(命名空间级) namespaceSelector: monitoring 未写 podSelector port: 9090 该命名空间全部 Pod ns: frontend Pod: app=web ✓ Pod: app=db ✗ ns: monitoring Pod: app=prometheus ✓ Pod: app=grafana ✓ ns: backend Pod: app=api(策略目标) port 8080 / 9090 匹配判定要点 同一 from 项内 namespaceSelector + podSelector = 与:命名空间与 Pod 标签都要命中 仅 namespaceSelector = 该命名空间下所有 Pod 均可访问 多个 from 列表项之间 = 或:任一来源满足即放行

五、实操验证与判断依据

策略下发后,不能只看 YAML 是否创建成功,而要用实际流量验证匹配结果。推荐按以下顺序排查:

  1. 确认目标 Pod 的标签:kubectl get pod -n backend -l app=api --show-labels,核对 podSelector 是否真的命中。
  2. 确认命名空间标签:kubectl get ns frontend --show-labels,检查 kubernetes.io/metadata.name 是否存在。
  3. 从来源 Pod 发起连接:kubectl exec -n frontend web-0 -- curl -s -o /dev/null -w "%{http_code}" http://api.backend:8080/health,预期返回 200。
  4. 从非授权 Pod 发起连接,预期超时或被拒绝,以此确认策略确实收紧了范围。

若授权来源访问失败,优先检查三件事:目标 Pod 是否真的带有 app=api 标签;namespaceSelector 是否误写成了 matchLabels 下的错误键;以及端口号是否与容器实际监听端口一致。若未授权来源仍能访问,则多半是把“与”写成了两个并列的 from 项,导致规则被放宽为“或”。

记忆口诀:同一项内是“与”,不同项间是“或”;只写命名空间选全部,只写 Pod 选本空间。把这三句话与上面的匹配链路图对照,绝大多数跨命名空间策略的歧义都能快速定位。

掌握选择器语义后,跨命名空间通信控制就从“凭感觉写 YAML”变成了可推理、可验证的工程操作:先明确来源命名空间与来源 Pod 标签,再决定它们应处于同一列表项还是不同列表项,最后用真实请求验证放行与拒绝是否符合预期。