Docker 之所以功能这么强大,其实就是充分利用了Linux Kernel的特性:NameSpace、CGroups、UnionFileSystem。通过这些特性实现了资源隔离、限制与分层等。本文这次就来揭晓Docker中的容器是如何做到网络互通的。
1、通信
两台机器如果要实现通信,其实就是通过底层的网卡进行数据传输,每个网卡都有一个唯一的MAC地址,网卡又会绑定一个ip地址,只要两台机器的网络可以互通,那么这两台机器就可以进行通信。

2、Linux中的网卡
-
查看网卡信息
-
ip a
-
ip link show
-
[root@10 bin]# ip a
# ...省略其他网卡组件...
# 这是docker安装后用于容器与宿主机通信的网卡
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ea:00:33:b7 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:eaff:fe00:33b7/64 scope link
valid_lft forever preferred_lft forever
# 这是docker在创建容器之后生成的网卡,后面再解释
8: veth3543ea3@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether c2:7a:e1:ac:8b:db brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::c07a:e1ff:feac:8bdb/64 scope link
valid_lft forever preferred_lft forever
想要实现通信,就得有两个同一网段的网卡,两个网卡必须是可以 ping 通的。
Docker在安装成功后,会在宿主机创建一个docker0网卡,这个网卡就是负责容器与宿主机之间通信的桥梁。
通过Docker创建一个容器之后,会在宿主机再创建一个网卡,也就是上面的veth3543ea3@if7
,容器内也会创建一个网卡。
[root@10 bin]# docker exec -it 8944 bash
root@8944b2c2cfad:/usr/local/tomcat# ip a
# 容器内部的网卡
7: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
一般成对的网卡,网卡组件名称后面的数字是连续的,比如宿主机的@if7
和容器内的@if8
,正是这成对的网卡,才实现了容器与宿主机之间的通信。这其实是利用Linux Kernel
的特性NameSpace
实现的网卡隔离,不同NameSpace下的网卡是独立的,就像Java程序中的 package
一样。
- 通信方式
从上面的例子中看到容器与宿主机之间的通信好像并不是通过docker0网卡实现的?
其实这只是单容器的状态,可能看不出docker0的作用。用图来表示一下单容器的网卡通信情况。

通过docker生成的eth0
和 veth
两个网卡实现同一网段内的通信,而这个网卡又是桥接在docker0网卡上的。
再看下有多个容器的情况。

