网络策略
如果你想在 TCP、UDP 和 SCTP 协议的 IP 地址或端口级别控制流量,那么你可能会考虑在集群中的特定应用程序中使用 Kubernetes NetworkPolicies。网络策略是一种以应用程序为中心的构造,它允许你指定一个 Pod 如何被允许通过网络与各种网络“实体”进行通信(我们在此使用“实体”一词,以避免与更常见的术语如“端点”和“服务”产生混淆,这些术语在 Kubernetes 中有特定的含义)。网络策略适用于两端或其中一端为 Pod 的连接,与其它连接无关。
Pod 可以与之通信的实体通过以下三个标识符的组合来识别:
- 允许访问的其他 Pod(例外:Pod 不能阻止对其自身的访问)
- 允许访问的命名空间
- IP 块(例外:与 Pod 或节点运行所在的节点的流量始终被允许,无论 Pod 或节点的 IP 地址是什么)
当定义基于 Pod 或命名空间的 NetworkPolicy 时,你使用 选择器 来指定与匹配该选择器的 Pod 之间允许哪些流量。
同时,当创建基于 IP 的 NetworkPolicy 时,我们根据 IP 块(CIDR 范围)来定义策略。
先决条件
网络策略由网络插件实现。要使用网络策略,你必须使用支持 NetworkPolicy 的网络解决方案。创建 NetworkPolicy 资源而没有实现它的控制器将不起作用。
两种 Pod 隔离类型
Pod 有两种隔离类型:出口隔离和入口隔离。它们关系到可以建立哪些连接。“隔离”在这里不是绝对的,而是指“某些限制适用”。另一种情况,“非隔离 for $direction”,意味着在指定方向上不适用任何限制。这两种隔离类型(或不隔离)是独立声明的,并且都与 Pod 之间的连接相关。
默认情况下,Pod 对出口流量是不隔离的;所有出站连接都允许。如果存在任何 NetworkPolicy 同时选择了该 Pod 并且其 `policyTypes` 中包含 “Egress”,则该 Pod 对出口流量是隔离的;我们称这样的策略适用于该 Pod 的出口流量。当 Pod 对出口流量是隔离的,唯一允许的来自该 Pod 的连接是那些由适用于该 Pod 的某个 NetworkPolicy 的 `egress` 列表所允许的连接。这些被允许连接的回复流量也将被隐式允许。这些 `egress` 列表的效果是累加的。
默认情况下,Pod 对入站流量是不隔离的;所有入站连接都允许。如果存在任何 NetworkPolicy 同时选择了该 Pod 并且其 `policyTypes` 中包含 “Ingress”,则该 Pod 对入站流量是隔离的;我们称这样的策略适用于该 Pod 的入站流量。当 Pod 对入站流量是隔离的,唯一允许的入站连接是来自该 Pod 的节点以及由适用于该 Pod 的某个 NetworkPolicy 的 `ingress` 列表所允许的连接。这些被允许连接的回复流量也将被隐式允许。这些 `ingress` 列表的效果是累加的。
网络策略之间不冲突;它们是累加的。如果任何一个或多个策略适用于给定 Pod 的给定方向,则该 Pod 在该方向上允许的连接是所有适用策略允许的连接的并集。因此,评估顺序不影响策略结果。
要允许从源 Pod 到目标 Pod 的连接,源 Pod 上的出口策略和目标 Pod 上的入口策略都需要允许该连接。如果任何一方不允许连接,则连接将不会发生。
NetworkPolicy 资源
有关资源的完整定义,请参阅 NetworkPolicy 参考。
一个 NetworkPolicy 示例可能如下所示:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
- Egress
ingress:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 6379
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978
注意
将此文件 POST 到集群的 API 服务器将不起作用,除非你选择的网络解决方案支持网络策略。强制字段:与所有其他 Kubernetes 配置一样,NetworkPolicy 需要 `apiVersion`、`kind` 和 `metadata` 字段。有关配置文件的通用信息,请参见配置 Pod 使用 ConfigMap 和 对象管理。
spec: NetworkPolicy spec 包含定义给定命名空间中特定网络策略所需的所有信息。
podSelector:每个 NetworkPolicy 都包含一个 `podSelector`,用于选择策略所适用的 Pod 分组。示例策略选择带有“role=db”标签的 Pod。空的 `podSelector` 选择命名空间中的所有 Pod。
policyTypes:每个 NetworkPolicy 都包含一个 `policyTypes` 列表,其中可以包含 `Ingress`、`Egress` 或两者。`policyTypes` 字段指示给定策略是否适用于选定 Pod 的入站流量、选定 Pod 的出站流量,或两者。如果在 NetworkPolicy 上未指定 `policyTypes`,则默认情况下 `Ingress` 将始终被设置,并且如果 NetworkPolicy 有任何出口规则,则 `Egress` 将被设置。
ingress: 每个 NetworkPolicy 可以包含一个允许的 `ingress` 规则列表。每条规则允许匹配 `from` 和 `ports` 部分的流量。示例策略包含一条规则,匹配单个端口上的流量,该流量来自以下三个源之一:第一个通过 `ipBlock` 指定,第二个通过 `namespaceSelector` 指定,第三个通过 `podSelector` 指定。
egress: 每个 NetworkPolicy 可以包含一个允许的 `egress` 规则列表。每条规则允许匹配 `to` 和 `ports` 部分的流量。示例策略包含一个规则,它匹配一个端口上的流量,目标是 `10.0.0.0/24` 中的任何目的地。
因此,示例 NetworkPolicy
将 `default` 命名空间中带有 `role=db` 标签的 Pod 隔离起来,用于入站和出站流量(如果它们尚未隔离)
(入站规则) 允许连接到 `default` 命名空间中所有带有 `role=db` 标签的 Pod 的 TCP 端口 6379,连接来源为:
- 在 `default` 命名空间中带有 `role=frontend` 标签的任何 Pod
- 在带有 `project=myproject` 标签的命名空间中的任何 Pod
- IP 地址范围在 `172.17.0.0`–`172.17.0.255` 和 `172.17.2.0`–`172.17.255.255` 之间(即,`172.17.0.0/16` 中除了 `172.17.1.0/24` 的所有 IP)
(出口规则) 允许来自 `default` 命名空间中所有带有 `role=db` 标签的 Pod 的连接,目标为 CIDR `10.0.0.0/24` 的 TCP 端口 5978
有关更多示例,请参见 声明网络策略 演练。
“to”和“from”选择器的行为
`ingress` `from` 部分或 `egress` `to` 部分可以指定四种选择器:
podSelector:这会在与 NetworkPolicy 相同的命名空间中选择特定的 Pod,这些 Pod 应被允许作为入站源或出站目的地。
namespaceSelector:这选择特定的命名空间,其中所有 Pod 都应被允许作为入站源或出站目的地。
namespaceSelector _和_ podSelector:一个同时指定 `namespaceSelector` 和 `podSelector` 的 `to` / `from` 条目选择特定命名空间中的特定 Pod。请注意使用正确的 YAML 语法。例如:
...
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
podSelector:
matchLabels:
role: client
...
此策略包含一个 `from` 元素,允许来自带有标签 `role=client` 的 Pod(位于带有标签 `user=alice` 的命名空间中)的连接。但以下策略有所不同:
...
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
- podSelector:
matchLabels:
role: client
...
它在 `from` 数组中包含两个元素,允许来自本地命名空间中带有 `role=client` 标签的 Pod 的连接,_或_ 来自任何命名空间中带有 `user=alice` 标签的任何 Pod 的连接。
如有疑问,请使用 `kubectl describe` 查看 Kubernetes 如何解释该策略。
ipBlock:这选择特定的 IP CIDR 范围作为允许的入口源或出口目的地。这些应该是集群外部的 IP,因为 Pod IP 是临时的且不可预测的。
集群入口和出口机制通常需要重写数据包的源或目标 IP。在这种情况下,尚未明确此操作是在 NetworkPolicy 处理之前还是之后发生,并且在网络插件、云提供商、`Service` 实现等不同组合下,行为可能会有所不同。
对于入口而言,这意味着在某些情况下,你可能能够根据实际的原始源 IP 过滤入站数据包,而在其他情况下,NetworkPolicy 所作用的“源 IP”可能是 `LoadBalancer` 的 IP 或 Pod 所在节点的 IP 等。
对于出口,这意味着从 Pod 到 `Service` IP 的连接(这些连接会被重写为集群外部 IP)可能受或可能不受基于 `ipBlock` 的策略的约束。
默认策略
默认情况下,如果命名空间中不存在策略,则允许该命名空间中 Pod 的所有入站和出站流量。以下示例允许你更改该命名空间中的默认行为。
默认拒绝所有入站流量
你可以通过创建一个 NetworkPolicy 来为命名空间创建“默认”入站隔离策略,该策略选择所有 Pod 但不允许任何入站流量到达这些 Pod。
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
这确保即使未被任何其他 NetworkPolicy 选中的 Pod 仍将因入站而隔离。此策略不影响任何 Pod 的出站隔离。
允许所有入站流量
如果你想允许命名空间中所有 Pod 的所有入站连接,你可以创建一个明确允许的策略。
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-ingress
spec:
podSelector: {}
ingress:
- {}
policyTypes:
- Ingress
有了这个策略,任何附加策略都不能导致拒绝这些 Pod 的任何入站连接。此策略对任何 Pod 的出站隔离没有影响。
默认拒绝所有出口流量
你可以通过创建一个 NetworkPolicy 来为命名空间创建“默认”出口隔离策略,该策略选择所有 Pod 但不允许任何来自这些 Pod 的出口流量。
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
spec:
podSelector: {}
policyTypes:
- Egress
这确保了即使未被任何其他 NetworkPolicy 选择的 Pod 也不会被允许出口流量。此策略不会改变任何 Pod 的入站隔离行为。
允许所有出站流量
如果你想允许命名空间中所有 Pod 的所有连接,你可以创建一个策略,明确允许该命名空间中 Pod 的所有出站连接。
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-egress
spec:
podSelector: {}
egress:
- {}
policyTypes:
- Egress
有了这个策略,任何附加策略都不能导致任何来自这些 Pod 的出站连接被拒绝。此策略对任何 Pod 的入站隔离没有影响。
默认拒绝所有入站和所有出站流量
你可以在该命名空间中创建以下 NetworkPolicy,为命名空间创建一个“默认”策略,以阻止所有入站和出站流量。
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
这确保即使未被任何其他 NetworkPolicy 选择的 Pod 也不会被允许入站或出站流量。
网络流量过滤
NetworkPolicy 是为第四层连接(TCP、UDP 和可选的 SCTP)定义的。对于所有其他协议,行为可能因网络插件而异。
注意
你必须使用支持 SCTP 协议网络策略的 CNI 插件。当定义 `deny all` 网络策略时,只保证拒绝 TCP、UDP 和 SCTP 连接。对于其他协议,例如 ARP 或 ICMP,其行为是未定义的。同样适用于允许规则:当允许特定 Pod 作为入站源或出站目的地时,对于(例如)ICMP 数据包会发生什么情况是未定义的。ICMP 等协议可能被某些网络插件允许,而其他插件则拒绝。
针对一系列端口
Kubernetes v1.25 [稳定]
在编写 NetworkPolicy 时,你可以针对一个端口范围而不是单个端口。
这可以通过使用 `endPort` 字段来实现,如下例所示:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: multi-port-egress
namespace: default
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 32000
endPort: 32768
上述规则允许 `default` 命名空间中带有 `role=db` 标签的任何 Pod 通过 TCP 与 `10.0.0.0/24` 范围内的任何 IP 通信,前提是目标端口在 32000 到 32768 之间。
使用此字段时,适用以下限制:
- `endPort` 字段必须等于或大于 `port` 字段。
- 只有在定义 `port` 的情况下才能定义 `endPort`。
- 两个端口都必须是数字。
注意
你的集群必须使用支持 NetworkPolicy 规范中 `endPort` 字段的 CNI 插件。如果你的网络插件不支持 `endPort` 字段并且你指定了带有该字段的 NetworkPolicy,则该策略将仅应用于单个 `port` 字段。按标签定位多个命名空间
在此场景中,你的 `Egress` NetworkPolicy 使用标签名称定位多个命名空间。为此,你需要为目标命名空间添加标签。例如:
kubectl label namespace frontend namespace=frontend
kubectl label namespace backend namespace=backend
在 NetworkPolicy 文档的 `namespaceSelector` 下添加标签。例如:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: egress-namespaces
spec:
podSelector:
matchLabels:
app: myapp
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchExpressions:
- key: namespace
operator: In
values: ["frontend", "backend"]
注意
无法直接在 NetworkPolicy 中指定命名空间的名称。你必须使用带有 `matchLabels` 或 `matchExpressions` 的 `namespaceSelector` 来根据其标签选择命名空间。按名称定位命名空间
Kubernetes 控制平面在所有命名空间上设置了一个不可变标签 `kubernetes.io/metadata.name`,该标签的值是命名空间的名称。
虽然 NetworkPolicy 无法通过对象字段按其名称定位命名空间,但你可以使用标准化标签来定位特定的命名空间。
Pod 生命周期
注意
以下内容适用于具有符合要求的网络插件和符合要求的 NetworkPolicy 实现的集群。当一个新的 NetworkPolicy 对象被创建时,网络插件可能需要一些时间来处理这个新对象。如果一个受 NetworkPolicy 影响的 Pod 在网络插件完成 NetworkPolicy 处理之前被创建,该 Pod 可能会在未受保护的情况下启动,并且隔离规则将在 NetworkPolicy 处理完成后应用。
一旦 NetworkPolicy 由网络插件处理,
所有受给定 NetworkPolicy 影响的新创建的 Pod 都将在启动前进行隔离。NetworkPolicy 的实现必须确保在 Pod 的整个生命周期内,甚至从 Pod 中任何容器启动的第一刻起,过滤都是有效的。由于它们应用于 Pod 级别,NetworkPolicy 对 init 容器、sidecar 容器和普通容器同样适用。
允许规则最终会在隔离规则之后(或可能同时)应用。在最坏的情况下,如果隔离规则已经应用但尚未应用任何允许规则,新创建的 Pod 在首次启动时可能完全没有网络连接。
每个创建的 NetworkPolicy 最终都会由网络插件处理,但无法从 Kubernetes API 中准确得知何时发生。
因此,Pod 必须能够应对以与预期不同的网络连接方式启动的情况。如果你需要确保 Pod 在启动前可以到达某些目的地,你可以使用 init 容器 来等待这些目的地可达,然后再由 kubelet 启动应用程序容器。
每个 NetworkPolicy 最终都会应用于所有选定的 Pod。由于网络插件可能以分布式方式实现 NetworkPolicy,因此当 Pod 首次创建时,或者当 Pod 或策略发生变化时,Pod 可能会看到稍微不一致的网络策略视图。例如,一个新创建的 Pod,原本应该能够同时访问节点 1 上的 Pod A 和节点 2 上的 Pod B,可能会发现它能立即访问 Pod A,但直到几秒钟后才能访问 Pod B。
NetworkPolicy 和 `hostNetwork` Pod
`hostNetwork` Pod 的 NetworkPolicy 行为是未定义的,但应限制为两种可能性:
- 网络插件可以区分 `hostNetwork` Pod 流量与所有其他流量(包括能够区分同一节点上不同 `hostNetwork` Pod 的流量),并将 NetworkPolicy 应用于 `hostNetwork` Pod,就像应用于 Pod 网络 Pod 一样。
- 网络插件无法正确区分 `hostNetwork` Pod 流量,因此在匹配 `podSelector` 和 `namespaceSelector` 时会忽略 `hostNetwork` Pod。`hostNetwork` Pod 的流量被视为与所有其他进出节点 IP 的流量相同。(这是最常见的实现方式。)
这适用于以下情况:
一个 `hostNetwork` Pod 被 `spec.podSelector` 选中。
... spec: podSelector: matchLabels: role: client ...
一个 `hostNetwork` Pod 被 `ingress` 或 `egress` 规则中的 `podSelector` 或 `namespaceSelector` 选中。
... ingress: - from: - podSelector: matchLabels: role: client ...
同时,由于 `hostNetwork` Pod 具有与其所在节点相同的 IP 地址,它们的连接将被视为节点连接。例如,你可以使用 `ipBlock` 规则允许来自 `hostNetwork` Pod 的流量。
你无法通过网络策略实现的功能(至少目前不能)
截至 Kubernetes 1.34,NetworkPolicy API 中不存在以下功能,但你可能可以通过使用操作系统组件(例如 SELinux、OpenVSwitch、IPTables 等)或 Layer 7 技术(Ingress 控制器、服务网格实现)或准入控制器来实现变通方法。如果你是 Kubernetes 网络安全的新手,值得注意的是,以下用户场景(目前)无法使用 NetworkPolicy API 实现。
- 强制内部集群流量通过通用网关(这可能最好通过服务网格或其他代理实现)。
- 任何与 TLS 相关的功能(请为此使用服务网格或 Ingress 控制器)。
- 节点特定策略(你可以使用 CIDR 符号来表示这些策略,但无法通过其 Kubernetes 标识符专门定位节点)。
- 按名称定位服务(但是,你可以通过其标签定位 Pod 或命名空间,这通常是一种可行的变通方法)。
- 创建或管理由第三方履行的“策略请求”。
- 适用于所有命名空间或 Pod 的默认策略(有一些第三方 Kubernetes 发行版和项目可以做到这一点)。
- 高级策略查询和可达性工具。
- 记录网络安全事件(例如被阻止或接受的连接)的能力。
- 明确拒绝策略的能力(目前 NetworkPolicies 的模型是默认拒绝,只具有添加允许规则的能力)。
- 阻止回环或传入主机流量的能力(Pod 目前无法阻止本地主机访问,也无法阻止来自其驻留节点的访问)。
NetworkPolicy 对现有连接的影响
当适用于现有连接的 NetworkPolicy 集合发生变化时——这可能是由于 NetworkPolicy 发生变化,或者策略所选定的命名空间/Pod(主体和对等体)的相关标签在现有连接进行中途发生变化——该变化是否会对现有连接生效取决于具体实现。例如:创建了一个策略,导致拒绝之前允许的连接,底层网络插件实现负责定义该新策略是否会关闭现有连接。建议不要以可能影响现有连接的方式修改策略/Pod/命名空间。