# Docker教程 - 4 Docker网络

先说我们现在遇到的问题:

我们现在有一个 Redis 容器,一个 SpringBoot 项目容器,在 SpringBoot 项目的代码中如何访问 Redis 容器中的服务呢?

在 SpringBoot 项目中肯定不能使用 localhost,因为 localhost 表示当前的容器。之前都是将容器的端口映射到宿主机上的,所以我们访问容器中的服务器,都是通过 宿主机IP+端口 的方式来访问容器中的服务。所以在 SpringBoot 项目的容器中也可以通过 宿主机IP + redis:6379端口 来访问到 Redis 容器中的服务。

如果宿主机的IP不固定,会发生变化呢?那么容器之间该如何通信呢?

这就需要 Docker 网络了,Docker 网络主要解决的就是容器间的通信问题。


在启动了 Docker 之后,使用 ifconfig 命令查看网络信息,可以看到一个 docker0 的网络:

这个 docker0 就是 Docker 创建的虚拟网桥,用于容器与宿主机、容器与容器之间的网络通信。

上面的 enp0s5 是一个网络接口,是宿主机的物理或虚拟网络接口;

lo:是一个回环接口,通常用于允许计算机与其自身进行网络通信。

# 4.1 网络模式

我们在使用虚拟机的时候,虚拟机会有不同的网络模式,例如桥接、共享等;和虚拟机有些类似,安装 Docker 以后,也会默认创建三种网络模式,可以通过 docker network ls 查看。

# 命令
docker network ls
1
2

默认会有 bridgehostnone 三种模式。


网络操作有一些命令,这里先简单了解一下,可以通过帮助命令来查看有哪些命令:

可以看到 docker network 有 connect 、create、disconnect、inspect、ls、prune、rm 命令。

# 1 创建网络

我们可以创建自己的网络,命令如下:

# 命令
docker network create 网络名称

# 举个栗子:
docker network create doubi-network
1
2
3
4
5

创建未完成,可以查看到网络,默认也是 bridge 模式。

# 2 删除网络

删除也非常简单,命令如下:

# 命令
docker network rm 网络ID/名称

# 举个栗子:
docker network rm doubi-network
1
2
3
4
5

运行如下:

# 3 查看网络详细信息

可以通过如下命令查看网络的详细信息:

# 命令
docker network inspect 网络ID/名称

# 举个栗子:
docker network inspect bridge
1
2
3
4
5

查看名称为 bridge 的网络的详细信息:

上图可以看到 bridge 网络的网桥名称就是 docker0


下面来介绍一下各种网络模式,以及如何使用来解决问题的。

# 4.2 bridge网络模式

bridge网络模式:为每一个容器分配、设置 IP 等,并将容器连接到一个 docker0 虚拟网桥,Docker 网络默认使用的就是该模式。

所以我们一开始通过 ifconfig 查看网络的时候,看到的 docker0 就是为 bridge 网络模式提供的虚拟网桥,该模式下,每一个容器都会有一个 IP 地址,每个容器的网络是独立的,一般情况下我们都是使用的这种模式。

docker run 命令中使用 --network bridge 指定使用该模式,一般不用写,默认就是用的这一个,使用 docker0 网桥。其实可以理解为 docker0 是一个路由器,各个容器都是连接到这个路由器上,通过这个路由器实现容器间的互连。


下面演示一下,首先使用 ubuntu 镜像启动两个容器:

# 如果本地没有ubuntu镜像,则首先拉取一个ubuntu镜像
docker pull ubuntu

# 启动容器ubuntu-1
docker run -it --name ubuntu-1 ubuntu /bin/bash

# 启动容器ubuntu-2
docker run -it --name ubuntu-2 ubuntu /bin/bash
1
2
3
4
5
6
7
8

此时查看两个 ubuntu 容器的详细信息,详细信息很长,使用 | tail -n 20 只查看最后的20行,会显示出容器的网络信息:

# 查看容器ubuntu-1的详细信息
docker inspect ubuntu-1 | tail -n 20

# 查看容器ubuntu-2的详细信息
docker inspect ubuntu-2 | tail -n 20
1
2
3
4
5

运行如下:

通过上面可以看出,每个容器有自己的 IP。


