网络策略
如果想要为 TCP、UDP 和 SCTP 协议控制 IP 地址或端口级别的流量,那么你可以考虑在集群中的特定应用程序使用 Kubernetes NetworkPolicies。NetworkPolicies 是一种以应用程序为中心的构造,允许你指定 Pod 如何通过网络与各种网络“实体”通信(这里我们使用 “实体” 这个词,是为了避免重载诸如 “端点” 和 “服务” 等更常见的术语,它们具有特定的 Kubernetes 内涵)。NetworkPolicies 适用于与 Pod 的一端或两端的连接,与其他连接无关。
Pod 可以与之通信的实体通过以下三个标识符的组合进行标识
- 允许的其他 Pod(例外:Pod 不能阻止对其自身的访问)
- 允许的命名空间
- IP 块(例外:无论 Pod 或节点的 IP 地址如何,始终允许进出 Pod 运行所在节点的流量)
在定义基于 Pod 或命名空间的 NetworkPolicy 时,可以使用选择算符来指定允许进出与选择算符匹配的 Pod 的流量。
同时,在创建基于 IP 的 NetworkPolicies 时,我们基于 IP 块(CIDR 范围)定义策略。
先决条件
网络策略由网络插件实现。要使用网络策略,你必须使用支持 NetworkPolicy 的网络解决方案。创建没有实现它的控制器的 NetworkPolicy 资源将不起作用。
两种 Pod 隔离方式
Pod 有两种隔离方式:出口隔离和入口隔离。它们关系到可以建立哪些连接。这里的“隔离”并非绝对,而是表示“应用了一些限制”。另一种选择,“对于 $direction 非隔离”表示在指定方向上不应用任何限制。两种隔离(或不隔离)是独立声明的,并且都与从一个 Pod 到另一个 Pod 的连接相关。
默认情况下,Pod 对于出口是非隔离的;允许所有出站连接。如果存在任何 NetworkPolicy 既选择了 Pod 并在其 policyTypes
中包含 “Egress”,则该 Pod 对于出口是隔离的;我们说这样的策略适用于该 Pod 的出口。当 Pod 对于出口是隔离的时,Pod 允许的唯一连接是由某个适用于该 Pod 出口的 NetworkPolicy 的 egress
列表允许的连接。对于那些允许的连接,回复流量也将被隐式允许。这些 egress
列表的效果是累加组合的。
默认情况下,Pod 对于入口是非隔离的;允许所有入站连接。如果存在任何 NetworkPolicy 既选择了 Pod 并在其 policyTypes
中包含 “Ingress”,则该 Pod 对于入口是隔离的;我们说这样的策略适用于该 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
注意
除非你选择的网络解决方案支持网络策略,否则将其发布到你集群的 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 的入口和出口流量(如果它们尚未被隔离)(入口规则)允许从以下位置连接到带有标签
role=db
的default
命名空间中的所有 Pod 上的 TCP 端口 6379- 带有标签
role=frontend
的default
命名空间中的任何 Pod - 带有标签
project=myproject
的命名空间中的任何 Pod 172.17.0.0
-172.17.0.255
和172.17.2.0
-172.17.255.255
范围内的 IP 地址(即,除了172.17.1.0/24
之外的所有172.17.0.0/16
)
- 带有标签
(出口规则)允许从带有标签
role=db
的default
命名空间中的任何 Pod 连接到 CIDR10.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,并且这些 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 到被重写为集群外部 IP 的 Service
IP 的连接可能受或不受基于 ipBlock
的策略的约束。
默认策略
默认情况下,如果命名空间中不存在任何策略,则允许该命名空间中 Pod 的所有入口和出口流量。以下示例允许您更改该命名空间中的默认行为。
默认拒绝所有入口流量
您可以通过创建一个选择所有 Pod 但不允许任何入口流量进入这些 Pod 的 NetworkPolicy,来为命名空间创建“默认”入口隔离策略。
---
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 的出口隔离没有影响。
默认拒绝所有出口流量
您可以通过创建一个选择所有 Pod 但不允许任何出口流量从这些 Pod 流出的 NetworkPolicy,来为命名空间创建“默认”出口隔离策略。
---
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 是为 第 4 层 连接(TCP、UDP 和可选的 SCTP)定义的。对于所有其他协议,行为可能因网络插件而异。
注意
您必须使用支持 SCTP 协议 NetworkPolicy 的 CNI 插件。当定义 deny all
网络策略时,它仅保证拒绝 TCP、UDP 和 SCTP 连接。对于其他协议,例如 ARP 或 ICMP,行为是未定义的。这同样适用于允许规则:当允许特定 Pod 作为入口源或出口目标时,未定义(例如)ICMP 数据包会发生什么。某些网络插件可能允许 ICMP 等协议,而其他网络插件可能会拒绝这些协议。
指定端口范围
Kubernetes v1.25 [stable]
在编写 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 处理之前创建了受 NetworkPolicy 影响的 Pod,则该 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 行为是未定义的,但应限于 2 种可能性
- 网络插件可以区分
hostNetwork
Pod 流量与所有其他流量(包括能够区分同一节点上不同hostNetwork
Pod 的流量),并且将像对 pod-network Pod 一样将 NetworkPolicy 应用于hostNetwork
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.32 版本,以下功能在 NetworkPolicy API 中不存在,但您或许可以使用操作系统组件(例如 SELinux、OpenVSwitch、IPTables 等)或第 7 层技术(Ingress 控制器、服务网格实现)或准入控制器来实现一些变通方案。如果您是 Kubernetes 网络安全的新手,值得注意的是,以下用户故事目前无法使用 NetworkPolicy API 实现。
- 强制内部集群流量通过一个公共网关(使用服务网格或其他代理可能更适合)。
- 任何与 TLS 相关的功能(使用服务网格或 Ingress 控制器来实现)。
- 节点特定的策略(您可以使用 CIDR 表示法,但不能通过其 Kubernetes 身份专门定位节点)。
- 按名称定位服务(但是,您可以通过其标签定位 Pod 或命名空间,这通常是一种可行的变通方案)。
- 创建或管理由第三方实现的“策略请求”。
- 应用于所有命名空间或 Pod 的默认策略(一些第三方 Kubernetes 发行版和项目可以实现此功能)。
- 高级策略查询和可达性工具。
- 记录网络安全事件的能力(例如,被阻止或接受的连接)。
- 显式拒绝策略的能力(目前 NetworkPolicies 的模型是默认拒绝,只允许添加允许规则)。
- 阻止回环或传入主机流量的能力(Pod 目前无法阻止本地主机访问,也无法阻止来自其所在节点的访问)。
NetworkPolicy 对现有连接的影响
当应用于现有连接的 NetworkPolicies 集合发生变化时 - 这可能是由于 NetworkPolicies 的变化,或者如果策略(主体和对等体)选择的命名空间/Pod 的相关标签在现有连接的过程中发生变化 - 该更改是否对现有连接生效是由实现定义的。例如:创建一个策略,导致拒绝先前允许的连接,底层的网络插件实现负责定义新策略是否会关闭现有连接。建议不要以可能影响现有连接的方式修改策略/Pod/命名空间。