浅析Docker网络原理

2,001 阅读6分钟

一、Docker网络有哪些

学习Docker,会学习到docker网络,一个网络是一组可以相互联通的端点。Docker在安装时就会创建3个网络

# 查看网络
docker network ls

其余2个是自己创建的网络,可以暂时不关注

host网络:与宿主机在同一个网络中,与宿主机共用一个Network Namespace。容器将不会虚拟自己的网卡和IP,而是使用宿主机的IP和端口。若端口冲突就不能使用该模式了

none网络:关闭容器的网络,放在上面的容器都是不需要网络的。例如生成随机密码的功能

bridge网络:容器使用独立的Network Namespace,并连接到docker0虚拟网桥。如果不指明网络,默认创建的容器都会挂在docker0中。容器通过docker0网桥以及Iptables net表配置与宿主机通信。

二、Bridge网络是什么

Docker server启动的时候,主机会自动创建docker0的虚拟网桥,所有新创建的容器都会连接到这个网桥之上,这样主机的容器相当于在一个二层网络中。Bridge网络会为每个容器分配veth pair对,网卡的一头在容器中,一头在挂在网桥docker0上。

# 查看主机的网桥
brctl show

可以看到docker0网桥上已经挂载了3个网卡了。这里说明已经创建了三个容器,这三个就是容器的虚拟网卡。

# 查看bridge网络的相关信息
docker network inspect bridge

可以看到bridge网段是172.17.0.0/16,网关占用第一个ip(172.17.0.1),剩下的ip随机分配给创建的容器

接下来看一下容器的网络配置:

接下来看一下mysqltest容器的网络配置:

apt-get install net-tools // 安装ifconfig

apt-get install iputils-ping // 安装ping

apt-get install iproute2 // 安装ip

可以看到为该容器分配的ip是172.17.0.3,容器端网卡是eth0@if1232。

通过ip addr 命令找到另一端网卡vethc991ce1 通过brctl show 命令看到vethc991ce1挂在docker0上 我们可以看到mysqltest容器2端有一对veth pair(vethc991ce1和eth0@if1232),这2端一端(eth0@if1232)在容器中,一端(vethc991ce1)挂在docker0,这相当于eth0@if1232也挂在docker0上,从而将容器连接到bridge网络中

当前的容器网络拓扑图如下:

三、容器之间是怎么相互通信的

1、主机内部,相同网络

Docker基于veth pair网卡对,实现相同网络之间的通信,不是直接端对端,是通过bridge间接实现通信。

在mysql2容器内部,ping mysql1的ip,可以看到是可以互通的

在mysql2容器内部可以看到路由表:接口是eth0,通过网卡将信息传输到docker0,docker0广播地址,通过网卡找到mysql1

2、主机内部,不同网络

不同网络之间是不能直接进行通信的,也就是在172.17.0.2容器内部不能ping通172.18.0.4。如下图是连接不通的网络拓扑图:

可以通过以下2种方法实现不同网络直接的通信:

方法一:把容器加入到另一个网络中,把想要通信的容器放在同一容器

也就是将peer0.org2容器加入到bridge 通过ifconfig可以看到peer0.org2容器在之前的网卡基础上(lo,eth0)增加了eth1网卡,且分配了一个bridge网络网段的ip

# 将04aec1064812(peer0.org2)容器加入到bridge网络中
[centos@wunaichi-fabric ~]$ docker network connect bridge 04aec1064812
# 进入04aec1064812容器
[centos@wunaichi-fabric ~]$ docker exec -it 04aec1064812 bash
# 查看ip 可以看到在以前的基础上增加了eth1网卡,且分配了一个bridg网段的ip
root@04aec1064812:/opt/gopath/src/github.com/hyperledger/fabric/peer# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.0.4  netmask 255.255.0.0  broadcast 0.0.0.0
        ether 02:42:ac:12:00:04  txqueuelen 0  (Ethernet)
        RX packets 2487840  bytes 433949085 (413.8 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2455380  bytes 426722690 (406.9 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.5  netmask 255.255.0.0  broadcast 0.0.0.0
        ether 02:42:ac:11:00:05  txqueuelen 0  (Ethernet)
        RX packets 8  bytes 656 (656.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 38  bytes 3151 (3.0 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 38  bytes 3151 (3.0 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0


查看bridge网络也可以看到peer0.org2容器

进入peer0.org2容器可以看到该容器同时属于2个网络中

这样我们的网络拓扑图会变成这样:

peer0.org2加入到新网络之后,会产生一对veth pair在bridge网络上,这样就和在同一网络通信是一致的了。

方法二:修改底层iptables实现网络的通信
# iptables-save 查看iptables规则
[centos@wunaichi-fabric ~]$ sudo iptables-save
...
-A DOCKER-ISOLATION -i br-b86310df8e66 -o br-7a0183b7dfed -j DROP
-A DOCKER-ISOLATION -i br-7a0183b7dfed -o br-b86310df8e66 -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-7a0183b7dfed -j DROP
-A DOCKER-ISOLATION -i br-7a0183b7dfed -o docker0 -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-b86310df8e66 -j DROP
-A DOCKER-ISOLATION -i br-b86310df8e66 -o docker0 -j DROP
...

为什么2个网络不通其实原理就在这里:在iptables规则就drop掉了网桥docker0和br-b86310df8e66之间的双向流量。规则的命名DOCKER-ISOLATION也可知道docker 在设计上就是隔离了不同的network。

所以这里最直接的办法就是修改iptables规则:增加双向关系

sudo iptables -I DOCKER-USER -i docker0 -o br-b86310df8e66 -j ACCEPT
sudo iptables -I DOCKER-USER -i br-b86310df8e66 -o docker0 -j ACCEPT

验证一下:完美~

3、外部世界与容器的通信

在容器内ping www.baidu.com 是可以通的

可见,容器默认就可以访问外网。但是原理是什么呢?

先查一下iptables规则,在NAT表有这么一条规则:

-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

这条语句表明:网桥docker0收到来自172.17.0.0/16网段的外出包,把它交给MASQUERADE处理。MASQUERADE的处理方式将包的源地址替换成host的地址发送出去,也就是做了一次网络地址转换(NAT)

查看路由表,可以看到默认路由是通过eth0发送出去

我们通过tcpdump抓包来看下是怎么转换的

第一步:先让docker0网络的容器ping 百度的网址

第二步:看到docker0抓包情况

docker0收到mysqltest的ping包,源地址为容器的ip 172.17.0.3,然后交给MASQUERADE处理。

第三步:看到eth0抓包情况

在eth0看到把ping包源地址转成10.0.0.234,这就是NAT规则处理的结果,保证数据包到达外网。下图就是以上的流程:

看到这里你可能还有疑问,那不同主机之间的容器是怎么通信的,这个留到下次学习~