当前位置: 首页 > Web前端 > HTML

Docker网络虚拟化--桥接模式

时间:2023-03-28 14:42:45 HTML

容器技术是近几年最火的技术之一。它将程序依赖的环境打包成一个镜像文件,可以跨平台部署。真正做到一次编译,到处运行,对研发和运维系统都产生了巨大的影响。01一开始,有一天,易元融网络萌新在学习Docker容器。他在书中看到一段话是这样描述的:可以通过Docker镜像来固化运行环境,但是最终运行时需要运行时隔离技术。一是空间隔离,二是资源隔离。先说空间隔离。每个容器都有自己独立的命名空间,主要包括网络命名空间……萌新:“咦?是为了隔离每个容器的网络环境吗?怎么实现的?”怎么样?”萌新想了想——萌新:“算了,不懂,问问Bogo吧。”02Docker的网络解决方案这时,Bogo正在家里闲逛StackOverflow,突然电话响了”萌新:“哎!博格,你在做什么?问你一些小问题。”博哥:“我在冲浪养生,有什么问题可以告诉我。”萌新:“我只是想请你了解一下Docker的网络解决方案。我家掉线了!”Pogo:“好吧,我给你讲讲!”Docker默认的网络模式分为:Host模式、Bridge模式或None模式。Host模式就是和宿主机共享协议栈,然后就可以在容器中看到宿主机的网络IP等信息,可以通过localhost访问宿主机上的服务,这里需要注意的是,启动容器中的服务需要避免与容器中的端口冲突host,None模式不联网,这个主要有两个用途——a.在一些业务场景下,容器不需要联网,比如一些本地的批处理任务;b.允许用户自己添加网络,用户可以通过ovs-docker等工具为容器自定义网卡,Bridge模式是Docker默认的网络模式,也是最常用的模式,这种模式下的容器会在172.17.0.0/16中分配一个IP网段。并且容器和宿主机/其他Bridge模式的容器可以互相访问,容器也可以访问外网,但是外网不能直接访问容器。萌新:“那Bridge模式是怎么实现的?”03背景知识博哥:“你了解过NetworkNamespace、veth、linuxbridgemodule、Netfilter……吗?”萌新:“不是,这是什么?”博哥:“好吧,在告诉你Bridge模式的实现原理之前,我得简单介绍一下这几个前置知识点!”NetworkNamespaceNetworkNamespace是Linux内核提供的一种资源隔离机制。它可以用自己的网络堆栈信息创建多个隔离的网络空间。无论是虚拟机还是容器,运行时都仿佛处于一个独立的网络中。我们可以使用ipnetns来管理命名空间,比如我们可以使用ipnetnsadd创建一个命名空间,创建的网络命名空间会出现在/var/run/netns下;使用ipnetnsls查看/var/run/netns命名空间下已有的,如果需要管理其他非ipnetns创建的命名空间,只需在该目录下创建网络命名空间文件的链接即可。(比如你会发现容器所在的networknamespace是不能直接管理的,想知道如何管理容器的networknamespace,请稍后继续阅读)...(点击查看一个largerimage)Veth有了不同的NetworkNamespace之后,就会与网络隔离,但是如果它们之间没有办法进行通信,那是没有实际用处的。不同的网络命名空间如何通信?Linux内核还提供了一个特殊的网络接口设备:Veth。Veth接口是成对创建的。一端发送的消息,另一端会收到,相当于网线的两端。然后,可以将一对Veth分别放在两个NetworkNamespace中,通过消息转发实现不同NetworkNamespace之间的通信。(点击查看大图)Linux的Bridge模块Vethpair可以实现两个NetworkNamespaces之间的通信,但是当有多个NetworkNamespaces需要通信时,就只能坐等Linux中一个叫Bridge的大哥来解决了。LinuxBridge(网桥)是工作在第二层的虚拟网络设备,其功能类似于物理交换机。它是一种网络接口,可以将其他网络接口添加到网桥接口。属于同一个Bridge的接口相当于连接到同一个二层交换机,可以转发二层报文。假设创建一个Bridge接口br0,eth0、eth1、eth2、br0使用vethpair连接,那么eth0收到的数据包不会进入协议栈,而是在br0中转发,所以会加入到Bridge中br0接口设置的ip没有作用,br0接口收到的报文会进入协议栈,所以可以设置br0的ip地址。(点击查看大图)Netfilter理解了以上内容,我们就已经知道了不同NetworkNamespaces的通用通信原理。他们对互联网的访问情况如何?这里我们还需要了解一套linux内核机制——Netfilter。Netfilter是Linux内核中的一套控制包转发的机制,可以在包转发路径上的不同时间点对包进行不同的控制。时间点包括接收包进入协议栈进行路由之前(NF_IP_PRE_ROUTING)、接收包路由结果为本地(NF_IP_LOCAL_IN)、接收包路由结果为转发(NF_IP_FORWARD)、发送包之前本机被路由(NF_IP_LOCAL_OUT)、路由后将离开本机的消息(NF_IP_POST_ROUTING),对消息的控制包括转发、丢弃、nat地址转换、修改消息等行为。Netfilter的功能非常复杂和强大,可以实现对报文转发的灵活控制,是Linux下很多防火墙的基础。(点击查看大图)04Bridge模式实现原理新:“停!停!停!我想了解Docker的Bridge模式的实现,你干嘛给我搞防火墙!”博哥:“别着急!前面不是铺垫知识,下面就给大家讲讲Bridge模式的实现~”在Docker的Bridge网络模式下,首先要做的就是隔离,Docker会为每个容器创建一个NetworkNamespace,容器内的进程都运行在容器所属的命名空间中,因此容器内进程的网络资源与外界是隔离的。萌新:“那怎么查看容器所属的命名空间?以及这个命名空间的一些信息?”博格:“问得好!为了方便给大家演示,还是把画面分享出来吧!我给大家演示一下操作,语音听不出来!”萌新:“好!我们开个网吧!”博格:“???你不是没有网络吗?”容器名为etcd_test(点击查看大图),然后我们可以查看这个容器的命名空间。我们首先使用dockerinspect命令查看容器的Pid,可以看到Pid为7236(点击查看大图)然后我们可以使用命令sudols-l/proc/7236/ns来查看容器的NetworkNamespace对应的文件号。得到的contenttypenet对应的值为所需命名空间的文件号4026532644(点击查看大图)得到文件号后,我们可以直接到/var/run/docker/netns路径下查找对应的命名空间,其中544777b0b671是容器etcd_test的命名空间(点击查看大图)因为我们前面提到的ipnetns只能管理位于/var/run/netns下的命名空间,为此我们需要使用sudoln-s/proc/7236/ns/net/var/run/netns/544777b0b671链接命名空间,这样我们就可以使用ipnetns来管理这个命名空间(点击查看大图)容器中的eth0其实是一个veth接口,而界面的另一端在宿主机的Networknamespace中,可以通过ip命令查看。接口名后面的@if14表示接口另一端的ifindex为14,所以可以看出容器的eth0和宿主机的veth0fc045f是一对veth接口,也可以看到masterdocker0inveth0fc045f的信息,说明veth0fc045f属于docker0的接口。(点击查看大图)docker0接口是docker服务器创建的桥接接口。通过在宿主机一端的docker0中加入veth,可以实现容器与宿主机/其他容器的二层通信。可以看到docker0的ip是172.17.0.1,和容器的ip是同一个网段,所以可以通过docker0和容器通信。(点击查看大图)我们还可以使用brctl查看网桥的相关信息,可以看到veth0fc045f是属于docker0的一个接口。(点击查看大图)(点击查看大图)到这里我们已经知道了容器是如何访问的,那么容器和外网呢?docker创建的容器默认可以访问外网,但是容器的ip是私有网段的ip,那么容器如何与外网ip通信呢?答案是nat地址转换。我们使用iptables查看nat表:(点击查看大图)我们会发现docker服务器创建了一条netfilter规则:-APOSTROUTING-s172.17.0.0/16!-odocker0-jMASQUERADE`,表示源地址为172.17.0.0/16,不是要转发给docker0的包。路由后,将源地址替换为出接口地址。上面提到的docker0接口充当网关。容器中的默认网关设置为docker0(172.17.0.1)的地址。访问外网IP时,数据包首先到达主机接口docker0,此时netfilter规则生效,将报文的源地址改写为出接口ip,报文相当于从主机发送到外网ip,当收到外网发回的报文时,将目的地址转换为源地址。这样容器就可以访问外网了。外网如何访问容器?通常在创建容器时设置端口映射,将访问宿主机指定端口转发到容器指定端口,将宿主机的ip和端口暴露给外部。就像我们的etcd_test容器一样,它将宿主机的2380和2379端口映射到容器的2380和2379端口。(点击查看大图)在上述查询的filter和nat表中,还可以找到两条这样的规则:1.Filter表:控制数据包是否允许进入、退出和转发。可以控制的链接包括INPUT和FORWARD以及OUTPUT。2、nat表:控制数据包中的地址转换,可以控制的链接有PREROUTING、INPUT、OUTPUT和POSTROUTING。(点击查看大图)在创建带有端口映射的容器时,docker会为每一对端口映射添加三个规则。以etcd_test容器的2380端口为例:(点击查看大图)因此,整个外部访问容器过程酱紫:外部访问2380端口的数据包先被-APREROUTING命中-maddrtype--dst-typeLOCAL-j进入路由前的DOCKER规则,并转移到DOCKER链上处理;在DOCKER链DOCKER中命中规则-A!-idocker0-ptcp-mtcp--dport2380-jDNAT--to-destination172.17.0.2:2380,报文的目的ip和端口改为172.17.0.2:2380;消息继续走进程,路由结果显示出接口为docker0,于是命中-AFORWARD-odocker0-jDOCKER规则,进入DOCKER链;在DOCKER链中,命中规则-ADOCKER-d172.17.0.2/32!-idocker0-odocker0-ptcp-mtcp--dport2380-jACCEPT,接受消息,消息进入docker0,修改目的mac,通过arp获取172.17.0.2对应的mac地址(veth),并按照bridge的二层转发流程,将消息发送到docker0下的指定接口(即上面提到的容器eth对应的veth接口);根据veth的特性,容器中的eth0接收消息,消息进入容器的协议栈;容器响应,根据之前设置的DNAT和CONNTRACK,将报文的源ip和端口改为宿主机出口的ip和端口,发送出去。