这是我参与更文挑战的第9天,活动详情查看: 更文挑战
Docker网络介绍
引言
本文简单介绍Docker每种网络类型是如何进行网络交换的,Docker内容器在被NetworkNamespace隔离的情况下如何进行网络通信,跨主机容器间是如何进行网络通信
预准备
我这里创建了两台centos7的虚拟机,分别关闭了selinux和swap,filewalld。两台主机之间是可以相互ping通的。
| 主机 |
|---|
| 172.16.3.141 |
| 172.16.3.142 |
1.Bridge网络
当需要多个容器在同一个 Docker 主机上进行通信时,用户定义的桥接网络是最佳选择
Bridge是Docker默认使用的网络模型,我们来看下Bridge是如何使两个容器可以相互通信的,此时该主机所有配置均为默认配置。
粗略画一张图
[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
- 我们来创建两个沙盒容器,然后查看网卡的变化
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 |
|---|---|
| busybox | 172.17.0.2 |
| busybox1 | 172.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模式就是,多个容器使用一个容器的网络命名空间。
我们启动两个容器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