docker 网络
环境信息
- Centos 7.9.2009
- docker-ce-19.03.15
Docker 网络模式
Bridge 模式
bridge 模式是 docker 的默认网络模式,不使用 --network
参数,就是 bridge 模式。
当 Docker 进程启动时,会在主机上创建一个名为 docker0 的虚拟网桥,默认主机上启动的 Docker 容器会连接到这个虚拟网桥上。
容器启动时,docker 会从 docker0 网桥的子网中分配一个 IP 地址给容器中的网卡。大体流程为在主机上创建一个 `veth pair`,Docker 将 veth pair
的一端放在容器中,命名为 eth0
并配置 IP,网关,路由等信息,将 veth pair
的另一端加入 docker0 网桥。
通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。
Host 模式
如果启动容器的时候使用 host 模式,那么这个容器将不会获得一个独立的 Network Namespace,而是和宿主机一样在 Root Network Namespace,容器中看到的网络方面的信息和宿主机一样,容器使用的网络资源在整个 Root Network Namespace 不能出现冲突。容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口,主机名也是使用宿主机的。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。
host 模式下的容器可以看到宿主机上的所有网卡信息,可以直接使用宿主机 IP 或主机名与外界通信,无需额外的 NAT,也无需通过 Linux bridge 进行转发或者数据包的封装,可以访问主机上的其他任一容器。
使用如下命令参数启动 host 网络模式的容器
docker run --network host --name test1 -p 80:80 -d -it centos:centos7.9.2009 |
host 模式的容器,没有自己的 network namespace,在 root network namespace 中。进入测试容器 test1
,查看网卡、 IP 信息及端口、主机名信息,会看到和宿主机一样的信息。
ip link |
host 模式的缺点
- 容器没有自己的
network namespace
,网络和宿主机或其他使用host
模式的容器未隔离,容易出现资源冲突,比如同一个宿主机上,使用host
模式的容器中启动的端口不能相同。
None 模式
使用 none
模式,Docker 容器拥有自己的 Network Namespace
,但是,系统并不为 Docker 容器进行任何网络配置。也就是说,这个 Docker 容器没有网卡(lo
回环网卡除外)、IP、路由等信息。需要我们自己为 Docker 容器添加网卡、配置 IP 等。
参考以下命令创建 none
模式的容器
docker run --network none --name test-none -p 82:80 -d -it centos7:my |
容器创建后,进入容器中,查看网卡和 IP 等信息,容器中默认只存在 lo
网卡,不存在其他网卡
ip add |
以下操作演示手动为容器配置网络
创建
veth pair
ip link add veth0 type veth peer name veth0_p
将
veth pair
的一端veth0
放入 docker 默认的网桥docker0
,另一端veth0_p
放入容器中首先使用命令
docker inspect test-none | grep "Pid"
找到容器对应的 PID,此处为 84040,根据此 PID 将 veth 的一端放入容器的 network namespace 中ip link set dev veth0 master docker0
ip link set dev veth0 up
ip link set veth0_p netns 84040在宿主机上面检查
veth0
,确定其已经加入网桥docker0
,并且veth0_p
已不在root network namespace
中ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:e7:c0:27 brd ff:ff:ff:ff:ff:ff
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether 02:42:f2:1b:dc:ea brd ff:ff:ff:ff:ff:ff
12: veth0@if11: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue master docker0 state LOWERLAYERDOWN mode DEFAULT group default qlen 1000
link/ether 16:7f:98:d8:9d:dc brd ff:ff:ff:ff:ff:ff link-netnsid 0重新进入容器,检查网卡信息,可以看到容器中已经有了网卡
veth0_p
,状态为DOWN
ip -d link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0 addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
11: veth0_p@if12: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether be:f1:94:9f:b8:c9 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
veth addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535为容器中的网卡配置 IP 及网关等信息
为了能在宿主机对容器的
network namespace
进行操作,首先需要将容器的network namespace
暴露出来,之后可以在宿主机通过network namespace
名称(此处为 84040,可以自定义)操作network namespace
。Linux network namespace 参考ln -s /proc/84040/ns/net /var/run/netns/84040
ip netns ls
84040 (id: 0)通过
network namespace
名称(此处为 84040)配置容器中网卡的 IP 地址信息ip netns exec 84040 ip link set dev veth0_p name eth0
ip netns exec 84040 ip link set dev eth0 up
ip netns exec 84040 ip add add 172.17.0.10/16 dev eth0
ip netns exec 84040 ip route add default via 172.17.0.1进入容器检查网络信息
ip add
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 qlen 1000
link/ether 7e:36:b3:20:a1:8c brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.10/16 scope global eth0
valid_lft forever preferred_lft forever
ip route show
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.10进入容器测试网络连接
ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=127 time=37.4 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=127 time=37.0 ms
^C
--- 8.8.8.8 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 37.047/37.234/37.422/0.269 ms
Container 模式
在创建容器时通过参数 --network container:已运行的容器名称|ID
指定,处于这个模式下的 Docker 容器会共享一个网络栈,这样两个容器之间可以使用 localhost 高效快速通信。
Container 网络模式即新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围、主机名等。同样两个容器除了网络方面相同之外,其他的如文件系统、进程列表等还是隔离的。
Kubernetes 的 POD 网络采用的就是 Docker 的 container 模式网络。
Macvlan 模式
linux macvlan 网卡虚拟化技术ipvlan 模式
IPVLAN/MACVLAN 实现 Docker 和物理局域网络真正互联互通
容器网络组网类型
常见的非主机网络(host network
)的容器组网类型有 L2 overlay、L3 overlay、L2 underlay、L3 underlay。
overlay 网络
overlay
网络,也称为隧道网络或覆盖网络。
overlay
网络是在传统网络(数据链路层、网络层)上虚拟出一个虚拟网络,承载虚拟网络的底层传统网络不再需要进行任何适配和变更。在容器的世界了,底层物理网络只需要承载主机的网络通信,虚拟网络只承载容器网络通信。
overlay
网络的任何协议都要求发送方对报文进行封装(在虚拟网络报文的头部添加底层物理网络的地址信息,以将报文传输到容器所在的节点),接收方对报文进行解封装,使用 UDP 进行封装时,性能损失在 50% 以上,使用 VXLAN 也会有 20%-30% 的损耗。
overlay
网络最大的优点是适用于几乎所有的网络基础架构,唯一要求是主机 IP 互通,问题是随着规模的增长,复杂度会随之增加,封包和解封包性能损坏较大且难于定位问题。
L2 overlay
传统的 L2 网络,通信双方在同一个逻辑网段内,如 172.17.1.10/16
和 172.17.2.10/16
L2 overlay
是构建在底层物理网络之上的 L2 网络,相较于传统的 L2 网络,L2 overlay
是个 大二层
(可以跨越多个数据中心,即可以跨 L3 underlay 进行 L2 通信)。
VXLAN 就是 L2 overlay
网络的典型实现,其通过在 UDP 包中封装原始的 L2 报文,实现了容器的跨主机通信。
L2 overlay
网络的容器可以在任意的宿主机间迁移而不改变其 IP 地址,这种特性使得构建在大二层 overlay 网络上的容器在动态迁移时具有很高的灵活性。
L3 overlay
L2 overlay
类似于 L2 overlay
,但会在节点上增加个网关,每个节点上的容器都在同一个子网内,可以直接进行二层通信。跨接点间的通信只能通过 L3,都会经过网关转发,性能相比于 L2 overlay 弱。优点是跨节点通信的容器可以在不同的网段。
flannel
的 UDP 模式采用的就是 L3 overlay
模式。
underlay 网络
underlay
网络通常指底层网络,即传统的网络组网,主要要来区别于 overlay
网络。
L2 underlay
指传统的二层网络。 ipvlan
的 L2 模式属于 L2 underlay
类型的网络
L3 underlay
指传统的三层网络。
ipvlan
的 L3 模式、flannel
的 host-gw
模式和 Calico 的 BGP 组网都是 L3 underlay 类型的网络
容器互联
为了使容器互联,新版本建议将容器加入自定义的 Docker 网络
来连接多个容器,而不是使用 --link
参数。
从 Docker 1.10 版本开始,docker daemon
实现了一个内嵌的 DNS server,使容器可以直接通过容器名称通信。方法很简单,只要在创建容器时使用 --name
为容器命名即可。但是使用 Docker DNS 有个限制:只能在 user-defined
网络中使用。也就是说,默认的 bridge
网络是无法使用 DNS 的,所以我们就需要自定义网络。
先创建一个新的 Docker 网络
。
docker network create -d bridge my-net |
-d
参数指定 Docker 网络类型,可选 bridge
overlay
查看 Docker network
docker network ls |
新建容器并连接到刚刚新建的 Docker network
docker run -d -it --name c1 --network my-net centos |
以上命令运行了 2 个 centos
最新版本的容器,并连接到了刚刚创建的网络: --network my-net
登录到容器 c1
的终端,并 ping
容器 c2
docker exec -it c1 bash |
可以看到,容器 c1
可以直接使用容器名 c2
,来识别容器 c2
,同理,容器 c2
也可以使用同样的方式识别到容器 c1
,连接到同一个 Docker network
的容器 c1
, c2
可以互相连接
使用以下命令,可以查看 Docker network
的详细信息,包括 ip 网段,ip 分配信息等
docker network inspect my-net |
如果容器启动时使用了自定义的
Docker network
,并且启动过程中挂载了宿主机的/etc/resolv.conf
,则容器启动后无法再使用容器名和其他容器互联,比如使用以下命令启动容器c3
,c3
无法使用容器名和c1
,c2
互联,但是c1
,c2
可以使用容器名c3
连接到容器c3
docker run -d -ti --network my-net -v /etc/resolv.conf:/etc/resolv.conf --name c3 centos以上示例中,如果登录
c1
终端后,修改/etc/resolv.conf
文件,再次使用c2
,会无法连接c2
容器 相关原理参考 Docker DNS 服务
如果容器启动时,需要为容器指定域名 ip 映射关系,可以使用选项 --add-host host:ip
docker run --network my-net --add-host db:172.18.0.3 --name c3 centos |
容器运行过程中,可以直接修改容器内的 /etc/hosts
文件
Docker DNS 服务
从 Docker 1.10 版本开始,Docker 实现了一个内嵌的 DNS server,Docker 容器可以使用内部的 DNS 解析机制,而不是直接使用宿主机的 DNS 设置。在容器 /etc/resolv.conf
中看到 nameserver 127.0.0.11
表示 Docker 容器使用了 Docker 内部的 DNS 解析服务。
通过使用此内部 DNS 服务器,Docker 实现了其 服务发现 ( 在容器运行的 Docker 网络中,每个容器的主机名和服务名(如 docker compose
中的 service
)都会自动注册到 Docker 内部 DNS 中,这使得在同一网络中的容器可以通过服务名互相访问,而不需要知道彼此的 IP 地址 。)相关功能:
- 当容器中的应用需要解析域名时,如果该请求是要 解析服务发现相关的内部 Docker 网络中的主机名 ,Docker 的 DNS 服务器会优先解析容器网络中的主机名。例如,容器之间的互相访问可以通过服务名来解析。
- 如果请求的域名不属于 Docker 内部网络(如外部的互联网域名
google.com
),Docker 会将请求转发到宿主机的 DNS 服务器(通常是由宿主机的/etc/resolv.conf
文件中的配置来决定)。
127.0.0.11
是 Docker 引擎提供的内部虚拟 DNS 服务器地址,负责处理容器的 DNS 请求。这个虚拟 DNS 服务会捕获容器发出的所有 DNS 查询,并根据 Docker 引擎的配置做进一步的处理。
127.0.0.11
在系统环回地址段(本地网络)内(127.0.0.0/8
), Linux 网络栈对回环地址有特殊的处理机制:
- 当数据包的目标地址是回环地址(
127.x.x.x
),内核会将其视为本地地址,不会通过默认网关路由转发,而是直接在本地处理。 - 回环地址
127.0.0.0/8
是保留用于本地通信的,这意味着任何发往回环地址的数据包都不会经过网络接口,而是直接在本地主机上处理。
以下为 Docker Daemon 内嵌 DNS 服务的大体工作流程及相关关键点: [1]
Docker Daemon 创建容器时,会为容器生成
/etc/resolv.conf
并写入Docker Embedded DNS Server
的 IP,默认为nameserver 127.0.0.11
。并生成相关的iptables
转发规则用于处理 DNS 请求。相关信息如下:cat /etc/resolv.conf
nameserver 127.0.0.11
options edns0 trust-ad ndots:0Docker Daemon 启动容器时,会在容器中启动 2 个随机可用的端口, 作为
Embedded DNS Server
(127.0.0.11
)的 TCP/UDP 监听端口,本示例中的tcp:127.0.0.11:42345
和udp:127.0.0.11:60253
ip add
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
4990: eth0@if4991: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:1b:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.27.0.3/16 brd 172.27.255.255 scope global eth0
valid_lft forever preferred_lft forever
ip route
default via 172.27.0.1 dev eth0
172.27.0.0/16 dev eth0 proto kernel scope link src 172.27.0.3
netstat -anutp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.11:42345 0.0.0.0:* LISTEN -
udp 0 0 127.0.0.11:60253 0.0.0.0:* -
ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2696 1116 ? Ss Sep10 0:00 sleep 31536000
root 7 0.0 0.0 4588 3976 pts/0 Ss+ Sep10 0:00 bash
root 231 0.4 0.0 4588 3912 pts/1 Ss 05:45 0:00 bash
root 239 0.0 0.0 7888 3952 pts/1 R+ 05:45 0:00 ps aux对容器内的
iptables
(或者其他防火墙)进行配置,对 DNS 请求和响应分别做 DNAT 和 SNATiptables -t nat -L -v -n
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
6 485 DOCKER_OUTPUT 0 -- * * 0.0.0.0/0 127.0.0.11
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
6 485 DOCKER_POSTROUTING 0 -- * * 0.0.0.0/0 127.0.0.11
Chain DOCKER_OUTPUT (1 references)
pkts bytes target prot opt in out source destination
0 0 DNAT 6 -- * * 0.0.0.0/0 127.0.0.11 tcp dpt:53 to:127.0.0.11:42345
6 485 DNAT 17 -- * * 0.0.0.0/0 127.0.0.11 udp dpt:53 to:127.0.0.11:60253
Chain DOCKER_POSTROUTING (1 references)
pkts bytes target prot opt in out source destination
0 0 SNAT 6 -- * * 127.0.0.11 0.0.0.0/0 tcp spt:42345 to::53
0 0 SNAT 17 -- * * 127.0.0.11 0.0.0.0/0 udp spt:60253 to::53
根据以上配置,详细的 DNS 请求过程如下:
容器中的进程发起 DNS 请求,数据报文中 TCP/IP 头部地址信息(源地址及目标地址)如下:
172.27.0.3:12345 -> 127.0.0.11:53
数据报文从网络协议栈发出,到达
iptables
的OUTPUT
链,根据iptables
规则(DOCKER_OUTPUT
),数据报文进行了 DNAT,由172.27.0.3:12345 -> 127.0.0.11:53
转换为172.27.0.3:12345 -> 127.0.0.11:42345
数据报文中的目标 IP 地址(
127.0.0.11
)属于 环回地址段,根据 Linux 网络协议栈对回环地址特殊的处理机制,其不会根据路由表进行路由,而是直接由本地进程(127.0.0.11:42345
)处理。127.0.0.11:42345
由 Docker 内部 DNS 代理服务监听,它是由 Docker 引擎在宿主机上通过 虚拟网络层 和 内部机制 提供的 DNS 服务,因此你无法在容器内部使用标准进程监控工具(如ps
或netstat
)直接看到负责监听127.0.0.11
端口的进程。Docker 在启动容器时自动配置了一个 虚拟网络栈,包括虚拟的 DNS 中继服务。该服务是在宿主机的网络命名空间中运行的,而不是在每个容器内部运行单独的 DNS 进程。
由于 DNS 代理运行在 Docker 守护进程之下,它不需要在容器内通过用户空间进程直接提供服务。因此,容器内部不会显示负责监听 127.0.0.11 的具体进程。
DNS 请求(
172.27.0.3:12345 -> 127.0.0.11:42345
) 由 Docker 内部 DNS 代理服务处理- 如果该请求是针对 Docker 网络内部的容器或服务名,Docker DNS 代理会直接解析并返回对应的 IP 地址。
- 如果是外部域名(如
google.com
),Docker DNS 服务会将请求转发给宿主机配置的外部 DNS 服务器进行解析,即 DNS 转发。
DNS 请求(
172.27.0.3:12345 -> 127.0.0.11:42345
)的响应类似(127.0.0.11:42345 -> 172.27.0.3:12345
),虽然这个数据包是本地生成的,但经过 Docker 内部使用的 虚拟网络栈 ,数据包在离开 Docker Daemon 的虚拟 DNS 处理服务(127.0.0.11:42345
)后,被视为一个出站数据包,并进入POSTROUTING
链响应数据报文(
127.0.0.11:42345 -> 172.27.0.3:12345
)到达iptables
的POSTROUTING
链,根据规则进行源端口转换,由127.0.0.11:42345 -> 172.27.0.3:12345
SNAT 为127.0.0.11:53 -> 172.27.0.3:12345
,确保发起 DNS 请求的进程识别响应的数据报文。
如果在 使用了 Docker 内嵌 DNS 服务的容器中启用防火墙,为了让内嵌 DNS 服务正常工作 ,可以参考以下防火墙(nftables
)规则
!/usr/sbin/nft -f |
Docker 网络相关问题
定位 Docker 容器中的网卡和宿主机上面的 veth 的 pair 关系
在经典容器组网中,主要是使用 veth
+ bridge
的模式,容器中的 eth0
实际上和宿主机上面的某个 veth
是成对(pair)关系,要查看容器中的网卡和宿主机上面的 veth
网卡的成对关系,可以参考以下方法
方法 1
- 在目标容器中查看
$ cat /sys/class/net/eth0/iflink
60 - 在宿主机上遍历
/sys/class/net/
下面的全部目录,查看子目录中的ifindex
文件的内容,找出和容器中/sys/class/net/eth0/iflink
的值一样的veth
的名称,这样就找到了容器和主机的veth pair
的关系本示例中,宿主机上的$ cat /sys/class/net/vethc41ba34/ifindex
60vethc41ba34
和容器中的网卡是veth pair
方法 2
- 目标容器中查看从上面的命令可以看到
ip link show eth0
59: eth0@if60: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:ac:16:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 059: eth0@if60
,其中59
是eth0
接口的 index,60
是和它成对的veth
的 index。 - 在 host 上面执行下面的命令,可以看到对应
60
的veth
网卡是哪一个ip link show | grep 60
60: vethc41ba34@if59: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-9d2a9fc0ff85 state UP mode DEFAULT group default
方法 3
通过 ethtool -S
命令列出 veth pair
对端的网卡 index
- 目标容器中执行
ethtool -S eth0
NIC statistics:
peer_ifindex: 60 - 在宿主机上面查找 index 为 60 的
veth
网卡是哪一个ip link show | grep 60
60: vethc41ba34@if59: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-9d2a9fc0ff85 state UP mode DEFAULT group default