# Docker网络

容器网络实质上也是由 Docker 为应用程序所创造的 虚拟环境 的一部分,它能让应用从宿主机操作系统的网络环境中独立出来,形成容器自有的 网络设备IP 协议栈端口套接字IP 路由表防火墙 等等与网络相关的模块。

Docker网络

Docker 网络中,有三个核心的概念:沙盒 ( Sandbox )网络 ( Network )端点 ( Endpoint )

  • 沙盒 提供了容器的虚拟网络栈,也就是之前所提到的端口套接字、IP 路由表、防火墙等的内容。其实现隔离了容器网络与宿主机网络,形成了完全独立的容器网络环境。
  • 网络 可以理解为 Docker 内部的虚拟子网,网络内的参与者相互可见并能够进行通讯。Docker 的这种虚拟网络也是于宿主机网络存在隔离关系的,其目的主要是形成容器间的安全通讯环境。
  • 端点 是位于容器或网络隔离墙之上的洞,其主要目的是形成一个可以控制的突破封闭的网络环境的出入口。当容器的端点与网络的端点形成配对后,就如同在这两者之间搭建了桥梁,便能够进行数据传输了。

这三者形成了 Docker 网络的核心模型,也就是容器网络模型 ( Container Network Model )。

# Docker 网络模式

通过 docker network ls 或是 docker network list 可以查看 Docker 中已经存在的网络。

