##前言
在使用docker的时候就会有网络的概念,处理了容易内部与外部的通信。那在更复杂的K8S中是怎样通信的,以及可以怎样优化
Docker网络
docker是通过内部的网桥加内部的IP已达到内部的容器的网络和外部世界是解耦的,无需占用宿主机的 IP 或者宿主机的资源。
Docker网络的设计初衷是:当需要访问外部世界时,会采用 SNAT 这种方法来借用 Node 的 IP 去访问外面的服务。比如容器需要对外提供服务的时候,所用的是 DNAT 技术,也就是在 Node 上开一个端口,然后通过 iptable 或者别的某些机制,把流 导入到容器的进程上以达到目的。
1. SNAT(入口)
场景:云上的vm主机用户作为客户端访问外网服务器
vm(client)--->SNAT(将数据包中的内网源IP转换为外网IP)--->Internet(服务器)--->SNAT(将数据包内的目的IP转换为内网IP)--->vm(client)
2. DNAT(出口)
场景:云上的VM主机作为服务器端为外网提供服务
Internet(client用户)--->DNAT(将数据包中的目的公网IP转换为目的内网IP)--->VM(server)--->DNAT(将数据包中的源内网IP转换为外网IP)--->Internet(client用户)
举个栗子
contain1通过虚拟网卡(eth0, veth类型,一半在容器、一半在主机)关联到主机的云桥docker0上,通过主机网卡(eth0)发出,但是为了返回信息能顺利返回该主机需要所NAT转换,假设Contanin1与contain2之间进行通信,contain1的报文通过主机网卡做<font color=Blue>SNAT</font>,然后经由物理网络到达Contain2的主机并做一次<font color=Blue>DNAT</font>,然后到达Contain2,Contain2到达Contain1的时候原理是一样的
该模型的问题在于,外部网络无法区分哪些是容器的网络与流量、哪些是宿主机的网络与流量。比如,如果要做一个高可用的时候,172.16.1.1 和 172.16.1.2 是拥有同样功能的两个容器,此时我们需要将两者绑成一个 Group 对外提供服务,而这个时候我们发现从外部看来两者没有相同之处,它们的 IP 都是借用宿主机的端口,因此很难将两者归拢到一起。
k8s网络
k8s网络通信可以分为4部分
- 容器间通信:同一个pod内的多个容器间的通信(lo实现)
- pod间通信:pod ip <—–> pod ip (pod之间ip可以直接通信, k8s规定)
- pod与service通信:pod ip <—–> cluster ip
- service与集群外部客户端的通信
k8s本身并不提供网络请求方案,而是通过第三方网络插件来实现通信(<font color=Blue>CNI</font>),当下较流行的CNI插件有很多,例如<font color=Blue>flannl</font>、<font color=Blue>calico</font>、<font color=Blue>cancel</font>等等
flannel
flannel是试用最普遍的方案,通过将backend机制独立,他目前已支持多种数据路径, 也可以试用与overlay/underlay等多中场景(<font color=Blue>udp</font> <font color=Blue>vxlan</font> <font color=Blue>host-gw</font>)
1、flannel的vxlan实现
图中两个node1与node2都有一个flannel1的网卡,他们就是vxlan所需的vtep设备(用于vxlan报文的封装和解封装),它既有IP地址也有MAC地址,当我们由container1访问container2时,首先通过cni0网桥,然后路由到被禁flannel1上处理。
通过命令查看一下node1上的flannel1以及路由的信息
ifconfig
···
inet addr:10.244.0.0 Bcast:0.0.0.0 Mask:255.255.255.255
···
route -n
···
10.244.1.0 10.244.1.0 255.255.255.0 UG 0 0 0 flannel.1
···
在查看一下node2上的信息
ifconfig
···
inet addr:10.244.1.0 Bcast:0.0.0.0 Mask:255.255.255.255
···
route -n
···
10.244.0.0 10.244.0.0 255.255.255.0 UG 0 0 0 flannel.1
···
从以上两个数据可以看出,10.244.1.0就是node2的vtep设备(flannel)的ip地址,他们的数据相互对应。node1上的vtep设备收到原始报文后,需要给原始报文加一个mac地址,使这个请求能找到具体的node,拿这个mac地址在哪里找哪?
具体的mac地址可以在arp表中进行查询,这个arp表数据时flanneld进程在node2节点启动时,自动添加到node1的,如下
ip neigh show dev flannel.1
10.244.1.0 lladdr 6a:ea:18:dd:71:e1 PERMANENT
有了mac地址,vtep会进行封装数据发送
2、flannel的host-gw实现方式 这个方案采用的是每个 Node 独占网段,每个 Subnet 会绑定在一个 Node 上,网关也设置在本地,或者说直接设在 cni0 这个网桥的内部端口上。该方案的好处是管理简单,坏处就是无法跨 Node 迁移 Pod。就是说这个 IP、网段已经是属于这个 Node 之后就无法迁移到别的 Node 上。
这个方案的精髓在于 route 表的设置,如上图所示
- 第一条很简单,我们在设置网卡的时候都会加上这一行。就是指定我的默认路由是通过哪个 IP 走掉,默认设备又是什么;
- 第二条是对 Subnet 的一个规则反馈。就是说我的这个网段是 10.244.0.0,掩码是 24 位,它的网关地址就在网桥上,也就是 10.244.0.1。这就是说这个网段的每一个包都发到这个网桥的 IP 上;
- 第三条是对对端的一个反馈。如果你的网段是 10.244.1.0(上图右边的 Subnet),我们就把它的 Host 的网卡上的 IP (10.168.0.3) 作为网关。也就是说,如果数据包是往 10.244.1.0 这个网段发的,就请以 10.168.0.3 作为网关。
假设容器 (10.244.0.2) 想要发一个包给 10.244.1.3,那么它在本地产生了 TCP 或者 UDP 包之后,再依次填好对端 IP 地址、本地以太网的 MAC 地址作为源 MAC 以及对端 MAC。一般来说本地会设定一条默认路由,默认路由会把 cni0 上的 IP 作为它的默认网关,对端的 MAC 就是这个网关的 MAC 地址。然后这个包就可以发到桥上去了。如果网段在本桥上,那么通过 MAC 层的交换即可解决。
这个例子中我们的 IP 并不属于本网段,因此网桥会将其上送到主机的协议栈去处理。主机协议栈恰好找到了对端的 MAC 地址。使用 10.168.0.3 作为它的网关,通过本地 ARP 探查后,我们得到了 10.168.0.3 的 MAC 地址。即通过协议栈层层组装,我们达到了目的,将 Dst-MAC 填为右图主机网卡的 MAC 地址,从而将包从主机的 eth0 发到对端的 eth0 上去。
所以大家可以发现,这里有一个隐含的限制,上图中的 MAC 地址填好之后一定是能到达对端的,但如果这两个宿主机之间不是二层连接的,中间经过了一些网关、一些复杂的路由,那么这个 MAC 就不能直达,这种方案就是不能用的。当包到达了对端的 MAC 地址之后,发现这个包确实是给它的,但是 IP 又不是它自己的,就开始 Forward 流程,包上送到协议栈,之后再走一遍路由,刚好会发现 10.244.1.0/24 需要发到 10.244.1.1 这个网关上,从而到达了 cni0 网桥,它会找到 10.244.1.3 对应的 MAC 地址,再通过桥接机制,这个包就到达了对端容器。
network policy(网络策略)
刚才提到了 Kubernetes 网络的基本模型是需要 pod 之间全互联,这个将带来一些问题:可能在一个 K8s 集群里,有一些调用链之间是不会直接调用的。比如说两个部门之间,那么我希望 A 部门不要去探视到 B 部门的服务,这个时候就可以用到策略的概念。
基本上它的想法是这样的:它采用各种选择器(标签或 namespace),找到一组 pod,或者找到相当于通讯的两端,然后通过流的特征描述来决定它们之间是不是可以联通,可以理解为一个白名单的机制。
注意:Network Policy 只是 K8s 提供的一种对象,并没有内置组件做落地实施,需要取决于你选择的容器网络方案对这个标准的支持与否及完备程度,如果你选择 Flannel 之类,它并没有真正去落地这个 Policy,所以试用flannel网络插件就并没有什么用