_链接:https://www.cnblogs.com/sally...作者:Mr_Zack_Docker的技术依赖于Linux内核虚拟化技术的发展。Docker使用的网络技术有NetworkNamespace、Vethdevicepair、Iptables/Netfilter、bridge、routing等,接下来详细介绍Docker容器网络实现的基本技术。在进入真正的容器章节之前,可以形成一个坚实的基础知识网络。NetworkNamespace为了支持网络协议栈的多实例,Linux在网络栈中引入了NetworkNamespace。这些独立的协议栈被隔离到不同的命名空间中。不同Namespace中的网络栈是完全隔离的,无法相互通信。关于LinuxNamespace的具体介绍,可以浏览之前写的《Linux Namespace》。Linux的网络协议栈非常复杂。为了支持独立的协议栈,必须修改相关的全局变量为协议栈私有。Linux实现NetworkNamespace的核心就是让这些全局变量成为NetworkNamespace变量的成员,然后在协议栈的函数调用中加入一个Namespace参数。同时,为了保证开发的程序与内核代码的兼容性,内核代码隐式使用了Namespace空间中的变量。如果应用对Namespace没有特殊要求,则不需要额外的代码,NetworkNamespace对应用是透明的。建立一个新的NetworkNamespace,并将一个进程关联到这个networknamespace后,这个namespace下的内核数据结构如下,所有的networkstack变量都放到了这个NetworkNamespace的数据结构中,这个NetworkNamespace是它的进程私有的组并且不与其他进程组冲突。Docker使用NetworkNamespace特性来实现不同容器之间的网络隔离。如果一个容器声明使用宿主机的网络栈(-net=host),即没有启用NetworkNamespace,例如:dockerrun–d–net=host--namec_namei_name这种情况下,容器是什么启动后监听的是宿主机的80端口。像这样直接使用宿主机网络栈,可以为容器提供良好的网络性能,但不可避免地会造成端口冲突等网络资源冲突。所以一般来说,我们都希望程序在NetworkNamespace中引入网络栈,即容器有自己的IP和端口。不过这时候也带来了一个新的问题,容器隔离进程如何通过网络与其他隔离进程通信?上面提到NetBridge,Linux可以支持不同的网络,它们是如何相互支持和通信的呢?如果有两台主机,可能只需要一根网线将它们连接到交换机。在Linux中,桥(Bridge)起着相应的作用。本质上,这是一个数据链路层(datalink)设备,根据Mac地址信息将信息转发到网桥的不同端口。并且Docker在宿主机上默认创建了一个docker0网桥,任何连接docker0的网桥都可以通过它进行通信。详细描述一下,Bridge网桥是一个二层的虚拟网络设备,它“连接”了若干个网络接口,使得网络接口之间的数据包可以转发。网桥可以分析发送和接收的报文,读取目标Mac地址信息,结合自身的Mac地址表,确定报文转发的目标网口。为了实现这些功能,网桥会学习源Mac地址。转发消息时,网桥只需要将消息转发到特定的端口即可,避免了不必要的网络交互。如果它遇到一个它从来没有学习过的地址,它就不知道该报文应该转发到哪个网口,所以它会将报文广播到除报文源之外的所有网口。在实际网络中,网络拓扑不能永久改变。如果设备移动到另一个端口,并且它没有发送任何数据,桥接设备将无法感知这种变化,因此,桥接器仍会向原始端口发送数据包,在这种情况下,数据将迷路了。因此,网桥还需要在学习到的Mac地址表中加入一个超时时间,默认为5分钟。如果网桥收到相应端口的MAC地址发回的数据包。然后再重新设置超时时间,不然过了超时时间,它会认为哪个设备不在那个端口,就会广播重发。为了支持越来越多的网卡和虚拟设备,Linux使用网桥提供二层设备,用于在这些设备之间转发数据。Linux内核支持网口(Ethernet接口)的桥接,这和简单的交换机是不一样的。交换机只是二层设备,收到的数据包要么转发,要么丢弃。运行Linux内核的机器本身就是一台主机,它可能是网络消息的目的地。接收到的消息要么被转发,要么被丢弃,并可能被发送到网络协议的网络层,从而被主机自己读取。协议栈被消化了,所以我们可以把网桥看成一个二层设备,也可以看成一个三层设备。Linux中的Bridge是通过Linux内核实现的一个虚拟桥接设备(NetDevice)来实现桥接的。这个虚拟设备可以绑定几个以太网接口来连接它们。NetDevice网桥与普通设备不同,最明显的是它还可以有ip地址。如上图所示,网桥设备br0绑定了eth0和eth1。对于网络协议栈的上层,只看到br0。因为桥接是在数据链路层实现的,上层不需要关心桥接的细节,所以把协议栈上层需要发送的数据包发送给br0,处理代码为网桥设备判断报文是转发到eth0还是eth1,还是Both都转发。反之,从eth0或eth1接收到的报文被提交给网桥的处理代码,在网桥处理代码中判断该报文是转发、丢弃还是提交给协议栈上层。有时eth0和eth1也可能作为报文的源地址或目的地址,直接参与报文的发送和接收,从而绕过网桥。常见的Bridge操作Docker自动完成Bridge的创建和维护。如果想进一步了解网桥,可以看看下面列出的一些常用操作命令。添加网桥:brctladdbrxxxxx在新添加的网桥的基础上添加网口。在Linux中,网口实际上就是一块物理网卡。连接物理网卡和网桥:brctladdifxxxxethx网桥的物理网卡作为网口。由于工作在链路层,不再需要IP地址,所以上面的IP地址自然就失效了:ipconfigethx0.0.0.0为网桥配置IP地址:ipconfigbrxxxxxx.xxx.xxx.xxx这样网桥就有了IP地址,与之相连的网卡就是一个纯链路层设备。上面VethPair提到docker在宿主机上创建docker0网桥后,任何连接到docker0的网桥都可以使用它进行通信。那么这里又有一个问题,这些容器是如何连接到docker0网桥上的呢?所以这就是VethPair虚拟设备的作用。VethPair用于不同网络命名空间之间的通信。使用它,可以连接两个网络命名空间。VethPair设备的特点是在创建之后,总是以两个虚拟网卡(VethPeer)的形式出现。而且其中一张网卡发送的数据包可以直接出现在另一张“网卡”上,即使两张网卡在不同的NetworkNamespace中。正是因为这个特性,VethPairs才会成对出现,很像一对以太网卡,常被视为直接连接不同NetworkNamespaces的“网线”。当Veth的一侧发送数据时,他会将数据发送到另一侧并触发另一侧的接收操作。我们可以将VethPair的一端视为另一端的Peer。VethPair创建VethPair的操作命令:iplinkaddveth0typevethpeernameveth1创建后查看VethPair信息:iplinkshow将其中一个VethPeer设置为另一个Namespace:iplinksetveth1netnsnetns1在netns1中查看veth1Device:ipnetnsexecnetns1iplinkshow当然在docker中,除了将Veth放入容器外,还要更名为eth0。如果要通信,首先要分配一个IP地址:ipnetnsexecnetns1ipaddradd10.1.1.1/24devveth1ipaddradd10.1.1.2/24devveth0启动它们:ipnetnsexecnetns1iplinksetdevveth1upiplinksetdevveth0up测试通信:ipnetnsexecnetns1ping10.1.1.2VethPair端到端查看实际操作VethPair时,可以使用ethtool方便操作。在一个Namespace中的设备列表中查看VethPair接口的序列号:ipnetnsexecnetns1ethtool-Sveth1如果知道另一端接口设备的序列号,如果序列号为6,则可以继续检查设备6代表什么:ipnetnsexecnetns2iplink|grep6Iptables/NetfilterLinux协议栈非常高效和复杂。如果我们在数据处理的过程中要对我们关心的数据进行一些操作,就需要Linux提供一套相应的机制来帮助用户实现自定义的数据包处理。Linux网络协议栈中有一套网络回调函数附着点。这些附着点函数所附着的钩子函数可以在Linux网络栈处理数据包的过程中对数据包进行一些操作,如过滤、修改、丢弃等。整个挂载点技术称为Iptables和Netfilter。Netfilter负责执行内核中的各种hooking规则,运行在内核态。iptables是一个运行在用户态的进程,在内核中负责协助维护Netfilter的各种规则表。通过两者的配合,实现了整个Linux网络协议栈中灵活的数据包处理机制。规则表Table可以附加到这些挂载点的规则也分为不同的类型。目前支持的主要表类型有:?RAW?MANGLE?NAT?FILTER以上四种规则链的优先级为RAW最高,FILTER最低。在实际应用中,不同的挂载点所需要的规则类型通常是不同的。比如Input的挂载点上显然不需要FILTER过滤规则,因为根据目标地址,已经在本机的上层协议栈中,所以不需要挂载FILTER过滤规则。RouteLinux系统包含了完整的路由功能。IP层在处理数据发送或转发时,会使用路由表来决定将数据发送到哪里。通常,如果主机直接连接到目的主机,则该主机可以直接向目的主机发送IP数据包。路由功能是通过IP层维护的路由表来实现的。当主机收到数据报时,它使用此表来决定下一步做什么。当从网络侧接收到一个数据包时,IP层会首先检查该数据包的IP地址是否与主机本身的地址相同。如果数据报文中的IP地址是自己主机的地址,那么该报文就会被送往相应的传输层协议栈。如果报文中的IP地址不是主机自己的地址并且配置了路由功能,则转发该报文,否则丢弃该报文。路由表的数据一般以表项的形式存在。一个典型的路由表条目通常包含以下主要条目:?目的IP地址?下一个路由器的IP地址?标志?当通过路由表转发网络接口规范时,如果任何条目的第一个字段与IP地址相匹配目标条目完全(主机)或部分(网络),然后它将指示下一个路由器的IP地址。此信息告诉主机应将数据包转发到哪个“下一个路由器”。条目中的所有其他字段将提供更多辅助信息来做出路由决策。如果没有完全匹配的IP,请继续搜索网络ID。如果找到,则将数据转发到指定的路由器。因此,网络上的所有主机都通过该路由表中的单个条目进行管理。如果以上两个条件不匹配,则将数据报转发到默认路由器。如果上述步骤失败,默认路由器不存在,则无法转发数据报。任何无法传送的数据都将生成ICMP主机不可达或ICMP网络不可达错误,该错误将返回给生成数据的应用程序。RouteTableLinux路由表至少有2张,一张是LOCAL,一张是MAIN。Local表用于Linux协议栈标识本地地址,以及在不同本地网络之间转发数据。MAIN表用于各种网络IP的转发。它的建立可以使用静态配置或动态路由发现协议生成。动态路由发现协议一般利用组播功能,通过发送路由发现数据动态获取和交换网络路由信息,并更新到路由表中。使用以下命令查看LOCAL表的内容:iprouteshowtablelocaltype查看本地路由表:iproutelist至此已经介绍了docker容器网络的核心基础部分,包括NetworkNamespace,Bridge,Veth配对、Iptables/Netfilter、路由。接下来,我们将在此基础上继续详细阐述Docker容器网络的具体实现。
