- 第一条很简单,我们在设置网卡的时候都会加上这一行 。就是指定我的默认路由是通过哪个 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 地址,再通过桥接机制,这个包就到达了对端容器 。
大家可以看到,整个过程总是二层、三层,发的时候又变成二层,再做路由,就是一个大环套小环 。这是一个比较简单的方案,如果中间要走隧道,则可能会有一条 vxlan tunnel 的设备,此时就不填直接的路由,而填成对端的隧道号 。
Service 究竟如何工作
Service 其实是一种负载均衡 (Load Balance) 的机制 。
我们认为它是一种用户侧(Client Side) 的负载均衡,也就是说 VIP 到 RIP 的转换在用户侧就已经完成了,并不需要集中式地到达某一个 Nginx 或者是一个 ELB 这样的组件来进行决策 。
文章插图
它的实现是这样的:首先是由一群 Pod 组成一组功能后端,再在前端上定义一个虚 IP 作为访问入口 。一般来说,由于 IP 不太好记,我们还会附赠一个 DNS 的域名,Client 先访问域名得到虚 IP 之后再转成实 IP 。Kube-proxy 则是整个机制的实现核心,它隐藏了大量的复杂性 。它的工作机制是通过 apiserver 监控 Pod/Service 的变化(比如是不是新增了 Service、Pod)并将其反馈到本地的规则或者是用户态进程 。
一个 LVS 版的 Service我们来实际做一个 LVS 版的 Service 。LVS 是一个专门用于负载均衡的内核机制 。它工作在第四层,性能会比用 iptable 实现好一些 。
假设我们是一个 Kube-proxy,拿到了一个 Service 的配置,如下图所示:它有一个 Cluster IP,在该 IP 上的端口是 9376,需要反馈到容器上的是 80 端口,还有三个可工作的 Pod,它们的 IP 分别是 10.1.2.3, 10.1.14.5, 10.1.3.8 。
文章插图
它要做的事情就是:
文章插图
- 第 1 步,绑定 VIP 到本地(欺骗内核);