在深入理解 Docker 的宿主机网关之前,我们需要先理解网络中「网关」的基本概念。
在传统网络中,网关 (Gateway) 是连接两个不同网络的设备或节点。它就像是两个网络之间的「门」,负责在不同网络之间转发数据包。举个生活中的例子:
你家里的路由器就是一个网关
你的电脑(如 192.168.1.100)想访问互联网上的网站
数据包必须先发送到路由器(如 192.168.1.1)
路由器再将数据包转发到互联网
1 2 [你的电脑] ---> [家庭路由器/网关] ---> [互联网] 192.168.1.100 192.168.1.1 外部网络
由此可见,网关有如下作用:
路由转发 : 将数据包从一个网络转发到另一个网络
地址转换 : 进行 NAT (网络地址转换)
协议转换 : 在不同协议的网络之间转换
访问控制 : 控制哪些数据可以通过
Docker 网络架构中的网关 Docker 桥接网络 (Bridge Network) 当你使用 Docker 时,Docker 会创建一个虚拟网桥 (Virtual Bridge) ,默认名为 docker0。这个虚拟网桥的工作原理类似于物理交换机,它连接了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 宿主机 (Host OS) ┌─────────────────────────────────┐ │ │ │ 物理网卡 (eth0) │ │ IP: 192.168.0.10 │ │ ↕ │ │ ┌─────────────────┐ │ │ │ docker0 │ │ │ │ (虚拟网桥) │ │ │ │ 172.17.0.1 ←── 这是网关! │ │ └────────┬────────┘ │ │ │ │ │ ┌─────┴──────┐ │ │ │ │ │ │ ┌──┴──┐ ┌──┴──┐ │ │ │veth1│ │veth2│ │ │ └──┬──┘ └──┬──┘ │ └─────┼──────────┼────────────────┘ │ │ ┌─────┴──────────┴─────┐ │ │ ┌───────┴────────┐ ┌───────┴──────────┐ │ 容器 A │ │ 容器 B │ │ eth0 │ │ eth0 │ │ 172.17.0.2 │ │ 172.17.0.3 │ │ │ │ │ │ 网关: 172.17.0.1│ │ 网关: 172.17.0.1 │ └─────────────────┘ └──────────────────┘
网关 IP 的含义 Docker 网关 IP (172.17.0.1) 实际上是:
虚拟网桥 docker0 在容器网络中的 IP 地址
容器访问外部网络的出口
容器访问宿主机的入口
为什么网关可以访问到宿主机? 这是最关键的理解点。让我们一步步分解:
1. 网桥在宿主机上 docker0 虚拟网桥实际上是宿主机上的一个网络设备,你可以在宿主机上看到它:
1 2 3 4 5 6 7 8 ip addr show docker0
2. 网关既在容器网络中,也在宿主机网络中 docker0 网桥是一个特殊的存在,它同时属于两个「世界」:
在容器的视角 : 172.17.0.1 是网关,是通往外部的出口
在宿主机的视角 : 172.17.0.1 是本机的一个网络接口
这就像是一扇门:
从容器内看,门通往外面(宿主机)
从宿主机看,门就在自己家里
3. 数据包的流转过程 当容器访问 172.17.0.1 时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 步骤 1: 容器发送数据包 [容器 172.17.0.2] ---> 目标: 172.17.0.1 "我要访问网关" 步骤 2: 数据包通过 veth pair (虚拟网线) [容器的 eth0] ---> [veth1] ---> [docker0 网桥] 步骤 3: docker0 收到数据包 docker0 发现: "这个包是发给我的 (172.17.0.1)" 步骤 4: 包被宿主机的网络栈处理 由于 docker0 是宿主机上的设备,数据包实际上被宿主机的内核处理 步骤 5: 如果访问的是宿主机端口 (如 6379) 数据包被转发到宿主机上监听 6379 端口的服务
详细示例:容器访问宿主机上的 Redis 让我们通过一个完整的例子来理解:
场景设置 1 2 3 4 5 docker run -d -p 6379:6379 redis docker network inspect bridge
输出类似:
1 2 3 4 5 6 7 8 9 10 11 { "Name" : "bridge" , "IPAM" : { "Config" : [ { "Subnet" : "172.17.0.0/16" , "Gateway" : "172.17.0.1" } ] } }
数据包流转详解 假设 OpenResty 容器 (172.17.0.5) 要访问宿主机的 Redis:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 第 1 步: OpenResty 容器内的代码 ──────────────────────────────── red:connect("172.17.0.1", 6379) ┌─────────────────────────┐ │ OpenResty 容器 │ │ IP: 172.17.0.5 │ │ │ │ 发送: SYN 包 │ │ 源: 172.17.0.5:随机端口 │ │ 目标: 172.17.0.1:6379 │ └─────────────────────────┘ │ │ 数据包通过容器的网络命名空间 ↓ 第 2 步: 通过虚拟网卡对 (veth pair) ──────────────────────────────── │ [容器内 eth0] │ │ (veth pair 就像一根虚拟网线) │ [宿主机上的 veth-xxx] │ ↓ 第 3 步: 到达 docker0 网桥 ──────────────────────────────── ┌──────────────────────────┐ │ docker0 虚拟网桥 │ │ IP: 172.17.0.1 │ │ │ │ 收到数据包: │ │ 目标地址是我自己! │ │ 目标端口是 6379 │ └──────────────────────────┘ │ │ docker0 是宿主机上的设备 │ 所以数据包实际上已经"到达"宿主机 ↓ 第 4 步: 端口转发规则生效 ──────────────────────────────── 宿主机网络栈处理这个包: - 目标 IP: 172.17.0.1 (这是本机 IP) - 目标端口: 6379 宿主机检查: "有程序在监听 6379 吗?" - 发现 Redis 容器通过 -p 6379:6379 映射了端口 - iptables 规则将请求转发到 Redis 容器 第 5 步: 到达 Redis 容器 ──────────────────────────────── │ ↓ ┌─────────────────────────┐ │ Redis 容器 │ │ IP: 172.17.0.2 │ │ │ │ Redis 服务收到连接请求 │ └─────────────────────────┘
关键的 iptables 规则 Docker 在宿主机上创建了一系列 iptables 规则来实现端口映射:
1 2 3 4 5 sudo iptables -t nat -L -n | grep 6379
这条规则的意思是:
任何到达宿主机 6379 端口的 TCP 连接
都会被 DNAT (目标地址转换) 到 172.17.0.2:6379 (Redis 容器)
不同场景下的网关 1. 默认 bridge 网络 1 docker network inspect bridge
网关: 通常是 172.17.0.1
子网: 172.17.0.0/16
2. 自定义 bridge 网络 1 2 docker network create --subnet=172.20.0.0/16 my-network docker network inspect my-network
网关: 通常是 172.20.0.1 (子网的第一个 IP)
子网: 172.20.0.0/16
3. docker-compose 创建的网络 1 2 3 4 version: '3' services: app: image: myapp
Docker Compose 会自动创建一个网络,名称类似 projectname_default:
1 docker network inspect projectname_default
输出可能是:
1 2 3 4 5 6 7 8 9 10 11 { "Name" : "projectname_default" , "IPAM" : { "Config" : [ { "Subnet" : "172.18.0.0/16" , "Gateway" : "172.18.0.1" } ] } }
这时容器的网关就是 172.18.0.1
如何查找容器的网关 IP 方法 1: 在容器内查看路由表 1 2 3 4 5 6 7 8 9 10 docker exec -it container_name sh ip route | grep default
方法 2: 查看容器网络信息 1 2 3 4 5 docker inspect container_name | grep Gateway
方法 3: 在容器内读取环境变量 有些情况下,网关 IP 也可以通过解析 DNS 获取:
1 2 getent hosts host.docker.internal
为什么通过网关能访问宿主机端口 总结一下关键点:
1. 网关 IP 是宿主机的一部分 1 2 3 172.17.0.1 不是一个"远程"地址 它是宿主机上 docker0 设备的 IP 地址 就像宿主机的 127.0.0.1 或其他网卡 IP 一样
2. 端口映射创建了转发规则 1 docker run -p 6379:6379 redis
这个命令做了什么?
在宿主机的所有网络接口上监听 6379 端口
包括 docker0 (172.17.0.1) 接口
创建 iptables 规则: 所有到 6379 的连接 → 转发到 Redis 容器
3. 容器访问网关 = 访问宿主机 1 2 3 4 5 6 7 容器访问 172.17.0.1:6379 ↓ 实际上是访问宿主机的 docker0 接口的 6379 端口 ↓ 宿主机的 iptables 规则生效 ↓ 连接被转发到 Redis 容器的 6379 端口
常见误区 误区 1: 「网关就是宿主机」 错误理解 : 网关 IP 就是宿主机 IP
正确理解 :
网关 IP 是 docker0 虚拟网桥在容器网络中的 IP
宿主机还有其他 IP (如物理网卡 IP: 192.168.0.10)
但 docker0 确实是宿主机上的一个设备
误区 2: 「容器可以直接访问宿主机的 127.0.0.1」 错误 :
1 red:connect("127.0.0.1" , 6379 )
正确 :
1 2 3 red:connect("172.18.0.1" , 6379 ) red:connect("host.docker.internal" , 6379 )
误区 3: 「只有映射了端口才能访问」 部分正确 :
通过网关访问宿主机端口,需要端口映射 (-p)
但容器之间直接通信不需要端口映射
容器可以直接访问其他容器的内部端口
实际应用示例 场景: OpenResty 需要连接宿主机上的多个服务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 local _M = {}local function get_gateway_ip () local f = io .popen ("ip route | grep default | awk '{print $3}'" ) local gateway = f:read ("*line" ) f:close () return gateway or "172.17.0.1" end _M.gateway = get_gateway_ip() _M.redis_host = _M.gateway _M.redis_port = 6379 _M.mysql_host = _M.gateway _M.mysql_port = 3306 _M.elasticsearch_host = _M.gateway _M.elasticsearch_port = 9200 return _M
场景: 在不同的 Docker Compose 项目间通信 最佳方案是使用共享网络,而不是通过网关:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 docker network create shared-net version: '3' services: redis: image: redis networks: - shared-net networks: shared-net: external: true version: '3' services: openresty: image: openresty/openresty networks: - shared-net networks: shared-net: external: true
这样 OpenResty 可以直接使用服务名:
1 red:connect("redis" , 6379 )
总结 Docker 宿主机网关 本质上是:
虚拟网桥 (docker0) 在容器网络中的 IP 地址
容器访问宿主机的桥梁
宿主机网络栈的一部分
通过网关访问宿主机端口的完整路径:
1 容器 → 容器网关 (docker0) → 宿主机网络栈 → iptables 规则 → 目标容器/服务
理解了网关的本质,就能明白:
为什么容器内 127.0.0.1 不能访问宿主机服务
为什么要使用网关 IP 才能访问
为什么共享网络是更好的解决方案
我在遇到前述问题之后,好好查找整理了这个文档,希望能帮助你我彻底理解 Docker 网络架构中的网关概念。