前言
Docker有4种网络通信模型,分别是:bridge、host、none、container,默认使用的网络模型是bridge,本文中用到的也是bridge网络模型
本文分享Docker网络原理,主要包含两部分内容:
- 容器之间通信
- 容器访问外网
1、前置网络知识
1)、veth pair
netns1这个network namespace目前没有任何配置,因此只有一块系统默认的本地回环设备lo
这两块网卡创建出来还都是DOWN状态,需要手动把状态设置成UP,同时绑定IP地址
上面两条命令首先进入netns1这个network namespace,为veth1绑定IP地址10.1.1.1/24,并把网卡的状态设置为UP,而仍在主机根network namespace中的网卡veth0被绑定了IP地址10.1.1.2/24。这样一来,就可以ping通veth pair的任意一头了
网桥是二层网络设备,Linux bridge就是Linux系统中的网桥,但是Linux bridge的行为更像是一台虚拟的网络交换机,任意的真实物理设备(例如eth0)和虚拟设备(例如veth pair)都可以连接到Linux bridge上。需要注意的是,Linux bridge不能跨机连接网络设备
Linux bridge与Linux其他网络设备的区别在于,普通的网络设备只有两端,从一端进来的数据会从另一端出去。例如,物理网卡从外面网络中收到的数据会转发给内核协议栈,而从协议栈过来的数据会转发到外面的物理网络中。Linux bridge则有多个端口,数据可以从任何端口进来,进来之后从哪个口出去取决于目的的MAC地址,原理和物理交换机差不多
案例:使用bridge连接veth pair实现跨network namespace之间的通信
使用bridge连接veth pair基本工作原理如下图:
3)、iptables
iptables是位于用户空间的一个面向系统管理员的Linux防火墙的管理工具,而iptables的底层实现是Netfilter,Linux内核通过Netfilter实现网络访问控制功能
之所以叫作链就是因为在访问该链的时候会按照每个链对应的表依次进行查询匹配执行的操作,如PREROUTING链对应的就是(->->),每个表按照优先级顺序进行连接,每个表中还可能有多个规则,因此最后看起来就像链一样,因此称为链
注意每一个链对应的表都是不完全一样的,表和链之间是多对多的对应关系。但是不管一个链对应多少个表,它的表都是按照下面的优先顺序来进行查找匹配的
表的处理优先级:>>>
四表:
iptables的四个表、、、,默认表是(没有指定表的时候就是filter表)
- :用来对数据包进行过滤,具体的规则要求决定如何处理一个数据包。对应的内核模块为:,其表内包括三个链:、、
- :nat全称是network address translation网络地址转换,主要用来修改数据包的IP地址、端口号信息。对应的内核模块为:,其表内包括三个链:、、
- :主要用来修改数据包的服务类型,生存周期,为数据包设置标记,实现流量整形、策略路由等。对应的内核模块为:,其表内包括五个链:、、、、
- :主要用来决定是否对数据包进行状态跟踪。对应的内核模块为:,其表内包括两个链:、
五链:
iptables的五个链、、、、
- :当收到访问防火墙本机地址的数据包时,将应用此链中的规则
- :当防火墙本机向外发送数据包时,将应用此链中的规则
- :当收到需要通过防火墙中转发给其他地址的数据包时,将应用此链中的规则,注意如果需要实现forward转发需要开启Linux内核中的ip_forward功能
- :在对数据包做路由选择之前,将应用此链中的规则
- :在对数据包做路由选择之后,将应用此链中的规则
2)iptables配置
iptables的基本语法命令格式
- 表名、链名:指定iptables命令所操作的表和链,未指定表名时将默认使用filter表
- 管理选项:表示iptables规则的操作方式,比如:插入、增加、删除、查看等
- 匹配条件:指定要处理的数据包的特征,不符合指定条件的数据包不处理
- 控制类型:指数据包的处理方式,比如:允许accept、拒绝reject、丢弃drop、日志log等
4)、NAT工作原理
接下来介绍一些NAT(Network Address Translation,网络地址转换)的基本知识,众所周知,IPv4的公网IP地址已经枯竭,但是需要接入互联网的设备还在不断增加,这其中NAT就发挥了很大的作用(此处不讨论IPv6)。NAT服务器提供了一组私有的IP地址池(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16),使得连接该NAT服务器的设备能够获得一个私有的IP地址(也称局域网IP/内网IP),当设备需要连接互联网的时候,NAT服务器将该设备的私有IP转换成可以在互联网上路由的公网IP(全球唯一)。NAT的实现方式有很多种,这里我们主要介绍三种:静态NAT、动态NAT和网络地址端口转换(NAPT)
1)BNAT
静态NAT:LVS的官方文档中也称为(N-to-N mapping) ,前面的N指的是局域网中需要联网的设备数量,后面的N指的是该NAT服务器所拥有的公网IP的数量。既然数量相等,那么就可以实现静态转换,即一个设备对应一个公网IP,这时候的NAT服务器只需要维护一张静态的NAT映射转换表
动态NAT:LVS的官方文档中也称为(M-to-N mapping) ,注意这时候的M>N,也就是说局域网中需要联网的设备数量多于NAT服务器拥有的公网IP数量,这时候就需要由NAT服务器来实现动态的转换,这样每个内网设备访问公网的时候使用的公网IP就不一定是同一个IP
2)NAPT
以上这两种都属于基本网络地址转换(Basic NAT),仅支持地址转换,不支持端口映射,这样就产生资源浪费的问题。我们知道一个IP实际上可以对应多个端口,而我们访问应用实际上是通过IP地址+端口号的形式来访问的,即客户端访问的时候发送请求到服务器应用程序坚挺的端口即可实现访问。那么NAPT就是在基础上的扩展,它在IP地址的基础上加上了端口号,支持了端口映射的功能
NAPT:NAPT实际上还可以分为源地址转换(SNAT)和目的地址转换(DNAT)两种,这个源地址和目的地址是针对NAT服务器而言
SNAT:
客户端上运行着一个浏览器,假设它使用的是5566端口,需要访问14.25.23.47这个Web服务器的HTTPS服务的443端口,它在访问的时候需要经过局域网出口的这个路由器网关(同时也是NAT服务器),路由器对它进行一个NAPT的源地址转换(SNAT),这时候客户端的请求经过NAT服务器之后变成了这个IP端口对Web服务器的443端口进行访问。这个过程中,目标服务器(Web服务器)的IP和端口是一直没有改变的
DNAT:
接下来在Web服务器接收到请求之后,需要返回数据给发送请求的设备,注意这时候Web服务器返回数据的指向IP应该是刚刚NAT服务器发送请求的这个IP端口,这时候路由器网关再进行一次NAPT的目标地址转换(DNAT),目标的IP端口就是最开始发送请求的这个端口
2、容器之间通信
1)、docker0网桥与veth pair
当我们安装完docker之后,docker会在宿主机上创建一个名叫docker0的网桥,默认IP是
凡是连接在docker0网桥上的容器就可以通过它来进行通信,而容器则通过veth pair连接到docker0网桥
启动一个叫作nginx-1的容器:
然后进入到这个容器中查看一下它的网络设备:
这个容器里有一张叫作eth0的网卡,正是一个veth pair设备在容器里的这一端
通过route命令查看nginx-1容器的路由表,这个eth0网卡是这个容器里的默认路由设备;所有对网段的请求也会被交给eth0来处理
而这个veth pair设备的另一端,则在宿主机上。可以通过查看宿主机的网络设备看到它:
通过ifconfig命令的输出可以看到,nginx-1容器对应的veth pair设备,在宿主机上是一张虚拟网卡,它的名字叫做veth5eda16a。 并且通过brctl show的输出,可以看到这张网卡插在了docker0上
如何找到docker和宿主机上veth pair设备的关系:
通过查看容器里的eth0网卡的iflink找到对应关系
这样就可以确定container f5e7b5b6b908在宿主机上对应的veth pair是veth5eda16a了
再启动另一个叫作nginx-2的容器:
就会发现一个新的、名叫veth6b9c3d8的虚拟网卡也被插在了docker0网桥上
2)、容器之间通信原理
在nginx-1容器里ping一下nginx-2容器的IP地址(172.17.0.3),就会发现同一宿主机上的两个容器默认就是相互连通的
当在nginx-1容器里访问nginx-2容器的IP地址(比如ping 172.17.0.3)的时候,这个目的IP地址会匹配到nginx-1容器里的第二条路由规则
可以看到,这条路由规则的网关(Gateway)是0.0.0.0,这就意味着这是一条直连规则,即:凡是匹配到这条规则的IP包,应该经过本机的eth0网卡,通过二层网络直接发往目的主机
而要通过二层网络设备到达nginx-2容器,就需要有172.17.0.3这个IP地址对应的MAC地址。所以nginx-1容器的网络协议栈,就需要通过eth0网卡发送一个ARP广播,来通过IP地址查找对应的MAC地址
这个eth0网卡是一个veth pair,它的一端在这个nginx-1容器的Network Namespace里,而另一端则位于宿主机上(Host Namespace),并且被插在了宿主机的docker0网桥上。一旦一张虚拟网卡被插在网桥上,它就会变成该网桥的从设备。从设备会被剥夺调用网络协议栈处理数据包的资格,从而降级成为网桥上的一个端口。而这个端口唯一的作用,就是接收流入的数据包,然后把这些数据包的处理(比如转发或者丢弃),全部交给对应的网桥
所以,在收到ARP请求之后,docker0网桥就会扮演二层交换机的角色,把ARP广播转发到其他被插在docker0上的虚拟网卡。这样,同样连接在docker0上的nginx-2容器的网络协议栈就会收到这个ARP请求,从而将172.17.0.3所对应的MAC地址回复给nginx-1容器
有了这个目的的MAC地址,nginx-1容器的eth0网卡就可以将数据包发出去
而根据veth pair设备的原理,这个数据包会立刻出现在宿主机上的veth5eda16a虚拟网卡上。不过,此时这个veth5eda16a网卡的网络协议栈的资格已经被剥夺,所以这个数据包就直接流入到了docker0网桥里
docker0处理转发的过程,则继续扮演二层交换机的角色。此时,docker0网桥根据数据包的目的MAC地址(也就是nginx-2容器的MAC地址),在它的CAM表(即交换机通过MAC地址学习维护的端口和MAC地址的对应表)里查到对应的端口(Port)为:veth6b9c3d8,然后把数据包发往这个端口
而这个端口,正是nginx-2容器插在docker0网桥上的另一块虚拟网卡,当然,它也是一个veth pair设备。这样,数据包就进入到了nginx-2容器的Network Namespace里
所以,nginx-2容器看到的情况是,它自己的eth0网卡上出现了流入的数据包。这样,nginx-2的网络协议栈就会对请求进行处理,最后将响应(Pong)返回到nginx-1
3、容器访问外网
在nginx-1容器中是能ping通的,说明容器里是能访问外网的
nginx-1位于docker0这个私有bridge网络中(172.17.0.0/16),当nginx-1从容器向外ping时,数据包是怎样到达的呢?
这里的关键就是NAT,查看下宿主机上的iptables规则:
在NAT表中有这么一条规则:
其含义就是:如果docker0网桥收到来自172.17.0.0/16网段外出包,把它交给MASQUERADE处理,而MASQUERADE的处理方式是将包的源地址替换成host的网址发送出去,做了一次源地址转换(SNAT)
下面我们通过tcpdump查看地址是如何转换的。先查看宿主机的路由表
通过route命令看到默认路由通过eth0发出去,所以我们同时要监控eth0和docker0上的icmp(ping)的数据包
当nginx-1容器中时,tcpdump输入如下:
docker0收到nginx-1的ping包,源地址为容器的IP:172.17.0.2,交给MASQUERADE处理。这时,在看eth0的变化
ping包的源地址变成eth0的172.19.216.110
这就是iptables的NAT规则的处理结果,从而保证数据包能够到达外网
- nginx-1发送ping包:172.17.0.2 > 202.89.233.101(对应的IP)
- docker0收到包,发现是发送到外网的,交给NAT处理
- NAT将源地址换成eth0的IP:172.19.216.110 > 202.89.233.101
- ping从eth0出去,到达
参考:
《Kubernetes网络权威指南:基础、原理与实践》
容器网络1.2-docker网桥原理实验
iptables的四表五链与NAT工作原理
浅谈容器网络