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
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

$ 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
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:e7:c0:27 brd ff:ff:ff:ff:ff:ff
inet 192.168.142.10/24 brd 192.168.142.255 scope global noprefixroute ens33
valid_lft forever preferred_lft forever
inet6 fe80::20c:29ff:fee7:c027/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:f2:1b:dc:ea 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

$ [root@test1 /]# netstat -anutp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:81 0.0.0.0:* LISTEN 124/nginx: master p
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 36 192.168.142.10:22 192.168.142.1:61396 ESTABLISHED -
tcp6 0 0 :::80 :::* LISTEN -
tcp6 0 0 :::81 :::* LISTEN 124/nginx: master p
tcp6 0 0 :::22 :::* LISTEN -

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
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

$ 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

以下操作演示手动为容器配置网络

  1. 创建 veth pair

    ip link add veth0 type veth peer name veth0_p
  2. 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
  3. 为容器中的网卡配置 IP 及网关等信息

    为了能在宿主机对容器的 network namespace 进行操作,首先需要将容器的 network namespace 暴露出来,之后可以在宿主机通过 network namespace 名称(此处为 84040,可以自定义)操作 network namespaceLinux 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/16172.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 模式、flannelhost-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
NETWORK ID NAME DRIVER SCOPE
1c751b3a52b9 bridge bridge local
f8d9861e5797 host host local
2e19366fb323 my-net bridge local
4c188c59ff20 none null local

新建容器并连接到刚刚新建的 Docker network

docker run -d -it --name c1 --network my-net centos
docker run -d -it --name c2 --network my-net centos

以上命令运行了 2 个 centos 最新版本的容器,并连接到了刚刚创建的网络: --network my-net
登录到容器 c1的终端,并 ping 容器 c2

$ docker exec -it c1 bash
[root@e520d1c8e30b /]# ping c2
PING c2 (172.18.0.3) 56(84) bytes of data.
64 bytes from c2.my-net (172.18.0.3): icmp_seq=1 ttl=64 time=0.115 ms
64 bytes from c2.my-net (172.18.0.3): icmp_seq=2 ttl=64 time=0.071 ms
^C
--- c2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.071/0.093/0.115/0.022 ms

可以看到,容器 c1 可以直接使用容器名 c2,来识别容器 c2,同理,容器 c2 也可以使用同样的方式识别到容器 c1,连接到同一个 Docker network 的容器 c1, c2 可以互相连接
使用以下命令,可以查看 Docker network 的详细信息,包括 ip 网段,ip 分配信息等

$ docker network inspect my-net
[
{
"Name": "my-net",
"Id": "2e19366fb32312235a3571192d1a19e6ebb7adc3e6e25c1f5e6007e8c3315f08",
"Created": "2022-08-30T15:44:54.241420767+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"4b6b1159073d3b70312f2b7e5da8c4a36133a6c00c4746fe2c58d37e81a86b12": {
"Name": "c2",
"EndpointID": "0cbe15ff760040d20cd10a04ffed5f4398e1c3c2b597a86461758606ebdc919d",
"MacAddress": "02:42:ac:12:00:03",
"IPv4Address": "172.18.0.3/16",
"IPv6Address": ""
},
"e520d1c8e30bf97ece44fae53840d13e801648ca02899a17416a75563d3a64f6": {
"Name": "c1",
"EndpointID": "214c7596e2610208402cfa55a225fa67c0dd02d4b585c62b43a9a6c42f360147",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]

如果容器启动时使用了自定义的 Docker network,并且启动过程中挂载了宿主机的 /etc/resolv.conf,则容器启动后无法再使用容器名和其他容器互联,比如使用以下命令启动容器 c3c3 无法使用容器名和 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]

  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:0
  2. Docker Daemon 启动容器时,会在容器中启动 2 个随机可用的端口, 作为 Embedded DNS Server127.0.0.11)的 TCP/UDP 监听端口,本示例中的 tcp:127.0.0.11:42345udp: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
  3. 对容器内的 iptables (或者其他防火墙)进行配置,对 DNS 请求和响应分别做 DNAT 和 SNAT

    # iptables -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 请求过程如下:

  1. 容器中的进程发起 DNS 请求,数据报文中 TCP/IP 头部地址信息(源地址及目标地址)如下:

    172.27.0.3:12345 -> 127.0.0.11:53
  2. 数据报文从网络协议栈发出,到达 iptablesOUTPUT 链,根据 iptables 规则(DOCKER_OUTPUT),数据报文进行了 DNAT,由 172.27.0.3:12345 -> 127.0.0.11:53 转换为 172.27.0.3:12345 -> 127.0.0.11:42345

  3. 数据报文中的目标 IP 地址(127.0.0.11)属于 环回地址段,根据 Linux 网络协议栈对回环地址特殊的处理机制,其不会根据路由表进行路由,而是直接由本地进程(127.0.0.11:42345)处理。

    127.0.0.11:42345 由 Docker 内部 DNS 代理服务监听,它是由 Docker 引擎在宿主机上通过 虚拟网络层内部机制 提供的 DNS 服务,因此你无法在容器内部使用标准进程监控工具(如 psnetstat)直接看到负责监听 127.0.0.11 端口的进程。

    Docker 在启动容器时自动配置了一个 虚拟网络栈,包括虚拟的 DNS 中继服务。该服务是在宿主机的网络命名空间中运行的,而不是在每个容器内部运行单独的 DNS 进程。

    由于 DNS 代理运行在 Docker 守护进程之下,它不需要在容器内通过用户空间进程直接提供服务。因此,容器内部不会显示负责监听 127.0.0.11 的具体进程。

  4. DNS 请求(172.27.0.3:12345 -> 127.0.0.11:42345) 由 Docker 内部 DNS 代理服务处理

    • 如果该请求是针对 Docker 网络内部的容器或服务名,Docker DNS 代理会直接解析并返回对应的 IP 地址。
    • 如果是外部域名(如 google.com),Docker DNS 服务会将请求转发给宿主机配置的外部 DNS 服务器进行解析,即 DNS 转发。
  5. 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

  6. 响应数据报文(127.0.0.11:42345 -> 172.27.0.3:12345)到达 iptablesPOSTROUTING 链,根据规则进行源端口转换,由 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)规则

