1. Docker网络
Docker网络在容器化环境中起着至关重要的作用,它允许容器之间相互通信,与外部网络进行连接,并实现容器的隔离和网络管理。随着容器化的发展,Docker网络架构采用容器网络模型方案(CNM),支持拔插式的驱动方式来提供网络拓扑。
1.1. Network Namespace
Docker 网络的底层原理是 Linux 的 Network Namespace,所以对于Linux Network Namespace 的理解对 Docker 网络底层原理的理解非常重要。
Network Namespace是Linux内核提供的用于实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,每个独立的网络空间内的防火墙、网卡、路由表、邻居表、协议栈都是独立的。不管是虚拟机还是容器,当运行在独立的命名空间时,就像是一台单独的主机一样。
示例说明:
如下图,通过手工方式创建两个Network Namespace,并最终让它们相互连通,即可以通过ping命令测试成功。以更容易理解Docker网络的底层原理。

1、创建两个命名空间,分别创建命名空间ns1和命名空间ns2
# ip netns add ns1
# ip netns add ns2
# ip netns list
ns2
ns1因为每个网络空间都是独立的,所以每个 Network Namespace 都具有一个回环网络适配器lo
# ip netns exec ns1 ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
# ip netns exec ns2 ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:002、创建网络接口veth pair
如果要让两个命名空间连通,则需要用到虚拟设备接口技术veth pair。该技术需要一对网络接口分别置于两个命名空间中。如下,创建一对网络接口veth-ns1和veth-ns2
# ip link add veth-ns1 type veth peer name veth-ns2此时通过 ip link 查看当前的网络地址情况,可以看到新增了两个相互连通的veth pair,它们都具有 MAC 地址,但它们的状态都是 DOWN,且都不具有 IP。
# ip link
......
94: veth-ns2@veth-ns1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 2e:7b:cc:c2:bc:b1 brd ff:ff:ff:ff:ff:ff
95: veth-ns1@veth-ns2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 46:f1:40:6e:3b:49 brd ff:ff:ff:ff:ff:ff3、命名空间添加veth接口
通过ip link set命令,将两个网络接口分别分配给两个命名空间。
# ip link set veth-ns1 netns ns1
# ip link set veth-ns2 netns ns2此时分别在两个命名空间中执行 ip link 命令,可以查看到,它们中分别新增了前面指定的一个网络接口。
# ip netns exec ns1 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
95: veth-ns1@if94: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 46:f1:40:6e:3b:49 brd ff:ff:ff:ff:ff:ff link-netnsid 1
# ip netns exec ns2 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
94: veth-ns2@if95: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 2e:7b:cc:c2:bc:b1 brd ff:ff:ff:ff:ff:ff link-netnsid 0此时再在主机中查看 ip link,发现原来的那两个网络接口已经消失了。
4、为veth接口分配IP
前面创建的两个网络接口是没有 IP 的。下面要通过 ip netns exec 命令,为每个指定的命名空间执行 IP 添加命令 ip addr add [ip] dev [网络接口]。
# ip netns exec ns1 ip addr add 192.168.1.1/24 dev veth-ns1
# ip netns exec ns2 ip addr add 192.168.1.2/24 dev veth-ns2为 ns1 的 veth-ns1 网络接口分配的 IP 为 192.168.1.1,掩码为 24;为ns2 的veth-ns2 网络接口分配的 IP 为 192.168.1.2,掩码为 24。 此时通过在 ns1 与 ns2 中运行 ip a 命令,便可看到为接口分配的IP 了。
# ip netns exec ns1 ip a
# ip netns exec ns2 ip a5、启动veth接口
以上两个命名空间中的 veth 接口已经具有了 IP,但其状态仍为DOWN,还没有开启。下面要通过 ip link set dev [接口] up 来启动指定的网络接口。
# ip netns exec ns1 ip link set dev veth-ns1 up
# ip netns exec ns2 ip link set dev veth-ns2 up此时再通过 ip a 命令查看两接口的状态,已经变为了 UP。
6、测试,相互ping
此时可以通过在两个命名空间中执行 ping 命令来与对方进行连通性测试了。
# ip netns exec ns1 ping 192.168.1.2
# ip netns exec ns2 ping 192.168.1.11.2. CNM
Docker网络架构由CNM (Container Network Model)、Libnetwork 和 Network Drivers(网络驱动程序)是三个关键组成部分,它们协同工作以实现容器的网络连接和管理。
CNM,容器网络模型,其是一种网络连接的解决方案,是一种设计规范、设计标准,它提供了一种标准化和抽象的方法来描述容器网络的基本组件和行为。
Container Network Model(CNM)中定义了三个基本要素,它们是沙盒(Sandbox)、终端(Endpoint)和网络(Network)。
沙盒(Sandbox):一个独立的网络栈,每个容器都有一个独立的沙盒,它包含了以太网接口、端口号、路由表、DNS设置等。
Linux Network Namespace是沙盒的标准实现。终端(Endpoint):虚拟网络接口,主要负责创建连接,即将沙盒连接到网络上。一个终端只能接入某一个网络。
网络(Network):网络是容器通信的逻辑实体,它定义了容器如何连接和通信。不同类型的网络可以用于不同的容器连接需求,如桥接网络、覆盖网络、Host网络等。类似于交换机的软件实现。