两个容器之间可以互相通信的原因就是因为docker0的存在,因为它们的网卡都是桥接在docker0上,所以也就有了和另一个容器通信的桥。
我们来验证一下是不是这样!
[root@10 bin]# yum install bridge-utils
[root@10 bin]# brctl show
# docker0中的网卡
bridge name bridge id STP enabled interfaces
docker0 8000.0242ea0033b7 no vethbd21536
vethf802a96
[root@10 bin]# ip a
# docker0 网卡
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ea:00:33:b7 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:eaff:fe00:33b7/64 scope link
valid_lft forever preferred_lft forever
# 与容器成对的网卡
10: vethf802a96@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether b6:9d:5d:2d:5a:c9 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::b49d:5dff:fe2d:5ac9/64 scope link
valid_lft forever preferred_lft forever
# 与容器成对的网卡
12: vethbd21536@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 9a:7e:8f:c5:a8:b1 brd ff:ff:ff:ff:ff:ff link-netnsid 2
inet6 fe80::987e:8fff:fec5:a8b1/64 scope link
valid_lft forever preferred_lft forever
通过工具查看,确认了与容器中的网卡对应的宿主机的网卡的确是在 docker0 中的。
2、brige
这种网络连接方法我们称为Bridge,这也是docker中默认的网络模式。可以通过命令查看docker中的网络模式:
- docker network ls
[root@10 bin]# docker network ls
NETWORK ID NAME DRIVER SCOPE
3f9cab018571 bridge bridge local
d2f72fb35175 host host local
a2f09c4b5e89 none null local
# 查看brige的详情
[root@10 bin]# docker network inspect bridge
[
{
"Name": "bridge",
"Id": "3f9cab01857158e89f23b725ecc9d1c7665b49cbc0b7baff41f18673a819736a",
"Created": "2020-03-12T05:28:35.790892956Z",
"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": {
"8f4e1f8fc31f548164700799284c9e77ca3888873f5b1edeb6f7569d8e24e48e": {
"Name": "tomcat01",
"EndpointID": "d7821f31180faab94fd7cb9b02d6521e1596d919f357b5ff67326b5b41008bfa",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
},
"bb9ee0e25ddac71de1a3662c9b895d6143cdbe1b234741e0c56d2569f42a0d03": {
"Name": "tomcat02",
"EndpointID": "d9bb8e5b817790ada67262206137ad07824cf61f44ed6f48574f6ce14ae7c3f3",
"MacAddress": "02:42:ac:11:00:04",
"IPv4Address": "172.17.0.4/16",
"IPv6Address": ""
}
}
...省略...
}
]
可以看到,之前创建的 tomcat01 和 tomcat02 都是用的 brige 模式。
3、Host
通过 docker network ls 命令查看到,docker提供了3种网络模式,brige模式我们已经知道了,那 host 和 none 又是什么意思呢?不妨来验证一下:
# 指定使用 host 网络模式
[root@10 bin]# docker run -it --name host-net-tomcat --network host tomcat bash
root@10:/usr/local/tomcat# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:8a:fe:e6 brd ff:ff:ff:ff:ff:ff
inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute eth0
valid_lft 59465sec preferred_lft 59465sec
inet6 fe80::5054:ff:fe8a:fee6/64 scope link
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:2a:e4:99 brd ff:ff:ff:ff:ff:ff
inet 192.168.50.224/24 brd 192.168.50.255 scope global dynamic noprefixroute eth1
valid_lft 59467sec preferred_lft 59467sec
inet6 fe80::a00:27ff:fe2a:e499/64 scope link
valid_lft forever preferred_lft forever
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ea:00:33:b7 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:eaff:fe00:33b7/64 scope link
valid_lft forever preferred_lft forever
8: veth3543ea3@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether c2:7a:e1:ac:8b:db brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::c07a:e1ff:feac:8bdb/64 scope link
valid_lft forever preferred_lft forever
10: vethf802a96@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether b6:9d:5d:2d:5a:c9 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::b49d:5dff:fe2d:5ac9/64 scope link
valid_lft forever preferred_lft forever
12: vethbd21536@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 9a:7e:8f:c5:a8:b1 brd ff:ff:ff:ff:ff:ff link-netnsid 2
inet6 fe80::987e:8fff:fec5:a8b1/64 scope link
valid_lft forever preferred_lft forever
其实 host 模式就是共享宿主机的网卡,不过一般不会使用它,在Docker中最常用的就是 brige 模式。
4、None
这种模式只会创建一个本地的环路网卡,无法与其他容器或宿主机进行通信。
[root@10 bin]# docker run -d --name tomcat-none-net --network none tomcat
aa2a2448c9851f652ede47c453c54c6d1a79408fde755d4965a8d761c7fa3be8
[root@10 bin]# docker exec -it tomcat-none-net bash
root@aa2a2448c985:/usr/local/tomcat# ip a
# 只会有一个local的本地环路网卡
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
5、创建自己的network
在创建自己的network之前先来解释一下为什么要创建新的network。
我们用一个例子来演示一下不同容器之间的通信。
# 创建两个容器
[root@10 bin]# docker run -d --name tomcat01 -p 8081:8080 tomcat
1404349dc899b6a7e6eb3da30bfc2d5ea8a559f6dbd51dd238c039458ed6504f
[root@10 bin]# docker run -d --name tomcat02 -p 8082:8080 tomcat
a3da6eeb60a12e727faa2d65b4ebc9352bea45700b01687c2edda26a3ade9690
[root@10 bin]# docker exec -it tomcat01 bash
root@1404349dc899:/usr/local/tomcat# ip a
# tomcat01的ip是 172.17.0.2
13: eth0@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
root@1404349dc899:/usr/local/tomcat# exit
exit
[root@10 bin]# docker exec -it tomcat02 bash
root@a3da6eeb60a1:/usr/local/tomcat# ip a
# tomcat02的ip是 172.17.0.3
15: eth0@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
# 在tomcat02 中可以 ping 通 tomcat01
root@a3da6eeb60a1:/usr/local/tomcat# 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=0.058 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.063 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.050 ms
64 bytes from 172.17.0.2: icmp_seq=4 ttl=64 time=0.055 ms
容器之间通过 ip 是可以正常访问的,但是有没有这种情况:如果一个容器出问题了,我们重启之后它的ip变了,那是不是其他用到这个容器的项目配置是不是都得改。
有没有可能直接通过容器名称来访问呢?来验证一下:
root@a3da6eeb60a1:/usr/local/tomcat# ping tomcat01
ping: tomcat01: Name or service not known
发现并不能 ping 通,但是可以使用别的手段来达到这个目的。
# 删掉容器
[root@10 bin]# docker rm -f tomcat01
tomcat01
# --link tomcat02 表示映射tomcat02的ip
[root@10 bin]# docker run -it --name tomcat01 --link tomcat02 tomcat bash
root@ada7cfa2630a:/usr/local/tomcat# ping tomcat02
PING tomcat02 (172.17.0.3) 56(84) bytes of data.
64 bytes from tomcat02 (172.17.0.3): icmp_seq=1 ttl=64 time=0.057 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=2 ttl=64 time=0.050 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=3 ttl=64 time=0.049 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=4 ttl=64 time=0.080 ms
通过上面这种方式就可以做到以容器名来 ping 通其他容器,其实它就跟windows系统中在 hosts 文件里加了个映射是一样的。
但是不推荐这么用,因为有更好的办法去实现,那就是创建自定义的 network。
# 创建自定义 network
[root@10 bin]# docker network create tomcat-net
8122e48506f67f003b39e15b9b14a5bfa50de89012381904fb60898774a60a47
[root@10 bin]# docker network ls
NETWORK ID NAME DRIVER SCOPE
3f9cab018571 bridge bridge local
d2f72fb35175 host host local
a2f09c4b5e89 none null local
8122e48506f6 tomcat-net bridge local
# 创建两个容器, 指定使用我们自建的 network
[root@10 bin]# docker run -d --name tomcat01 --network tomcat-net tomcat
c828635fb8899cdfc702ac808c1db770c2d39d636b745dfe512fefe560daec78
[root@10 bin]# docker run -d --name tomcat02 --network tomcat-net tomcat
d18cd55aa3d7cccefb1608644441707cc512c19a31e8e0d659869e9d977d9106
[root@10 bin]# docker exec -it tomcat01 bash
# 发现可以ping通了
root@c828635fb889:/usr/local/tomcat# ping tomcat02
PING tomcat02 (172.18.0.3) 56(84) bytes of data.
64 bytes from tomcat01.tomcat-net (172.18.0.3): icmp_seq=1 ttl=64 time=0.049 ms
64 bytes from tomcat01.tomcat-net (172.18.0.3): icmp_seq=2 ttl=64 time=0.040 ms
64 bytes from tomcat01.tomcat-net (172.18.0.3): icmp_seq=3 ttl=64 time=0.040 ms
64 bytes from tomcat01.tomcat-net (172.18.0.3): icmp_seq=4 ttl=64 time=0.081 ms
可以看到创建自定义的 network 自动帮我们实现了这个功能,而且使用自定义的 network 也方便管理,不同业务类型的容器可以指定不同的 network。
不同的 network ip网段也不一样,这样也可以增加单机中可以创建的容器的数量。
6、端口映射
在创建一个容器的时候,一般都需要讲容器需要暴露的端口映射到物理主机的相同端口或其他端口,因为在外网环境下是不方便直接连接到容器的,所以需要通过映射端口的方式,让外网访问宿主机的映射端口来访问容器。
# 创建一个tomcat容器,名称为port-tomcat
[root@10 bin]# docker exec -it port-tomcat bash
root@b35845089eb3:/usr/local/tomcat# curl localhost:8080
# 得到结果 test port
# 因为这是在容器内部,可以直接访问
# 如果在宿主机
[root@10 bin]# curl 172.17.0.2:8080
# 也能得到结果 test port
# 之所以能够访问成功,是因为centos上的docker0连接了port-tomcat的network namespace
如果想建立多个容器,势必需要端口映射,以满足不同容器使用相同端口的情况。
以上这些内容都是在单容器进行操作,容器之间通信也只是通过 docker0 实现的桥接模式。
如果要实现多机之间的docker通信,其实还是通过网卡,只不过需要其他的技术来实现了。

本章节就不在演示,到后面的章节再来分析!
网友评论