docker容器网络—单主机容器网络

时间:2023-03-08 20:50:08

当我们在单台物理机或虚拟机中运行多个docker容器应用时,这些容器之间是如何进行通信的呢,或者外界是如何访问这些容器的? 这里就涉及了单机容器网络相关的知识。docker 安装后默认

情况下会在宿主机上创建三种类型的网络,我们可以通过:docker network ls 查看,如下所示:

docker network ls
NETWORK ID NAME DRIVER SCOPE
8ad1446836a4 bridge bridge local
3be441aa5d9f host host local
e0542a92df5c none null local

下面将分别介绍这三种网络:

1. none

none网络,只有回环网络,容器内部只挂载了lo虚拟网卡,创建了该网络的容器是不能跟外界进行通信的。我们可以通过--network=none 指定none网络,下面创建一个none网络的容器,并查看

容器里的网络设备。

[root@VM_0_12_centos ~]# docker run -dit --net=none --name=bbox3 busybox
7c97d179f742bcd280d2f436427b18b2381c9588773ca612cd71bf840e0db2ea
[root@VM_0_12_centos ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7c97d179f742 busybox "sh" seconds ago Up seconds bbox3
进入容器只有lo网卡 

[root@VM_0_12_centos ~]# docker exec -it bbox3 sh
/ # ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU: Metric:
RX packets: errors: dropped: overruns: frame:
TX packets: errors: dropped: overruns: carrier:
collisions: txqueuelen:
RX bytes: (0.0 B) TX bytes: (0.0 B)
访问外界网络
/ # ping www.baidu.com
ping: bad address 'www.baidu.com' 可以ping通本地回环网络
/ # ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1): data bytes
bytes from 127.0.0.1: seq= ttl= time=0.068 ms
bytes from 127.0.0.1: seq= ttl= time=0.051 ms
bytes from 127.0.0.1: seq= ttl= time=0.067 ms
bytes from 127.0.0.1: seq= ttl= time=0.068 ms
bytes from 127.0.0.1: seq= ttl= time=0.071 ms
^C
--- 127.0.0.1 ping statistics ---
packets transmitted, packets received, % packet loss
round-trip min/avg/max = 0.051/0.065/0.071 ms 无法ping通外部
/ # ping 192.168.1.201
PING 192.168.1.201 (192.168.1.201): data bytes
ping: sendto: Network is unreachable

none 网络使用场景一般比较少见,主要应用于安全性比较高的场景,且不需要与外部进行通信的任务。

2. host 网络 

使用host网络的容器,会与宿主机共享网络栈,如共享ip与端口,优点:性能高,缺点:存在端口号冲突,多个容器无法对外暴露相同端口号。

容器使用host网络可以通过--net=host指定:如下所示:

给容器指定host网络模式
[root@VM_0_12_centos ~]# docker run -dit --net=host --name=nginx1 nginx
48e1df0c535193bfdef92f718de2c76427d88a371f14be1274022288cbe4ece6
可以通过宿主机ip与端口号访问容器应用。

[root@VM_0_12_centos ~]# telnet 172.26.0.12
Trying 172.26.0.12...
Connected to 172.26.0.12.
Escape character is '^]'.
在容器中可以看到 host 的所有网络设备

[root@VM_0_12_centos ~]# docker exec -it nginx1 ifconfig
docker0: flags=<UP,BROADCAST,MULTICAST> mtu
inet 172.17.0.1 netmask 255.255.0.0 broadcast 0.0.0.0
ether ::::: txqueuelen (Ethernet)
RX packets bytes (0.0 B)
RX errors dropped overruns frame
TX packets bytes (0.0 B)
TX errors dropped overruns carrier collisions eth0: flags=<UP,BROADCAST,RUNNING,MULTICAST> mtu
inet 172.26.0.12 netmask 255.255.240.0 broadcast 172.26.15.255
ether :::5d:: txqueuelen (Ethernet)
RX packets bytes (43.7 GiB)
RX errors dropped overruns frame
TX packets bytes (47.2 GiB)
TX errors dropped overruns carrier collisions lo: flags=<UP,LOOPBACK,RUNNING> mtu
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen (Local Loopback)
RX packets bytes (4.7 KiB)
RX errors dropped overruns frame
TX packets bytes (4.7 KiB)
TX errors dropped overruns carrier collisions

从host网络与宿主机共享网络栈特性可以得出,host网络能够支持容器的跨主机通信,不同容器之间的通信其实就是不同宿主机之间的不同端口之间的通信。这种方式的很大的弊端就是存在端口

号冲突,同一宿主机多个容器不能暴露同一端口号。其次,这种网络模式并没有充分发挥容器的隔离特性,容器与容器之间其实与宿主机共享网络栈。在大规模部署场景下,容器网络模式不会定义成

host模式。

3. bridge网络模式

默认情况下,我们不指定--net时,创建的容器都是使用的是bridge网络模式。

在该模式下,每创建一个容器都会给容器分配自己的network namespace,如 ip地址。

每个容器都有自己的ip,那么同一台宿主机上这些容器之间需要通信,则需要借助网桥的设备。我们在安装docker时,默认情况下会创建一个docker0 linux bridge的虚拟网桥设备,我们创建的容器

都会挂到docker0网桥上。关于linux虚拟网桥的知识我们可以参考这篇博文(https://segmentfault.com/a/1190000009491002

现在分别创建两个bridge网络的容器,如下:

[root@VM_0_12_centos ~]# docker run -dit  --name=bbox2 busybox
7b4221300b296026126d3cf600db39bed68e4048729982d6e09100f59ec900b7 [root@VM_0_12_centos ~]# docker run -dit --name=bbox1 busybox
91dd8c37571d69eacafe562f97a7c476f67a6189a0e58b4a95cc9a8f2ac013df
我们可以查看bridge网络的配置,子网分配,网关地址(docker0网桥)以及分配给每个容器的ip地址及mac地址,如下红色标注:
[root@VM_0_12_centos ~]# docker network inspect bridge
[
{
"Name": "bridge",
"Id": "5b6d64e4b4433bb639b26a3f4a0e828ecb1b2a54984cb01225dd682322fa61d4",
"Created": "2019-10-15T15:52:52.560070504+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"7b4221300b296026126d3cf600db39bed68e4048729982d6e09100f59ec900b7": {
"Name": "bbox2",
"EndpointID": "b3745248db52c2ea8e6eb7d05a5e581113105c4979717759d2d49bfebda2be49",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
},
"91dd8c37571d69eacafe562f97a7c476f67a6189a0e58b4a95cc9a8f2ac013df": {
"Name": "bbox1",
"EndpointID": "60b01cb330806b9fbbc9b212530b6561f7f56e697ac6ea1040f2e31419ed74d5",
"MacAddress": "02:42:ac:11:00:04",
"IPv4Address": "172.17.0.4/16",
"IPv6Address": ""
},
"f2f176f13894b434b4e7f6f6bcc68af1782559295d7ca430561eaf679d288deb": {
"Name": "nginx1",
"EndpointID": "d0f39a169126cd8222514eddfbba008a8e7f6727df8d49c25e19e35f6e53e191",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
通过ifconfig 可以看出,分别创建了两个以Veth开头的虚拟网卡

[root@VM_0_12_centos ~]# ifconfig
docker0: flags=<UP,BROADCAST,RUNNING,MULTICAST> mtu
inet 172.17.0.1 netmask 255.255.0.0 broadcast 0.0.0.0
ether ::::: txqueuelen (Ethernet)
RX packets bytes (278.0 B)
RX errors dropped overruns frame
TX packets bytes (452.0 B)
TX errors dropped overruns carrier collisions eth0: flags=<UP,BROADCAST,RUNNING,MULTICAST> mtu
inet 172.26.0.12 netmask 255.255.240.0 broadcast 172.26.15.255
ether :::5d:: txqueuelen (Ethernet)
RX packets bytes (43.7 GiB)
RX errors dropped overruns frame
TX packets bytes (47.2 GiB)
TX errors dropped overruns carrier collisions lo: flags=<UP,LOOPBACK,RUNNING> mtu
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen (Local Loopback)
RX packets bytes (4.7 KiB)
RX errors dropped overruns frame
TX packets bytes (4.7 KiB)
TX errors dropped overruns carrier collisions veth7614922: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
ether be:a8:fb:ed:e5:e8 txqueuelen (Ethernet)
RX packets bytes (378.0 B)
RX errors dropped overruns frame
TX packets bytes (420.0 B)
TX errors dropped overruns carrier collisions vethe3d2ca0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
ether f2:ec:f2:ab::a1 txqueuelen (Ethernet)
RX packets bytes (712.0 B)
RX errors dropped overruns frame
TX packets bytes (830.0 B)
TX errors dropped overruns carrier collisions

虚拟设备的一端的两个虚拟网卡都被挂载在docker0网桥上

[root@VM_0_12_centos ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024229183927 no veth7614922
vethe3d2ca0 而虚拟设备的另外一端分别挂在了两个容器上,在容器内部为eth0虚拟网卡,与宿主机 veth虚拟网卡一一对应。 分别进入两个容器内部查看网络设备

[root@VM_0_12_centos ~]# docker exec -it bbox1 ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:04  ------------ (对应虚拟设备的另外一端)
inet addr:172.17.0.4 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:11 errors:0 dropped:0 overruns:0 frame:0
TX packets:11 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:830 (830.0 B) TX bytes:712 (712.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:1
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)


[root@VM_0_12_centos ~]# docker exec -it bbox2 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:6 errors:0 dropped:0 overruns:0 frame:0
TX packets:5 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:420 (420.0 B) TX bytes:378 (378.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:1
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

 

可以看到两个容器内部都有自己的虚拟网卡eth0,mac地址,ip地址等。现在进入bbox1 来 ping bbox2的ip看是否能够ping通。

[root@VM_0_12_centos ~]# docker exec -it bbox1 ping 172.17.0.3
PING 172.17.0.3 (172.17.0.3): data bytes
bytes from 172.17.0.3: seq= ttl= time=0.087 ms
bytes from 172.17.0.3: seq= ttl= time=0.081 ms
bytes from 172.17.0.3: seq= ttl= time=0.063 ms
bytes from 172.17.0.3: seq= ttl= time=0.086 ms
bytes from 172.17.0.3: seq= ttl= time=0.082 ms
bytes from 172.17.0.3: seq= ttl= time=0.095 ms

以bbox1 ping容器bbox2对应的ip地址172.17.0.3来看两个容器之间是如何通信的。

首先bbox1 ping的过程中会发送 icmp包,ip层包头会填上:原地址为172.17.0.2,目的地址为172.17.0.3。容器的内核协议栈会经过路由选择:我们可以通过route来查看数据包从哪个端口发送。

/ # route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 172.17.0.1 0.0.0.0 UG eth0
172.17.0.0 * 255.255.0.0 U 0 0 0 eth0

根据路由表项可知,目的ip为172.17.0.3匹配的是第二项,因为Gateway为*, 可以通过容器bbox1的的虚拟网卡eth0在二层直接发送出去,不需要经过三层网络转发。数据包在经过二层封包的过程中

需要知道目的ip地址172.17.0.2对应的mac地址,进而将数据包

通过二层网络发送出去。初始通信的过程中bbox1是不知道ip(172.17.0.2)对应的mac地址的,所以需要向172.17.0.2发送ARP请求(广播包,目的mac地址填上以太网帧首部的硬件地址填

FF:FF:FF:FF:FF:FF),请求获取其mac地址。ARP包经过bbox1的eth0流向了另外一端

的虚拟网卡 veth7614922,该虚拟网卡作为网桥docker0的从设备,以端口号的形式挂载在docker0上。最终ARP包通过veth7614922设备端口号流入docker0网桥,docker0网桥处理ARP的过程如

下:

1. 首先会根据源mac地址与进入的端口号建立mac地址与端口号的映射关系存入CAM表(mac地址与端口映射表),学习用。

2. 判断该包是否为广播包(通过目的mac地址判断)如果为广播包,则进入4的流程。如果为非广播包,进入3流程。

3.从CAM表中查找目的mac地址对应的端口号是否存在,存在,则直接将这个数据包从对应端口转发出去。

4. 通过向所有的端口(除数据包进入的端口)发送arp包,在本例中,当bbox2中收到该arp包后,其会向bbox1回复响应包。响应包中会填上bbox2的mac地址。响应包经过bbox2的eth0网卡流入到

docker0的vethe3d2ca0端口进入docker0,docker0会根据1的规则建立bbox2的mac地址与端口号的映射关系,并通过先前建立 bbox1的mac地址与端口号的映射关系,将响应包通过veth7614922

端口回复给bbox1,bbox1拿到ARP的响应包后,通过二层封包,填上目的ip地址对应的mac地址将数据包以单播的形式经过docker0网桥转发出去。由于docker0网桥已经学习到bbox2的mac地址与

端口号的映射关系,这次可以直接通过目的mac地址对应的端口号,将数据包通过该端口号发送出去,从而完成从bbox1到bbox2的通信过程。

注:bbox1在获取到bbox2的ARP响应包后,会在本地存储对应的ip地址与mac地址的映射关系,如下图所示:

/ # arp -a
? (172.17.0.1) at ::::: [ether] on eth0
? (172.17.0.3) at 02:42:ac:11:00:03 [ether] on eth0
/ #