云计算的世界里,计算最基础,存储最重要,网络最复杂。

tun/tap设备到底是什么?从Linux文件系统的角度看,它是用户可以用文件句柄操作的字符设备;从网络虚拟化角度看,它是虚拟网卡,一端连着网络协议栈,另一端连着用户态程序。

tun/tap设备有什么作用呢?tun/tap设备可以将TCP/IP协议栈处理好的网络包发送给任何一个使用tun/tap驱动的进程,由进程重新处理后发到物理链路中。tun/tap设备就像是埋在用户程序空间的一个钩子,我们可以很方便地将对网络包的处理程序挂在这个钩子上,OpenVPN、Vtun、flannel都是基于它实现隧道包封装的。

普通的物理网卡通过网线收发数据包,而tun设备通过一个设备文件(/dev/tunX)收发数据包。

因此,iptables的规则就是挂在netfilter钩子上的函数,用来修改数据包的内容或过滤数据包,iptables的表就是所有规则的5个逻辑集合!

VXLAN的报文就是MAC in UDP,即在三层网络的基础上构建一个虚拟的二层网络。为什么这么说呢?VXLAN的封包格式显示原来的二层以太网帧(包含MAC头部、IP头部和传输层头部的报文),被放在VXLAN包头里进行封装,再套到标准的UDP头部(UDP头部、IP头部和MAC头部),用来在底层网络上传输报文。

总结一下,一个VXLAN报文需要确定两个地址信息:内层报文(对应目的虚拟机/容器)的MAC地址和外层报文(对应目的虚拟机/容器所在宿主机上的VTEP)IP地址。如果VNI也是动态感知的,那么VXLAN一共需要知道三个信息:内部MAC、VTEP IP和VNI。

Macvlan的主要用途是网络虚拟化(包括容器和虚拟机)。另外,有一些比较特殊的场景,例如,keepalived使用虚拟MAC地址。需要注意的是,使用Macvlan的虚拟机或者容器网络与主机在同一个网段,即同一个广播域中。

Calico的设计灵感源自通过将整个互联网的可扩展IP网络原则压缩到数据中心级别。Calico在每一个计算节点,利用Linux Kernel实现高效的vRouter来负责数据转发,而每个vRouter通过BGP把自己节点上的工作负载的路由信息向整个Calico网络传播。小规模部署可以直接互联,大规模下可通过指定的BGP Route Reflector完成。保证最终所有的容器之间的数据流量都是通过IP路由的方式完成互联的。

这个pause容器运行一个非常简单的进程,它不执行任何功能,一启动就永远把自己阻塞住了(见pause()系统调用)。正如你看到的,它当然不会只知道“睡觉”。它执行另一个重要的功能——即它扮演PID 1的角色,并在子进程成为“孤儿进程”的时候,通过调用wait()收割这些僵尸子进程。这样就不用担心我们的Pod的PID namespace里会堆满僵尸进程了。这也是为什么Kubernetes不随便找个容器(例如Nginx)作为父容器,让用户容器加入的原因。

进程可以使用fork和exec两个系统调用启动其他进程。当启动了其他进程后,新进程的父进程就是调用fork系统调用的进程。fork用于启动正在运行的进程的另一个副本,而exec则用于启动不同的进程。每个进程在操作系统进程表中都有一个条目。这将记录有关进程的状态和退出代码。当子进程运行完成后,它的进程表条目仍然保留,直到父进程使用wait系统调用获得其退出代码后才会清理进程条目。这被称为“收割”僵尸进程,并且僵尸进程无法通过kill命令清除。

https://gravitational.com/blog/troubleshooting-kubernetes-networking.

因为IPVS的netfilter钩子挂载INPUT链,我们需要把Service的访问IP绑定在dummy网卡上让内核“觉得”虚IP就是本机IP,进而进入INPUT链。

IPVS虽然有三种代理模式NAT、ipip和DR,但只有NAT模式支持端口映射。因此,Kube-proxy的IPVS使用了NAT模式,为的就是支持端口映射。

