Kubernetes 的网络模型,以及前面这些网络方案的实现,都只关注容器之间网络的“连通”,却并不关心容器之间网络的“隔离”。这跟传统的 IaaS 层的网络方案,区别非常明显。
Kubernetes 的网络方案对“隔离”到底是如何考虑的呢?难道 Kubernetes 就不管网络“多租户”的需求吗?接下来,在今天这篇文章中,我就来回答你的这些问题。在 Kubernetes 里,网络隔离能力的定义,是依靠一种专门的 API 对象来描述的,即: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
在上面这个例子里,你首先会看到 podSelector 字段。它的作用,就是定义这个 NetworkPolicy 的限制范围,比如:当前 Namespace 里携带了 role=db 标签的 Pod。而一旦 Pod 被 NetworkPolicy 选中,那么这个 Pod 就会进入“拒绝所有”(Deny All)的状态,即:这个 Pod 既不允许被外界访问,也不允许对外界发起访问。而 NetworkPolicy 定义的规则,其实就是“白名单”。例如,在我们上面这个例子里,我在 policyTypes 字段,定义了这个 NetworkPolicy 的类型是 ingress 和 egress,即:它既会影响流入(ingress)请求,也会影响流出(egress)请求。
然后,在 ingress 字段里,我定义了 from 和 ports,即:允许流入的“白名单”和端口。其中,这个允许流入的“白名单”里,我指定了三种并列的情况,分别是:ipBlock、namespaceSelector 和 podSelector。而在 egress 字段里,我则定义了 to 和 ports,即:允许流出的“白名单”和端口。这里允许流出的“白名单”的定义方法与 ingress 类似。只不过,这一次 ipblock 字段指定的,是目的地址的网段。
综上所述,这个 NetworkPolicy 对象,指定的隔离规则如下所示:该隔离规则只对 default Namespace 下的,携带了 role=db 标签的 Pod 有效。限制的请求类型包括 ingress(流入)和 egress(流出)。Kubernetes 会拒绝任何访问被隔离 Pod 的请求,除非这个请求来自于以下“白名单”里的对象,并且访问的是被隔离 Pod 的 6379 端口。
这些“白名单”对象包括: a. default Namespace 里的,携带了 role=fronted 标签的 Pod; b. 携带了 project=myproject 标签的 Namespace 里的任何 Pod; c. 任何源地址属于 172.17.0.0/16 网段,且不属于 172.17.1.0/24 网段的请求。
Kubernetes 会拒绝被隔离 Pod 对外发起任何请求,除非请求的目的地址属于 10.0.0.0/24 网段,并且访问的是该网段地址的 5978 端口。
像上面这样定义的 namespaceSelector 和 podSelector,是“或”(OR)的关系。所以说,这个 from 字段定义了两种情况,无论是 Namespace 满足条件,还是 Pod 满足条件,这个 NetworkPolicy 都会生效。
而下面这个例子,虽然看起来类似,但是它定义的规则却完全不同:
...
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
podSelector:
matchLabels:
role: client
...
注意看,这样定义的 namespaceSelector 和 podSelector,其实是“与”(AND)的关系。所以说,这个 from 字段只定义了一种情况,只有 Namespace 和 Pod 同时满足条件,这个 NetworkPolicy 才会生效。
此外,如果要使上面定义的 NetworkPolicy 在 Kubernetes 集群里真正产生作用,你的 CNI 网络插件就必须是支持 Kubernetes 的 NetworkPolicy 的。在具体实现上,凡是支持 NetworkPolicy 的 CNI 网络插件,都维护着一个 NetworkPolicy Controller,通过控制循环的方式对 NetworkPolicy 对象的增删改查做出响应,然后在宿主机上完成 iptables 规则的配置工作。在 Kubernetes 生态里,目前已经实现了 NetworkPolicy 的网络插件包括 Calico、Weave 和 kube-router 等多个项目,但是并不包括 Flannel 项目。
所以说,如果想要在使用 Flannel 的同时还使用 NetworkPolicy 的话,你就需要再额外安装一个网络插件,比如 Calico 项目,来负责执行 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
...
}
}
可以看到,这里的的 iptables 规则使用到了内置链:FORWARD。它是什么意思呢?
实际上,iptables 只是一个操作 Linux 内核 Netfilter 子系统的“界面”。顾名思义,Netfilter 子系统的作用,就是 Linux 内核里挡在“网卡”和“用户态进程”之间的一道“防火墙”。它们的关系,可以用如下的示意图来表示:
NetworkPolicy 实际上只是宿主机上的一系列 iptables 规则。这跟传统 IaaS 里面的安全组(Security Group)其实是非常类似的。
而基于上述讲述,你就会发现这样一个事实:Kubernetes 的网络模型以及大多数容器网络实现,其实既不会保证容器之间二层网络的互通,也不会实现容器之间的二层网络隔离。这跟 IaaS 项目管理虚拟机的方式,是完全不同的。
所以说,Kubernetes 从底层的设计和实现上,更倾向于假设你已经有了一套完整的物理基础设施。然后,Kubernetes 负责在此基础上提供一种“弱多租户”(soft multi-tenancy)的能力。并且,基于上述思路,Kubernetes 将来也不大可能把 Namespace 变成一个具有实质意义的隔离机制,或者把它映射成为“子网”或者“租户”。
毕竟你可以看到,NetworkPolicy 对象的描述能力,要比基于 Namespace 的划分丰富得多。这也是为什么,到目前为止,Kubernetes 项目在云计算生态里的定位,其实是基础设施与 PaaS 之间的中间层。这是非常符合“容器”这个本质上就是进程的抽象粒度的。
当然,随着 Kubernetes 社区以及 CNCF 生态的不断发展,Kubernetes 项目也已经开始逐步下探,“吃”掉了基础设施领域的很多“蛋糕”。这也正是容器生态继续发展的一个必然方向。
此文章为4月Day6学习笔记,内容来源于极客时间《深入剖析 Kubernetes》,强烈推荐该课程。