总的来说,CNM作为Docker容器网络的基础,通过标准化和抽象,提供了一种方法来组织和管理容器的网络配置和连接。它定义了容器网络的三个基本要素,即沙盒、终端和网络,以确保网络隔离、连接和一致性。这些要素的定义和标准化使Docker容器网络更加灵活、可扩展和易于管理。
1.3. Libnetwork
CNM是设计规范,而Libnetwork则是CNM的实现。Libnetwork是一个开源的项目,由Go语言编写、跨平台的CNM的标准实现。其Github地址
Libnetwork 除了实现了 CNM 的三个组件,还实现了本地服务发现、容器负载均衡,以及网络控制层与管理层功能。
1.4. Driver
"Driver"(驱动程序)是一种网络插件,用于实际控制和管理容器的网络连接和配置,是负责在主机上真正实现需要的网络功能。Docker提供了不同类型的网络驱动程序,每个驱动程序适用于不同类型的网络连接需求。
不论哪种网络类型,其工作方式都是类似的。通过调用Docker引擎的API发出请求,然后由Libnetwork作出框架性的处理,然后将请求转发给相应的Driver。

在docker主机上可以使用docker network ls查看当前主机所连接的网络及网络类型。
# docker network ls
NETWORK ID NAME DRIVER SCOPE
8ce9a311fa02 bridge bridge local
dba0d6de97d2 host host local
225dee6767ec none null local1.5. Docker网络模式
Docker提供了多种网络驱动,分别如下:
bridge(桥接): Docker默认的网络驱动程序。容器通过一对veth pair连接到docker0网桥上。桥接网络通常用于同一主机上运行的容器之间进行通信。
host(主机):直接使用宿主机的网络,容器与宿主机共享同一Network Namespace,共享同一套网络协议栈、路由表及iptables规则等。
overlay(覆盖): 覆盖网络连接多个Docker守护程序,并使Swarm服务和容器能够跨节点进行通信。这种策略消除了操作系统级的路由需求,适用于构建分布式应用程序。
none(无网络): 完全隔离容器与主机和其他容器。none网络驱动程序不适用于Swarm服务。在None模式下,容器不会被分配IP地址,也无法与其他容器和主机通信。
2. Bridge网络模式
bridge 网络,也称为单机桥接网络,是 Docker 默认的网络模式。该网络模式只能存在于单个 Docker 主机上,其只能用于连接所在 Docker 主机上的容器。
2.1. docker0网桥
Docker Daemon启动时会通过Bridge驱动在主机上创建一个Linux网桥(默认为docker0),docker启动一个容器时会根据docker网桥的网段创建一个veth-pair(虚拟网络接口)设备,由于veth设备的特点是成对存在,从一端进入的数据会同时出现在另一段,Docker会将一端挂载到docker0网桥上,另一段放入容器的Network Namespace内,并分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网络网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的Container-IP直接通信。
容器与外网间是通过网络地址转换NAT技术实现的连接,即将通信的数据包中的内网地址转换为外网地址。
Bridge 驱动的底层是基于 Linux 内核的 Linux Bridge 技术。该技术已经经历了近20 年的考验,这就意味着该模式是高性能且非常稳定的。

可以通过ip a或ifconfig命令查看docker0到网桥。

还可以使用docker network inspect命令来查看网络的相信信息。如下
# 查看默认网络bridge的相信信息
# docker network inspect bridge可以看到该网络的驱动为bridge,其网桥名字为docker0,可以发现Containers这里目前为空,这是由于当前还没有容器连接到该网络上。

2.2. 查看网络连接详情
为了查看网络连接的一个情况,我们先通过默认的网桥创建两个测试容器。命令如下
# docker run --name testcontainer1 -it -d busybox
6423c64c421ed481016d5fadc73ac016d2aae491dab7fc534b458967261bc98e
# docker run --name testcontainer2 -it -d busybox
69059cc8ef2a231f76965c0b19508ba5dd89d960d08ea2f8d91eb2f2dfff91cd
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
69059cc8ef2a busybox "sh" 2 seconds ago Up 2 seconds testcontainer2
6423c64c421e busybox "sh" 7 seconds ago Up 7 seconds testcontainer12.2.1. 查看bridge网络整体连接
通过docker network inspect查看当前bridge网络的整体连接情况。
# docker network inspect bridge
......
"Containers": {
"6423c64c421ed481016d5fadc73ac016d2aae491dab7fc534b458967261bc98e": {
"Name": "testcontainer1",
"EndpointID": "179e41b6c401753cf5dce57b77ba8ee9f535434d86da4f6b731dc8aaee23dd91",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
},
"69059cc8ef2a231f76965c0b19508ba5dd89d960d08ea2f8d91eb2f2dfff91cd": {
"Name": "testcontainer2",
"EndpointID": "9bfea49ac731940e509f33995fccf1a76f892a98adbb42bbec8fac00b0594626",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
}
},
......在Containers字段中可以看到当前名称为bridge的网络中连接的testcontainer1和testcontainer2两个容器。这两个容器及宿主机,其实就是三个完全独立的Network Namespace。
2.2.2. 查看宿主机接口
此时在宿主机上通过ip a命令查看当前主机的网络接口情况。如下