在内核中,所有由netfilter框架实现的连接跟踪模块称作conntrack(connection tracking)。在DNAT的过程中,conntrack使用状态机启动并跟踪连接状态。为什么需要记录连接的状态呢?因为iptables/IPVS做了DNAT后需要记住数据包的目的地址被改成了什么,并且在返回数据包时将目的地址改回来。除此之外,iptables还可以依靠conntrack的状态(cstate)决定数据包的命运。其中最主要的4个conntrack状态如下:

需要注意的是,上面输出的最后一栏显示的不是进程的ID和名称,而是一个破折号“-”,这说明8472这个UDP端口不是由用户态的进程监听的,而是flannel的VXLAN模式工作在内核态下。

也可以使用-j REJECT,这样就会发一个连接拒绝的回程报文,客户端收到后立刻结束。不像-j DROP那样不返回任何响应,客户端只能一直等待直到请求超时。

Kubernetes为“normal”和“headless”服务分配不同的A Record name。“headless”服务与“normal”服务的不同之处在于它们未分配Cluster IP且不执行负载均衡。

SNAT导致Linux内核丢包的原因在于其conntrack的实现。SNAT代码在POSTROUTING链上被调用两次。首先通过修改源地址和/或端口修改包的结构,如果包在这个过程中没有丢失,则内核在conntrack表中记录这个转换。这意味着在SNAT端口分配和插入conntrack表之间有一个时延,如果有冲突的话可能最终导致插入失败及丢包。

具体来说,IPVS模式的Kube-proxy将在以下4种情况下依赖iptables: ·Kube-proxy配置启动参数masquerade-all=true,即集群中所有经过Kube-proxy的包都做一次SNAT; ·Kube-proxy启动参数指定集群IP地址范围; ·支持Load Balancer类型的服务,用于配置白名单; ·支持NodePort类型的服务,用于在包跨节点前配置MASQUERADE,类似于上文提到的iptables模式。

具体来说,IPVS模式的Kube-proxy将在以下4种情况下依赖iptables: ·Kube-proxy配置启动参数masquerade-all=true,即集群中所有经过Kube-proxy的包都做一次SNAT; ·Kube-proxy启动参数指定集群IP地址范围; ·支持Load Balancer类型的服务,用于配置白名单; ·支持NodePort类型的服务,用于在包跨节点前配置MASQUERADE,类似于上文提到的iptables模式。

换句话说,CNI的ADD/DELETE接口其实只是实现了docker network connect和docker network disconnect两个命令。

最让Kubernetes这些外部平台头疼的是,Docker网络驱动的设计不具备良好的开放性和可扩展性。例如,Docker网络驱动使用了Docker内部分配的一个ID,而不是一个通用的网络名字来指向一个容器所属的网络。这样的设计导致一个被定义在外部系统中(例如Kubernetes)的网络很难被Docker的网络驱动理解和识别。

(4)flanneld将封装好的UDP报文经eth0发出,从这里可以看出网络包在通过eth0发出前先是加上了UDP头(8个字节),再加上IP头(20个字节)进行封装,这也是flannel0的MTU要比eth0的MTU小28个字节的原因,即防止封包后的以太网帧超过eth0的MTU,而在经过eth0时被丢弃。

总的来说,可以认为Calico把主机作为容器的默认网关使用,所有的报文发到主机,主机根据路由表进行转发。和经典的网络架构不同的是,Calico并没有给默认网关配置一个IP地址,而是通过“ARP proxy”和修改容器路由表的机制实现。

Calico的原理决定了它不可能支持VPC,容器只能从Calico设置的网段中获取IP。

网络设备都以MAC地址唯一地标识自己,而交换机要实现设备之间的通信就必须知道自己的哪个端口连接着哪台设备,因此就需要一张MAC地址与端口号一一对应的表,以便在交换机内部实现二层数据转发。这张二层转发表就是FDB表。它主要由MAC地址、VLAN号、端口号和一些标志域等信息组成。如果收到数据帧的目的MAC地址不在FDB地址表中,那么该数据将被发送给除源端口外,该数据包所属VLAN中的其他所有端口。把数据发给其他所有端口的行为称为洪泛。

·tun设备的/dev/tunX文件收发的是IP包,因此只能工作在L3,无法与物理网卡做桥接,但可以通过三层交换(例如ip_forward)与物理网卡连通;

·tap设备的/dev/tapX文件收发的是链路层数据包,可以与物理网卡做桥接。