Docker网络模型介绍

582 阅读11分钟

这是我参与更文挑战的第9天,活动详情查看: 更文挑战

Docker网络介绍

引言

本文简单介绍Docker每种网络类型是如何进行网络交换的,Docker内容器在被NetworkNamespace隔离的情况下如何进行网络通信,跨主机容器间是如何进行网络通信

预准备

我这里创建了两台centos7的虚拟机,分别关闭了selinux和swap,filewalld。两台主机之间是可以相互ping通的。

主机
172.16.3.141
172.16.3.142

1.Bridge网络

当需要多个容器在同一个 Docker 主机上进行通信时,用户定义的桥接网络是最佳选择

Bridge是Docker默认使用的网络模型,我们来看下Bridge是如何使两个容器可以相互通信的,此时该主机所有配置均为默认配置。

粗略画一张图

image.png

[root@localhost ~]# docker -v
Docker version 20.10.7, build f0df350

我们发现这个出现了个docker0的网桥设备,同一台主机的容器都需要与本机的docker0网桥进行通信

[root@localhost ~]# ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:d2ff:fe1d:22a5  prefixlen 64  scopeid 0x20<link>
        ether 02:42:d2:1d:22:a5  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 5  bytes 446 (446.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.16.3.141  netmask 255.255.0.0  broadcast 172.16.255.255
        inet6 fe80::8d1e:c5fb:7a84:89a  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:81:ed:94  txqueuelen 1000  (Ethernet)
        RX packets 104250  bytes 139817031 (133.3 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 42132  bytes 4974745 (4.7 MiB)
        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
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 198  bytes 16420 (16.0 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 198  bytes 16420 (16.0 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
  1. 我们来创建两个沙盒容器,然后查看网卡的变化
docker run -it -d --name busybox busybox
docker run -it -d --name busybox1 busybox
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED          STATUS          PORTS     NAMES
c7d208c90e23   busybox   "sh"      19 minutes ago   Up 19 minutes             busybox1
15e045ae83f8   busybox   "sh"      25 minutes ago   Up 25 minutes             busybox

我们再次查看发现多了两个虚拟网卡分别为veth7c9ae7d,vethd5dab10,veth pair是一种虚拟设备,它通常是成对存在的,从其中一个网卡接收的请求会立即出现在对端的网卡上,也就是说宿主机上面的两个veth pair分别对应容器内的eth0

veth7c9ae7d: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet6 fe80::bc27:7fff:fe48:9a77  prefixlen 64  scopeid 0x20<link>
        ether be:27:7f:48:9a:77  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 8  bytes 656 (656.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

vethd5dab10: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet6 fe80::e409:5dff:fef4:6293  prefixlen 64  scopeid 0x20<link>
        ether e6:09:5d:f4:62:93  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 13  bytes 1102 (1.0 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

我们看下网桥设备,上面的两个虚拟设备插在了docker0网桥上,看到这就算不知道原理也应该知道容器之间通过这个docekr0网桥进行交互

[root@localhost ~]# brctl show
bridge name	bridge id		STP enabled	interfaces
docker0		8000.0242d21d22a5	no		veth7c9ae7d
							vethd5dab10

在看一下docker network,为了方便看我删除了一些信息,我们可以看到,新创建的两个容器已经出现在这个默认Bridge的配置里了,并分别分配了子网的ip172.17.0.2和172.17.0.3

[root@localhost ~]# docker network inspect 0f2e3e50513e
[
    {
        "Name": "bridge",
        .........
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                 #配置的子网范围
                {
                    "Subnet": "172.17.0.0/16"
                }
            ]
        },
        "Containers": {
            "15e045ae83f863c0c74588f8a1cea51e49f11128e46c37df7707c91fafb5a996": {
                "Name": "busybox",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
            },
            "c7d208c90e23137846c6fbdba0af8f3e28c79503dbd37cbc41ec5677343a3ae5": {
                "Name": "busybox1",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
            }
        },
    }
]

我们尝试在容器内进行ping另一个容器的ip看是否可以通,此时的网络情况

容器ip
busybox172.17.0.2
busybox1172.17.0.3
[root@localhost ~]# docker exec -it busybox sh -c 'ping 172.17.0.3'
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.419 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.830 ms

[root@localhost ~]# docker exec -it busybox1 sh -c 'ping 172.17.0.2'
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.419 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.942 ms

他们是如何进行通信的?不是被NetworkNamespace隔离了吗?我们进入容器看下网卡信息和路由表,所有对172.17.0.0网段的请求会经过eth0,这个eth0是个啥,他是怎么将请求路由到另一个容器里的eth0的?

[root@localhost ~]# docker exec -it busybox1 sh -c 'ifconfig'
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:03
          inet addr:172.17.0.3  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:20 errors:0 dropped:0 overruns:0 frame:0
          TX packets:12 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:1496 (1.4 KiB)  TX bytes:840 (840.0 B)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

[root@localhost ~]# docker exec -it busybox1 sh -c 'route -n'
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.17.0.1      0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

我们来看下宿主机的路由表,所有对172.17.0.0网段的请求都会被路由到docker0网桥上,那么我们现在知道从172.17.0.3(busybox1)容器发出的请求会被经过自己的eth0网卡经由对应宿主机上的vethpair发送给docker0,那么docker0是如何找到172.17.0.2这个目标ip的呢?

[root@localhost ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.16.3.1      0.0.0.0         UG    100    0        0 ens33
172.16.0.0      0.0.0.0         255.255.0.0     U     100    0        0 ens33
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0

由容器中的路由表可知,目标地址为172.17.0.0会走eth0,而Flags=U,说明他是一个直连规则,而直连规则只需要走二层网络,但是二层网络需要目的ip 172.17.0.2的mac地址,此时会通过docker0发一个Arp广播找到172.17.0.2所对应的mac地址,然后发起直连请求,下面我们来验证一下这个过程


开启TRACE功能,看下数据包怎么走的

iptables -t raw -A OUTPUT -p icmp -j TRACE
iptables -t raw -A PREROUTING -p icmp -j TRACE

此时我们在busybox(172.17.0.2)容器中ping一下busybox1(172.17.0.3)

跟踪下iptables trace日志查看下对应的信息,从内容中我们知道,我们从172.17.0.2发起了一个icmp请求到172.17.0.3上

tail -f /var/log/message
Jun 23 08:17:17 localhost kernel: TRACE:
raw:PREROUTING
:rule:4 
IN=docker0     #数据包进入的接口,若为空表示本机产生
OUT=           #数据包离开的接口,若为空表示本机接收
PHYSIN=vethd5dab10
#目标mac地址02:42:ac:11:00:03 ,源 02:42:ac:11:00:02
MAC=02:42:ac:11:00:03:02:42:ac:11:00:02:08:00  #08:00 为上层协议代码,即表示IP协议
SRC=172.17.0.2 #源ip
DST=172.17.0.3 #目标ip
LEN=84 
TOS=0x00 
PREC=0x00 
TTL=64 
ID=41094 
DF 
PROTO=ICMP  #传输协议(因为这里是ping了一下所以是icmp)
TYPE=8 
CODE=0 
ID=20 
SEQ=0

看下arp表,目标172.17.0.3对应的mac地址是02:42:ac:11:00:03,然后通过CAM表获取到目标的veth pari,由于veth pair的特性,直接就进入了容器内部,自然就可以ping通了(CAM是mac地址学习的功能获取到的)

[root@localhost ~]# cat /proc/net/arp
IP address       HW type     Flags       HW address            Mask     Device
172.17.0.3       0x1         0x2         02:42:ac:11:00:03     *        docker0
172.16.3.1       0x1         0x2         3e:22:fb:a1:32:64     *        ens33
172.17.0.2       0x1         0x2         02:42:ac:11:00:02     *        docker0

知道了容器直接网络传输的流程,那我们在宿主机机上直接ping容器的ip会怎么样呢?可以看到也是ping的通的,此时的数据走向是如何的呢

[root@localhost ~]# ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=1.68 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.445 ms

根据宿主机的路由表可知,发往172.17.0.2的数据包会经过docker0网桥设备,可以看到也是一个直连类型,然后又回到了上面arp的流程

172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0

ojbk,那如果是外部请求访问容器呢?比如我起了一个nginx容器端口为80,但是请求的目的ip为宿主机的ip比如172.16.3.140,此时如何转发到容器内呢?其实很容易猜到了,就是DNAT(目标地址转换)

[root@localhost ~]# iptables -t nat -L -n
Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:80 to:172.17.0.4:80

然后又回到了路由表、arp的操作

参考官网文档

2.Host网络

这个就比较简单了,它的网络命名空间使用的其实就是宿主机的(其他的依然是独立的,被隔离的)

docker run -d -it --name busybox2 --network host  busybox

此时我们查看ifconfig,是没有veth pair的设备被创建出来的,进入容器后使用ifconfig网络视图和宿主机上看到的是一模一样的。

 "Containers": {
            "45e964d3ccf99c044f0bae5aef145e8036ecda5da7bb6dd8495a4c0ca7588a84": {
                "Name": "busybox2",
                "EndpointID": "fecfcdbf4a8324362b6e7fdda835c9ad110bd31b72254beba8e3e04ef42d33d7",
                "MacAddress": "",
                "IPv4Address": "",
                "IPv6Address": ""
            }
        },

3.container模式

container模式就是,多个容器使用一个容器的网络命名空间。

image.png

我们启动两个容器busybox busybox1,busybox1使用busybox的网络视图

docker run -d -it --name busybox  busybox
docker run -d -it --name busybox1 --network container:busybox  busybox

查看一下两个容器的网络命名空间,是一模一样的。

[root@localhost ~]# docker exec -it busybox sh -c 'ifconfig'
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:13 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:1102 (1.0 KiB)  TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

[root@localhost ~]# docker exec -it busybox1 sh -c 'ifconfig'
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:13 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:1102 (1.0 KiB)  TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

4.MacVlan(跨主机)

您的网络设备需要能够处理“混杂模式”,即一个物理接口可以分配多个 MAC 地址。

使用macvalan需要开启网卡的混杂模式,目前显然并不支持(两台主机均需要执行)

ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
#我这里虚拟机网卡叫ens33
ifconfig ens33 promisc
#看到PROMISC就代表开启了
ens33: flags=4419<UP,BROADCAST,RUNNING,PROMISC,MULTICAST>  mtu 1500

此时我们来创建一个驱动为macvaln的网络(两台主机均需要执行)

docker network create -d macvlan \
  --subnet=172.16.3.0/24 \   #必须和宿主机一个网段
  --gateway=172.16.3.1 \
  -o parent=ens33 sup-net   #指定物理网卡名称

分别在两台主机创建一个容器

#172.16.3.140
docker run -itd --name aaa --ip=172.16.3.100 --network sup-net busybox
#172.16.3.141
docker run -itd --name bbb --ip=172.16.3.101 --network sup-net busybox

两个容器已经启动完成

[root@localhost ~]# docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS     NAMES
dbd7da305fcf   busybox   "sh"                     22 seconds ago   Up 20 seconds             aaa
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED          STATUS          PORTS     NAMES
70a6f2d0a2aa   busybox   "sh"      38 seconds ago   Up 37 seconds             bbb

我们来ping一下,可以发现已经互通了

[root@localhost ~]# docker exec -it aaa sh -c 'ping 172.16.3.101'
PING 172.16.3.101 (172.16.3.101): 56 data bytes
64 bytes from 172.16.3.101: seq=0 ttl=64 time=1.822 ms
#反向
[root@localhost ~]# docker exec -it bbb sh -c 'ping 172.16.3.100'
PING 172.16.3.100 (172.16.3.100): 56 data bytes
64 bytes from 172.16.3.100: seq=0 ttl=64 time=0.679 ms
64 bytes from 172.16.3.100: seq=1 ttl=64 time=0.621 ms

参考官网文档