/etc/nftables.conf
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
chain input {
type filter hook input priority filter; policy drop;
#ip protocol icmp accept; # allow all IPv4 ICMP
#ip6 nexthdr icmpv6 accept; # allow all IPv6 ICMP
ip protocol icmp icmp type echo-reply accept;
ip protocol icmp icmp type echo-request accept;

ip saddr 127.0.0.11 counter accept; # Docker Embedded DNS 服务必须
ip daddr 127.0.0.11 counter accept; # Docker Embedded DNS 服务必须

counter drop;
}
chain forward {
type filter hook forward priority filter; policy drop;
counter drop;
}
chain output {
type filter hook output priority filter;
counter accept;
}
}

## 以下 nat 表中的规则为 Docker Embedded DNS 服务必须,其中的随即端口要根据实际情况修改
table inet nat {
chain OUTPUT {
type nat hook output priority dstnat; policy accept;
ip daddr 127.0.0.11 counter jump DOCKER_OUTPUT;
}

chain DOCKER_OUTPUT {
meta l4proto tcp ip daddr 127.0.0.11 tcp dport 53 counter dnat to 127.0.0.11:37313;
meta l4proto udp ip daddr 127.0.0.11 udp dport 53 counter dnat to 127.0.0.11:33631;
}

chain POSTROUTING {
type nat hook postrouting priority srcnat; policy accept;
ip daddr 127.0.0.11 counter jump DOCKER_POSTROUTING;
}

chain DOCKER_POSTROUTING {
meta l4proto tcp ip saddr 127.0.0.11 tcp sport 37313 counter snat to :53;
meta l4proto udp ip saddr 127.0.0.11 udp sport 33631 counter snat to :53;
}
}

Docker 网络相关问题

定位 Docker 容器中的网卡和宿主机上面的 veth 的 pair 关系

在经典容器组网中,主要是使用 veth + bridge 的模式,容器中的 eth0 实际上和宿主机上面的某个 veth 是成对(pair)关系,要查看容器中的网卡和宿主机上面的 veth 网卡的成对关系,可以参考以下方法

方法 1

  1. 在目标容器中查看
    $ cat /sys/class/net/eth0/iflink 
    60

  2. 在宿主机上遍历 /sys/class/net/ 下面的全部目录,查看子目录中的 ifindex 文件的内容,找出和容器中 /sys/class/net/eth0/iflink 的值一样的 veth 的名称,这样就找到了容器和主机的 veth pair 的关系
    $ cat /sys/class/net/vethc41ba34/ifindex 
    60
    本示例中,宿主机上的 vethc41ba34 和容器中的网卡是 veth pair

方法 2

  1. 目标容器中查看
    $ 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 0
    从上面的命令可以看到 59: eth0@if60,其中 59eth0 接口的 index,60 是和它成对的 veth 的 index。
  2. 在 host 上面执行下面的命令,可以看到对应 60veth 网卡是哪一个
    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

  1. 目标容器中执行
    $ ethtool -S eth0
    NIC statistics:
    peer_ifindex: 60
  2. 在宿主机上面查找 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

参考链接

docker container DNS配置介绍和源码分析

脚注