1、 概述
Kubernetes要求集群中所有Pod,无论是节点内还是跨节点,都可以直接通信。默认情况下,集群中所有Pod之间、Pod与节点之间可以互通。网络主要解决两个问题,一个是连通性,实体之间能够通过网络互通。另一个是隔离性,出于安全、限制网络流量的目的,又要控制实体之间的连通性。网络策略(NetworkPolicy)用来实现隔离性,只有匹配规则的流量才能进入Pod,同理只有匹配规则的流量才可以离开Pod。NetworkPolicy 资源使用标签选择Pod,并定义选定Pod所允许的通信规则。
但请注意,kubernetes支持的用以实现Pod网络的network plugin有很多种,并不是全部都支持Network Policy,为kubernetes选择network plugin时需要考虑到这点,是否需要隔离?可用network plugin及是否支持Network Policy请参考这里(支持网络策略的 CNI 网络插件有很多,包括 Calico、Cilium、Kube-router、Romana 和 Weave Net 等,但是flannel不支持)。
2、NetworkPolicy 资源
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: test-network-policy namespace: default spec: # 必须设置此参数 podSelector: matchLabels: role: db # 假设配置文件中没设置此参数,资源成功后会自动带着policyTypes->Ingress参数配置 policyTypes: - Ingress - Egress # ingress规则白名单列表,满足底下任意一个规则即可匹配成功(注意底下的"-"字符,同一缩进的"-"是或关系,"-"底下的所有规则是并关系) 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规则白名单列表 egress: - to: - ipBlock: cidr: 10.0.0.0/24 ports: - protocol: TCP port: 5978
说明:
- spec: NetworkPolicy 规约中包含了在一个命名空间中定义特定网络策略所需的所有信息。
- podSelector: 每个 NetworkPolicy 都包括一个 podSelector ,它对该策略所应用的一组 Pod进行选择。示例中的策略选择带有 "role=db" 标签的 Pod。空的 podSelector 选择命名空间下的所有 Pod。
- policyTypes: 每个 NetworkPolicy 都包含一个 policyTypes 列表,其中包含 Ingress 或 Egress 或两者兼具。policyTypes 字段表示给定的策略是否应用于进入所选 Pod 的入口流量或者来自所选 Pod的出口流量,或两者兼有。如果 NetworkPolicy 未指定 policyTypes 则默认情况下始终设置 Ingress(假设创建networkPolicy资源实例时没有指定policyTypes,创建完成后查看资源详细信息会发现给自动带上Ingress信息),如果NetworkPolicy 有任何出口规则的话则设置 Egress,不设置Egress的话表示对于所选择Pod出口不做任何限制。
- ingress: 每个 NetworkPolicy 可包含一个 ingress 规则的白名单列表。每个规则都允许同时匹配 from 和ports 部分的流量。示例策略中包含一条简单的规则: 它匹配一个单一的端口,来自三个来源中的一个, 第一个通过 ipBlock指定,第二个通过namespaceSelector 指定,第三个通过 podSelector 指定。
- egress: 每个 NetworkPolicy 可包含一个 egress 规则的白名单列表。每个规则都允许匹配 to 和 port部分的流量。该示例策略包含一条规则,该规则将单个端口上的流量匹配到 10.0.0.0/24 中的任何目的地。
所以,该网络策略示例:
1、隔离 “default” 命名空间下 “role=db” 的 Pod (如果它们不是已经被隔离的话)。
2、(Ingress 规则)允许以下 Pod 连接到 “default” 命名空间下的带有 “role=db” 标签的所有 Pod 的6379 TCP 端口:
-
- “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.1.0/24 之外的所有 172.17.0.0/16)
3、(Egress 规则)允许从带有 “role=db” 标签的命名空间下的任何 Pod 到 CIDR 10.0.0.0/24 下 5978TCP 端口的连接。
3、 选择器 to 和 from 的行为
可以在 ingress from 部分或 egress to 部分中指定四种选择器:
3.1 podSelector
这将在与 NetworkPolicy 相同的命名空间中选择特定的 Pod,应将其允许作为入口源或出口目的地。
3.2 namespaceSelector
这将选择特定的命名空间,应将所有 Pod 用作其输入源或输出目的地。
3.3 namespaceSelector 和 podSelector
一个指定 namespaceSelector 和 podSelector的 to/from 条目选择特定命名空间中的特定 Pod。注意使用正确的 YAML 语法(注意底下的"-"字符,同一缩进的"-"是或关系,"-"底下的所有规则是并关系,3.3表示的选择器是"-"底下的规则需要全部匹配);这项策略:
... 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 的连接。
这两种定义方式的区别,请你一定要分清楚。
3.4 ipBlock
这将选择特定的 IP CIDR 范围以用作入口源或出口目的地。 这些应该是群集外部 IP,因为 Pod IP 存在时间短暂的且随机产生。
群集的入口和出口机制通常需要重写数据包的源 IP 或目标 IP。在发生这种情况的情况下,不确定在 NetworkPolicy 处理之前还是之后发生,并且对于网络插件,云提供商,Service 实现等的不同组合,其行为可能会有所不同。
在进入的情况下,这意味着在某些情况下,您可以根据实际的原始源 IP 过滤传入的数据包,而在其他情况下,NetworkPolicy 所作用的 源IP 则可能是 LoadBalancer 或 Pod 的节点等。
对于出口,这意味着从 Pod 到被重写为集群外部 IP 的 Service IP 的连接可能会或可能不会受到基于 ipBlock 的策略的约束。
4. 默认策略
默认情况下,如果命名空间中不存在任何策略,则所有进出该命名空间中的 Pod 的流量都被允许。以下示例使您可以更改该命名空间中的默认行为。
4.1 默认拒绝所有入口流量
您可以通过创建选择所有容器但不允许任何进入这些容器的入口流量的 NetworkPolicy 来为命名空间创建 “default” 隔离策略。
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-ingress spec: podSelector: {} policyTypes: - Ingress
这样可以确保即使容器没有选择其他任何 NetworkPolicy
,也仍然可以被隔离。由于policyTypes中没有配置Egress,所以此策略不会更改默认的出口隔离行为。
4.2 默认允许所有入口流量
如果要允许所有流量进入某个命名空间中的所有 Pod(即使添加了导致某些 Pod 被视为“隔离”的策略),则可以创建一个策略来明确允许该命名空间中的所有流量。
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-all-ingress spec: podSelector: {} ingress: - {} policyTypes: - Ingress
4.3 默认拒绝所有出口流量
您可以通过创建选择所有容器但不允许来自这些容器的任何出口流量的 NetworkPolicy 来为命名空间创建 “default” egress 隔离策略。
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-egress spec: podSelector: {} policyTypes: - Egress
4.4 默认允许所有出口流量
如果要允许来自命名空间中所有 Pod 的所有流量(即使添加了导致某些 Pod 被视为“隔离”的策略),则可以创建一个策略,该策略明确允许该命名空间中的所有出口流量。
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-all-egress spec: podSelector: {} egress: - {} policyTypes: - Egress
4.5 默认拒绝所有入口和所有出口流量
您可以为命名空间创建 “default” 策略,以通过在该命名空间中创建以下 NetworkPolicy 来阻止所有入站和出站流量。
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-all spec: podSelector: {} policyTypes: - Ingress - Egress
5、NetworkPolicy原理
在具体实现上,凡是支持 NetworkPolicy 的 CNI 网络插件,都维护着一个 NetworkPolicy Controller,通过控制循环的方式对 NetworkPolicy 对象的增删改查做出响应,然后在宿主机上完成 iptables 规则的配置工作。
在 Kubernetes 生态里,目前已经实现了 NetworkPolicy 的网络插件包括 Calico、Weave 和 kube-router 等多个项目,但是并不包括 Flannel 项目。所以说,如果想要在使用 Flannel 的同时还使用 NetworkPolicy 的话,你就需要再额外安装一个网络插件,比如 Calico 项目,来负责执行 NetworkPolicy。(安装 Flannel + Calico 的流程可以参考这个文档)
那么,这些网络插件,又是如何根据 NetworkPolicy 对 Pod 进行隔离的呢?
接下来,以三层网络插件为例(比如 Calico),来为你分析一下这部分的原理。NetworkPolicy 对象示例如下所示:
apiVersion: extensions/v1beta1 kind: NetworkPolicy metadata: name: test-network-policy namespace: default spec: podSelector: matchLabels: role: db ingress: - from: - namespaceSelector: matchLabels: project: myproject - podSelector: matchLabels: role: frontend ports: - protocol: tcp port: 6379
可以看到,我们指定的 ingress“白名单”,是任何 Namespace 里,携带 project=myproject 标签的 Namespace 里的 Pod;以及 default Namespace 里,携带了 role=frontend 标签的 Pod。允许被访问的端口是:6379。
而被隔离的对象,是default命名空间下所有携带了 role=db 标签的 Pod。
那么这个时候,Kubernetes 的网络插件就会使用这个 NetworkPolicy 的定义,在宿主机上生成 iptables 规则。这个过程,我可以通过如下所示的一段 Go 语言风格的伪代码来为你描述:
for dstIP := range 所有被networkpolicy.spec.podSelector选中的Pod的IP地址 for srcIP := range 所有被ingress.from.podSelector选中的Pod的IP地址 for port, protocol := range ingress.ports { iptables -A KUBE-NWPLCY-CHAIN -s $srcIP -d $dstIP -p $protocol -m $protocol --dport $port -j ACCEPT } } }
可以看到,这是一条最基本的、通过匹配条件决定下一步动作的 iptables 规则。这条规则的名字是 KUBE-NWPLCY-CHAIN,含义是:当 IP 包的源地址是 srcIP、目的地址是 dstIP、协议是 protocol、目的端口是 port 的时候,就允许它通过(ACCEPT)。
而正如这段伪代码所示,匹配这条规则所需的这四个参数,都是从 NetworkPolicy 对象里读取出来的。
可以看到,Kubernetes 网络插件对 Pod 进行隔离,其实是靠在宿主机上生成 NetworkPolicy 对应的 iptable 规则来实现的。
此外,在设置好上述“隔离”规则之后,网络插件还需要想办法,将所有对被隔离 Pod 的访问请求,都转发到上述 KUBE-NWPLCY-CHAIN 规则上去进行匹配。并且,如果匹配不通过,这个请求应该被“拒绝”。
在 CNI 网络插件中,上述需求可以通过设置两组 iptables 规则来实现。
第一组规则,负责“拦截”对被隔离 Pod 的访问请求。生成这一组规则的伪代码,如下所示:
for pod := range 该Node上的所有Pod { if pod是networkpolicy.spec.podSelector选中的 { iptables -A FORWARD -d $podIP -m physdev --physdev-is-bridged -j KUBE-POD-SPECIFIC-FW-CHAIN iptables -A FORWARD -d $podIP -j KUBE-POD-SPECIFIC-FW-CHAIN ... } }
其中,第一条 FORWARD 链“拦截”的是一种特殊情况:它对应的是同一台宿主机上容器之间经过 CNI 网桥进行通信的流入数据包。其中,--physdev-is-bridged 的意思就是,这个 FORWARD 链匹配的是,通过本机上的网桥设备,发往目的地址是 podIP 的 IP 包。
而第二条 FORWARD 链“拦截”的则是最普遍的情况,即:容器跨主通信。这时候,流入容器的数据包都是经过路由转发(FORWARD 检查点)来的。
不难看到,这些规则最后都跳转(即:-j)到了名叫 KUBE-POD-SPECIFIC-FW-CHAIN 的规则上。它正是网络插件为 NetworkPolicy 设置的第二组规则。
而这个 KUBE-POD-SPECIFIC-FW-CHAIN 的作用,就是做出“允许”或者“拒绝”的判断。这部分功能的实现,可以简单描述为下面这样的 iptables 规则:
iptables -A KUBE-POD-SPECIFIC-FW-CHAIN -j KUBE-NWPLCY-CHAIN iptables -A KUBE-POD-SPECIFIC-FW-CHAIN -j REJECT --reject-with icmp-port-unreachable
可以看到,首先在第一条规则里,我们会把 IP 包转交给前面定义的 KUBE-NWPLCY-CHAIN 规则去进行匹配。按照我们之前的讲述,如果匹配成功,那么 IP 包就会被“允许通过”。而如果匹配失败,IP 包就会来到第二条规则上。可以看到,它是一条 REJECT 规则。通过这条规则,不满足 NetworkPolicy 定义的请求就会被拒绝掉,从而实现了对该容器的“隔离”。
以上,就是 CNI 网络插件实现 NetworkPolicy 的基本方法了。
参考:https://kubernetes.io/zh/docs/concepts/services-networking/network-policies/