Docker 网络模式详解及容器间网络通信

一、引入

Docker本身在它创建之初,它就有自己的网络驱动器,叫Container Network Manager,简称CNM。本身这个CNM会支持多种模式,本节来看一看这些模式的区别,然后去了解一下,要让一个容器网络真正地配置好,让容器真正地模拟成一个虚拟机,我们最终要做哪些配置。

二、网络模式

2.1 单主机网络模式

安装 Docker 以后,会默认创建三种网络,可以通过 docker network ls 查看。

[root@localhost ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
688d1970f72e        bridge              bridge              local
885da101da7d        host                host                local
f4f1b3cf1b7f        none                null                local

在学习 Docker 网络之前,我们有必要先来了解一下这几种网络模式都是什么意思。

网络模式 简介
Bridge 为每一个容器分配、设置 IP 等,使用 Linux 网桥和 iptables 提供容器互联,Docker 在每台主机上创建一个名叫 dockero的网桥,通过 veth pair 来连接该主机的每一个 EndPoint默认为该模式。
Host 容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口。
None 容器有独立的 Network namespace,但并没有对其进行任何网络设置,用户需要通过运行docker network命令完成网络设置。
Container 新创建的容器不会创建自己的网卡和配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。

2.2 跨主机网络模式

2.2.1 Overlay(libnetwork, libkv)

  • 通过网络封包实现

举个例子:就是当容器里面出来的数据包,在从主机要往外传输的时候,那么在主机这里封一层。它在原始的数据包的基础之上再加一些,比如说通过各种协议加一层包头。那这个包头会,把当前的主机地址作为原地址,把对端的主机地址作为目标地址。封装好的数据包,就可以在基础架构网络里面,进行传输了。那么这个包到对端主机以后,再有一个解包的过程,就是把外层主机这一层的数据包解开,然后内部就剩下容器的数据包头。这样的话这个包,就可以传到对端的容器。这种封包解包的技术叫做 Overlay。

2.2.2 Underlay

  • 使用现有底层网络,为每一个容器配置可路由的网络IP。

举个例子:Underlay就是我的容器网络跟主机网络是一样的。我容器的IP段在主机这里面也知道是如何路由的,在基础架构层知道如何路由。所以这样的话我这个数据包就可以自由传输。它的局限性在哪里?就是因为我的容器网络和主机网络会共用,但容器对IP的消耗是巨大的。所以这里面就要,如果你要用Underlay网络,那么你需要对网络进行很好的规划,比如说IP有多大的网段分给容器,有多大的网段分给基础架构。那么对IP的规划会有比较高的要求。

三、none 网络模式

  • none 网络模式是指禁用网络功能,只有 lo 接口,local 的简写,代表 127.0.0.1,即 localhost 本地环回接口。在创建容器时通过参数 --net none 或者 --network none 指定;
  • none 网络模式即不为 Docker Container 创建任何的网络环境,容器内部就只能使用 loopback 网络设备,不会再有其他的网络资源。可以说 none 模式为 Docke Container 做了极少的网络设定,但是俗话说得好“少即是多”,在没有网络配置的情况下,作为 Docker 开发者,才能在这基础做其他无限多可能的网络定制开发。这也恰巧体现了 Docker 设计理念的开放。

比如我基于 none 网络模式创建了一个基于 nginx 镜像构建的容器并且命名为n1

我们可以通过 docker network inspect none 查看所有 none 网络模式下的容器,在 Containers 节点中可以看到容器名称。

  

接下来我们来探究一下docker网络桥接的过程,因为我们这里已经安装了docker,我们需要安装Linux的brctl命令:

apt install bridge-utils # 安装brctl

使用brctl这样的命令来查看虚拟以太网桥 docker0,通过brctl show我们就能看到。

有了这样的设备,其实我们要让容器的里面的网络跟主机互通很简单对吧?我们就第一为这个容器配置网络,第二为这个容器的网络牵一根线出来,到主机的namespace,并且插在这个BridgeDocker里面的Bridge上面。那么这样的话主机网络和容器网络其实就可以实现互通的这样一个目标了。接下来,我们一步一步地去实现手动创建一个网络命名空间(network namespace),并将一个没有网络模式的 Nginx Docker 容器与之连接,然后配置网络,使容器能够访问外部网络。

首先,创建网络命名空间:

mkdir -p /var/run/netns #创建目录用来保存进程关联到网络namespace的信息

接下来,启动 Nginx Docker 容器(无网络模式):

docker run --network=none -d nginx

查看容器进程PID:

docker ps|grep nginx 
docker inspect 容器ID |grep -i pid

将PID导入环境变量:

export pid=18149

检查容器的网络配置:

nsenter -t 18149 -n ip a

使用 nsenter命令进入容器的网络命名空间,并查看 IP 地址配置。这里的 18149 是容器的 PID。

将网络命名空间链接到容器:

ln -s /proc/$pid/ns/net /var/run/netns/$pid # 使用软连接让容器进程与网络产生绑定关系
ip netns list # 列出所有网络命名空间

可以看到时我们刚刚导入环境变量的PID

创建 veth 对:

ip link add A type veth peer name B

这会创建一对虚拟以太网接口(veth),A 和 B,它们像一根网线一样连接两个网络命名空间。

配置 veth 对的一端(A):

brctl addif docker0 A
ip link set A up

将 A 接口添加到 Docker 网桥上,并激活它。

设置环境变量:

SETIP=172.17.0.10  # 这个变量定义了容器网络接口的 IP 地址
SETMASK=16  # 容器网络接口的子网掩码,这里是 16,对应的十进制值是 256,即 255.255.0.0
GATEWAY=172.17.0.1 # 定义了容器的默认网关,这里是 172.17.0.1

配置 veth 对的另一端(B):

ip link set B netns $pid # 将名为 `B` 的 veth 接口设置到由 `$pid` 指定的容器的网络命名空间中。`B` 是之前创建的 veth 对的一端,现在它将被移动到容器的网络命名空间中,以便在容器内部使用。
ip netns exec $pid ip link set dev B name eth0 # 这个命令在容器的网络命名空间内部执行,它将 veth 接口 `B` 重命名为 `eth0`。这样做是为了让容器内部的网络配置与传统的网络接口命名方式一致,从而更容易理解和操作。
ip netns exec $pid ip link set eth0 up # 这个命令激活重命名后的 `eth0` 接口,使其处于启用状态,这样容器就可以通过这个接口发送和接收网络流量。
ip netns exec $pid ip addr add$SETIP/$SETMASK dev eth0 # 这个命令为 `eth0` 接口分配一个 IP 地址和子网掩码。`$SETIP` 和 `$SETMASK` 是之前定义的变量,分别代表 IP 地址和子网掩码。这样,容器就具有了一个有效的 IP 地址,可以参与网络通信。
ip netns exec $pid ip route add default via$GATEWAY # 这个命令为容器设置默认网关。`$GATEWAY` 是之前定义的变量,代表容器的默认网关地址。这样,当容器需要发送数据到非直接连接的网络时,它会通过默认网关进行路由。

这些命令序列一起,为容器配置了一个基本的网络接口,使其能够连接到网络并与之通信。

检查连通性:

curl 172.17.0.10

使用 curl 命令测试容器是否可以访问其 IP 地址,以验证网络配置是否正确。

以上就是docker 启动一个容器默认网络配置的过程。

四、Bridge 网络模式

在该模式下,Docker 守护进程创建了一个虚拟以太网桥 docker0,新建的容器会自动桥接到这个接口,附加在其上的任何网卡之间都能自动转发数据包。

默认情况下,守护进程会创建一对对等虚拟设备接口 veth pair,将其中一个接口设置为容器的 eth0 接口(容器的网卡),另一个接口放置在宿主机的命名空间中,以类似 vethxxx 这样的名字命名,从而将宿主机上的所有容器都连接到这个内部网络上。

比如我运行一个基于 nginx 镜像构建的容器 n01,查看 ip addr

通过以上的比较可以发现,证实了之前所说的:守护进程会创建一对对等虚拟设备接口 veth pair,将其中一个接口设置为容器的 eth0 接口(容器的网卡),另一个接口放置在宿主机的命名空间中,以类似 vethxxx 这样的名字命名。

同时,守护进程还会从网桥 docker0 的私有地址空间中分配一个 IP 地址和子网给该容器,并设置 docker0 的 IP 地址为容器的默认网关。通过 brctl show 命令查看网桥信息。

对于每个容器的 IP 地址和 Gateway 信息,我们可以通过 docker inspect 容器名称|ID 进行查看,在 NetworkSettings 节点中可以看到详细信息。

我们可以通过 docker network inspect bridge 查看所有 bridge 网络模式下的容器,在 Containers 节点中可以看到容器名称。

关于 bridge 网络模式的使用,只需要在创建容器时通过参数 --net bridge 或者 --network bridge 指定即可,当然这也是创建容器默认使用的网络模式,也就是说这个参数是可以省略的。

Bridge(桥接)模式的实现步骤主要如下:

  • Docker Daemon 利用 veth pair 技术,在宿主机上创建一对对等虚拟网络接口设备,假设为 veth0veth1。而 veth pair 技术的特性可以保证无论哪一个 veth 接收到网络报文,都会将报文传输给另一方。
  • Docker Daemon 将 veth0 附加到 Docker Daemon 创建的 docker0 网桥上。保证宿主机的网络报文可以发往 veth0
  • Docker Daemon 将 veth1 添加到 Docker Container 所属的 namespace 下,并被改名为 eth0。如此一来,宿主机的网络报文若发往 veth0,则立即会被 Container 的 eth0 接收,实现宿主机到 Docker Container 网络的联通性;同时,也保证 Docker Container 单独使用 eth0,实现容器网络环境的隔离性。  

五、host 网络模式

  • host 网络模式需要在创建容器时通过参数 --net host 或者 --network host 指定;
  • 采用 host 网络模式的 Docker Container,可以直接使用宿主机的 IP 地址与外界进行通信,若宿主机的 eth0 是一个公有 IP,那么容器也拥有这个公有 IP。同时容器内服务的端口也可以使用宿主机的端口,无需额外进行 NAT 转换;
  • host 网络模式可以让容器共享宿主机网络栈,这样的好处是外部主机与容器直接通信,但是容器的网络缺少隔离性。

比如我基于 host 网络模式创建了一个基于 busybox 镜像构建的容器 bbox02,查看 ip addr

然后宿主机通过 ip addr 查看信息如下:

我们可以通过 docker network inspect host 查看所有 host 网络模式下的容器,在 Containers 节点中可以看到容器名称。

/resources/articles/docker/image-20200818124047216.png

六、container 网络模式

  • Container 网络模式是 Docker 中一种较为特别的网络的模式。在创建容器时通过参数 --net container:已运行的容器名称|ID 或者 --network container:已运行的容器名称|ID 指定;
  • 处于这个模式下的 Docker 容器会共享一个网络栈,这样两个容器之间可以使用 localhost 高效快速通信。

Container 网络模式即新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样两个容器除了网络方面相同之外,其他的如文件系统、进程列表等还是隔离的。

比如我基于容器 bbox01 创建了 container 网络模式的容器 bbox04,查看 ip addr

容器 bbox01ip addr 信息如下:

宿主机的 ip addr 信息如下:

通过以上测试可以发现,Docker 守护进程只创建了一对对等虚拟设备接口用于连接 bbox01 容器和宿主机,而 bbox04 容器则直接使用了 bbox01 容器的网卡信息。

这个时候如果将 bbox01 容器停止,会发现 bbox04 容器就只剩下 lo 接口了。

然后 bbox01 容器重启以后,bbox04 容器也重启一下,就又可以获取到网卡信息了。

七、自定义网络

Docker 提供的默认网络使用比较简单,但是为了保证各容器中应用的安全性,在实际开发中更推荐使用自定义的网络进行容器管理,以及启用容器名称到 IP 地址的自动 DNS 解析。

从 Docker 1.10 版本开始,docker daemon 实现了一个内嵌的 DNS server,使容器可以直接通过容器名称进行通信。方法很简单,只要在创建容器时使用 --name 为容器命名即可。

但是使用 Docker DNS 有个限制:只能在 user-defined 网络中使用。也就是说,默认的 bridge 网络是无法使用 DNS 的,所以我们就需要自定义网络。

7.1 创建网络

通过 docker network create 命令可以创建自定义网络模式,命令提示如下:

进一步查看 docker network create 命令使用详情,发现可以通过 --driver 指定网络模式且默认是 bridge 网络模式,提示如下:

创建一个基于 bridge 网络模式的自定义网络模式 custom_network,完整命令如下:

docker network create custom_network

通过 docker network ls 查看网络模式:

[root@localhost ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
b3634bbd8943        bridge              bridge              local
062082493d3a        custom_network      bridge              local
885da101da7d        host                host                local
f4f1b3cf1b7f        none                null                local

通过自定义网络模式 custom_network 创建容器:

docker run -di --name bbox05 --net custom_network busybox

通过 docker inspect 容器名称|ID 查看容器的网络信息,在 NetworkSettings 节点中可以看到详细信息。

7.2 连接网络

通过 docker network connect 网络名称 容器名称 为容器连接新的网络模式。

docker network connect bridge bbox05

通过 docker inspect 容器名称|ID 再次查看容器的网络信息,多增加了默认的 bridge

7.3 断开网络

通过 docker network disconnect 网络名称 容器名称 命令断开网络。

docker network disconnect custom_network bbox05

通过 docker inspect 容器名称|ID 再次查看容器的网络信息,发现只剩下默认的 bridge

7.4 移除网络

可以通过 docker network rm 网络名称 命令移除自定义网络模式,网络模式移除成功会返回网络模式名称。

docker network rm custom_network

注意:如果通过某个自定义网络模式创建了容器,则该网络模式无法删除。

八、容器间网络通信

接下来我们通过所学的知识实现容器间的网络通信。首先明确一点,容器之间要互相通信,必须要有属于同一个网络的网卡。

我们先创建两个基于默认的 bridge 网络模式的容器。

docker run -di --name default_bbox01 busybox
docker run -di --name default_bbox02 busybox

通过 docker network inspect bridge 查看两容器的具体 IP 信息。

然后测试两容器间是否可以进行网络通信。

经过测试,从结果得知两个属于同一个网络的容器是可以进行网络通信的,但是 IP 地址可能是不固定的,有被更改的情况发生,那容器内所有通信的 IP 地址也需要进行更改,能否使用容器名称进行网络通信?继续测试。

/resources/articles/docker/image-20200818142849899.png

经过测试,从结果得知使用容器进行网络通信是不行的,那怎么实现这个功能呢?

从 Docker 1.10 版本开始,docker daemon 实现了一个内嵌的 DNS server,使容器可以直接通过容器名称通信。方法很简单,只要在创建容器时使用 --name 为容器命名即可。

但是使用 Docker DNS 有个限制:只能在 user-defined 网络中使用。也就是说,默认的 bridge 网络是无法使用 DNS 的,所以我们就需要自定义网络。

我们先基于 bridge 网络模式创建自定义网络 custom_network,然后创建两个基于自定义网络模式的容器。

docker run -di --name custom_bbox01 --net custom_network busybox
docker run -di --name custom_bbox02 --net custom_network busybox

通过 docker network inspect custom_network 查看两容器的具体 IP 信息。

/resources/articles/docker/image-20200818143417653.png

然后测试两容器间是否可以进行网络通信,分别使用具体 IP 和容器名称进行网络通信。

经过测试,从结果得知两个属于同一个自定义网络的容器是可以进行网络通信的,并且可以使用容器名称进行网络通信。

那如果此时我希望 bridge 网络下的容器可以和 custom_network 网络下的容器进行网络又该如何操作?其实答案也非常简单:让 bridge 网络下的容器连接至新的 custom_network 网络即可。

docker network connect custom_network default_bbox01

posted @ 2024-03-19 14:03  贾维斯Echo  阅读(5)  评论(0编辑  收藏  举报