可以发现除了回环地址lo、本地网卡ens33、网桥docker0外,还有两个veth网络接口,这两个veth就是Libnetwork生成的veth pair中的宿主机中的EndPoint。
97: vethde28d5a@if96:表示这是第97个接口,其用于连接容器的if96接口。99: veth6897ce9@if98:表示这是第99个接口,其用于连接容器的if98接口。
2.2.3. 查看容器接口
通过进入容器执行命令,查看容器的网络接口信息。
# docker exec testcontainer1 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
96: eth0@if97: <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
# docker exec testcontainer2 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
98: eth0@if99: <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在两个容器中分别使用 ip a 命令查看它们的地址情况,可以看到均包含eth0 的接口。其中testcontainer1中的接口96,即eth0@if97,其用于连接宿主机的第 97 个接口;testcontainer2的接口98,即eth0@if99,其用于连接宿主机的第 99 个接口。 它们的接口正好与宿主机中的接口构成两对 pair。
2.2.4. 查看网桥连接
这里使用一个专门用于网桥控制管理的命令brctl,由于该命令默认在linux中没有安装,所以需要先安装一个网桥的工具包bridge-utils。
yum install bridge-utils -y接着,使用brctl show命令即可以看到当前主机所有网桥及其连接情况。如下所示,可以看到,当前宿主机中只有一个网桥docker0,其上连接着两个vethxxx的接口,也就是前面连接的testcontainer1与testcontainer2容器上两个eth0的两个接口。
# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024228665961 no veth6897ce9
vethde28d5a2.2.5. 查看容器详情
通过 docker inspect 查看容器的详情,可以看到,其网络连接中的网关Geteway 的IP 地址就是 docker0 网桥的地址。
# docker inspect --format='{{.NetworkSettings.Gateway}}' testcontainer1
172.17.0.1
# docker inspect --format='{{.NetworkSettings.Gateway}}' testcontainer2
172.17.0.1此时,服务器上两个容器与宿主机之间的整体网络拓扑如下:

