
Kubernetes和Docker网络原理(一)
Kubernetes和Docker网络原理(一)
Docker 网络实现
平时在进行Kubernetes开发和运维的时候,接触到的最多的概念就是Docker和Kubernetes的网络概念了,尤其是Kubernetes各种各样的IP、Port,有时候会混淆,因此有必要对Docker和Kubernetes的底层网络实现进行学习。这篇文章就先针对Docker的网络实现进行一下分析介绍。
Docker的网络基础
Docker的网络实现主要利用到的还是Linux网络相关的技术,如Network Namespace、Veth设备对、网桥、iptables、路由。
Network Namespace 基本原理
作用可以用一句话概括:
实现Linux网络虚拟化,即容器间网络协议栈层面的隔离。
通过Network Namespace技术可以实现不同的Docker容器拥有自己完全隔离的网络环境,就像各自拥有自己独立的网卡一样。不同的Network Namespace下默认是不可以直接通信的。
Linux的Network Namespace中可以有自己独立的路由表及独立的iptables设置来提供包转发、NAT及IP包过滤等操作。为了隔离出独立的协议栈,需要纳入命名空间的元素有进程、套接字、网络设备等。进程创建的套接字必须属于某个命名空间,套接字的操作也必须在命名空间中进行。同样,网络设备也必须属于某个命名空间。因为网络设备属于公共资源,所以可以通过修改属性实现在命名空间之间移动。
Linux的网络协议是非常复杂的,这里因为毕竟不是做系统底层开发,所以争取从概念层面对于Linux的Network Namespace这种网络隔离机制进行理解:
通过查阅相关书籍知道,Linux的网络协议为了支持Namespace这种隔离机制,方法就是让一些与网络协议栈相关的全局变成称为一个Network Namespace变量的成员,协议栈函数调用时指定Namespace参数,这个就是Linux实现Network Namespace的核心原理,通过这种方式,实现一些协议栈全局变量的私有化,保证有效的隔离。
新生成的Network Namespace里只有一个回环设备,其他网络设备都不存在,Docker容器都是在启动时才创建和配置的这些网络设备。物理设备一般只能关联到root这个Network Namespaca中,而虚拟的网络设备则可以关联到自定义的一些Network Namespace中,而且可以在这些Namespace中转移。
Network Namespace代表的是一个完全独立的网络协议栈,即与外界是隔离的,可以理解为连网线都没连接的一台"主机",因此如何实现Network Namespace下与外界进行网络通信是个问题,最基本的实现就是:Veth设备对。
注意: 对于Network Namespace相关的操作可以使用Linux iproute2系列工具实现,要求root用户
Veth 设备对
Veth其实就是虚拟的以太网卡,平时在Linux下配置以太网卡时我们一般减到的都是包含ethxxx这种名字,这里加了个V,明显就是指虚拟的网卡。因此Veth设备对就更好理解了,刚才提到的引入Veth设备对的核心目的就是为了实现两个完全隔离的NN之间通信,说白了就是利用它可以直接将两个NN连接起来,所以Veth设备都是成对出现的,才称作是Veth设备对,最贴切的形容就是:像一对以太网卡,并且中间有一根直连的网线,就构成了所谓的设备对。既然是一对网卡,那么我们将其中一端称为另一端的peer。在Veth设备的一端发送数据时,它会将数据直接发送到另一端,并触发另一端的接受操作。
值得一提的是,在Docker实现中,将Veth的一端放进容器NN后,Veth的名称会被改为eth...不知道的还以为是本地网卡呢!
Linux网桥
网桥的作用,顾名思义,就是一个桥接的作用,桥接的对象如果是网络设备,目的是实现各网络中的主机之间的相互通信。
网桥是一个二层的虚拟网络设备,把若干个网络接口"连接"起来,以使得网络接口之间的保温能够相互转发,二层网络的转发依据就是网络设备的MAC地址。网桥能够解析收发的报文,读取目标MAC地址的信息,和自己记录的MAC表结合,来决策报文的转发目标网络接口。为了实现这些功能,网桥是具有自学习功能的,它会学习源MAC地址。在转发报文时,网桥只需要向特定的网口进行转发,来避免不必要的网络交互。如果它遇到一个自己从未学习到的地址,就无法知道这个报文应该向哪个网络接口转发,就将报文广播给所有的网络接口(报文来源的网络接口除外),其中为了适应网络拓扑的变化,网桥学习到的MAC地址表是有过期时间的,长时间没有收到对应MAC回发的包,就认为设备已经不在那个转发端口上了,下一次给这个MAC的包就会以广播的形式转发。
Linux内核是通过一个虚拟的网桥设备(Net Device)来实现桥接的。这个虚拟设备可以绑定若干个以太网接口设备,从而将它们桥接起来,这种Net Device网桥和普通的设备不同,最明显的一个特性是它还可以有一个IP地址。这里说Linux网桥和传统的交换机概念还不能画等号,因为交换机是个单纯的二层设备,对于报文的操作要么转发要么丢弃,而Linux网桥除此之外,还有可能会将报文送至协议栈上层,即网络层,因此Linux网桥既可以看作二层设备,又可以看作三层设备。
Linux路由
Linux系统包含一个完整的路由功能。当IP层在处理数据发送或者转发时,会使用路由表来决定发往哪里。在通常情况下,如果主机与目的主机直接相连,那么主机可以直接发送IP报文到目的主机。这个过程比较简单。例如,通过点对点的链接或网络共享,如果主机与目的主机没有直接相连,那么主机会将IP报文发送给默认的路由器,然后路由器来决定往哪里发送IP报文。路由功能由IP层维护的一张路由表来实现。当主机收到数据报文时,它用此表来决策接下来应该做什么操作。当从网络侧接收到数据报文时,IP层首先会检查报文的IP地址是否与主机自身的地址相同。如果数据报文中的IP地址是主机自身的地址,那么报文将被发送到传输层相应的协议中。如果报文中的IP地址不是主机自身的地址,并且主机配置了路由功能,那么报文将被转发,否则报文将被丢弃。
Docker bridge的网络实现
Docker支持下列四种网络模式:bridge模式(默认)、none模式、host模式、container模式。由于目前涉及到容器云运维的工作都是以Kubernetes为核心,而Docker的这四种网络通信模式中只有bridge模式在Kubernetes中被用到,因此主要介绍一下Docker的bridge网络模式。
Docker默认为容器采用bridge的网络模式。在bridge模式下,Docker Daemon首次启动时会创建一个虚拟的网桥,默认的名称是docker0,然后在私有网络空间中给这个网桥分配一个子网(通常IP以172开头)。针对由Docker创建的每一个容器,都会创建一个之前讲的Veth设备对,其中一端关联到网桥上,另一端使用Linux的Network Namespace技术,映射到容器内的eth0(Veth)设备,然后从网桥的地址段内给eth0接口分配一个IP地址,相应的MAC地址也根据这个IP地址进行相应的生成操作。(是的,你没看错,可以分配IP...所以说不要当做是交换机)
Docker网络局限与简单探讨
从Docker支持的网络模式上我们就能感受到,Docker从一开始就没有考虑到这种大规模容器集群,容器跨主机通信的问题,像Kubernetes这种容器编排框架是之后才发布的,因此Docker网络最大的局限性就在于一开始没有考虑到多主机互联的网络解决方案。
从Docker的设计理念来讲,其一直以来的理念都是"简单为美",Docker最大的贡献就是让容器化这个提了几十年的概念在如今"飞入寻常百姓家",我认为做到这一步其实是已经非常优秀了,这也是Docker迅速走红的一个原因。
综上,对于大规模的分布式集群,容器集群,如何实现容器间优雅的跨主机节点通信的问题,就有一些其他的框架来解决,比如之后要写的Kubernetes,之后我会详细介绍在Kubernetes中是如何解决其包含容器在内各个组件网络通信问题的。