[root@localhost ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
eba5171473f1        bridge              bridge              local
ce8ad9d5f571        host                host                local
3bba82dbcbc1        none                null                local

# Bridge模式

bridge 模式是 docker 的默认⽹络模式。

默认情况下,此主机上启动的 Docker 容器之间通过 bridge 模式,进行通信。

bridge 模式如下图所示:

Bridge模式

Docker 进程启动时,会在主机上创建⼀个名为 docker0 的虚拟⽹桥,此主机上启动的 Docker 容器会连接到这个虚拟⽹桥上。

虚拟⽹桥的⼯作⽅式和物理交换机类似,这样主机上的所有容器就通过交换机连在了⼀个⼆层⽹络中。

docker0 ⼦⽹中分配⼀个 IP 给容器使⽤,并设置 docker0IP 地址为容器的 默认⽹关

每当一个容器启动时,会生成一个 eth0* 这种类似命名的网卡。 同时在 docker0 上,生成一个 veth* 类似命名的网桥。这两者是一一对应的。相关联的。

在主机上创建⼀对虚拟⽹卡 veth pair 设备,Dockerveth pair 设备的⼀端放在新创建的容器中,并命名为 eth0 (容器的⽹卡),另⼀端放在主机中,以 vethxxx 这样类似的名字命名,并将这个⽹络设备加⼊到 docker0 ⽹桥中。

可以通过 brctl show 命令,查看网桥配置情况

brctl show

# Host 模式

如果启动容器的时候使⽤ host 模式,那么这个容器将不会获得⼀个独⽴的 Network Namespace ,⽽是和宿主机共⽤⼀个 Network Namespace。容器将不会虚拟出⾃⼰的 ⽹卡,配置⾃⼰的 IP 等,⽽是使⽤宿主机的 IP端⼝。但是,容器的其他⽅⾯,如 ⽂件系统进程列表 等还是和宿主机 隔离的。

容器创建时可以通过 --network=host 指定使用 host 网络。

直接使用宿主机的 Network Namespace,一个好处就是性能,如果容器对网络传输效率要求较高可以使用 host 网络。但是,同时也会牺牲一些灵活性。比如,宿主机上已经使用的端口不能再使用了,要考虑端口冲突。

# Container 模式

这个模式指定新创建的容器和已经存在的⼀个容器共享⼀个 Network Namespace,⽽不是和 宿主机 共享。新创建的容器不会创建⾃⼰的 ⽹卡,配置⾃⼰的 IP,⽽是和⼀个指定的容器 共享 IP端⼝范围等。

同样,两个容器除了 ⽹络⽅⾯,其他的如 ⽂件系统进程列表 等还是隔离的。两个容器的进程可以通过 lo ⽹卡设备通信。

# None模式

使⽤ none 模式,Docker 容器拥有⾃⼰的 Network Namespace,挂载在这个网络下的容器除了 lo,没有任何其他网卡。

容器创建时可以通过 --network=none 指定使用 none 网络。

当遇到对安全性要求比较高,且不需要联网的的应用可以使用 none 网络。

比如某个容器的唯一用途是生成随机密码,那么就可以使用 none 网络防止密码被窃取。

# 容器互联

要让一个容器连接到另外一个容器,实现容器间通信。我们可以在容器通过 docker createdocker run 创建时通过 --link 选项进行配置。

一个典型的场景就是我们有一个运行 MySQL 服务的容器,以及运行 web 服务的容器,我们需要打通两个容器的网络,实现它们之间的网络互通。

$ sudo docker run -d --name mysql -e MYSQL_RANDOM_ROOT_PASSWORD=yes mysql
$ sudo docker run -d --name webapp --link mysql webapp:latest

通过 --link 容器间的网络已经打通。此外,Docker 为容器间连接提供了一种非常友好的方式,我们只需要将容器的网络命名填入到连接地址中,web 服务就可以访问 MySQL 服务容器了。Docker 会将其指向 MySQL 容器的 IP 地址。

# 创建网络

前面我们使⽤ --link 参数来使容器互联。

随着 Docker ⽹络的完善,强烈建议⼤家将容器加⼊ ⾃定义的Docker ⽹络 来连接多个容器,⽽不是使⽤ --link 参数。

docker CLI 里与网络相关的命令都以 docker network 开头,其中创建网络的命令是 docker network create

docker network create -d bridge my-net

-d 参数指定 Docker ⽹络类型,其值可以是 bridge、host、overlay、maclan、none,也可以是其他网络驱动插件所定义的类型。

运⾏⼀个容器并连接到新建的 my-net ⽹络

docker run -it --rm --name busybox1 --network my-net busybox sh

打开新的终端,再运⾏⼀个容器并加⼊到 my-net ⽹络

docker run -it --rm --name busybox2 --network my-net busybox sh

再打开⼀个新的终端查看容器信息

$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b47060aca56b busybox "sh" 11 minutes ago Up 11 minutes busybox2
8720575823ec busybox "sh" 16 minutes ago Up 16 minutes busybox1

在容器 busybox1busybox1 中可以通过 ping 互相访问。busybox1 容器和 busybox2 容器建⽴了互联关系。

如果你有多个容器之间需要互相连接,推荐使⽤ Docker Compose

# 管理网络

我们通过 docker inspect 命令查看容器,可以在 Networks 部分看到容器网络相关的信息。

这里我们能够看到 test1 容器在 bridge 网络中所分配的 IP 地址,其自身的 端点Mac 地址bridge网络网关地址等信息。

Docker 默认创建的这个 bridge 网络是非常重要的,在没有明确指定容器网络时,容器都会连接到这个网络中。

docker inspect test1

docker inspect

# 端口映射

上面是容器内部通过网络进行的互相访问,在实际使用中,我们需要通过宿主机网络去访问容器提供的服务。

最简单的一个例子,我们需要提供一种方式访问运行在容器中的 Web 应用。

在 Docker 中,提供了一个 端口映射 的功能实现这样的需求。

通过 Docker 端口映射 功能,我们可以把 容器的端口 映射到 宿主操作系统的端口 上,当我们从外部访问宿主操作系统的端口时,数据请求就会自动发送给与之关联的容器端口。

要映射端口,我们可以在创建容器时使用 -p 或者是 --publish 选项。

$ sudo docker run -d --name nginx -p 81:80 -p 443:443 nginx:1.12

通过端口映射,我们将容器 80 端口,映射到了宿主机 81 端口。通过访问宿主机的 ip81 端口,我们就可以访问 nginx 容器提供的服务了。

我们可以在容器列表里看到端口映射的配置。

$ sudo docker ps
CONTAINER ID    IMAGE        COMMAND         CREATED    STATUS       PORTS                   NAMES
bc79fc5d42a6    nginx:1.12     "nginx -g 'daemon of…"  4 seconds ago Up 2 seconds    0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp  nginx

# 验证 Docker Bridge 网络模式

# 启动两个后台常驻的容器

docker run -d --name test1 busybox /bin/sh -c "while true; do sleep 3600; done"

docker run -d --name test2 busybox /bin/sh -c "while true; do sleep 3600; done"

# 查看容器网卡信息

[root@localhost ~]# docker exec -it test1 bin/sh
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

运行容器 test1 会生成独立的 网络namespace

[root@localhost ~]# docker exec -it test2 bin/sh 
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

运行容器 test2 会生成独立的 网络namespace

# 查看宿主机的网络信息

[root@localhost ~]# 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: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:10:d3:ec brd ff:ff:ff:ff:ff:ff
    inet 192.168.125.118/24 brd 192.168.125.255 scope global noprefixroute dynamic enp0s3
       valid_lft 79774sec preferred_lft 79774sec
    inet6 fe80::e60b:79dc:7b07:78bc/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:51:8d:43:ca 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:51ff:fe8d:43ca/64 scope link 
       valid_lft forever preferred_lft forever
9: vethdc87e52@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 2a:36:d3:48:9d:86 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::2836:d3ff:fe48:9d86/64 scope link 
       valid_lft forever preferred_lft forever
11: veth13613f0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 2a:d3:b8:42:5c:f9 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::28d3:b8ff:fe42:5cf9/64 scope link 
       valid_lft forever preferred_lft forever

启动容器 test1 ,容器会生成一个 eth0@if9 网卡。 同时在 docker0 上,生成一个 vethdc87e52@if8 网桥。这两者是一一对应的。相关联的。

启动容器 test2 ,容器会生成一个 eth0@if11 网卡。 同时在 docker0 上,生成一个 veth13613f0@if10 网桥。这两者是一一对应的。相关联的。

# 测试 ip 可达

容器 test1 与容器 test2之间网络是相通的,可以互相通信。

[root@localhost ~]# docker exec -it test1 bin/sh
/ # 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.075 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.159 ms
64 bytes from 172.17.0.3: seq=2 ttl=64 time=0.101 ms
64 bytes from 172.17.0.3: seq=3 ttl=64 time=0.097 ms
[root@localhost ~]# docker exec -it test2 bin/sh
/ # 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.141 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.098 ms
64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.088 ms
64 bytes from 172.17.0.2: seq=3 ttl=64 time=0.062 ms

# 参考

更新时间: 8/2/2020, 12:54:59 PM