为什么需要学习 Docker 网络?我们先试想这么一个场景:
我们在 Docker 里面运行了 Tomcat 和 Mysql 两个容器,在 Tomcat 容器里面部署了一个 Web 项目,当我们 Web 项目启动之后需要调用 MySQL 里面的数据,此时需要什么网络去连接 Tomcat 和 Mysql 容器?这就是 Docker 网络需要研究的内容。
3.1 Docker 网络的基本概述
接下来我们来认识一下什么是 Docker 网络:
当我们没有启动 Docker 的时候,我们使用 ifconfig 命令查看 CentOS7 网络情况:

在 CentOS7 的安装过程中如果有选择相关虚拟化的的服务安装系统后,启动网卡时会发现有一个以网桥连接的私网地址的 virbr0 网卡(virbr0 网卡:它还有一个固定的默认 IP 地址 192.168.122.1),是做虚拟机网桥的使用的,其作用是为连接其上的虚机网卡提供 NAT 访问外网的功能。
当我们启动 Docker 的时候,我们使用 ifconfig 命令查看 CentOS7 网络情况:
[root@shjava101 ~]# systemctl start docker

这个 docker0 是什么?也是一个虚拟网桥。通过虚拟网桥可以实现宿主机和 Docker 容器之间的通信。同时容器与容器之间的通信也需要通过虚拟网桥实现。
当我们安装 Docker 之后,默认会帮助我们创建 3 种网络模式:
[root@shjava101 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
402a5ca8f6ac bridge bridge local
b34e9a79539a host host local
0d6fe04a197b none null local
下面我们分别对这三种默认的网络模式做一个简单的介绍:
| 网络模式 | 简介 |
|---|---|
| bridge | 为每一个容器分配、设置 ip,并将容器连接到一个名为 docker0 的虚拟网桥,默认为该模式。 |
| host | 容器将不会虚拟出自己的虚拟网卡,设置自己的 ip 等。而是使用宿主机的 ip 的端口。 使用 --network host 参数可以指定 |
| none | 容器有独立的 network namespace,但并没有对其进行任何网络设置,入分配 veth pair 和网络桥接 ip 等。 使用 --network none 参数指定。一般很少使用。 |
| container | 新创建的容器不会创建自己的网卡和设置自己的 ip。而是和一个指定的容器共享 ip 端口范围等。 |
3.2 Docker 网络的基本命令
我们可以使用 help 命令去查看 Docker 的常用命令有哪些?
[root@shjava101 ~]# docker network help
Usage: docker network COMMAND
Manage networks
Commands:
connect Connect a container to a network
create Create a network
disconnect Disconnect a container from a network
inspect Display detailed information on one or more networks
ls List networks
prune Remove all unused networks
rm Remove one or more networks
Run 'docker network COMMAND --help' for more information on a command.
下面我们对这些基本的网络命令进行介绍:
[root@shjava101 ~]# docker network ls # 查看网络
NETWORK ID NAME DRIVER SCOPE
402a5ca8f6ac bridge bridge local
b34e9a79539a host host local
0d6fe04a197b none null local
[root@shjava101 ~]# docker network inspect bridge # 查看指定网络的详细信息

[root@shjava101 ~]# docker network create network_abc # 创建一个网络
c9e6626f584e215a77e9d317de11c8e673153985ce5410002cd12710b1ebcced
[root@shjava101 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
402a5ca8f6ac bridge bridge local
b34e9a79539a host host local
c9e6626f584e network_abc bridge local
0d6fe04a197b none null local
[root@shjava101 ~]# docker network rm network_abc # 删除指定的网络
network_abc
[root@shjava101 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
402a5ca8f6ac bridge bridge local
b34e9a79539a host host local
0d6fe04a197b none null local
3.3 容器内部网络 IP 的产生规则
现在我们通过一个案例给大家演示容器内部网络的产生规则,首先我们启动两个 Ubuntu 容器实例。
[root@shjava101 ~]# docker run -it --name u1 ubuntu bash
[root@shjava101 ~]# docker run -it --name u2 ubuntu bash
[root@shjava101 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e66faecadd6e ubuntu "bash" 11 seconds ago Up 10 seconds u2
6786c41d50fe ubuntu "bash" About a minute ago Up About a minute u1
现在我们查看两个容器的详细信息:
容器 u1:

容器 u2:

现在我们关闭容器 u2,启动容器 u3,观察 u3 的网络 ip:
[root@shjava101 ~]# docker rm -f u2 # 删除容器u2
u2
[root@shjava101 ~]# docker run -it --name u3 ubuntu bash # 启动容器u3

我们发现容器 u3 的地址和之前容器 u2 的地址是一样的!
现在我们得出一个结论:在默认的网络情况下(bridge),容器内部的 ip 地址是可能会发生变化的,这种情况十分危险,比如我们想通过 172.17.0.3 这个 ip 去访问 u2。但是现在却访问到了 u3。
所以我们有必要详细了解 Docker 的网络模式,来对 Docker 网络进行规划。
3.4 Docker 网络模式的详细介绍
3.4.1 bridge 模式
- 什么是 bridge 模式?
Docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),该桥接网络的名称为 docker0,它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信。
- bridge 模式的详细解读
1. Docker 使用 Linux 桥接,在宿主机虚拟一个 Docker 容器网桥 (docker0),Docker 启动一个容器时会根据 Docker 网桥的网段分配给容器一个 IP 地址,称为 Container-IP,同时 Docker 网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的 Container-IP 直接通信。
2. docker run 的时候,没有指定 network 的话默认使用的网桥模式就是 bridge,使用的就是 docker0。在宿主机 ifconfig,就可以看到 docker0 和自己 create 的 network(后面讲)eth0,eth1,eth2,... 代表网卡一,网卡二,网卡三,...,lo 代表 127.0.0.1,即 localhost,inet addr 用来表示网卡的 IP 地址。
3. 网桥 docker0 创建一对对等虚拟设备接口一个叫 veth,另一个叫 eth0,成对匹配。
3.1 整个宿主机的网桥模式都是 docker0,类似一个交换机有一堆接口,每个接口叫 veth,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫 veth pair)。
3.2 每个容器实例内部也有一块网卡,每个接口叫 eth0。
3.3 docker0 上面的每个 veth 匹配某个容器实例内部的 eth0,两两配对,一一匹配。
通过上述,将宿主机上的所有容器都连接到这个内部网络上,两个容器在同一个网络下,会从这个网关下各自拿到分配的 IP 地址,此时两个容器的网络是互通的。

现在我们通过一个案例来验证默认的 bridge 网络模式:
我们分别启动两台 Tomcat 容器:
[root@shjava101 ~]# docker run -d -p 8081:8080 --name tomcat81 billygoo/tomcat8-jdk8
6d0287d26a16ae2e2e7a79068546e2d0ba02f67fbc4c1bd7502e2e124ccd80fd
[root@shjava101 ~]# docker run -d -p 8082:8080 --name tomcat82 billygoo/tomcat8-jdk8
0b646f197f3911887297f23179eb2005a0b766fd7e248a1301440a6de6547f55
我们使用 ip addr 命令查看 CentOS7 网络情况:

这个时候我们发现我们多了两行网络信息(图上红色标注部分)。其实这个就是我们上面讲到的虚拟网桥上的 veth。
接下来我们分别进入两个 Tomcat 容器的内部:
首先我们进入 tomcat81 容器内部:
[root@shjava101 ~]# docker exec -it tomcat81 bash

接着我们进入容器 tomcat82 内部:
[root@shjava101 ~]# docker exec -it tomcat82 bash

3.4.2 host 模式
直接使用宿主机的 IP 地址与外界进行通信,不再需要额外进行 NAT 转换。容器将不会获得一个独立的 Network Namespace,而是和宿主机共用一个 Network Namespace。容器将不会虚拟出自己的网卡而是使用宿主机的 IP 和端口。

现在我们基于 host 模式启动一个 Tomcat 容器:
[root@shjava101 ~]# docker run -d -p 8083:8080 --network host --name tomcat83 billygoo/tomcat8-jdk8
WARNING: Published ports are discarded when using host network mode
444732c1768290f12eeed07020c801b5c56b67fdd9f00d2570eb0f30ee08b8a3
[root@shjava101 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
444732c17682 billygoo/tomcat8-jdk8 "catalina.sh run" 10 seconds ago Up 9 seconds tomcat83
0b646f197f39 billygoo/tomcat8-jdk8 "catalina.sh run" 25 minutes ago Up 25 minutes 0.0.0.0:8082->8080/tcp, :::8082->8080/tcp tomcat82
6d0287d26a16 billygoo/tomcat8-jdk8 "catalina.sh run" 25 minutes ago Up 25 minutes 0.0.0.0:8081->8080/tcp, :::8081->8080/tcp tomcat81
此时我们发现会报一个警告!通过 docker ps 发现容器成功启动,但是对应的端口映射并没有出现,这是为什么呢?
原因:docker 启动时指定 --network=host 或 -net=host,如果还指定了 -p 映射端口,那这个时候就会有此警告,并且通过 -p 设置的参数将不会起到任何作用,端口号会以主机端口号为主。
解决方案:启动容器的时候,不再指定端口映射。
[root@shjava101 ~]# docker run -d --network host --name tomcat83 billygoo/tomcat8-jdk8
f08a48fd5f8b3ab6b2ae345050d372cb58a3234b9d819b149d205043b4dc7220
现在我们再查看这个容器的详细信息:
[root@shjava101 ~]# docker inspect tomcat83 | tail -n 20

现在访问 Tomcat 的 ip 就是本地宿主机 ip + tomcat8080 端口访问。

3.4.3 none 模式
在 none 模式下,并不为 Docker 容器进行任何网络配置。 也就是说,这个 Docker 容器没有网卡、IP、路由等信息,只有一个 lo 需要我们自己为 Docker 容器添加网卡、配置 IP 等。
现在我们启动一个 Tomcat 容器:
[root@shjava101 ~]# docker exec -it tomcat84 bash
root@72e422922cb8:/usr/local/tomcat# ip addr
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
我们发现这个容器没有进行任何网络相关的设置。需要我们自己手动进行网络设置。这种网络模式很少用,大家了解即可。
3.4.4 container 模式
新建的容器和已经存在的一个容器共享一个网络 IP 配置而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。

为了方便演示,我们启动两个 Alpine 操作系统的容器。
什么是 Alpine 操作系统:Alpine Linux 是一款独立的、非商业的通用 Linux 发行版,专为追求安全性、简单性和资源效率的用户而设计。可能很多人没听说过这个 Linux 发行版本,但是经常用 Docker 的朋友可能都用过,因为他小,简单,安全而著称,所以作为基础镜像是非常好的一个选择,可谓是麻雀虽小但五脏俱全,镜像非常小巧,不到 6M 的大小,所以特别适合容器打包。
现在我们分别启动 2 个终端。
在终端 1:
[root@shjava101 ~]# docker run -it --name alpine1 alpine /bin/sh
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
59bf1c3509f3: Pull complete
Digest: sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300
Status: Downloaded newer image for alpine:latest
/ #
在终端 2:
[root@shjava101 ~]# docker run -it --network container:alpine1 --name alpine2 alpine /bin/sh
/ #
我们在终端 1 里面查看网络情况:

我们再在终端 2 里面查看网络情况:

我们发现容器 2 的网络地址和容器 1 里面的是一样的。说明容器 2 共享了容器 1 的网络信息。
3.4.5 自定义网络模式
上面讲过,默认使用 bridge 网络模式,容器内部的 IP 在某些情况下会发生变化,如果此时通过容器的 IP 地址进行通信可能会遇到问题,如果我们想和某个容器之间建立通信的话,应该怎么做呢?可以使用容器名称建立通信(类似于微服务中通过服务名称实现服务之间的远程调用)。
接下来我们通过一个案例演示自定义网络模式。
我们还是分别启动两台 Tomcat 容器:
[root@shjava101 ~]# docker run -d -p 8081:8080 --name tomcat81 billygoo/tomcat8-jdk8
6d0287d26a16ae2e2e7a79068546e2d0ba02f67fbc4c1bd7502e2e124ccd80fd
[root@shjava101 ~]# docker run -d -p 8082:8080 --name tomcat82 billygoo/tomcat8-jdk8
0b646f197f3911887297f23179eb2005a0b766fd7e248a1301440a6de6547f55
上述成功启动并用 docker exec 进入各自容器实例内部
[root@shjava101 ~]# docker exec -it tomcat81 bash # 在终端 1 进入 tomcat81 容器
root@6d0287d26a16:/usr/local/tomcat# ip addr
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
13: eth0@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:04 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.4/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
[root@shjava101 ~]# docker exec -it tomcat82 bash # 在终端 2 进入 tomcat82 容器
root@0b646f197f39:/usr/local/tomcat# ip addr
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
15: eth0@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:05 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.5/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
下面我们在容器1 (tomcat81) 里面 ping 容器 2 (tomcat82)
root@6d0287d26a16:/usr/local/tomcat# ping 172.17.0.5
PING 172.17.0.5 (172.17.0.5) 56(84) bytes of data.
64 bytes from 172.17.0.5: icmp_seq=1 ttl=64 time=0.335 ms
64 bytes from 172.17.0.5: icmp_seq=2 ttl=64 time=0.048 ms
64 bytes from 172.17.0.5: icmp_seq=3 ttl=64 time=0.050 ms
--- 172.17.0.5 ping statistics ---
8 packets transmitted, 8 received, 0% packet loss, time 6999ms
rtt min/avg/max/mdev = 0.045/0.083/0.335/0.095 ms
我们发现可以 ping 通,反过来我们在容器 1 (tomcat82) 里面 ping 容器 2 (tomcat81)
root@0b646f197f39:/usr/local/tomcat# ping 172.17.0.4
PING 172.17.0.4 (172.17.0.4) 56(84) bytes of data.
64 bytes from 172.17.0.4: icmp_seq=1 ttl=64 time=0.092 ms
64 bytes from 172.17.0.4: icmp_seq=2 ttl=64 time=0.047 ms
64 bytes from 172.17.0.4: icmp_seq=3 ttl=64 time=0.047 ms
--- 172.17.0.4 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.047/0.062/0.092/0.021 ms
我们发现也可以 ping 通。现在我们通过容器名称去 ping 呢?
我们在容器 1 (tomcat81) 里面通过服务名称去 ping 容器 2 (tomcat82)
root@6d0287d26a16:/usr/local/tomcat# ping tomcat82
ping: tomcat82: Name or service not known
我们在容器 1 (tomcat82) 里面通过服务名称去 ping 容器 2 (tomcat81)
root@0b646f197f39:/usr/local/tomcat# ping tomcat81
ping: tomcat81: Name or service not known
我们发现使用服务名称不能 ping 通对方的容器。此时我们可以使用自定义网络来解决这个问题。我们先删除 tomcat81 和 tomcat82 两台容器。
[root@shjava101 ~]# docker rm -f tomcat81
tomcat81
[root@shjava101 ~]# docker rm -f tomcat82
tomcat82
我们先自定义网络
[root@shjava101 ~]# docker network create wen_network
4908cb50bd5aaf3e576979d3eb9d23ebdff8624b747bdee40ab4f673ce347c95
接下来我们按照自定义网络模式启动容器
[root@shjava101 ~]# docker run -d -p 8081:8080 --network wen_network --name tomcat81 billygoo/tomcat8-jdk8
d8cc378a1760987df457d85c58ddeb05faab214a67e793f93db251f57b2b2389
[root@shjava101 ~]# docker run -d -p 8082:8080 --network wen_network --name tomcat82 billygoo/tomcat8-jdk8
f6ed10697e5ba7629853142ed8520c4f132441b31f937cc3b6006a7c40c50722
最后我们按照服务名称来 ping 对方的容器。
[root@shjava101 ~]# docker exec -it tomcat81 bash
root@d8cc378a1760:/usr/local/tomcat# ping tomcat82
PING tomcat82 (172.19.0.3) 56(84) bytes of data.
64 bytes from tomcat82.wen_network (172.19.0.3): icmp_seq=1 ttl=64 time=0.075 ms
64 bytes from tomcat82.wen_network (172.19.0.3): icmp_seq=2 ttl=64 time=0.047 ms
64 bytes from tomcat82.wen_network (172.19.0.3): icmp_seq=3 ttl=64 time=0.047 ms
--- tomcat82 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.047/0.056/0.075/0.014 ms
[root@shjava101 ~]# docker exec -it tomcat82 bash
root@f6ed10697e5b:/usr/local/tomcat# ping tomcat81
PING tomcat81 (172.19.0.2) 56(84) bytes of data.
64 bytes from tomcat81.wen_network (172.19.0.2): icmp_seq=1 ttl=64 time=0.041 ms
64 bytes from tomcat81.wen_network (172.19.0.2): icmp_seq=2 ttl=64 time=0.152 ms
64 bytes from tomcat81.wen_network (172.19.0.2): icmp_seq=3 ttl=64 time=0.047 ms
--- tomcat81 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3000ms
rtt min/avg/max/mdev = 0.041/0.071/0.152/0.047 ms
现在我们基于自定义网络模式成功通过容器名称 ping 通了对方。
小结:自定义网络本身就维护好了容器名称和 IP 的对应关系(IP 和域名都能 ping 通)。