Docker 网络工业级实战手册

目录

[一、Docker 网络概述](#一、Docker 网络概述)

生活类比

[核心命令逐行解析(Line-by-Line Breakdown)](#核心命令逐行解析(Line-by-Line Breakdown))

坑点(Gotchas)

企业级生产应用

课后防宕机指南(Troubleshooting)

[5.1 Docker 原生 bridge 网络(网桥模式)](#5.1 Docker 原生 bridge 网络(网桥模式))

生活类比

[核心代码逐行解析(Line-by-Line Breakdown)](#核心代码逐行解析(Line-by-Line Breakdown))

坑点(Gotchas)

企业级生产应用

课后防宕机指南(Troubleshooting)

[5.2 Docker 原生网络 host](#5.2 Docker 原生网络 host)

生活类比

[核心代码逐行解析(Line-by-Line Breakdown)](#核心代码逐行解析(Line-by-Line Breakdown))

坑点(Gotchas)

企业级生产应用

课后防宕机指南(Troubleshooting)

[5.3 Docker 原生网络 none](#5.3 Docker 原生网络 none)

原命令与输出

生活类比

[核心代码逐行解析(Line-by-Line Breakdown)](#核心代码逐行解析(Line-by-Line Breakdown))

坑点(Gotchas)

企业级生产应用

课后防宕机指南(Troubleshooting)

[5.4 Docker 的自定义网络](#5.4 Docker 的自定义网络)

[5.4.1 自定义桥接网络](#5.4.1 自定义桥接网络)

原命令与输出

生活类比

[核心代码逐行解析(Line-by-Line Breakdown)](#核心代码逐行解析(Line-by-Line Breakdown))

坑点(Gotchas)

企业级生产应用

课后防宕机指南(Troubleshooting)

[5.4.2 为什么要自定义桥接](#5.4.2 为什么要自定义桥接)

原命令与输出

生活类比

[核心代码逐行解析(Line-by-Line Breakdown)](#核心代码逐行解析(Line-by-Line Breakdown))

坑点(Gotchas)

企业级生产应用

课后防宕机指南(Troubleshooting)

[5.4.3 如何让不同的自定义网络互通](#5.4.3 如何让不同的自定义网络互通)

原命令与输出

生活类比

[核心代码逐行解析(Line-by-Line Breakdown)](#核心代码逐行解析(Line-by-Line Breakdown))

坑点(Gotchas)

企业级生产应用

课后防宕机指南(Troubleshooting)

[5.4.4 joined 容器网络](#5.4.4 joined 容器网络)

​编辑

原命令与输出

生活类比

[核心代码逐行解析(Line-by-Line Breakdown)](#核心代码逐行解析(Line-by-Line Breakdown))

坑点(Gotchas)

企业级生产应用

课后防宕机指南(Troubleshooting)

[5.4.5 joined 网络示例演示](#5.4.5 joined 网络示例演示)

一、导入镜像

[二、运行 phpMyAdmin 容器](#二、运行 phpMyAdmin 容器)

[三、运行 MySQL 容器](#三、运行 MySQL 容器)

​编辑

[四、访问 phpMyAdmin](#四、访问 phpMyAdmin)

生活类比

[核心代码逐行解析(Line-by-Line Breakdown)](#核心代码逐行解析(Line-by-Line Breakdown))

坑点(Gotchas)

企业级生产应用

课后防宕机指南(Troubleshooting)

[5.5 容器内外网的访问](#5.5 容器内外网的访问)

[5.5.1 容器访问外网](#5.5.1 容器访问外网)

原命令与输出

生活类比

[核心代码逐行解析(Line-by-Line Breakdown)](#核心代码逐行解析(Line-by-Line Breakdown))

坑点(Gotchas)

企业级生产应用

课后防宕机指南(Troubleshooting)

[5.5.2 外网访问 docker 容器](#5.5.2 外网访问 docker 容器)

生活类比

[核心代码逐行解析(Line-by-Line Breakdown)](#核心代码逐行解析(Line-by-Line Breakdown))

坑点(Gotchas)

企业级生产应用

课后防宕机指南(Troubleshooting)

[5.6 Docker 跨主机网络](#5.6 Docker 跨主机网络)

跨主机网络解决方案

[5.6.1 CNM (Container Network Model)](#5.6.1 CNM (Container Network Model))

[5.6.2 macvlan 网络方式实现跨主机通信](#5.6.2 macvlan 网络方式实现跨主机通信)

原命令与输出

一、设定硬件

[二. 开启新加网卡的混杂模式](#二. 开启新加网卡的混杂模式)

三.配置docker自建网络```

四.测试跨主机docker是否可以通信```bash

[1. 通俗生活类比](#1. 通俗生活类比)

[2. 企业生产环境实际应用场景](#2. 企业生产环境实际应用场景)

[3. 核心命令逐行解释](#3. 核心命令逐行解释)

[步骤 1:开启物理网卡混杂模式(Macvlan 的前置要求)](#步骤 1:开启物理网卡混杂模式(Macvlan 的前置要求))

[步骤 2:两台主机创建相同配置的 Macvlan 网络](#步骤 2:两台主机创建相同配置的 Macvlan 网络)

[步骤 3:测试跨主机容器通信](#步骤 3:测试跨主机容器通信)

[4. 生产必知的 Macvlan 核心限制](#4. 生产必知的 Macvlan 核心限制)

检验理解的小问题

总结


一、Docker 网络概述

Docker 的镜像分层技术是其核心优势,但网络功能相对薄弱,是生产环境故障高发区。Docker 安装完成后会自动创建 3 种默认网络 ,分别是 bridgehostnone

bash 复制代码
[root@docker harbor]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
2a93d6859680   bridge    bridge    local
bridge         bridge    local
4d81ddd9ed10   host      host      local
host          host      local
8c8c95f16b68   none      null      local
none          null      local

生活类比

这 3 种默认网络就像小区自带的 3 种户型:

  • bridge:标准两居室,有独立门禁(网络隔离),通过小区大门(docker0 网桥)进出
  • host:临街商铺,没有独立门禁,直接和小区共用同一个大门和地址
  • none:地下室储物间,没有门窗,完全与世隔绝
核心命令逐行解析(Line-by-Line Breakdown)

bash

运行

复制代码
docker network ls
  • 底层动作 :调用 Docker 守护进程的 /networks API,读取 /var/lib/docker/network/files/local-kv.db 中的网络元数据,返回所有已创建的网络列表
  • 列含义
    • NETWORK ID:网络的唯一标识符(前 12 位)
    • NAME:网络名称
    • DRIVER:网络驱动类型(决定网络实现方式)
    • SCOPE:网络作用域(local= 单主机,swarm= 跨主机集群)
坑点(Gotchas)

⚠️ 不要删除默认的 bridgehostnone 网络!删除后 Docker 守护进程会崩溃,且无法自动恢复,只能重新安装 Docker。

企业级生产应用
  • 生产环境禁止直接使用默认 bridge 网络,必须使用自定义 bridge 网络
  • 默认 bridge 网络仅用于测试和快速验证,不具备 DNS 解析和细粒度隔离能力
课后防宕机指南(Troubleshooting)
  • 报错Error response from daemon: network bridge not found
  • 排查思路
    1. 执行 systemctl restart docker 尝试自动重建默认网络
    2. 若失败,手动重建:docker network create --driver bridge --subnet 172.17.0.0/16 --gateway 172.17.0.1 bridge
    3. 检查 /etc/docker/daemon.json 中是否有错误的网络配置

5.1 Docker 原生 bridge 网络(网桥模式)

bash 复制代码
[root@docker mnt]# ip link show type bridge
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether 02:42:5f:e2:34:6c brd ff:ff:ff:ff:ff:ff

核心原理:bridge 模式下容器没有公有 IP,只有宿主机可以直接访问;容器通过宿主机的 NAT 规则访问外网。

bash 复制代码
[root@docker mnt]# docker run -d --name web -p 80:80 nginx:1.23
defeba839af1b95bac2a200fd1e06a45e55416be19c7e9ce7e0c8daafa7dd470
bash 复制代码
[root@docker mnt]# ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
inet6 fe80::42:5fff:fee2:346c prefixlen 64 scopeid 0x20<link>
ether 02:42:5f:e2:34:6c txqueuelen 0 (Ethernet)
RX packets 21264 bytes 1497364 (1.4 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 27358 bytes 215202237 (205.2 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.25.254.100 netmask 255.255.255.0 broadcast 172.25.254.255
inet6 fe80::30b2:327e:b13a:31cf prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:ec:fc:d3 txqueuelen 1000 (Ethernet)
RX packets 1867778 bytes 2163432019 (2.0 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 822980 bytes 848551940 (809.2 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 11819 bytes 1279944 (1.2 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 11819 bytes 1279944 (1.2 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

veth022a7c9: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet6 fe80::a013:5fff:fefc:c9e4 prefixlen 64 scopeid 0x20<link>
ether a2:13:5f:fc:c9:e4 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 15 bytes 2007 (1.9 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

注:veth022a7c9 为容器使用的虚拟网卡对(veth pair)的宿主机端

bash 复制代码
[root@docker mnt]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000 no veth022a7c9

生活类比

docker0 网桥就是小区的核心路由器

  • 每个容器是小区里的一户人家,有自己的内网 IP(172.17.0.x)
  • veth pair 就是连接每家每户和路由器的网线
  • 宿主机的 eth0 是小区的公网出口
  • NAT 规则就是小区的地址转换系统,把所有内网住户的请求都转换成小区的公网 IP 发出去
核心代码逐行解析(Line-by-Line Breakdown)
bash 复制代码
docker run -d --name web -p 80:80 nginx:1.23
  • -d:后台运行容器(detached 模式)
    • 底层动作:创建容器进程后,父进程立即退出,容器进程被 systemd 托管
  • --name web:给容器命名为web
    • 底层动作:在 Docker 的元数据库中注册容器名称与 ID 的映射关系
  • -p 80:80:端口映射(宿主机 80 端口 → 容器 80 端口)
    • 底层核心动作
      1. 创建两个docker-proxy进程(IPv4 和 IPv6 各一个),监听宿主机的 0.0.0.0:80 和:::80
      2. 在 iptables 的nat表中添加 DNAT 规则:将所有访问宿主机 80 端口的数据包转发到 172.17.0.2:80
      3. 在 iptables 的filter表中添加 ACCEPT 规则,允许 80 端口的入站流量
  • nginx:1.23:使用 nginx 1.23 版本镜像启动容器
    • 底层动作:检查本地是否有该镜像,没有则从镜像仓库拉取;然后基于镜像创建容器文件系统和网络命名空间
坑点(Gotchas)

⚠️ 端口映射顺序不能写反-p 容器端口:宿主机端口会导致宿主机端口无法访问,且 Docker 不会报错⚠️ 若宿主机开启了firewalldufw,需要手动开放映射的端口,否则外网无法访问⚠️ 默认 bridge 网络中,容器 IP 是动态分配的,重启后会变化,不能用 IP 进行容器间通信

企业级生产应用
  • 千万级并发场景下,禁用 docker-proxy ,仅使用 iptables DNAT 进行端口转发,性能提升 30% 以上
    • 配置方法:在/etc/docker/daemon.json中添加"userland-proxy": false
  • 生产环境使用-p 宿主机IP:宿主机端口:容器端口的格式,避免监听所有接口(0.0.0.0)带来的安全风险
  • 对于高流量服务,使用--network host模式替代端口映射,消除 NAT 开销
课后防宕机指南(Troubleshooting)
  • 报错Error starting userland proxy: listen tcp4 0.0.0.0:80: bind: address already in use
  • 排查思路
    1. 执行netstat -antlupe | grep 80查看哪个进程占用了 80 端口
    2. 停止占用端口的进程,或使用其他宿主机端口
    3. 若确认没有进程占用,检查是否有残留的 docker-proxy 进程:ps aux | grep docker-proxy,杀死残留进程

5.2 Docker 原生网络 host

host 模式直接绕过 docker0 网桥,连接宿主机物理网卡 eth0。

host 网络模式需要在容器创建时指定 --network=host

核心原理:host 模式让容器共享宿主机的网络栈,外部主机可以直接与容器通信,但容器网络缺少隔离性。

bash 复制代码
[root@docker ~]# docker run -it --name test --network host busybox
/ # ifconfig
docker0 Link encap:Ethernet HWaddr 02:42:5F:E2:34:6C
docker0
inet addr:172.17.0.1 Bcast:172.17.255.255 Mask:255.255.0.0
inet6 addr: fe80::42:5fff:fee2:346c/64 Scope:Link
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:21264 errors:0 dropped:0 overruns:0 frame:0
TX packets:27359 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1497364 (1.4 MiB) TX bytes:215202367 (205.2 MiB)

eth0 Link encap:Ethernet HWaddr 00:0C:29:EC:FC:D3
eth0
inet addr:172.25.254.100 Bcast:172.25.254.255 Mask:255.255.255.0
inet6 addr: fe80::30b2:327e:b13a:31cf/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:1902507 errors:0 dropped:0 overruns:0 frame:0
TX packets:831640 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:2202443300 (2.0 GiB) TX bytes:849412124 (810.0 MiB)

lo Link encap:Local Loopback
1o
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:11819 errors:0 dropped:0 overruns:0 frame:0
TX packets:11819 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:1279944 (1.2 MiB) TX bytes:1279944 (1.2 MiB)

注意:如果多个容器使用 host 模式,所有网络资源都是共享的。比如启动了一个 nginx 容器占用了宿主机的 80 端口,再启动第二个 nginx 容器就会失败。


生活类比

host 模式就像临街商铺

  • 商铺没有独立的门牌号,直接使用小区的地址
  • 顾客可以直接走到商铺门口,不需要经过小区保安(NAT)
  • 但商铺和小区共用同一个水电管网和出入口,一家商铺占用了某个出入口,其他商铺就不能用了
核心代码逐行解析(Line-by-Line Breakdown)

bash

运行

复制代码
docker run -it --name test --network host busybox
  • --network host:指定容器使用 host 网络模式
    • 底层核心动作
      1. 不创建独立的网络命名空间(Network Namespace)
      2. 容器直接使用宿主机的/proc/net目录下的所有网络配置
      3. 不创建 veth pair 虚拟网卡对
      4. 容器的所有端口直接绑定在宿主机的网卡上
  • -it:交互式运行容器,分配伪终端
    • 底层动作:将宿主机的标准输入、输出、错误流重定向到容器的终端
坑点(Gotchas)

⚠️ host 模式下容器没有网络隔离,容器可以访问宿主机的所有网络资源,存在严重的安全风险⚠️ 容器的端口会直接占用宿主机的端口,容易出现端口冲突⚠️ host 模式仅支持 Linux 系统,Windows 和 MacOS 系统不支持

企业级生产应用
  • 千万级并发场景下,高流量的反向代理(Nginx)、负载均衡(HAProxy)、数据库(MySQL) 优先使用 host 模式
  • 消除了 NAT 和 docker-proxy 的开销,网络吞吐量提升 40%-60%
  • 注意:使用 host 模式时,必须通过宿主机的防火墙(iptables/nftables)进行访问控制
课后防宕机指南(Troubleshooting)
  • 报错Error starting container: listen tcp4 0.0.0.0:3306: bind: address already in use
  • 排查思路
    1. 执行lsof -i :3306查看宿主机上是否有 MySQL 进程在运行
    2. 停止宿主机上的 MySQL 服务,或修改容器内服务的端口
    3. 若必须同时运行,改用 bridge 模式并映射不同的宿主机端口

5.3 Docker 原生网络 none

none 模式是指禁用网络功能,只有 lo(回环)接口,在容器创建时使用--network=none指定。

原命令与输出

bash 复制代码
[root@docker ~]# docker run -it --name test --rm --network none busybox
/ # ifconfig
lo Link encap:Local Loopback
10
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

核心原理 :none 模式下的容器没有任何网络接口,无法与外界通信,只能通过docker exec进入容器内部操作。

原生:你安装好后它默认的就这样


生活类比

none 模式就像没有窗户和门的地下室储物间

  • 完全与世隔绝,没有任何对外的通道
  • 只有持有钥匙的人(通过 docker exec)才能进入
  • 适合存放不需要网络的敏感数据或执行离线计算任务
核心代码逐行解析(Line-by-Line Breakdown)

bash

运行

复制代码
docker run -it --name test --rm --network none busybox
  • --network none:指定容器使用 none 网络模式
    • 底层核心动作
      1. 创建独立的网络命名空间
      2. 仅在命名空间内创建 lo 回环接口
      3. 不创建任何 veth pair 虚拟网卡
      4. 不添加任何路由规则和 iptables 规则
  • --rm:容器退出后自动删除
    • 底层动作:在容器的元数据中设置AutoRemove: true,容器退出时 Docker 自动清理其文件系统和网络资源
坑点(Gotchas)

⚠️ none 模式下容器无法访问外网,也无法被其他容器访问⚠️ 即使后续通过docker network connect连接到其他网络,容器也不会自动获取 DNS 配置,需要手动添加

企业级生产应用
  • 用于运行离线计算任务(如数据加密、哈希计算)
  • 用于存储敏感数据(如密码、密钥),防止数据泄露
  • 用于测试无网络环境下的应用行为
课后防宕机指南(Troubleshooting)
  • 问题:容器无法连接到任何网络,包括其他容器
  • 排查思路
    1. 执行docker inspect test | grep NetworkMode确认容器是否使用了 none 模式
    2. 若需要网络,执行docker network connect bridge test将容器连接到 bridge 网络
    3. 手动添加 DNS 配置:docker exec test echo "nameserver 8.8.8.8" > /etc/resolv.conf

5.4 Docker 的自定义网络

Docker 提供了三种自定义网络驱动:

  • bridge:类似默认 bridge 网络,但增加了 DNS 解析和细粒度隔离功能
  • overlay:用于创建跨主机的 Swarm 集群网络
  • macvlan:用于创建跨主机的二层网络,性能接近物理网卡

生产环境建议:使用自定义网络控制容器间通信,自定义网络支持自动 DNS 解析容器名称到 IP 地址。

5.4.1 自定义桥接网络

建立自定义网络时,默认使用桥接模式。

原命令与输出

bash 复制代码
[root@docker ~]# docker network create my_net1
f2aae5ce8ce43e8d1ca80c2324d38483c2512d9fb17b6ba60d05561d6093f4c4
bash 复制代码
[root@docker ~]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
2a93d6859680   bridge    bridge    local
bridge         bridge    local
4d81ddd9ed10   host      host      local
local
f2aae5ce8ce4   my_net1   bridge    local
my_net1        bridge
8c8c95f16b68   none      null      local
none          null      local
bash 复制代码
[root@docker ~]# ifconfig
br-f2aae5ce8ce4: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.18.0.1 netmask 255.255.0.0 broadcast 172.18.255.255
ether 02:42:70:57:f2:82 txqueuelen 0 (Ethernet)
RX packets 21264 bytes 1497364 (1.4 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 27359 bytes 215202367 (205.2 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
inet6 fe80::42:5fff:fee2:346c prefixlen 64 scopeid 0x20<link>
ether 02:42:5f:e2:34:6c txqueuelen 0 (Ethernet)
RX packets 21264 bytes 1497364 (1.4 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 27359 bytes 215202367 (205.2 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

桥接网络支持自定义子网和网关:

bash 复制代码
[root@docker ~]# docker network create my_net2 --subnet 192.168.0.0/24 --gateway 192.168.0.100
7e77cd2e44c64ff3121a1f1e0395849453f8d524d24b915672da265615e0e4f9
bash 复制代码
[root@docker ~]# docker network inspect my_net2
[
{
"Name": "my_net2",
"Id": "7e77cd2e44c64ff3121a1f1e0395849453f8d524d24b915672da265615e0e4f9",
"Created": "2024-08-17T17:05:19.167808342+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "192.168.0.0/24",
"Gateway": "192.168.0.100"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]

生活类比

自定义桥接网络就像小区里的独立单元楼

  • 每个单元楼有自己的门禁系统(网络隔离)
  • 同一单元楼的住户可以通过房间号(容器名称)互相找到对方
  • 不同单元楼的住户默认不能互相访问
  • 你可以自己决定单元楼的门牌号范围(自定义子网)和单元楼的入口地址(自定义网关)
核心代码逐行解析(Line-by-Line Breakdown)
复制代码
docker network create my_net2 --subnet 192.168.0.0/24 --gateway 192.168.0.100
  • docker network create:创建自定义网络
    • 底层动作:在 Docker 的元数据库中注册新网络,创建对应的 Linux 网桥(br-<网络 ID 前 12 位>)
  • my_net2:自定义网络名称
  • --subnet 192.168.0.0/24:指定网络的子网为 192.168.0.0/24
    • 底层动作:设置 IPAM(IP 地址管理)的子网配置,容器将从这个子网中分配 IP 地址
  • --gateway 192.168.0.100:指定网络的网关为 192.168.0.100
    • 底层动作:将 Linux 网桥br-7e77cd2e44c6的 IP 地址设置为 192.168.0.100,作为该网络中所有容器的默认网关
坑点(Gotchas)

⚠️ 自定义子网不能与宿主机的物理网络子网冲突,否则会导致网络不通⚠️ 网关地址必须属于指定的子网范围,否则 Docker 会报错⚠️ 删除自定义网络前,必须先断开所有连接到该网络的容器,否则删除失败

企业级生产应用
  • 生产环境中按业务模块划分不同的自定义网络,实现微服务的细粒度隔离
  • 例如:前端服务在frontend_net,后端 API 在backend_net,数据库在db_net
  • 仅允许必要的服务之间通过docker network connect进行通信
  • 千万级并发场景下,使用--opt com.docker.network.bridge.enable_icc=false禁用同一网络内的容器间通信,进一步提升安全性
课后防宕机指南(Troubleshooting)
  • 报错Error response from daemon: Pool overlaps with other one on this address space
  • 排查思路
    1. 执行docker network ls查看已存在的网络及其子网
    2. 选择一个未被使用的子网重新创建网络
    3. 若需要使用指定子网,先删除冲突的网络:docker network rm <冲突网络名称>

5.4.2 为什么要自定义桥接

多容器之间如何互访?通过ip可以,但是有什么问题?

多容器之间可以通过 IP 互访,但存在严重问题:容器 IP 是动态分配的,重启后会变化。

原命令与输出

bash 复制代码
[root@docker ~]# docker run -d --name web1 nginx
d5da7eaa913fa6cdd2aa9a50561042084eca078c114424cb118c57eeac473424
bash 复制代码
[root@docker ~]# docker run -d --name web2 nginx
0457a156b02256915d4b42f6cc52ea71b18cf9074ce550c886f206fef60dfae5
bash 复制代码
[root@docker ~]# docker inspect web1
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"MacAddress": "02:42:ac:11:00:03",
"DriverOpts": null,
"NetworkID": "2a93d6859680b45eae97e5f6232c3b8e070b1ec3d01852b147d2e1385034bce5",
"EndpointID": "4d54b12aeb2d857a6e025ee220741cbb3ef1022848d58057b2aab544bd3a4685",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2", #注意ip信息
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"DNSNames": null
}
}
bash 复制代码
[root@docker ~]# docker inspect web1
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"MacAddress": "02:42:ac:11:00:03",
"DriverOpts": null,
"NetworkID": "2a93d6859680b45eae97e5f6232c3b8e070b1ec3d01852b147d2e1385034bce5",
"EndpointID": "4d54b12aeb2d857a6e025ee220741cbb3ef1022848d58057b2aab544bd3a4685",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.3", #注意ip信息
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"DNSNames": null
}
}

关闭容器后重启,调换启动顺序:

bash 复制代码
[root@docker ~]# docker stop web1 web2
web1
web2
bash 复制代码
[root@docker ~]# docker start web2
web2
bash 复制代码
[root@docker ~]# docker start web1
web1

docker引擎在分配ip时时根据容器启动顺序分配到,谁先启动谁用,是动态变更的

多容器互访用ip很显然不是很靠谱,那么多容器访问一般使用容器的名字访问更加稳定

docker原生网络是不支持dns解析的,自定义网络中内嵌了dns

现象 :容器 IP 发生了颠倒(web2 变成 172.17.0.2,web1 变成 172.17.0.3)。原因:Docker 引擎按容器启动顺序分配 IP,先启动的容器先获得 IP,IP 是动态变更的。

解决方案 :使用容器名称进行通信,更加稳定。但默认 bridge 网络不支持 DNS 解析,自定义网络中内嵌了 DNS 服务器。

bash 复制代码
[root@docker ~]# docker run -d --network my_net1 --name web nginx
d9ed01850f7aae35eb1ca3e2c73ff2f83d13c255d4f68416a39949ebb8ec699f
bash 复制代码
[root@docker ~]# docker run -it --network my_net1 --name test busybox
/ # ping web
PING web (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.197 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.096 ms
64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.087 ms

注意:不同的自定义网络之间默认不能通信。

  • RHEL7 中使用 iptables 实现网络隔离
  • RHEL9 中使用 nftables 实现网络隔离
  • 执行nft list ruleset可以查看详细的网络隔离策略

生活类比

默认 bridge 网络就像没有通讯录的小区

  • 你只能通过门牌号(IP)找到别人,但门牌号会经常变
  • 今天住在 301 的人,明天可能就搬到 302 了

自定义桥接网络就像有通讯录的小区

  • 每个人都有固定的名字(容器名称)
  • 你只需要记住名字,小区物业(内嵌 DNS)会自动帮你找到对应的门牌号
  • 不同小区之间没有通讯录,默认不能互相联系
核心代码逐行解析(Line-by-Line Breakdown)

bash

运行

复制代码
docker run -it --network my_net1 --name test busybox
  • --network my_net1:将容器连接到自定义网络my_net1
    • 底层核心动作
      1. 创建 veth pair 虚拟网卡对,一端连接到br-f2aae5ce8ce4网桥,另一端放入容器的网络命名空间
      2. 为容器分配my_net1子网中的 IP 地址(172.18.0.x)
      3. 将容器的默认网关设置为br-f2aae5ce8ce4的 IP 地址(172.18.0.1)
      4. 配置容器的 DNS 服务器为 Docker 内嵌的 DNS 服务器(127.0.0.11)
      5. 在 Docker 的 DNS 服务器中注册容器名称test与 IP 地址的映射关系
坑点(Gotchas)

⚠️ 默认 bridge 网络不支持 DNS 解析,即使给容器命名,也无法通过名称 ping 通⚠️ 容器名称在同一网络中必须唯一,否则会出现 DNS 解析冲突⚠️ 自定义网络的 DNS 解析仅在同一网络内有效,跨网络无法通过名称解析

企业级生产应用
  • 所有微服务都部署在自定义网络中,通过服务名称进行通信
  • 配合 Docker Compose 或 Kubernetes 使用,实现服务发现和负载均衡
  • 千万级并发场景下,使用--dns参数指定外部 DNS 服务器,减轻 Docker 内嵌 DNS 的压力
课后防宕机指南(Troubleshooting)
  • 问题:同一自定义网络中的容器无法通过名称 ping 通
  • 排查思路
    1. 执行docker exec test cat /etc/resolv.conf确认 DNS 服务器是否为 127.0.0.11
    2. 执行docker exec test nslookup web测试 DNS 解析是否正常
    3. 检查容器名称是否正确,是否在同一网络中
    4. 重启 Docker 服务:systemctl restart docker

5.4.3 如何让不同的自定义网络互通

**左边单网卡,右边双网卡**

就是给单网卡在连接目标网络,就行了

不同自定义网络默认隔离,实现互通的方法是:给需要通信的容器添加第二个网卡,连接到目标网络。

原命令与输出

bash 复制代码
[root@docker ~]# docker run -d --name web1 --network my_net1 nginx
bash 复制代码
[root@docker ~]# docker run -it --name test --network my_net2 busybox
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:C0:A8:00:01
eth0
inet addr:192.168.0.1 Bcast:192.168.0.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:36 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:5244 (5.1 KiB) TX bytes:0 (0.0 B)

lo Link encap:Local Loopback
1o
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
bash 复制代码
/ # ping 172.18.0.2
PING 172.18.0.2 (172.18.0.2): 56 data bytes
^C
# 无法ping通
bash 复制代码
[root@docker ~]# docker network connect my_net1 test
# 将test容器连接到my_net1网络
bash 复制代码
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:C0:A8:00:01
eth0
inet addr:192.168.0.1 Bcast:192.168.0.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:45 errors:0 dropped:0 overruns:0 frame:0
TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:5879 (5.7 KiB) TX bytes:602 (602.0 B)

eth1 Link encap:Ethernet HWaddr 02:42:AC:12:00:03
inet addr:172.18.0.3 Bcast:172.18.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:15 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:2016 (1.9 KiB) TX bytes:0 (0.0 B)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:4 errors:0 dropped:0 overruns:0 frame:0
TX packets:4 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:212 (212.0 B) TX bytes:212 (212.0 B)

现象:test 容器现在有两个网卡(eth0 和 eth1),分别连接到 my_net2 和 my_net1 网络,可以 ping 通 web1 容器。


生活类比

不同自定义网络互通就像给一个人办两个小区的门禁卡

  • 这个人原本只能进入 A 小区(my_net2)
  • 给他办一张 B 小区(my_net1)的门禁卡后,他就可以同时进入 A 和 B 两个小区
  • 但其他没有 B 小区门禁卡的人,仍然不能进入 B 小区
核心代码逐行解析(Line-by-Line Breakdown)

bash

运行

bash 复制代码
docker network connect my_net1 test
  • docker network connect:将容器连接到指定网络
    • 底层核心动作
      1. 创建新的 veth pair 虚拟网卡对
      2. 将 veth pair 的一端连接到my_net1对应的网桥br-f2aae5ce8ce4
      3. 将 veth pair 的另一端放入test容器的网络命名空间,命名为eth1
      4. eth1分配my_net1子网中的 IP 地址(172.18.0.3)
      5. test容器的路由表中添加my_net1子网的路由规则
      6. 在 Docker 的 DNS 服务器中更新test容器的 IP 地址映射
坑点(Gotchas)

⚠️ 容器最多可以连接到 32 个网络⚠️ 连接多个网络的容器会有多个默认网关,可能导致路由冲突⚠️ 断开网络时,需要手动指定网络名称:docker network disconnect my_net1 test

企业级生产应用
  • 用于实现跨业务模块的有限通信
  • 例如:API 网关需要同时连接前端网络和后端网络,数据库只允许后端网络访问
  • 千万级并发场景下,避免给单个容器连接过多网络,否则会增加网络延迟和故障风险
课后防宕机指南(Troubleshooting)
  • 问题:容器连接多个网络后,无法访问外网
  • 排查思路
    1. 执行docker exec test route -n查看路由表,确认默认网关是否正确
    2. 若有多个默认网关,删除多余的默认网关:docker exec test route del default gw <错误网关地址>
    3. 保留连接外网的网络的默认网关

5.4.4 joined 容器网络

joined 容器是一种特殊的网络模式:多个容器共享同一个网络栈。

现在开启一个容器,有网络栈;开启第二个容器时候,我让他也使用这个网络栈

Joined容器一种较为特别的网络模式,•在容器创建时使用--network=container:vm1指定。(vm1指定的是运行的容器名)

处于这个模式下的 Docker 容器会共享一个网络栈,这样两个容器之间可以使用localhost高效快速通信。

原命令与输出

因为两个容器共用了同一个网络栈;

两个业务结合的特别紧的时候,比如web和数据库;让他走回环会更好一点

bash 复制代码
[root@docker ~]# docker run -it --rm --network container:web1 busybox
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:12:00:02
eth0
inet addr:172.18.0.2 Bcast:172.18.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:28 errors:0 dropped:0 overruns:0 frame:0
TX packets:4 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:3046 (2.9 KiB) TX bytes:280 (280.0 B)

lo Link encap:Local Loopback
1o
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
bash 复制代码
[root@docker ~]# docker run -it --rm --network container:web1 centos:7
[root@efae66874371 /]# curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>

核心原理 :处于 joined 模式的容器共享同一个网络命名空间,它们的 IP 地址、端口、路由表完全相同,可以通过localhost互相通信。


生活类比

joined 容器网络就像合租一套房子

  • 所有合租的人共用同一个门牌号(IP 地址)和同一个大门(网络接口)
  • 你可以直接敲室友的房门(localhost: 端口)找到他,不需要出门
  • 但如果一个人占用了客厅(某个端口),其他人就不能再用了
核心代码逐行解析(Line-by-Line Breakdown)
bash 复制代码
docker run -it --rm --network container:web1 centos:7
  • --network container:web1:指定容器使用web1容器的网络栈
    • 底层核心动作
      1. 不创建新的网络命名空间
      2. 直接加入web1容器的网络命名空间
      3. 共享web1容器的所有网络接口、IP 地址、端口和路由表
      4. 两个容器之间的通信通过 lo 回环接口进行,不需要经过网络栈
  • curl localhost:访问本地的 80 端口
    • 底层动作:数据包直接通过 lo 回环接口发送到web1容器的 nginx 进程,延迟 < 1ms
坑点(Gotchas)

⚠️ joined 模式下,所有容器共享端口,不能有端口冲突⚠️ 如果被共享网络的容器(web1)停止,所有 joined 容器的网络都会失效⚠️ joined 模式下,容器的网络隔离性完全丧失,存在安全风险

企业级生产应用
  • 用于紧密耦合的服务,如 Web 服务器和应用服务器、应用服务器和数据库
  • 消除了容器间通信的网络开销,性能接近本地进程间通信
  • 千万级并发场景下,用于部署 Sidecar 模式的服务(如日志收集、监控代理)
课后防宕机指南(Troubleshooting)
  • 报错Error response from daemon: No such container: web1
  • 排查思路
    1. 执行docker ps -a确认web1容器是否存在且正在运行
    2. web1容器已停止,先启动web1容器:docker start web1
    3. 检查容器名称是否拼写正确

5.4.5 joined 网络示例演示

一、导入镜像
bash 复制代码
[root@docker-node1 ~]# docker load -i phpmyadmin-latest.tar.gz
[root@docker-node1 ~]# docker load -i mysql-8.0.tar
二、运行 phpMyAdmin 容器

bash

运行

bash 复制代码
[root@docker-node1 ~]# docker run -d --name phpmyadmin -e PMA_ARBITRARY=1 -p 80:80 phpmyadmin:latest
三、运行 MySQL 容器
bash 复制代码
[root@docker-node1 ~]# docker run -d --name mysql -e MYSQL_ROOT_PASSWORD='lee' --network container:phpmyadmin mysql:8.0
bash 复制代码
#运行phpmysqladmin
[root@docker ~]# docker run -d --name mysqladmin --network my_net1 \
-e PMA_ARBITRARY=1 \                #在web页面中可以手动输入数据库地址和端口;在外部直接指定变量
-p 80:80 phpmyadmin:latest
四、访问 phpMyAdmin

利用 joined 模式部署 phpMyAdmin 管理 MySQL 数据库。

bash 复制代码
http://172.25.254.10

登录信息

  • 服务器:localhost:3306
  • 用户名:root
  • 密码:lee

原理 :MySQL 容器和 phpMyAdmin 容器共享同一个网络栈,所以 phpMyAdmin 可以通过localhost:3306访问 MySQL 数据库。


生活类比

这个部署方式就像超市和仓库开在同一个商铺里

  • 超市(phpMyAdmin)面向顾客(用户),有临街的门面(80 端口)
  • 仓库(MySQL)在超市里面,不需要单独的门面
  • 超市员工可以直接从仓库取货(localhost:3306),不需要出门
核心代码逐行解析(Line-by-Line Breakdown)

bash

运行

复制代码
docker run -d --name phpmyadmin -e PMA_ARBITRARY=1 -p 80:80 phpmyadmin:latest
  • -e PMA_ARBITRARY=1:设置环境变量PMA_ARBITRARY为 1
    • 底层动作:允许 phpMyAdmin 在登录页面手动输入任意 MySQL 服务器的地址和端口
  • -p 80:80:将宿主机的 80 端口映射到 phpMyAdmin 容器的 80 端口

bash

运行

复制代码
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD='lee' --network container:phpmyadmin mysql:8.0
  • -e MYSQL_ROOT_PASSWORD='lee':设置 MySQL 的 root 用户密码为lee
    • 底层动作:在 MySQL 容器启动时,自动创建 root 用户并设置密码
  • --network container:phpmyadmin:让 MySQL 容器共享 phpMyAdmin 容器的网络栈
    • 底层动作:MySQL 的 3306 端口直接绑定在 phpMyAdmin 容器的网络接口上
坑点(Gotchas)

⚠️ MySQL 容器必须在 phpMyAdmin 容器之后启动,否则会因为网络命名空间不存在而启动失败⚠️ 不要在生产环境中使用PMA_ARBITRARY=1,这会允许任何人访问任意 MySQL 服务器⚠️ 生产环境中应该使用PMA_HOST=mysql指定固定的 MySQL 服务器地址

企业级生产应用
  • 用于快速部署开发和测试环境的数据库管理工具
  • 生产环境中建议使用独立的数据库服务,不要将数据库和 Web 应用部署在同一个容器网络中
  • 千万级并发场景下,数据库应该部署在物理机或专用虚拟机上,不要使用容器化部署
课后防宕机指南(Troubleshooting)
  • 问题:phpMyAdmin 无法连接到 MySQL 数据库,提示 "Connection refused"
  • 排查思路
    1. 执行docker ps确认 MySQL 容器是否正在运行
    2. 执行docker exec phpmyadmin netstat -ant | grep 3306确认 3306 端口是否在监听
    3. 检查 MySQL 的 root 密码是否正确
    4. 确认服务器地址是否填写为localhost:3306

5.5 容器内外网的访问

5.5.1 容器访问外网

NAT的火墙策略

NAT有几种:

DNAT:把目的地址转换

SNAT:图中的;原地址转换

  • 在rhel7中,docker访问外网是通过iptables添加地址伪装策略来完成容器网文外网

  • 在rhel7之后的版本中通过nftables添加地址伪装来访问外网

容器访问外网通过宿主机的 SNAT(源地址转换)实现。

原命令与输出

bash 复制代码
[root@docker-nodel~]# iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target
target prot opt source destination

Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
target
target
target

Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE 6 -- 172.17.0.2 172.17.0.2 tcp dpt:80
# 内网访问外网策略

Chain DOCKER (0 references)
target prot opt source destination
DNAT 6 -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80

火墙策略

核心原理

  • 容器发送的数据包首先到达 docker0 网桥
  • 宿主机的内核根据路由表将数据包转发到 eth0 网卡
  • iptables 的 POSTROUTING 链中的 MASQUERADE 规则将数据包的源 IP 地址转换为宿主机的 eth0 网卡 IP 地址
  • 外网服务器返回的数据包到达宿主机后,内核根据连接跟踪表将目的 IP 地址转换回容器的 IP 地址
  • 数据包通过 docker0 网桥转发到对应的容器

注意

  • RHEL7 中使用 iptables 实现 SNAT
  • RHEL9 中使用 nftables 实现 SNAT
  • 必须开启内核的 IP 转发功能:net.ipv4.ip_forward = 1

生活类比

容器访问外网就像小区住户寄快递

  • 住户(容器)把快递(数据包)交给小区保安(docker0 网桥)
  • 保安把快递上的寄件人地址改成小区的公网地址(SNAT)
  • 快递员(外网服务器)把回件寄到小区的公网地址
  • 保安根据快递上的备注(连接跟踪表)把回件交给对应的住户
核心代码逐行解析(Line-by-Line Breakdown)
bash 复制代码
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
  • 这是 Docker 自动添加的 SNAT 规则
  • 底层动作:将所有来自 172.17.0.0/16 子网(默认 bridge 网络)的数据包的源 IP 地址转换为宿主机的出口网卡 IP 地址
  • MASQUERADE:动态地址伪装,会自动使用出口网卡的 IP 地址,适合宿主机 IP 不固定的场景
坑点(Gotchas)

⚠️ 如果关闭了内核的 IP 转发功能,容器将无法访问外网⚠️ 如果删除了 POSTROUTING 链中的 MASQUERADE 规则,容器将无法访问外网⚠️ 宿主机的防火墙必须允许转发流量,否则容器无法访问外网

企业级生产应用
  • 千万级并发场景下,使用SNAT替代MASQUERADE,指定固定的源 IP 地址,性能提升 10%-20%
    • 配置方法:iptables -t nat -I POSTROUTING -s 172.17.0.0/16 -j SNAT --to-source 宿主机公网IP
  • 生产环境中应该限制容器的外网访问权限,只允许访问必要的外部服务
课后防宕机指南(Troubleshooting)
  • 问题:容器无法访问外网,但宿主机可以
  • 排查思路
    1. 执行sysctl net.ipv4.ip_forward确认 IP 转发是否开启
    2. 执行iptables -t nat -nL确认 POSTROUTING 链中是否有 MASQUERADE 规则
    3. 执行iptables -nL FORWARD确认 FORWARD 链是否允许转发流量
    4. 检查宿主机的 DNS 配置是否正确

5.5.2 外网访问 docker 容器

外网访问容器通过DNAT(目的地址转换)+ docker-proxy双通道实现。

端口映射 -p 本机端口:容器端口来暴漏端口从而达到访问效果

bash 复制代码
[root@docker ~]# docker run -d --name webserver -p 80:80 nginx
bash 复制代码
[root@docker ~]# ps ax
133986 s1 0:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.17.0.2 -container-port 80
133995 s1 0:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 80 -container-ip 172.17.0.2 -container-port 80
134031 s1 0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id cae79497a01c0b8c488c7597b43de4a43f361f21a398ff423b4504c0905db143 -address /run/containerd/containerd.sock
134059 Ss 0:00 nginx: master process nginx -g daemon off;
134099 S 0:00 nginx: worker process
134100 S 0:00 nginx: worker process
bash 复制代码
[root@docker ~]# iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target prot opt source destination

Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE 6 -- 172.17.0.2 172.17.0.2 tcp dpt:80

Chain DOCKER (0 references)
target prot opt source destination
DNAT 6 -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80

!NOTE

docker-proxy和dnat在容器建立端口映射后都会开启,那个传输速录高走那个

核心原理

  • DNAT 通道:内核在 PREROUTING 链中将访问宿主机 80 端口的数据包的目的 IP 地址转换为容器的 IP 地址(172.17.0.2),然后转发到容器
  • docker-proxy 通道:docker-proxy 进程监听宿主机的 80 端口,将接收到的数据包转发到容器的 80 端口
  • 两个通道同时工作,哪个传输速度高就用哪个

生活类比

外网访问容器就像小区的快递代收点

  • 快递员(外网用户)把快递寄到小区的代收点地址(宿主机 IP: 端口)
  • 代收点有两个工作人员:
    • 一个是自动化分拣机(DNAT),直接把快递送到住户家里(容器)
    • 另一个是人工快递员(docker-proxy),把快递送到住户家里
  • 自动化分拣机速度更快,所以大部分快递都走自动化通道
核心代码逐行解析(Line-by-Line Breakdown)

bash

运行

bash 复制代码
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
  • 这是 Docker 自动添加的 DNAT 规则
  • 底层动作:将所有访问宿主机 80 端口的 TCP 数据包的目的 IP 地址和端口转换为 172.17.0.2:80
  • 该规则在 PREROUTING 链中执行,在路由判断之前完成地址转换

bash

运行

bash 复制代码
/usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.17.0.2 -container-port 80
  • docker-proxy 进程
  • 底层动作:创建一个 TCP 套接字,监听 0.0.0.0:80,将接收到的所有数据转发到 172.17.0.2:80
  • 主要用于处理宿主机本地访问容器的流量(localhost:80
坑点(Gotchas)

⚠️ docker-proxy 进程会占用宿主机的端口,即使容器停止,残留的 docker-proxy 进程也会导致端口无法使用⚠️ 若宿主机有多个网卡,-p 80:80会监听所有网卡,存在安全风险⚠️ 生产环境中建议禁用 docker-proxy,仅使用 DNAT 通道

企业级生产应用
  • 千万级并发场景下,必须禁用 docker-proxy ,消除用户态进程的转发开销
    • 配置方法:在/etc/docker/daemon.json中添加"userland-proxy": false
  • 使用-p 宿主机IP:宿主机端口:容器端口的格式,只监听指定的内网网卡
  • 对于高流量服务,使用 host 模式替代端口映射
课后防宕机指南(Troubleshooting)
  • 问题:外网无法访问容器的端口,但宿主机可以访问
  • 排查思路
    1. 执行iptables -t nat -nL确认 DOCKER 链中是否有对应的 DNAT 规则
    2. 执行iptables -nL DOCKER-USER确认是否有拒绝该端口的规则
    3. 检查宿主机的防火墙是否开放了该端口
    4. 检查云服务商的安全组是否开放了该端口

5.6 Docker 跨主机网络

一台主机之间通信,用jiiond等

在生产环境中,我们的容器不可能都在同一个系统中,所以需要容器具备跨主机通信 的能力

  • 跨主机网络解决方案

  • docker原生的overlay和macvlan

  • 第三方的flannel、weave、calico

  • 众多网络方案是如何与docker集成在一起的

  • libnetwork docker容器网络库

  • CNM (Container Network Model)这个模型对容器网络进行了抽象

生产环境中,容器不可能都运行在同一台主机上,需要实现跨主机容器通信。

跨主机网络解决方案

  • Docker 原生:overlaymacvlan
  • 第三方:flannelweavecalico

所有网络方案都通过libnetwork(Docker 容器网络库)和CNM(Container Network Model)与 Docker 集成。

5.6.1 CNM (Container Network Model)

CNM 将容器网络抽象为三类组件:

  1. Sandbox:容器网络栈,包含容器接口、DNS、路由表(对应 Linux 的 Network Namespace)
  2. Endpoint:将 Sandbox 接入 Network(对应 veth pair)
  3. Network:包含一组 Endpoint,同一 Network 的 Endpoint 可以通信(对应 Linux 网桥、macvlan 等)

;外部的网络插件


5.6.2 macvlan 网络方式实现跨主机通信

macvlan网络方式

  • Linux kernel提供的一种网卡虚拟化技术。内核提供

  • 无需Linux bridge,直接使用物理接口,性能极好

  • 容器的接口直接与主机网卡连接,无需NAT或端口映射。

  • macvlan会独占主机网卡,但可以使用vlan子接口实现多macvlan网络

  • vlan可以将物理二层网络划分为4094个逻辑网络,彼此隔离,vlan id取值为1~4094

macvlan网络间的隔离和连通

  • macvlan网络在二层上是隔离的,所以不同macvlan网络的容器是不能通信的

  • 可以在三层上通过网关将macvlan网 络连通起来

  • docker本身不做任何限制,像传统vlan网络那样管理即可

macvlan 是 Linux 内核提供的网卡虚拟化技术,无需 Linux 网桥,直接使用物理接口,性能极好。

原命令与输出

一、设定硬件

两台主机添加网卡并设定为host-only

两个主机跨主机得直连;

两台主机各添加一块网卡,设置为仅主机模式(host-only)

二. 开启新加网卡的混杂模式

```bash

bash 复制代码
[root@docker-node1 ~]# ip a s eth1
54: eth1: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:21:54:d9 brd ff:ff:ff:ff:ff:ff
    altname enp19s0
    altname ens224

[root@docker-node2 ~]# ip a s eth1
4: eth1: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:cf:83:84 brd ff:ff:ff:ff:ff:ff
    altname enp19s0
    altname ens224

```

三.配置docker自建网络```

bash 复制代码
[root@docker-node1 ~]# docker network create  \
-d macvlan \
--subnet 1.1.1.0/24 \
--gateway 1.1.1.1 \
-o parent=eth1 lee
0cad05f438fbae92c0bb5b7119de158c9d81cfd3e588bfc14f980593447a8f9c
[root@docker-node1 ~]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
5b1ea5c41484   bridge    bridge    local
6c9d8cbdb795   host      host      local
0cad05f438fb   lee       macvlan   local
dbc0eb18cd23   none      null      local

[root@docker-node2 ~]# docker network create  \
-d macvlan \
--subnet 1.1.1.0/24 \
--gateway 1.1.1.1 \
-o parent=eth1 lee
2bf081fa613f638ce4dd6e62b2d0f4e9b91a8a38148b973e0255fd9349cd1388
[root@docker-node2 ~]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
1b261a818d3b   bridge    bridge    local
ab21ae50e215   host      host      local
2bf081fa613f   lee       macvlan   local
a3da18725c89   none      null      local
```

四.测试跨主机docker是否可以通信```bash

bash 复制代码
[root@docker-node1 ~]#  docker run  -it --name busybox --rm --network lee --ip 1.1.1.100 --rm busybox
/ # 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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
55: eth0@if54: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 66:46:be:d5:ca:6a brd ff:ff:ff:ff:ff:ff
    inet 1.1.1.100/24 brd 1.1.1.255 scope global eth0
       valid_lft forever preferred_lft forever


[root@docker-node2 ~]# docker run  -it --name busybox --rm --network lee --ip 1.1.1.200 --rm busybox
/ # 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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
5: eth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 96:d7:cb:bc:56:79 brd ff:ff:ff:ff:ff:ff
    inet 1.1.1.200/24 brd 1.1.1.255 scope global eth0
       valid_lft forever preferred_lft forever
/ # ping 1.1.1.100                            #  在node2开启的容器中ping node1主机中开启容器的地址
PING 1.1.1.100 (1.1.1.100): 56 data bytes
64 bytes from 1.1.1.100: seq=0 ttl=64 time=0.276 ms
64 bytes from 1.1.1.100: seq=1 ttl=64 time=0.406 ms
64 bytes from 1.1.1.100: seq=2 ttl=64 time=0.427 ms

1.在两台docker主机上各添加一块网卡,打开网卡混杂模式:

```bash

bash 复制代码
[root@docker ~]# ip link set eth1 promisc on
[root@docker ~]# ip link set up eth1
[root@docker ~]# ifconfig  eth1
eth1: flags=4419<UP,BROADCAST,RUNNING,PROMISC,MULTICAST>  mtu 1500
        ether 00:0c:29:ec:fc:dd  txqueuelen 1000  (Ethernet)
        RX packets 83  bytes 8696 (8.4 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

```

> [!NOTE]

>

> eth1这款网卡在vmware中要设定为仅主机模式

2.添加macvlan网路```

bash 复制代码
[root@docker ~]# docker network create  \
-d macvlan \
--subnet 1.1.1.0/24 \
--gateway 1.1.1.1 \
-o parent=eth1 macvlan1
```

3.测试

```
#在docker-node1中
[root@docker ~]# docker run  -it --name busybox --network macvlan1 --ip 1.1.1.100 --rm busybox
/ # ping 1.1.1.200


[root@docker-node2 ~]# docker run  -it --name busybox --network macvlan1 --ip 1.1.1.200 --rm busybox
/ #

1. 通俗生活类比

咱们用公司局域网的场景,把 Macvlan 和默认 Bridge 网络的区别讲得一目了然:

  • Docker 默认 Bridge 网络 = 公司里的「部门内网」:每个部门有自己的路由器,部门内的电脑能互相通信,但不同部门的电脑不能直接通信,必须通过公司总路由器做 NAT 转发,速度慢、有额外开销。
  • Macvlan 网络 = 给每个容器直接接一根公司的物理网线 :容器和宿主机一样,成为公司局域网里的独立设备,有自己的 MAC 地址和 IP 地址,不同宿主机上的容器就像同一层楼的不同电脑,直接就能 ping 通,不需要经过任何 NAT 转发,速度几乎和物理网卡一样快。
  • 网卡混杂模式 = 把宿主机的物理网卡调成「快递代收点模式」:原本网卡只收写着自己名字的快递,开启混杂模式后,会收下所有快递,再转交给对应容器的虚拟子接口。

2. 企业生产环境实际应用场景

Macvlan 是 Docker 原生性能最高的跨主机网络方案,适合以下核心场景:

  1. 高性能数据库集群:MySQL、PostgreSQL、Redis 集群对网络延迟要求极高,Macvlan 几乎零网络损耗,能提供接近物理网卡的性能。
  2. 传统企业应用迁移:很多老的企业应用依赖固定 IP 地址,Macvlan 可以给容器分配固定的局域网 IP,不需要修改应用代码就能直接迁移到 Docker。
  3. 网络监控 / 抓包场景:Macvlan 容器可以直接接收物理网卡的所有数据包,适合部署网络监控、流量分析工具。
  4. 需要二层网络互通的场景:比如需要组播、广播的应用,默认 Bridge 网络不支持跨主机广播,Macvlan 可以完美支持。

3. 核心命令逐行解释

我把你的实验分成3 个核心步骤,逐行讲清每一步的底层逻辑和为什么要这么做:

步骤 1:开启物理网卡混杂模式(Macvlan 的前置要求)

bash

运行

复制代码
# 开启eth1网卡的混杂模式
[root@docker ~]# ip link set eth1 promisc on
# 启用eth1网卡
[root@docker ~]# ip link set up eth1
# 验证混杂模式是否开启
[root@docker ~]# ip a s eth1
54: eth1: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
  • 核心原理:Macvlan 是基于物理网卡创建多个虚拟子接口,每个子接口有自己的 MAC 地址。物理网卡默认只接收目标 MAC 是自己的数据包,开启混杂模式后,会接收所有经过的数据包,再转发给对应的虚拟子接口。
  • 注意:VMware 虚拟机里的网卡必须先设置为「仅主机模式」或「桥接模式」,NAT 模式下无法开启混杂模式。
步骤 2:两台主机创建相同配置的 Macvlan 网络

bash

运行

复制代码
# 在node1和node2上执行完全相同的命令
[root@docker-node1 ~]# docker network create \
-d macvlan \                  # 指定网络驱动为macvlan
--subnet 1.1.1.0/24 \         # 容器使用的子网,两台主机必须完全一致
--gateway 1.1.1.1 \           # 网关地址,两台主机必须完全一致
-o parent=eth1 \              # 绑定的物理父网卡,两台主机必须一致
lee                           # 自定义网络名称
  • 核心原理 :Macvlan 是local scope(本地范围)的网络,每个主机的 Macvlan 网络是独立的。只有两台主机配置完全相同的子网、网关、父网卡,才能让所有容器处于同一个二层广播域,实现跨主机直接通信。
  • 注意 :生产环境不要用1.1.1.0/24这种公网 IP 段,应该用私有 IP 段(192.168.x.x/10.x.x.x/172.16.x.x)。
步骤 3:测试跨主机容器通信

bash

运行

复制代码
# node1启动容器,指定固定IP 1.1.1.100
[root@docker-node1 ~]# docker run -it --name busybox --rm --network lee --ip 1.1.1.100 busybox
/ # ip a
55: eth0@if54: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    inet 1.1.1.100/24 brd 1.1.1.255 scope global eth0

# node2启动容器,指定固定IP 1.1.1.200
[root@docker-node2 ~]# docker run -it --name busybox --rm --network lee --ip 1.1.1.200 busybox
/ # ping 1.1.1.100
PING 1.1.1.100 (1.1.1.100): 56 data bytes
64 bytes from 1.1.1.100: seq=0 ttl=64 time=0.276 ms
  • 核心原理:两个容器都直接接入了物理局域网,有自己独立的 MAC 和 IP,ping 包直接通过物理交换机转发,不需要经过 Docker 网桥、NAT,所以延迟极低(只有 0.2-0.4ms)。

4. 生产必知的 Macvlan 核心限制

  1. 容器无法直接和宿主机通信:物理网卡会拒绝接收发往自己 MAC 地址的数据包,所以容器 ping 宿主机 IP 会不通,反之亦然。
  2. IP 地址不能冲突:容器的 IP 必须和局域网内的其他设备(包括宿主机)不重复,否则会出现 IP 冲突。
  3. 不支持端口映射:容器直接暴露在物理网络中,不需要端口映射,直接用容器 IP + 端口就能访问。
  4. 只能跨同一局域网的主机:Macvlan 是二层网络技术,只能在同一个广播域内通信,跨网段需要路由器转发。

检验理解的小问题

如果我在 node1 的1.1.1.100容器里,ping node2 宿主机的 eth1 网卡 IP(假设是1.1.1.2),能 ping 通吗?为什么?


总结

Docker 网络是生产环境中最复杂也最容易出问题的部分。掌握不同网络模式的底层原理、适用场景和排错方法,是成为一名合格的云原生工程师的必备技能。

生产环境网络选型原则

  1. 优先使用自定义 bridge 网络,实现细粒度隔离和 DNS 解析
  2. 高流量服务使用 host 模式,消除 NAT 开销
  3. 跨主机通信优先使用 macvlan(性能最好)或 calico(功能最丰富)
  4. 禁用 docker-proxy,仅使用内核级的 DNAT 和 SNAT
  5. 按业务模块划分网络,最小化攻击面
相关推荐
桌面运维家1 小时前
服务器异常登录日志排查方法与安全防护实战
运维·服务器·安全
Flittly1 小时前
【日常小问】解决 Jenkins 部署 Spring Cloud 微服务到 Docker 容器启动失败的问题
运维·笔记·docker·微服务·jenkins
AC赳赳老秦1 小时前
故障自愈实战:用 OpenClaw 实现服务器日志自动化分析、根因定位、解决方案自动生成
大数据·运维·服务器·自动化·github·deepseek·openclaw
晓梦林1 小时前
Fuzzz靶场学习笔记
笔记·学习·安全·web安全
一只积极向上的小咸鱼1 小时前
Linux 下 Claude Code 配置文件位置总结
linux·运维·服务器
小则又沐风a1 小时前
Linux下的Git的上传(版本控制器)
linux·数据库·git
七牛云行业应用1 小时前
GPT-5.5 Instant vs Grok 4 完整对比【2026年5月最新】:哪个大模型更适合开发者?
人工智能·docker·github·ai实战·大模型部署·claude opus 4.7·api接入
烛衔溟1 小时前
TypeScript 接口实战 —— 处理复杂嵌套对象
linux·ubuntu·typescript
j_xxx404_1 小时前
Linux共享内存原理与实战:从内核到C++实现|附源码
linux·运维·开发语言·c++·人工智能