没错Docker 装了、容器跑了但等你要把 MySQL、Redis、Nginx 和业务服务串起来的时候网络这块就会成为第一个拦路虎。容器互相连不上、IP 一重启就变、跨主机访问乱成麻这些问题的根源都在于没吃透 Docker 网络的底层逻辑。这篇文章我不会给你堆概念而是从 Linux 内核的隔离机制讲起一步步拆解 Docker 默认的 bridge 网络、host、none 以及自定义网络。重点放在自定义网络的原理和实操上你可以直接照着做把容器间的互通、服务发现、静态 IP 这些问题一次性彻底解决。无论你是刚入门的小白还是已经被网络坑过几次的老手这篇文章都值得收藏。1. 内容整体设计与思路拆解1.1 为什么 Docker 网络是学习容器绕不过去的坎很多人刚开始用 Docker 的时候跑个 Nginx、挂个 MySQL用-p 3306:3306映射一下端口发现挺顺的就觉得网络没多难。真正让你崩溃的时刻是在后面你用docker-compose部署一套微服务应用十几个容器互相调用突然发现服务 A 访问服务 B 的 IP 全是错的或者容器重启之后之前配好的 IP 全部失效又或者你在部署跨主机的集群时容器之间的通信更是无从下手。这些问题的根源全都在于 Docker 网络。说白了一句话容器是进程隔离网络是内核级的虚拟化技术。Docker 能让你一条命令跑起应用但网络这块如果原理不懂出了问题你连排查的方向都没有。我见过太多人遇到容器连不上就重启 Docker遇到 IP 变了就手动改配置这是治标不治本。要真正把网络玩明白你得先搞清楚几个底层概念网络命名空间Network Namespace、虚拟网卡设备veth pair、Linux 网桥bridge、还有 iptables 的 NAT 规则。这些就好比是住酒店的时候每个房间有独立的门锁和电话线但是要能互相打电话、能上外网需要前台网桥和总机NAT来协调。1.2 技术选型背后的逻辑为什么 Docker 默认选择了 bridgeDocker 官网文档里列了很多网络模式但默认永远是 bridge。这不是随便定的是权衡了隔离性、性能、易用性之后的结果。bridge 模式相当于在宿主机上创建了一个虚拟交换机linux bridge每个容器通过 veth pair 虚拟网线接入这个交换机容器之间通过这个网桥互相通信。在这个模式下容器有自己的 IP 地址段默认是 172.17.0.0/16与宿主机物理网络是隔离开的。这样做的最大好处是安全容器不能直接访问宿主机所在的局域网只能通过端口映射暴露有限的端口。你想想如果每个容器直接拿局域网 IP那你的内网安全策略就形同虚设了。但 bridge 模式也有它的短板最典型的就是默认网络不支持容器名 DNS 解析只支持 IP 访问。容器一旦重建IP 就会变配置就全乱了。这也就是为什么我强烈推荐你使用自定义网络后面我会重点展开讲。2. 核心细节解析与实操要点2.1 Linux 网络命名空间容器网络隔离的基石要理解 Docker 网络你首先得明白 Network Namespace。这是 Linux 内核提供的一种资源隔离机制简单说每个 Network Namespace 拥有自己独立的网络协议栈包括自己的网卡、路由表、iptables 规则和网络设备。你可以把 Network Namespace 理解成一间独立房间里面有自己的电话接口网卡、电话簿路由表和通话规则iptables。默认情况下房间之间是隔音的里面的设备互不干扰。Docker 启动容器时就是为每个容器创建了一个全新的 Network Namespace。在宿主机上执行查看网络命名空间的命令ip netns list不过在 Docker 场景下容器的 Network Namespace 是被 Docker 管理的宿主机的ip命令不一定能直接看到。你可以用另一种方式验证docker inspect --format {{.State.Pid}} container_id # 通过进程 PID 查看容器的网络命名空间 sudo ls -la /proc/pid/ns/net你会发现输出里有一个长链接比如net:[4026532384]。这个数字就是内核分配给该容器的网络命名空间 ID。每个容器的这个 ID 都是不同的这就是隔离的基础。怎么理解这种隔离打个比方宿主机是一个大型办公楼每个容器就是一间独立的办公室。每个办公室有自己独立的网线接口veth有独立的电话本路由表电话只能打到自己房间能识别到的号码范围内。这间办公室要不要装上外线电话NAT要不要加装分机端口映射由物业管理处Docker daemon统一安排。2.2 veth pair 与 Linux 网桥容器间通信的两个核心部件光是隔离还不够容器之间还要通信这就需要用到了 veth pair 和 Linux 网桥。veth pair 是一对虚拟网卡成对出现像一个虚拟的网线一头插在容器里一头插在网桥上。数据从一端进去必定从另一端出来而且是完全对称的。Docker 创建每个容器时都会自动创建一对 veth 设备。你在宿主机上执行ip addr看看会看到一堆veth开头的设备比如veth3f81d5a这些就是已经插到网桥上的虚拟网卡的另一端。每个 veth 一头在容器里称为eth0一头在宿主机上称为vethxxx两者成对存在。而 Linux 网桥Linux Bridge是内核层的一个虚拟二层交换机它在数据链路层工作维护一个 MAC 地址表根据目标 MAC 地址决定把数据帧从哪个端口转发出去。Docker 默认创建了一个名为docker0的网桥所有默认 bridge 网络的容器它的 veth 另一端都插在这个docker0上。于是当容器 A 想要访问容器 B 时数据流是这样的容器A eth0 - vethA - docker0 网桥查MAC表转发- vethB - 容器B eth0这条链路是纯二层的所以在同一个网桥下的容器互访速度非常快且不走任何 NAT 转换。这也是为什么同一台宿主机上容器之间通信用自定义网络更高效的原因——数据链路直接互通跟宿主机外网完全无关。你可以用这个命令查看 docker0 网桥上挂载的接口sudo brctl show docker0或者在新版的 ip 命令下sudo ip link show master docker0看到多个 veth 接口挂在 docker0 下面就说明容器已经在用这个网桥通信了。2.3 iptables 与 NAT容器访问外网的隐形桥梁容器访问外网的链路要比容器间互通多一个关键环节NAT网络地址转换。默认的 bridge 网络下容器是一个私有网段比如 172.17.0.0/16这个网段在宿主机外部是不存在的外部网络根本不知道怎么把数据包路由回来。所以 Docker 在宿主机上通过 iptables 的 MASQUERADE 规则把容器发出的数据包的源 IP 替换成宿主机的 IP这样数据包才能顺利发出去。你可以查看 NAT 规则验证sudo iptables -t nat -L -n你会看到类似这样的规则Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0这条规则的意思就是所有来自 172.17.0.0/16 网段、去往任意目标的数据包在离开宿主机的时候源地址都被伪装成宿主机地址。这就是容器能上外网的根本原因。同理外部访问容器需要端口映射。你用-p 8080:80发布端口时Docker 会往 iptables 里添加 DNAT 规则把宿主机 8080 端口收到的流量转发给容器 80 端口。查看命令sudo iptables -t nat -L DOCKER -n你会看到宿主机 IP 加 8080 端口被映射到了 172.17.0.x:80。这里有一个重要的排查经验如果你在宿主机上启用了自己的防火墙firewalld 或 ufw很多莫名的网络问题比如容器之间互通正常外部访问不通或者外部访问映射端口失败都源于 Docker 直接操作 iptables 与防火墙的优先级冲突。之后遇到这种问题先检查是不是防火墙管理工具把 Docker 写入的规则给清了或者覆盖了。3. 实操过程与核心环节实现3.1 亲手创建第一个自定义网络现在进入干货实操环节。默认的 bridge 网络我用过只能满足单容器跑通的需求。一旦进入多容器联调我几乎必建自定义网络。原因很简单默认 bridge 下容器只能通过 IP 互访不能通过容器名。默认 bridge 下的容器创建后自动分配的 IP 可能变容器重建后 IP 大概率会变。自定义网络自带 DNS 解析可以用容器名直接访问。自定义网络可以设置固定 IP对依赖 IP 的场景更友好。创建自定义网络两条命令搞定# 创建一个 bridge 驱动的自定义网络 docker network create --driver bridge --subnet172.28.0.0/16 --gateway172.28.0.1 my-net解释一下参数--driver bridge网络驱动默认就是 bridge可省略。--subnet自定义网段建议避开 Docker 默认的 172.17.0.0/16防止冲突。--gateway网关地址通常为网段的第一个可用地址。my-net网络名字后续创建容器时指定。你也可以不指定--subnet让 Docker 自动分配但如果你计划给容器固定的 IP就必须自己指定子网。我的习惯是显式指定方便管理和记忆。查看网络列表docker network ls3.2 使用自定义网络部署容器并验证连通性网络创建好之后接下来就是创建容器并把它们连接到这个网络里。有两种玩法创建时直接指定网络或者先创建容器再手动连接。方式一创建容器时直接指定网络docker run -itd --name nginx-web --network my-net nginx docker run -itd --name redis-server --network my-net redis:alpine方式二先创建再连接网络docker run -itd --name mysql-server mysql:8.0 docker network connect my-net mysql-server我个人推荐方式一一句话的事逻辑也清晰。如果你想要固定 IP在创建容器时加个参数docker run -itd --name nginx-web --network my-net --ip 172.28.10.10 nginx现在验证连通性。进入 nginx 容器用容器名去访问 redis 容器docker exec -it nginx-web ping redis-server如果网络配置正确你会看到PING redis-server (172.28.0.3)这样的输出这说明自定义网络的 DNS 解析生效了。这就是自定义网络最核心的价值点容器名就是 DNS 名字。再从容器内部测试 TCP 端口通不通用 bash 内置的 /dev/tcp 或者 nc 工具都可以docker exec -it nginx-web bash exec 3/dev/tcp/redis-server/6379 echo TCP connection successful || echo Failed如果输出TCP connection successful说明端口也通了。这个技巧在排查网络问题时特别有用你可以快速确认中间链路是否正常。3.3 自定义网络与默认网络对比测试性能与隔离性差异有人会问自定义网络和默认网络除了 DNS 解析还有其他差别吗性能上我觉得差别不算特别大因为都是走网桥底层是一样的。但有一点非常值得注意自定义网络支持单独管理你可以把容器从一个网络迁到另一个网络实现动态调整。比如生产环境你可能会把前端 Nginx、后端服务、数据库分别放在三个不同的自定义网络里通过把后端同时连接两个网络来实现前后端网络隔离。这是安全加固的常见玩法。再补一个细节自定义网络创建后docker0网桥会多出一个子接口比如br-xxxxxx它是独立于 docker0 的。你可以这样验证ip addr show | grep br-你会看到br-abc123def这类网桥设备它的 IP 就是自定义网络的网关地址。这个网桥和 docker0 一样所有连接到该网络的容器 veth 都会挂到这个新网桥上。自定义网络之间是相互隔离的容器 A 在 net1 网络中容器 B 在 net2 网络中两地互不连通除非你主动把容器加到两个网络里。最后补充一个我经常用的操作断开容器与默认网络的连接只保留自定义网络。这样可以避免容器同时出现在两个网络中造成的路由混淆。# 先断开默认网桥的连接 docker network disconnect bridge nginx-web断开之后再次通过docker inspect查看docker inspect nginx-web | grep -A 20 Networks你会看到只有my-net这个网络的信息这样整个容器的流量都只需走一条通道排查问题的时候你会感谢自己做了这个操作。4. 常见问题与排查技巧实录4.1 容器之间无法通过容器名互相访问这个现象在默认 bridge 网络下太常见了。如果你用的是默认网络容器之间只能用 IP想要用容器名互访最简单的解法就是改用自定义网络。但还有一种情况是你明明使用了自定义网络容器名解析仍然失败。这时候要检查容器是否真的连接到了同一个自定义网络docker inspect container_id | grep -A 20 Networks确认两个容器都在同一个网络下。如果网络没问题还需要检查容器的 DNS 配置docker exec container_id cat /etc/resolv.conf正常来说Docker 自带的内嵌 DNS server 地址是 127.0.0.11这就是 Docker 用来解析容器名的关键。如果你看到这个文件里的 IP 不是 127.0.0.11说明你手动挂载了 resolv.conf 或者容器镜像里有特殊设置解析失败也就正常了。4.2 容器重启后 IP 发生了变化默认 bridge 下容器重启 IP 可能不变也可能变全凭 Docker 管理无法控制。如果你有依赖固定 IP 的场景强烈建议使用自定义网络并指定固定 IP# 为容器分配固定IP docker network create --driver bridge --subnet172.28.0.0/16 my-net docker run -itd --name web --network my-net --ip 172.28.0.100 nginx不过这里有个坑要提醒你不要给已经运行的容器直接改 IP因为 Docker 的网络规则是跟着容器创建时的配置走的。如果需要改 IP最稳妥的做法是重新创建容器在创建时指定新的 IP。4.3 容器访问外网失败但容器间可以互通如果你遇到容器间互通正常但容器无法访问互联网的情况需要排查两个方向第一iptables 的 SNAT/MASQUERADE 规则是否正常sudo iptables -t nat -L POSTROUTING -n -v如果规则不存在可能就是其他管理工具把 Docker 的规则清掉了。重启 Docker 服务可以让它重新写入规则sudo systemctl restart docker第二检查宿主机的net.ipv4.ip_forward是否开启sudo sysctl net.ipv4.ip_forward如果输出为 0需要打开sudo sysctl -w net.ipv4.ip_forward1 echo net.ipv4.ip_forward 1 | sudo tee -a /etc/sysctl.confip_forward是内核的 IP 转发开关。Docker 的 bridge 模式下容器访问外网时数据包需要经过宿主机内核转发出去如果这个开关是关闭的即使 NAT 规则存在也没用。4.4 容器端口映射后外部还是访问不了这种情况分两步排查先看宿主机上的端口监听状态sudo netstat -tlnp | grep 8080再看 iptables 的 DOCKER 链:sudo iptables -t nat -L DOCKER -n如果规则都在但是外部还是不通很大概率是宿主机的防火墙拦截了流量。临时关闭防火墙测试sudo systemctl stop firewalld排除法确认后再决定是调整防火墙规则还是直接优化 Docker 的端口发布方式。另外提醒一下有些云平台的安全组也会拦端口如果检查完宿主机还是不通去你的云控制台看看安全组规则。4.5 Docker 服务启动失败或提示网络相关错误在 Windows 上使用 Docker Desktop 时候热词里反复出现 virtualization support 检测不到、Docker Engine 启动不了的问题。这类问题多数发生在老电脑或者开了 Hyper-V 但没开 Windows 虚拟化的情况。以 Windows 为例确认 BIOS 里虚拟化Intel VT-x 或 AMD SVM已经开启。任务管理器-性能- CPU 页面能看到虚拟化状态。如果显示“已启用”说明 BIOS 没问题。如果显示“已禁用”需要重启进 BIOS 开启。还有一个常见错误是permission denied while trying to connect to the Docker API at unix:///var/run/docker.sock。在 Linux 环境处理方式很简单把当前用户加入 docker 组需要注销重登生效或者用 sudo 执行 docker 命令。但更规范的解决办法sudo usermod -aG docker $USER # 重新登录一下终端或者执行 newgrp docker4.6 容器运行正常但一直显示 Restarting 状态热词里还有“docker一直starting”这类问题配合网络场景有一种可能的原因是容器启动时依赖的网络资源还没就绪比如需要连接数据库容器但是数据库还没启动完成。这时候别急着改代码先看容器日志docker logs container_name如果你发现日志里一直报 connection refused就可以确认是容器启动顺序问题。解决方案有两个比较实用一是增加应用层的重试机制让应用连接数据库失败后自动重试而不是直接退出。这是从根上解决依赖顺序问题。二是使用docker compose的depends_on配合healthcheck。注意depends_on只保证容器启动了并不保证里面的服务已经就绪。所以健康检查才是关键。举个最简单的写法services: app: image: my-app:latest depends_on: mysql: condition: service_healthy mysql: image: mysql:8.0 healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 5s retries: 10这样一来app 容器只会在 mysql 通过了健康检查之后才会启动而不是在 mysql 容器刚创建时就抢跑。5. 跨主机容器通信方案简述生产中躲不开的场景如果你的场景只在一台宿主机上那上面讲的内容已经足够日常使用了。但现实中微服务部署往往需要多台机器协同跨主机的容器通信就成了躲不开的话题。Docker 提供了多种跨主机网络方案最常用的是 overlay 网络和 macvlan。overlay 网络基于 VXLAN 技术在底层网络之上叠加一层虚拟网络容器可以分配独立的 IP 段在不同宿主机上直接互通最典型的使用场景就是 Docker Swarm。macvlan 则是让容器直接绑定宿主机物理网卡的 MAC 地址容器就像一台独立的物理设备直接接到局域网里性能损耗极小但是需要局域网内有足够的 IP 地址且交换机可能需要配置端口安全策略。在正式环境里这两种方案各有适用场景。overlay 适合容器数量大、节点跨网段、需要弹性伸缩的微服务架构macvlan 适合对网络延迟敏感、需要容器直接暴露在局域网的应用比如某些老旧系统改造、需要抓包分析的场景。考虑到篇幅跨主机的完整实操我会在后续单独写一篇这里先让你建立基本的认知框架。有了前面单机网络原理的底子跨主机网络理解起来会顺畅很多。6. 实操心得与个人建议最后再分享几个我在实际项目中沉淀下来的网络使用习惯你踩过几次坑之后大概率也会认同第一从一开始就规划网络拓扑。不要图省事全部塞进一个自定义网络里。我见过不少生产事故就是因为多个环境测试、预发、生产混在同一个网络里容器名冲突、网络隔离失效。建议按项目或者环境来建多个自定义网络用docker network ls一眼看清拓扑。第二固定 IP 是反模式。能用容器名通信就不要强求固定 IP。容器名的 DNS 由 Docker 内置 DNS 自动管理容器重建后会自动更新解析记录这比手动维护 IP 列表优雅得多。固定 IP 只应该在少数需要白名单的场景下使用。第三理解默认网络和你自定义网络的差异。默认的 bridge 网络不推荐用于生产。它是 Docker 的默认值但它不支持容器名解析也不支持动态连接和断开。如果你的服务将来要做编排扩展自定义网络是底线不是可选项。第四排查网络问题用最小化复现法。网络出问题的时候不要一上来就直接看整个集群先把规模缩小。创建两个简单的容器一个 nginx一个 redis放到同一个自定义网络里逐个测试 ping、TCP 端口、外网访问一步一步缩小排查范围。这个方法看着笨实际效率极高。Docker 网络这块当你把它想成一栋楼的水电管道布局去看很多问题其实就有迹可循了。把每个容器当成独立房间把网桥当成楼层弱电间把 NAT 当成统一出口的网关你再去处理容器之间的沟通脑子里的地图就清晰了。希望这篇文章能把你的网络底子打扎实下次再遇到连接乱象不用重启大法直接定位到问题的根源去解决。