此时在宿主机上,使用 ip addr 命令查看网络,会发现多了两个 veth 开头的虚拟网络接口,这两个虚拟网络接口用来和上面创建的两个容器进行通信,如下:


此时我们进入 ubuntu-1 容器内,使用 ip addr 查看一下网络,可以看到容器内有名称为 eth0 的虚拟网卡,在 ubuntu-2 容器内也是一样的:

如果容器中没有 ip addr 命令,可以使用 apt 命令安装一下:apt update && apt-get install -y iproute2

可以看到容器中的 5: eth0@if6,这里表示 eth05 和 宿主机的 veth6 对应,可以再看一下上面宿主机的网络。

也就是说 docker0 网桥在宿主机上会给每个容器创建一个 veth 开头的虚拟网络接口,在容器内,会为每个容器会创建 eth0 的虚拟网络接口,使用 veth-pair 技术,每个 veth 会匹配容器内部的 eth0,两两配对,实现通信。如下图:

所以容器间通信都需要经过 docker0docker0 相当于各个容器的网关。


默认情况下,容器在创建时会连接到这个默认桥接网络 docker0docker0 网络通常使用默认的网段(172.17.0.0/16),并且它的IP地址是 172.17.0.1。而使用这个默认网络模式的容器,IP 地址一般从 172.17.0.2 开始。

所以上面 ubuntu-1 容器的 IP 是 172.17.0.2 ,ubuntu-2 容器的 IP 是 172.17.0.3,此时进入到容器 ubuntu-1 可以通过 ping 172.17.0.3 ping 通 ubuntu-2 容器的,如果容器内没有 ping 命令,可以通过 apt update && apt install iputils-ping 命令安装。

但是这里有一个问题,如果我们停掉容器 ubuntu-2 ,启动另外一个容器 ubuntu-3,那么上面容器ubuntu-2的 IP 172.17.0.3 可能会被重新分配给新的容器 ubuntu-3,也就是说容器的 IP 会发生变化。所以在实际生产环境中,容器之间通常不能直接使用IP地址。

想想我们在一个容器中部署了服务器,通过 IP 连接到其他容器的 mysql 或 redis 服务,容器 IP 变化,会导致无法连接到服务。

怎么解决这个问题呢?使用默认的网络是不行的,需要使用后面的自定义网络。

# 4.3 host网络模式

host网络模式:容器将不会虚拟出自己的网卡,所以没有自己的 IP,而是使用宿主机的 IP 和端口。


docker run 命令中使用 --network host 指定使用该模式。

举个栗子,启动一个 tomcat 容器:

之前部署tomcat容器的时候,官方镜像的tomcat,首页在webapp.dist目录下,导致访问首页是404,这里为了简单,直接使用一个可以直接访问到首页的镜像。

# x86架构,使用host模式启动tomcat镜像
docker search billygoo/tomcat8-jdk8
docker run -d --network host --name tomcat-1 billygoo/tomcat8-jdk8

# 我的宿主机是arm64架构,上面的用不了,我自己弄了一个,如果你主机是arm架构,你可以使用我的这个
docker search doubibiji/tomcat10
docker run -d --network host --name tomcat-1 doubibiji/tomcat10
1
2
3
4
5
6
7

上面的命令使用 host 模式启动 tomcat 容器,这里没有进行端口映射,因为 host 模式使用的就是宿主机的端口,所以 tomcat 就是使用宿主机的 8080 端口。

如果指定端口,不仅没有作用,还会有一个警告:


访问启动的 tomcat 直接使用 http://宿主机IP:8080

# 4.4 none网络模式

none网络模式:容器有独立的 Network namespace,但并没有对容器进行任何网络设置,如分配网卡、IP 等。这种模式下,容器禁用了网络功能,不能进行网络通信。如果需要,只能自己添加网络配置。

docker run 命令中使用 --network none 指定使用该模式。

举个栗子:

# 启动ubuntu容器,此时容器没有网络配置
docker run -it --network none --name ubuntu-1 ubuntu /bin/bash
1
2

none 网络模式一般很少用,这里不多解释。

# 4.5 container网络模式

container网络模式:容器不会创建自己的虚拟网卡和 IP ,而是和一个指定的容器共享 IP、端口,也就是用别的容器的IP和端口。

docker run 命令中使用 --network container:别的容器ID或名称 指定使用该模式。

举个栗子:

