首页 / 资讯中心 / 文章详情

Docker网络详解:从bridge模式到跨主机容器通信与排障

Docker网络详解:从bridge模式到跨主机容器通信与排障 ★ FEATURED ARTICLE
搞Docker的人迟早会在网络配置上翻车。我见过太多刚接触容器的人docker run 一连起了好几个容器结果 MySQL 连不上、Redis 主从一直报错、网页应用访问不到数据库最后排查下来发现根本不是服务本身的问题而是容器之间的网络根本没理清楚。这一篇就把 Docker network 彻底讲明白从默认的 bridge 模式到自定义网络、端口映射原理、跨主机组网再到排障的思路和命令全程用实战场景带大家走一遍。这篇内容适合所有在用 Docker 的人尤其是刚入门才几天、被各种容器间连接问题折磨的新手以及想系统梳理 Docker 网络知识的中级开发者。我会尽量把原理用大白话讲透同时保证每一步操作都能直接落地复现让你读完就能把项目里的容器网络理得明明白白。1. 网络模型先理清Docker 为什么不让容器直接共享网络很多初学者会有一个疑问我明明在宿主机上装好了 Docker容器跑起来就是一个进程为什么不能像普通进程一样直接访问宿主机的 IP 和端口非要弄一堆网桥、端口映射、虚拟网卡搞得这么复杂图什么1.1 容器网络的工作原理namespace 加 veth 再加网桥要回答这个问题得先理解容器的隔离机制。每个容器启动时Docker 默认会为它创建一个独立的网络命名空间也就是一套独立的网络协议栈自己的网卡、IP 地址、路由表、防火墙规则。你可以把网络命名空间理解成给每个容器分配了一间独立的“网络房间”房间里有一扇门veth 设备通向外面门上的地址牌就是容器的 IP。Docker 默认的 bridge 网络模式会在宿主机上创建一个虚拟网桥通常是 docker0这个网桥相当于一个小区的总出入口。容器的 veth 设备一端连着容器里的 eth0另一端连着 docker0数据就从这个门进进出出。宿主机上的所有普通容器默认都在这个小区里IP 段通常是 172.17.0.0/16网关是 172.17.0.1也就是 docker0 的地址。这套设计的核心好处是隔离性。容器之间默认无法直接访问对方的全部端口只有通过 iptables 规则明确放行的端口才能互相到达。这跟传统的虚拟机网络完全不一样虚拟机像是租了独立别墅而容器更像是合租公寓里的一个个房间共享公共走廊但房门是锁着的。这种轻量级隔离是容器可以大规模部署的基础。1.2 五种网络模式的选型逻辑Docker 内置了几种网络模式分别解决不同场景的问题。我用最简单的话把每种模式说清楚bridge 模式默认模式。容器通过 docker0 连到宿主机再由宿主机转发到外网。适合单机上多个容器需要互相通信、同时又要访问外网的场景。大部分业务部署都是这种模式。host 模式容器直接复用宿主机的网络命名空间没有自己的 IP。启动容器时加 --network host 就行。这时候容器里的进程监听的端口就是宿主机端口不需要也没法做端口映射。适合对网络性能要求极高的场景比如一些代理服务、性能压测工具但代价是失去了隔离性容器可以绑定宿主机的任何端口风险比较明显。none 模式容器只有 loopback 网卡没有网络连接。适合跑一些完全不需要网络的离线任务或者你想自己手动配置复杂网络时先启动一个空容器。container 模式多个容器共享同一个网络命名空间使用另一个容器的 IP 和端口空间。这种模式适合那些必须通过本地回环地址通信的进程比如某个监控 agent 要连接运行在同一网络命名空间里的服务。overlay 模式用于 Docker Swarm 集群的跨主机通信。它把多台机器上的容器拉进同一个虚拟二层网络里实现跨节点容器直接通过 IP 或服务名互访。多机部署时才需要用到。选型时不要问哪个模式好要问你的场景到底是不是单机、需不需要跨主机、能不能接受隔离性损失。绝大部分人的业务都只用得到 bridge 模式所以下面的重心也是放在 bridge 的使用和管理上。1.3 自定义网络与默认 bridge 的关键差异Docker 启动后默认只有一个名为 bridge 的默认网桥也就是 docker0。如果你直接用 docker run 不加 --network 参数容器就会加入这个默认网络。单机小规模跑一两个容器没问题但一旦容器变多默认 bridge 的短板就暴露了容器之间无法通过容器名自动解析对方地址。什么叫无法通过容器名解析就是在容器 A 里 ping 容器 B 的名字会提示无法解析。你只能先 docker inspect 查到容器 B 的具体 IP再用 IP 来访问。这种做法不仅麻烦而且容器一旦重建IP 很容易变化配置就得跟着改。这就像你记住朋友家的具体门牌号结果朋友搬了家你就找不到人了。解决办法是创建自定义 bridge 网络。自定义网络内置 DNS 解析同一个网络里的容器可以直接通过容器名互相访问不需要管 IP 是否变化。这也是为什么现在做容器间通信我强烈建议一律使用自定义网络而不是默认的 docker0。2. docker network 命令实操从创建到清理的完整闭环Docker 的网络管理命令比起 run、exec 这些高频命令存在感低很多但真正用得上的就那几条完全可以一口气掌握。这一节把 docker network 的完整用法拆开讲附带参数选择的思路。2.1 核心子命令速查docker network 下面有这些常用子命令我按使用频率排个序docker network ls列出所有网络。docker network create创建一个自定义网络。docker network inspect查看某个网络的详细配置和已连接的容器。docker network connect给运行中的容器动态添加一块网卡加入指定网络。docker network disconnect把容器从网络里断开。docker network rm删除网络。docker network prune清理所有未被容器使用的网络。先说 docker network ls输出结果里有 NETWORK ID、NAME、DRIVER、SCOPE。刚装好 Docker 的机器一般能看到 bridge、host、none 这几种内置网络。SCOPE 是 local 还是 swarm决定这个网络是单机可用还是集群范围可用。docker network inspect 是排障第一神器的说法一点都不过分。输入 docker network inspect my-net输出里有这个网络的名称、驱动、IP 段、网关、是否开了 IPv6以及当前连接了哪些容器包括每个容器的 IP 地址。排查网络不通的问题时这个命令能帮你快速确认容器到底拿到的是不是预期网段的 IP。2.2 创建自定义网络的参数怎么选创建网络的基础命令很简单但我建议每次创建时都主动指定网段和网关不要图省事让 Docker 自动分配。原因后面排查时会说到手动指定网段能让你对容器 IP 的范围心里有数生产环境里跟防火墙、数据库白名单对接也更好规划。下面是一个常用的创建命令参数逐条说明docker network create \ --driver bridge \ --subnet 172.20.0.0/24 \ --gateway 172.20.0.1 \ --ip-range 172.20.0.128/25 \ app-net--driver bridge 表示网络类型单机场景就用这个。--subnet 定义这个网络的 IP 段这里用的 172.20.0.0/24意味着最多可以有 254 个可用地址。--gateway 指定网关也就是这个网段的 .1 地址。--ip-range 用于限制 Docker 自动分配给容器的 IP 范围比 subnet 更小这样你可以预留一部分 IP 给固定使用。最后的 app-net 是网络名称后续启动容器时通过 --network app-net 加入。如果你不想手动指定这些参数运行 docker network create app-net 也能创建成功Docker 会从自身的地址池里挑一段网段给它。但这就失去规划性了一旦容器多起来网段满天飞后面想统一管理就很被动。还有一种常见需求是创建容器时直接指定 IPdocker run -d \ --name nginx-test \ --network app-net \ --ip 172.20.0.10 \ nginx:alpine一个容器一个固定 IP适合那种下游服务配置里必须写死 IP 的场景。但这种做法要求这个 IP 必须在 --ip-range 允许的范围内否则 Docker 会拒绝启动并报错。还有一点要说清楚的是容器可以通过 --network 同时加入多个网络吗答案是容器默认可以在启动后通过 docker network connect 再挂到另一个网络里这样同一个容器会有两个网卡两个 IP。多网络接法在需要让一个容器同时访问两个隔离网络时很实用比如一个数据库同时要被办公网的客户端和管理运维网的工具访问。2.3 网络数据流向与 iptables 规则解读了解完命令还得知道数据到底怎么流。容器里的进程发数据到本机 eth0数据通过 veth 对到达宿主机上的网桥网桥根据目标 MAC 地址决定是转发到宿主机协议栈、还是转发到同一个网段的其他容器。如果目标地址是外网数据会由宿主机转发到物理网卡出去同时 iptables 的 SNAT/MASQUERADE 规则会把源 IP 改成宿主机的地址这就是为什么容器默认可以访问外网。要亲眼看到这些规则可以执行iptables -t nat -L -n | grep -E DOCKER|MASQ你会看到 DOCKER 链相关的 DNAT 规则以及 POSTROUTING 链的 MASQUERADE 规则。每做一次端口映射Docker 就会往 DOCKER 链里追加一条 DNAT 规则把宿主机某个端口的流量转发到容器 IP 的对应端口。有一个细节值得注意Docker 服务重启或 iptables 规则被其他工具清除后网络转发可能会异常。如果你在宿主机上同时使用 ufw、firewalld 这类防火墙管理工具一定要小心它们对 DOCKER 链的干预。我见过不止一次有人为了安全加固清空了 iptables 所有规则结果所有容器的端口映射全部失效容器之间的通信也断了恢复的办法通常是把 Docker 服务重启一遍让它重新写入规则。3. 典型部署场景MySQL、Redis 主从、Web 应用的网络配置实战命令会用了接下来走入实战。这一节挑选了三个最有代表性的部署场景每个场景都踩过坑我把完整的网络配置方案和操作命令写出来。3.1 场景一容器内的 MySQL 怎么让外部客户端连上很多人在 Windows 或 Linux 上装了 Docker 后第一件事就是想跑一个 MySQL 容器给本地开发用。启动命令通常长这样docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ -v mysql-data:/var/lib/mysql \ mysql:8.0这里的关键就在 -p 3306:3306。左边是宿主机端口右边是容器内 MySQL 监听的端口。加了这个参数宿主机上访问 127.0.0.1:3306 就会自动把流量转发到容器里的 3306 端口。但这里有几个坑要提醒你第一-p 3306:3306 默认只绑定宿主机所有网卡也就是说局域网里其他人也能通过宿主机 IP 访问你的数据库安全风险很高。如果只是本机开发用建议显式绑定回环地址docker run -d \ --name mysql8 \ -p 127.0.0.1:3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ mysql:8.0访问时用 127.0.0.1 就能连上局域网其他机器访问不到。第二MySQL 容器里的 root 用户默认只允许从 localhost 登录。用 Navicat、DataGrip 这类客户端连接时经常会报 1130 Host is not allowed to connect to this MySQL server。解决办法是进容器创建一个允许任意来源的专用账号docker exec -it mysql8 mysql -uroot -proot123CREATE USER dev% IDENTIFIED BY dev123456; GRANT ALL PRIVILEGES ON *.* TO dev%; FLUSH PRIVILEGES;这个操作完成之后客户端才能顺利连上来。3.2 场景二Redis 主从的容器间通信配置Redis 的主从复制是容器网络最常见的练手场景。新手如果直接把主从两台 Redis 用默认网络启动然后从节点的配置里写上 172.17.0.x 这样的 IP过一阵子主节点容器一重建IP 变了从节点就失联了。正确做法就是让主从节点都加入同一个自定义网络从节点直接引用主节点的容器名。创建网络并启动主节点docker network create redis-net docker run -d \ --name redis-master \ --network redis-net \ redis:7 \ redis-server --appendonly yes启动从节点docker run -d \ --name redis-slave \ --network redis-net \ redis:7 \ redis-server --replicaof redis-master 6379注意第七版之后 Redis 官方用 --replicaof 替代了原来的 --slaveof网络上的很多老教程还在用旧参数拿新版本镜像跑会直接报 unknown argument。启动后验证主从状态docker exec -it redis-slave redis-cli info replication输出里可以看到 master_link_status:up说明从节点通过容器名成功解析到了主节点地址。这个方案的优越性在于主节点容器即使重建只要容器名不变从节点不需要任何配置修改就能自动重连。3.3 场景三Web 应用容器如何稳定引用数据库容器再举一个更完整的例子。假设你用 Docker 部署 Kodbox 或者一个 PHP 项目需要同时跑 PHP-FPM、Nginx 和 MySQL 三个容器PHP 代码里要连数据库。我第一次遇到这个问题时就直接在连接配置里写了宿主机的 IP结果容器里的 PHP 进程访问宿主 IP 时经常超时后来才意识到说的是容器之间的网络互通问题。正确做法是把所有服务容器加入同一个自定义网络docker network create web-net docker run -d \ --name db \ --network web-net \ -e MYSQL_ROOT_PASSWORDroot123 \ mysql:8.0 docker run -d \ --name php-app \ --network web-net \ -v /www/html:/var/www/html \ php:8.2-fpm docker run -d \ --name web \ --network web-net \ -p 80:80 \ -v /www/html:/var/www/html \ nginx:alpinePHP 配置文件里的数据库地址直接写成 db 这个名字端口写 3306。注意这里不是你习惯的 localhost而是容器名。因为在同一个自定义网络里容器名会被自动解析为容器的 IP这个机制就是 Docker 内置 DNS 提供的。有个容易忽略的细节是Nginx 容器想转发请求给 PHP-FPM通常是在 nginx 配置里写入 fastcgi_pass php-app:9000这里同样用的是容器名。如果 Nginx 容器和 php-app 容器不在同一个网络里这一步就无法解析成功。很多人把服务起好后页面报 502根源就在这里。3.4 跨主机场景overlay 网络与 macvlan 简介如果你的应用需要部署在多台机器上容器需要跨宿主机互相通信那就要用到 overlay 网络。overlay 本质上是一个虚拟网络层把各台宿主机上的容器网络打通让容器之间感觉不到物理机器的边界。使用前提是先把 Docker 切到 swarm 模式docker swarm init docker network create -d overlay --attachable cluster-net之后在这个网络里启动的容器可以跨主机解析服务名并相互访问。--attachable 参数的作用是允许非 swarm service 的普通容器也能挂接这个 overlay 网络调试时非常方便。另外一个有意思的模式是 macvlan它能把容器直接接入物理局域网的网段每个容器拥有一个真实的局域网 IP。这种模式适合某些局域网内的设备发现场景比如 NAS 上的 Docker 容器需要被其他设备直接访问。但它的缺点也比较明显需要宿主机网卡支持并且局域网 IP 充足配置失误时可能导致宿主机网络异常。我在本机测试环境用过一次折腾了一会儿 IP 才通建议新手先不要在生产环境直接尝试。4. 网络故障排查实录那些让人挠头的 Error 怎么破这一节是我最想跟大家分享的。网络排障是个熟练工遇到问题只要思路清晰大多能在几分钟内定位到原因。下面按照频率从高到低列出我实际遇到的典型问题和解决路径。4.1 容器间连不通、端口映射失效的排查思路容器间连不通的第一件事一定是确认两个容器是不是在同一网络里。很多人以为只要都在同一台宿主机的 Docker 里就能互通忽略了网络隔离这个前提。排查时先用 docker inspect 查看两个容器的 NetworkSettingsdocker inspect nginx-test | grep -A 20 Networks看看两个容器的网络名称是否一致IP 网段是否相同。如果一个是默认 bridge一个是自定义网络那肯定不通把其中一个容器用 docker network connect 挂到另一个网络里就行。端口映射失效是另一个高频问题。用户在宿主机上访问 8080 端口访问不到容器里的 Nginx排查步骤如下第一步看容器是否真的在监听端口docker ps | grep nginx-test docker exec nginx-test ss -tlnp第二步确认映射配置docker port nginx-test这个命令会输出 8080/tcp - 0.0.0.0:8080说明映射关系已经建立。第三步如果配置都在但访问不通重点查防火墙规则。有些 Linux 发行版默认开着 firewalld 或 ufw它们会在系统层面拦截转发流量。可以让 Docker 使用托管 iptables 规则或者检查 DOCKER 链是否存在。第四步还有一个极容易踩的坑是容器 IP 变化导致 DNAT 规则失效。假如容器重建后拿到了新的 IP而 iptables 里遗留的规则还指向旧 IP访问自然不通。重启 Docker 服务能够重新生成全部规则但最稳妥的做法是把所有业务容器都加入自定义网络避免依赖默认网络的 IP 稳定性。4.2 Docker API 权限报错与 daemon 问题使用 Docker 命令时经常遇到 permission denied while trying to connect to the Docker API。这个问题几乎是新手标配原因是当前用户不在 docker 用户组里没有权限访问 Docker 守护进程的 socket。解决办法是把当前用户加入 docker 组然后重新登录会话sudo usermod -aG docker $USER newgrp docker重新登录后执行 docker version 验证是否已经正常。还有一类情况是 Docker Desktop 报 network: unavailable、reconnecting... waiting for network connection failed: error sending request 这类错误。这里要区分一下如果用的是 Linux 原生 Docker多半是 daemon 没起来执行 systemctl status docker 查看状态如果是 Windows 上用 Docker Desktop这类网络报错往往出在 WSL2 虚拟网络适配器上可以尝试重启 Docker Desktop或者在 PowerShell 里执行 wsl --shutdown 后重新启动 Docker Desktop。有一次我遇到这个报错重启电脑就好了说明虚拟网卡状态异常了这类问题本质上跟容器网络配置无关更多是宿主环境的问题。4.3 容器内网络异常DNS、外网访问、镜像下载慢容器里能 ping 通 IP但用域名访问不了外网一般是 DNS 配置问题。Docker 自带的 DNS 配置会在容器里写入 /etc/resolv.conf通常指向 127.0.0.11这是 Docker 内置的 DNS 服务。如果宿主机 DNS 配置有问题容器里的 DNS 也会跟着出问题。排查容器内 DNSdocker exec nginx-test cat /etc/resolv.conf docker exec nginx-test nslookup baidu.com如果找不到 DNS 服务器可以在启动容器时用 --dns 参数指定可靠的外部 DNS 服务器比如docker run -d --name test --dns 223.5.5.5 nginx:alpine镜像下载慢的问题本质上是容器运行时在拉镜像时走的网络路径不够优化。Docker 支持通过 daemon.json 配置镜像加速器这是一个标准的运维实践。配置文件通常位于 /etc/docker/daemon.json修改后重启 Docker 生效。这里只提醒一点不要使用来历不明的加速源选一个传输速度稳定的公开源或者在内网搭建自己的 registry 缓存这才是长期可维护的方案。另外提醒一下容器里运行的 git clone、包管理器下载等报 transport error、network error 这类错误时很多人第一反应就是换网络或开代理。但你先别急先确认是不是容器本身没有外网访问能力。执行 docker exec 容器名 ping 一下公网 IP如果连 IP 都不通那问题在 NAT 转发如果 IP 通域名不通才是 DNS 问题。排查方向对了问题也就解决了一半。4.4 常见问题速查表把前面提到的典型问题和解决方法整理成了一张速查表方便以后按图索骥现象常见原因解决手段容器间用 IP 能通用容器名不通使用的是默认 bridge 网络没有内置 DNS创建自定义 bridge 网络并把容器加入宿主机访问容器端口不通防火墙拦截转发流量检查 iptables DOCKER 链确认规则存在容器重启后端口映射失效容器 IP 变化DNAT 指向旧地址使用自定义网络并固定 IP或重启 docker 重新生成规则容器能通 IP但不能解析域名DNS 配置异常检查容器的 resolv.conf--dns 指定外部 DNSPermission denied 连接 Docker API用户不在 docker 组usermod -aG docker 并重新登录Docker Desktop 报 network unavailableWSL2 虚拟网卡异常重启 Docker Desktop必要时 wsl --shutdown除了表里的这些我还想特别说一个排查技巧几乎所有网络问题都可以用 docker network inspect docker exec 组合定位。前者看网络拓扑和容器地址后者进入容器实际验证连通性。不要凭猜不要上来就重启所有容器先瞄准再开火。我见过太多人遇到网络问题第一反应是把容器全部删了重建结果问题依旧就是没用 inspect 先看清楚网络状态。5. 网络规划建议与个人经验讲了这么多最后说点实际的规划建议。我个人的习惯是所有业务容器一律使用自定义网络默认 bridge 只用来跑那些不需要被其他容器访问的临时容器。自定义网络统一指定网段和网关并且给每个网络起一个能一眼认出用途的名字比如 app-net、redis-net、web-net。这样做的最大好处是当你 docker network ls 看到一堆网络时不会一脸懵不知道哪个网络是干嘛的。还要养成的习惯是容器创建前先想清楚它的网络拓扑。比如一个数据库容器谁可以访问它它需要访问谁要不要暴露给宿主机以外的客户端。想清楚了再创建比事后拿着 inspect 输出折腾半天下班要舒服得多。一句话网络配置里省下的每一分钟都是当初规划时多花的那一分钟还回来的。最后分享一个小技巧给生产环境容器创建自定义网络时把网段选得跟公司办公网段明显区分开。比如办公网是 192.168.1.0/24docker 网络就选 172.20.0.0/24 这样的网段这样万一出现路由冲突或地址重叠一眼就能识别是容器网络还是物理网络省去大量排查成本。这个小习惯我是在一次办公网和 docker 默认 172.17 网段冲突导致整个开发环境网络瘫痪之后才养成的吃过大亏才懂得规划的重要性。
阅读完成 · 觉得有帮助?
咨询建站