2.3. 网络创建
docker network create 用于创建自定义的Docker网络。同时创建网络时可以自定义网络的名称、驱动程序、子网、网关等参数。
使用示例:
1、创建网络
docker network create \
--driver=bridge \
--subnet=172.28.0.0/16 \
--ip-range=172.28.5.0/24 \
--gateway=172.28.5.254 \
br0参数说明:
-d,--driver: 指定使用的网络驱动程序--subnet:定义网络的子网段--ip-range:定义了ip地址划分范围--gateway:定义了网络的网关地址
上面这条语句表示创建了一个名称为br0、类型为bridge的网络,并配置了该网络的子网、IP地址范围和网关。
2、查看宿主机当前的所有网络
# docker network ls
NETWORK ID NAME DRIVER SCOPE
c384fb2456b6 br0 bridge local
8ce9a311fa02 bridge bridge local
dba0d6de97d2 host host local
225dee6767ec none null local3、查看宿主机网桥
通过brctl show命令可以看到此时新增了一个网桥,只不过该网桥暂时还没有任何连接的网络接口。该网桥就是在创建新的网络的时候自动创建的。
# brctl show
bridge name bridge id STP enabled interfaces
br-c384fb2456b6 8000.02422d6bcea9 no
docker0 8000.024228665961 no veth6897ce9
vethde28d5a此时,我们创建容器时候就可以指定使用自己创建的网络了。
2.4. 创建容器指定网络
上面我们创建了一个网络,这里我们创建容器时候指定网络进行创建。
1、创建容器,创建一个名为testcontainer3的容器,并连接在新建的br0网络上。
docker run --name testcontainer3 -it -d --network br0 busybox说明,在创建容器的时候通过--network指定要连接的网络,如果没有指定,则默认连接到默认的bridge(docker0)网络上。
2、查看新建网络详情
此时查看br0的网络详情,可以看到容器testcontainer3已经连接到上面。
# docker network inspect br0
......
"Containers": {
"96a1bbea693a1868d7e8af1b8636f75bb324b0c5530e04a97cee4f416dc085eb": {
"Name": "testcontainer3",
"EndpointID": "a5f53f3595b02d89c922f503ae68437f368cc2c51cfc808f7e66ac607d72ca97",
"MacAddress": "02:42:ac:1c:05:00",
"IPv4Address": "172.28.5.0/16",
"IPv6Address": ""
}
},
......3、查看宿主机网络接口ip a
此时查看当前宿主机的网络接口情况,发现多出了两个接口。其中一个是 100 号接口,其为网桥接口 br-xxx,一个是 102 号接口,其是连接容器 testcontainer3 的接口 vethf991cb3@if101。
4、查看新增容器网络接口
此时,查看容器testcontainer3到网络接口,发现其101号接口正好是宿主机的102号接口的pair接口。
# docker exec testcontainer3 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
101: eth0@if102: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:1c:05:00 brd ff:ff:ff:ff:ff:ff
inet 172.28.5.0/16 brd 172.28.255.255 scope global eth0
valid_lft forever preferred_lft forever5、查看宿主机网桥
此时再查看网桥情况,发现新增网桥上增加了一个接口,而该接口正好就是宿主机上的100 号接口。并且,该接口的 IP 为 172.28.0.0/16,与 bridge 网络 172.17.xx.xx/16 不是同一网段,所以它们之间是不能相互通信的。
# brctl show
bridge name bridge id STP enabled interfaces
br-c384fb2456b6 8000.02422d6bcea9 no vethf991cb3
docker0 8000.024228665961 no veth6897ce9
vethde28d5a此时,服务器上三个容器与宿主机之间的整体网络拓扑如下:
2.5. 容器连接到指定网络
如下图所示,我们现在想将testcontainer2容器,连接到上面新创建的bridge类型网络br0上。使testcontainer2容器同时连接两个网络,并且testcontainer2能与testcontainer1通信,也能与testcontainer3通信。
2.5.1. 连接到指定网络
docker中使用docker network connect命令将已运行的容器连接到一个或多个网络。在连接的时候还可以通过--ip方式为容器分配一个指定IP。
如上图所示,将testcontainer2容器连接到br0网络上。
# docker network connect br0 testcontainer22.5.2. 查看两个网络详情
此时,查看br0网络详情中的容器情况,可以发现testcontainer2与testcontainer3都在该网络上。
# docker network inspect br0
......
"Containers": {
"69059cc8ef2a231f76965c0b19508ba5dd89d960d08ea2f8d91eb2f2dfff91cd": {
"Name": "testcontainer2",
"EndpointID": "0d4f9f15a5bad771db99f442428c6d2b1b34be1a14e75881b7ae988242babf5f",
"MacAddress": "02:42:ac:1c:05:01",
"IPv4Address": "172.28.5.1/16",
"IPv6Address": ""
},
"96a1bbea693a1868d7e8af1b8636f75bb324b0c5530e04a97cee4f416dc085eb": {
"Name": "testcontainer3",
"EndpointID": "a5f53f3595b02d89c922f503ae68437f368cc2c51cfc808f7e66ac607d72ca97",
"MacAddress": "02:42:ac:1c:05:00",
"IPv4Address": "172.28.5.0/16",
"IPv6Address": ""
}
},
......而查看原来的bridge网络详情中容器情况,可以发现testcontainer2仍连接在其上,即容器testcontainer2同时连接在了两个网络上。
# docker network inspect bridge
......
"Containers": {
"6423c64c421ed481016d5fadc73ac016d2aae491dab7fc534b458967261bc98e": {
"Name": "testcontainer1",
"EndpointID": "179e41b6c401753cf5dce57b77ba8ee9f535434d86da4f6b731dc8aaee23dd91",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
},
"69059cc8ef2a231f76965c0b19508ba5dd89d960d08ea2f8d91eb2f2dfff91cd": {
"Name": "testcontainer2",
"EndpointID": "9bfea49ac731940e509f33995fccf1a76f892a98adbb42bbec8fac00b0594626",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
}
},
......2.5.3. 查看容器接口
此时,查看容器testcontainer2的网络接口情况,发现其同时具有两个网络接口,分别连接在两个不同的网络上。
# docker exec testcontainer2 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
98: eth0@if99: <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
103: eth1@if104: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:1c:05:01 brd ff:ff:ff:ff:ff:ff
inet 172.28.5.1/16 brd 172.28.255.255 scope global eth1
valid_lft forever preferred_lft forever2.5.4. 查看容器详情
查看 testcontainer2 容器详情,可以看到其连接在两个网络上,具有两个 IP。
# docker inspect testcontainer2
......
"Networks": {
"br0": {
"IPAMConfig": {},
"Links": null,
"Aliases": [
"69059cc8ef2a"
],
"NetworkID": "c384fb2456b6599516e3db7279470efac7a72ee5de2cd4dd31f89b7622959591",
"EndpointID": "0d4f9f15a5bad771db99f442428c6d2b1b34be1a14e75881b7ae988242babf5f",
"Gateway": "172.28.5.254",
"IPAddress": "172.28.5.1",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:1c:05:01",
"DriverOpts": {}
},
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "8ce9a311fa022b42c7c328588d2cacac6f9a0179cde525d2edb84ade6c1ddb56",
"EndpointID": "9bfea49ac731940e509f33995fccf1a76f892a98adbb42bbec8fac00b0594626",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:03",
"DriverOpts": null
}
}
......2.5.5. 测试网络
测试容器之间互相是否可以通信,这里采用ping命令进行测试。
通过IP进行ping测试
1、容器testcontainer2与testcontainer3相互测试,可以发现是能ping通的。
2、容器testcontainer2与testcontainer1相互测试,可以发现是能ping通的。
3、容器testcontainer3要ping容器testcontainer2的另一个网断是ping不通的。
通过容器名进行ping测试
除了可以使用ip进行ping,还可以直接去ping对方的容器名称。
该方式在生产中非常重要。因为生产中容器的 IP 可能会发生变化,但容器名称一般是不会变的。如果某服务总是直接通过 IP 与容器相连接,那么一旦容器IP 变化,则该服务将连接不上容器。但如果是通过容器名称相连接的,那么无论容器 IP 如何变化,都将不影响服务与容器的连接。
此时,服务器上三个容器与宿主机之间的整体网络拓扑则变为如下:
2.5.6. 定向连接容器(容器互联)
对于自定义的 bridge 网络,其具有一个特性:该网络上的容器可以通过容器名互ping。但默认的 bridge 网络是不行的。如果在默认的 bridge 网络上实现通过容器名进行的连接,则需要创建容器时通过--link 选项指定。
--link`参数有两种格式,一种是`--link <name or id>:alias`,另一种是`--link <name or id><name or id>:表示要连接的容器的名称或者ID<alias>:表示这个链接的名字(别名)
示例,创建一个testcontainer4容器,通过--link的方式,连接到testcontainer1容器。
docker run --name testcontainer4 -it -d --link testcontainer1 busybox此时,容器testcontainer4直接通过testcontainer1的容器名称就能连上testcontainer1
# docker exec testcontainer4 ping testcontainer1
PING testcontainer1 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.080 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.178 ms
64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.111 ms
^C
[root@docker ~]#但是容器testcontainer1却无法通过容器名连接testcontainer4,这是因为--link指定的连接是一种定向连接,是带有指向性与方向性的。
[root@docker ~]# docker exec testcontainer1 ping testcontainer4
ping: bad address 'testcontainer4'使用场景
比如,我们有两个容器,一个是应用程序,一个是数据库容器,而我们的应用程序需要链接数据库,而数据库不需要链接应用程序容器。此时,便可以使用容器互联的方式,建立一个安全的隧道,保证了数据库容器的安全。示例如下:
1、先运行一个数据库容器
docker run -itd --name mydb -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0.342、再运行一个应用程序容器(这里只是模拟,用nginx代替)
docker run -itd --name myweb -p 8081:80 --link mydb:db nginx:1.24.0此时,mydb容器就和myweb容器建立了互联关系。Docker通过2种方式为容器公开连接信息:
环境变量
更新
/etc/hosts文件。
3、查看myweb容器的环境变量,可以看到mydb相关环境变量(协议、端口、密码等)也存在。
# docker exec -it myweb env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=64193427b506
TERM=xterm
DB_PORT=tcp://172.17.0.6:3306
DB_PORT_3306_TCP=tcp://172.17.0.6:3306
DB_PORT_3306_TCP_ADDR=172.17.0.6
DB_PORT_3306_TCP_PORT=3306
DB_PORT_3306_TCP_PROTO=tcp
DB_PORT_33060_TCP=tcp://172.17.0.6:33060
DB_PORT_33060_TCP_ADDR=172.17.0.6
DB_PORT_33060_TCP_PORT=33060
DB_PORT_33060_TCP_PROTO=tcp
DB_NAME=/myweb/db
DB_ENV_MYSQL_ROOT_PASSWORD=123456
DB_ENV_GOSU_VERSION=1.16
DB_ENV_MYSQL_MAJOR=8.0
DB_ENV_MYSQL_VERSION=8.0.34-1.el8
DB_ENV_MYSQL_SHELL_VERSION=8.0.34-1.el8
NGINX_VERSION=1.24.0
NJS_VERSION=0.7.12
PKG_RELEASE=1~bullseye
HOME=/root4、查看myweb容器的hosts文件,可以看打hosts文件中会把mydb容器的host解析放在该文件中,此时,myweb容器便可以与mydb容器连接了。
# docker exec -it myweb cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.6 db 484c94633884 mydb
172.17.0.7 64193427b5063. Host网络模式
host 网络,即与宿主机 host 共用一个 Network Namespace。该网络类型的容器没有独立的网络空间,没有独立的 IP,全部与 host 共用。使用host模式的容器可以直接使用宿主机的IP地址与外界通信,容器内部的服务端口也可以使用宿主机的端口,不需要进行NAT,host最大的优势就是网络性能比较好。但缺点也很明显,就是容器的网络缺少隔离性。
host网络模式的使用只需要在创建容器的时候通过--net host或者--network host进行指定即可。
注意:docker宿主机上已经使用的端口就不能再用了。
Host网络模式示例:
1、创建一个nginx的容器,直接使用host模式
# docker run -it -d --name my_nginx --network host nginx:1.24.0
ab70ee7c364bf1b7de695a71bebfa157337dc82be0db8bd424ac5e2a336a175e2、查看容器的ip地址信息,可以发现并没有相关ip地址等信息。
docker inspect my_nginx3、查看宿主机监听端口,我们知道默认的Nginx服务监听的端口为80,则我们创建的这个容器中会监听80端口,而使用host模式,则直接监听在宿主机的80端口。
# netstat -nlutp |grep :80
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 37008/nginx: master
tcp6 0 0 :::80 :::* LISTEN 37008/nginx: master4、查看host网络信息,还可以通过查看网络详情中的Containers字段可以看到处于当前网络模式下的容器。
# docker network inspect host
[
{
"Name": "host",
"Id": "dba0d6de97d2899dd122d16be8f16161fcc67bbc505cd01266b1480192f868a4",
"Created": "2023-10-15T11:23:25.925580648+08:00",
"Scope": "local",
"Driver": "host",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": []
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"ab70ee7c364bf1b7de695a71bebfa157337dc82be0db8bd424ac5e2a336a175e": {
"Name": "my_nginx",
"EndpointID": "6984cfafc71604e850e6be92d9e55978f522c271a2b3e737a0bd21b7ee2d2b0a",
"MacAddress": "",
"IPv4Address": "",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]关于Host网络模式下端口映射问题:
由于容器与宿主机共用一个 Network Namespace,所以无论是 IP 还是应用程序的Port,容器与宿主机的都是相同的,所以对于容器中应用程序的 Port 不存在映射的问题,host 中的 Port 与容器中的 Port 相同。
比如我们在运行一个基于tomcat镜像的容器,使用host网络模式,并加上-p端口映射。
# docker run -it -d --name mytomcat --network host -p 8081:8080 tomcat:9.0.82
WARNING: Published ports are discarded when using host network mode
588932c5ff584e1805a7342b397aaeb7f5dd1cb5ef2927a2d830d2e446a29e02上面的 tomcat 容器由于指定了网络模式为 host,在启动时指定的端口映射不会起作用。系统给出的 WARNING 指出,当使用 host 网络模式时,已发布的端口号被丢弃。此时,通过仍需通过 8080 端口访问。
查看服务器上监听端口情况
# netstat -nlutp |grep :808
tcp6 0 0 :::8080 :::* LISTEN 37733/java4. None网络模式
none 网络,即没有网络。容器仍是一个独立的 Network Namespace,但没有网络接口,在这个模式下的容器,不会被分配网卡、IP、路由等相关信息,容器内部就只能使用loopback网络设备,不会再有其它网络资源。意味着,当前容器处于不联网状态。
none网络模式的使用只需要在创建容器的时候通过--net none 或者--network none进行指定即可。
none网络模式示例:
1、创建一个基于busybox镜像的容器,直接使用none模式
# docker run -it -d --name none-test --net none busybox
8b9f0ee6ec2151bd41ddabaa2338435c66db7295bd5b51c6d94fae17eb1124bc2、查看容器的ip地址信息。
# docker exec -it none-test 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可以看到none-test容器只有一个回环地址。
3、同样,也可以查看none网络信息,还可以通过查看网络详情中的Containers字段可以看到处于当前网络模式下的容器。
# docker network inspect none
[
{
"Name": "none",
"Id": "225dee6767ece82f2a4f38987b9ef0a001320ef70dfb030149de46d76ef5ff17",
"Created": "2023-10-15T11:23:25.918711533+08:00",
"Scope": "local",
"Driver": "null",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": []
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"8b9f0ee6ec2151bd41ddabaa2338435c66db7295bd5b51c6d94fae17eb1124bc": {
"Name": "none-test",
"EndpointID": "34015aba0d59318054bc8174805771c2fa678e5d29c0af09875983318e4ade7c",
"MacAddress": "",
"IPv4Address": "",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]5. Container网络模式
Container网络模式是一种比较特殊的docker网络模式,其实就是在创建容器的时候通过参数--net container:已运行的容器名称|ID或者--network container:已运行的容器名称|ID指定,然后与其指定的容器共享Network Namespace,但要求该已经存在的容器采用的是 bridge 网络模式。
处于这个模式下的Docker容器会共享一个网络栈,这样两个容器之间可以使用localhost高效快速的通信。
Container 网络模式即新创建的容器不会创建自己的网卡,配置自己的 ip,而是和一个指定的容器共享 ip、端口范围等。同样两个容器除了网络方面相同之外,其他的如文件系统、进程列表等还是隔离的。
Container网络模式示例:
1、首先创建一个基于bridge网络模式的容器mynetc1
# docker run -itd --name mynetc1 busybox
e2d67687bbec8cdca2fe031f81bfce98b848b98f207a31b016aa257d4cbee8142、创建一个基于mynetc1容器创建container网络模式的容器mycontainer1
# docker run -itd --name mycontainer1 --net container:mynetc1 busybox
b3c70cb6e7987a6680dd9c5ca343b056466facf1ac7a16a15f7aeee8c3b8f81a3、进入mynetc1容器查看ip地址,并创建一个文件(用于验证是否只是共享Network Namespace)
# docker exec -it mynetc1 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
111: eth0@if112: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:05 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.5/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
/ #
/ # mkdir /testdir && echo "this is mynetc1" >> /testdir/file1
/ # cat /testdir/file1
this is mynetc1可以看到mynetc1容器的IP地址是172.17.0.5
4、进入mycontainer1容器查看ip地址,并验证上面在mynetc1创建的文件是否存在
# docker exec -it mycontainer1 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
111: eth0@if112: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:05 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.5/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
/ #
/ # ls /
bin dev etc home lib lib64 proc root sys tmp usr var
/ # cat /testdir/file1
cat: can't open '/testdir/file1': No such file or directory通过上面测试可以看出,mynetc1容器和mycontainer1容器使用的是同一个命名空间。而文件系统和进程还是隔离的。
6. 端口映射
端口映射是一种将Docker容器内部的端口映射到宿主机上端口的方法,通过端口映射,外部网络可以与Docker容器内的应用程序进行交互通信。
在Docker中,有两个主要的端口映射选项:-p 和 -P,它们具有不同的功能和用途:
-p
选项:
-p用于手动指定端口映射规则,允许您将容器内的特定端口映射到主机上的指定端口。语法:
-p 主机端口:容器端口例如,
docker run -d -p 8080:80 my-web-app将容器内的80端口映射到主机上的8080端口。这意味着访问主机的8080端口将定向到容器内的80端口。-p选项允许您具有灵活的控制权,可以选择要映射的端口。
-P
选项:
-P用于自动分配端口映射规则,允许Docker自动选择主机上的可用端口,并将它们映射到容器内的对应端口。语法:
-P例如,
docker run -d -P my-web-app将容器内的所有公开端口(EXPOSE的端口)自动映射到主机上的随机端口。Docker会自动选择可用端口,因此您无需手动指定。
使用示例:
(1)使用-P,自动分配宿主机端口,如下,可以看到这里自动分配的端口为32768
# docker run -itd --name mynginx -P nginx:1.24.0
dd4bedf99b81284e7de21261dd75eb81d0097699a60a66675c826642444f20a4
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dd4bedf99b81 nginx:1.24.0 "/docker-entrypoint.…" 2 seconds ago Up 1 second 0.0.0.0:32768->80/tcp, :::32768->80/tcp mynginx(2)映射指定端口,如下,这里表示,用宿主机的80端口映射到容器的80端口上
docker run -itd --name demoapp -p 80:80 vvoo/demoapp:v1.0(3)映射范围端口,如下,这里表示,把容器的80端口绑定到宿主机上8000~9000之间的随机端口。
docker run -itd --name demoapp1 -p 8000-9000:80 vvoo/demoapp:v1.0(4)映射指定接口,默认情况下,会映射宿主机的所有接口,如下,这里表示,映射在宿主机上127.0.0.1接口的80端口上。
docker run -itd --name demoapp2 -p 127.0.0.1:8801:80 vvoo/demoapp:v1.0(5)映射端口时指定协议,通过/tcp或/udp或/sctp指定相关协议,默认为tcp。
docker run -itd --name demoapp3 -p 8802:80/tcp vvoo/demoapp:v1.07. 扩展
7.1. 配置DNS
Docker 没有为每一个容器专门定制镜像,一个镜像可以启动多个容器,那么怎么自定义配置容器的主机名和DNS配置的呢?秘诀就是它利用虚拟文件来挂载到容器的3个相关的配置文件。
如下,我们运行一个容器,并使用mount命令可以看到挂载信息
[root@docker ~]# docker run --rm -it --name my-centos centos:7 /bin/bash
[root@9b3f74948c07 /]# mount
......
/dev/mapper/centos-root on /etc/resolv.conf type xfs (rw,relatime,attr2,inode64,noquota)
/dev/mapper/centos-root on /etc/hostname type xfs (rw,relatime,attr2,inode64,noquota)
/dev/mapper/centos-root on /etc/hosts type xfs (rw,relatime,attr2,inode64,noquota)
......这种机制可以让宿主机DNS信息发生更新后,所有Docker容器的 dns 配置通过 /etc/resolv.conf 文件立刻得到更新。
我们也可以手动指定容器的配置,可以使用-h HOSTNAME 或者 --hostname=HOSTNAME设置容器的主机名,它会被写到容器的/etc/hostname和/etc/hosts文件中。
运行容器时指定容器的hostname
[root@docker ~]# docker run --rm -it --name my-centos --hostname=my_centos_container centos:7 /bin/bash
[root@my_centos_container /]# cat /etc/hostname
my_centos_container
[root@my_centos_container /]# cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 my_centos_container我们还可以通过--dns选项来添加DNS解析到容器中的/etc/resolv.conf文件里面,让容器利用这个DNS服务器来解析所有不在/etc/hosts中的主机。
[root@docker ~]# docker run --rm -it --name my-centos --dns=114.114.114.114 --dns=8.8.8.8 centos:7 /bin/bash
[root@fe352bbe99e3 /]# cat /etc/resolv.conf
nameserver 114.114.114.114
nameserver 8.8.8.8同样,还可以通过--dns-search=DOMAIN来设置容器的搜索域,当设定搜索域为 .example.com时,再搜索一个名为 host的主机时,DNS不仅搜索host,还会搜索host.example.com。
注意:如果没有--dns 和--dns-search选项时,Docker会默认用宿主机上的 /etc/resolv.conf 来配置容器。
7.2. 容器访问控制
容器的访问控制,主要是通过Linux上的 iptables 防火墙规则来进行管理和控制。
7.2.1. 容器访问外部网络
容器所有到外部网络的连接,源地址都会被NAT成本地系统的IP地址,这是使用 iptables 的源地址伪装操作实现的。
[root@docker ~]# iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0其中,上述规则将所有源地址在 172.17.0.0/16 网段,目标地址为其它网络(外部网络)的流量动态伪装为从系统网卡发出。MASQUERADE 跟传统的 SNAT的好处就是它能动态从网卡获取地址。
容器要想访问外部网络,需要本地系统的数据包转发支持,在linux系统中,检查转发是否打开,如下:
# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1值说明,当net.ipv4.ip_forward的值为1时,说明开启了数据包转发,如果为0时,则表明未开启。设置方式如下:
# sysctl -w net.ipv4.ip_forward=1
或者
# echo 1 > /proc/sys/net/ipv4/ip_forward
# sysctl -p
再或者
# vim /etc/sysctl.conf
net.ipv4.ip_forward = 1
# sysctl -p示例1:开启数据包转发net.ipv4.ip_forward=1的情况。
运行一个容器测试是否可以访问外部网络(这里由于宿主机可以访问公网,所以验证是否容器是否可以访问公网)
# docker run --rm -it --name my-centos centos:7 /bin/bash
e
[root@e1ea2d85a685 /]# ping qq.com
PING qq.com (123.150.76.218) 56(84) bytes of data.
64 bytes from 123.150.76.218 (123.150.76.218): icmp_seq=1 ttl=50 time=39.8 ms
64 bytes from 123.150.76.218 (123.150.76.218): icmp_seq=2 ttl=50 time=38.3 ms示例2:关闭数据包转发net.ipv4.ip_forward=0的情况。
# echo 0 > /proc/sys/net/ipv4/ip_forward
# sysctl -p
# docker run --rm -it --name my-centos2 centos:7 /bin/bash
[root@96b3a8283c84 /]# ping qq.com
^C可以发现,当关闭ipv4数据包转发后,容器是无法访问外部网络的。
7.2.2. 外部网络访问容器
外部网络需要访问容器中的应用,最常见的两种方式:
一是直接使用
host网络模式二是通过
-p或-P选项进行端口映射。
当使用docker run时候通过-p 或 -P 参数来进行端口映射的时候,其实也是在宿主机的iptables的nat表中添加相应的规则。
示例:
1、首先运行一个容器,通过-p进行端口映射
# docker run -itd --name mydemoapp -p 80:80 vvoo/demoapp:v1.0
659add226e3d2a2af6d1a4fdc4cfb151f916f2012991d033f2217e5008d04cc2
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
659add226e3d vvoo/demoapp:v1.0 "/bin/sh -c 'python3…" 3 seconds ago Up 2 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp mydemoapp2、查看宿主机的iptables nat表规则
# iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.28.0.0/16 0.0.0.0/0
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
MASQUERADE tcp -- 172.17.0.2 172.17.0.2 tcp dpt:80
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80可以看到多出了一条DNAT规则,上面这个示例其实就是当外部网络访问宿主机的80端口时,通过nat进行地址转换,访问到容器的80端口中。