这里如果用两个 tomcat 容器来演示,会有一个问题,因为容器2使用container模式,将使用容器1的IP和端口,而对于容器1而言,8080端口已经被容器1使用了,容器2就无法再使用容器1的8080端口了,所以会有问题。

使用ubuntu来演示:

# 如果本地没有ubuntu镜像,则首先拉取一个ubuntu镜像
docker pull ubuntu

# 启动容器ubuntu-1
docker run -it --name ubuntu-1 ubuntu /bin/bash

# 启动容器ubuntu-2,使用ubuntu-1的IP和端口
docker run -it --network container:ubuntu-1 --name ubuntu-2 ubuntu /bin/bash
1
2
3
4
5
6
7
8

进入到两个容器查看 IP,可以看到两个容器的 IP 是一样的。

注意,此时如果停掉容器1,那么容器2的网络也没有了,容器2就无法通信了,因为容器2是依赖容器1的。

# 4.6 自定义网络模式

前面说了,因为容器的 IP 会发生变化,所以不能使用容器的 IP 来进行容器间的通信,需要使用自定义网络。

# 1 创建自定义网络

首先需要创建一个自定义网络,例如创建一个 doubi-network

# 创建doubi-network网络
docker network create doubi-network
1
2

创建完网络,可以通过 docker network ls 查看到存在的网络:

查看宿主机的网络,发现多了一个网络接口:

这里需要注意,我们创建了一个新的网络,也是桥接模式的,但是它是一个新的桥接网络,和docker0是独立开的,他们可以有不同的配置和属性,从上面也可以看到,他们是不同的网段的。

# 2 容器加入自定义网络

这里我们创建两个容器,ubuntu-1ubuntu-2 容器,然后在创建容器的时候加入自定义网络:

# 创建ubuntu-1容器,并加入到doubi-network网络
docker run -it --network doubi-network --name ubuntu-1 ubuntu /bin/bash

# 创建ubuntu-2容器,并加入到doubi-network网络
docker run -it --network doubi-network --name ubuntu-2 ubuntu /bin/bash
1
2
3
4
5

如果容器已经存在了,可以使用 docker network connect 命令加入到网络:

docker network connect 网络名 容器ID/名称

# 举个栗子
docker network connect doubi-network ubuntu-1
1
2
3
4

# 3 实现容器间通信

此时查看 ubuntu-1ubuntu-2 容器的网络,我这里 ubuntu-1 容器的 IP 是 172.19.0.2ubuntu-2 容器的 IP 是 172.19.0.3

此时进入到 ubuntu-1 ,肯定是可以通过命令 ping 172.19.0.3 来 ping 通 ubuntu-2 容器的。

但是这里我们不使通过 IP 来 ping,而是通过服务名称来 ping。

直接在容器 ubuntu-1 中执行命令 ping ubuntu-2 ,发现可以 ping 通 ubuntu-2 容器 。

通过自定义网络,可以实现通过服务名(容器名称)来实现容器之间的通信。

如果我们的项目部署在一个容器中,另外有一个名称为 mysql 的容器服务,那么我们就可以在项目的容器中通过 jdbc:mysql//mysql:3306/数据库名?characterEncoding=utf-8&serverTimezone=Asiz?Shanghai 来连接到数据库服务,不用使用 IP 地址了。

# 4 还有一个问题

docker 容器默认使用的网络是 bridge,也是桥接模式,我们创建的网络也是桥接模式。

为什么默认的网络 bridge 只能通过 IP 来进行容器间的访问,自定义网络才能实现使用服务名来进行容器间的访问呢?

因为默认桥接网络不提供内置的服务发现机制,在默认桥接网络中,容器可以通过相互的IP地址进行通信,但无法通过服务名称进行DNS解析,因为 Docker 默认并没有提供 DNS 服务来支持容器名称解析。

创建的自定义网络,Docker 会启用内置的 DNS 服务,允许容器使用服务名称进行 DNS 解析,这意味着在用户定义网络上的容器可以通过其服务名称相互访问,而不仅仅是通过 IP 地址。


总结:

使用自定义网络可以实现容器之间的隔离,因为不在这个网络内的容器网段是不一样的,无法实现通信。而且自定义网络自带内置的DNS服务,使容器可以通过服务名称进行通信,而无需关心底层的IP地址。