
1. 从一次容器间调用超时说起很多人第一次接触容器网络是在本地把两个服务分别跑进容器之后发现它们互相访问不了。应用日志里报的是连接超时或者域名解析失败但两个容器明明都在同一台机器上跑着docker ps看状态也全是 Up。这个场景我遇到过太多次问题往往不在应用本身而在于对容器网络模型的理解还停留在“它们应该能通”的直觉层面。容器和虚拟机不一样。虚拟机通常有自己完整的网络栈和独立的 IP默认走桥接就能互通而容器的网络是 Docker 在宿主机上“拼”出来的一套虚拟网络默认情况下每个容器被放进一个隔离的网络命名空间有自己的网卡、路由表和端口空间。两个容器能不能通、怎么通取决于它们是否接入了同一个网络、是否用了正确的寻址方式、以及宿主机上的转发和防火墙规则是否放行。这篇内容面向的是已经在用 Docker 跑服务、但被容器间通信卡住的人也适合想把这块知识系统梳理一遍的运维和开发。我会从默认网络的行为讲起把 bridge、自定义 bridge、host、none、container 这几种模式的实际差异说清楚再落到docker network系列命令的用法、容器间通过容器名互访的原理、端口映射与直连的区别最后给出几类典型故障的排查链路。全程以实操为主命令都可以直接复制执行。需要先明确一个前提下面所有讨论都基于 Linux 环境下的 Docker EngineWindows 和 macOS 上 Docker Desktop 的网络实现有额外一层虚拟化行为会有差异遇到具体问题时以实际环境为准。2. 默认网络到底给了容器什么2.1 安装完 Docker 后自动出现的三个网络刚装好 Docker执行docker network ls一般会看到三个网络NETWORK ID NAME DRIVER SCOPE a1b2c3d4e5f6 bridge bridge local f6e5d4c3b2a1 host host local 9c8b7a6d5e4f none null local这三个是 Docker 自动创建的。bridge是默认网络容器不指定--network时就会接入它host直接复用宿主机网络栈none则给容器一个只有 loopback 的孤立网络。理解这三个的差异是理解后面所有通信问题的起点。这里有个容易被忽略的点默认的bridge网络虽然叫 bridge但它和用户自己创建的 bridge 网络在行为上并不完全一样。最关键的差异是默认 bridge 网络里的容器之间不能通过容器名互相解析只能用 IP 直连而自定义 bridge 网络内置了自动服务发现容器名可以直接当主机名用。这个差异是很多“为什么我 ping 不通对方容器名”问题的根源。2.2 默认 bridge 网络的地址分配与隔离默认 bridge 网络在宿主机上对应一个叫docker0的虚拟网桥。可以用ip addr show docker0看到它ip addr show docker0 # 输出类似 # 3: docker0: BROADCAST,MULTICAST,UP,LOWER_UP mtu 1500 # inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0172.17.0.1是这个网桥在宿主机侧的地址也是默认 bridge 网络里所有容器的网关。每启动一个接入默认 bridge 的容器Docker 会从172.17.0.0/16里分配一个 IP并在网桥上挂一对 veth 设备一端在容器内通常显示为eth0一端在宿主机上接到docker0。容器内的路由表大致是这样默认路由指向172.17.0.1同网段172.17.0.0/16直接可达。所以同一默认 bridge 网络里的两个容器用 IP 是可以互通的因为它们在同一广播域内二层直接转发不经过网关。但要注意默认 bridge 网络里的容器之间是可以互相访问所有端口的没有隔离。这在单机多租户或者跑着不信任服务的场景下是有风险的。自定义网络可以通过--internal等参数做更细的控制这也是生产环境更推荐自定义网络的原因之一。2.3 容器访问外网与端口映射是两回事经常有人把“容器能不能访问外网”和“外面能不能访问容器”混为一谈。前者靠的是宿主机的 NATMASQUERADE规则Docker 在启动时会往 iptables 的 nat 表里写规则让来自172.17.0.0/16的流量出宿主机时做源地址转换。只要宿主机能上网容器默认就能上网不需要任何额外配置。后者靠的是-p端口映射。docker run -p 8080:80 nginx的意思是在宿主机上监听 8080把进来的流量 DNAT 到容器的 80 端口。这里的关键是端口映射解决的是“从宿主机外部访问容器”的问题它和容器之间互相通信没有直接关系。两个容器在同一网络里互访走的是容器 IP 直连根本不经过宿主机的端口映射。我见过有人为了让容器 A 访问容器 B给 B 映射了一堆端口然后 A 去连宿主机的映射端口。这种做法能通但绕了一圈而且依赖宿主机 IP容器迁移或者换环境就容易出问题。正确做法是让两个容器接入同一自定义网络直接用容器名加容器内端口访问。3. 五种网络模式的实际行为差异3.1 bridge 模式默认但有坑bridge 是默认模式也是问题最多的模式。它的核心特征是容器有独立网络命名空间、独立 IP通过docker0网桥与外界通信。默认 bridge 网络不支持自动 DNS 解析这是它和自定义 bridge 最大的区别。如果你非要用默认 bridge 让两个容器互访只能这样# 启动两个容器拿到各自 IP docker run -d --name app1 nginx docker run -d --name app2 nginx # 查 IP docker inspect -f {{.NetworkSettings.IPAddress}} app1 # 172.17.0.2 docker inspect -f {{.NetworkSettings.IPAddress}} app2 # 172.17.0.3 # 在 app2 里访问 app1 docker exec app2 curl http://172.17.0.2能用但 IP 是动态分配的容器重建后 IP 可能变硬编码 IP 的配置迟早出问题。所以默认 bridge 只适合临时调试不适合有稳定通信需求的服务。3.2 自定义 bridge生产环境的首选自定义 bridge 网络是解决容器间通信最推荐的方式。它和默认 bridge 用同样的驱动但多了两个关键能力自动 DNS 解析和更好的隔离性。创建和使用都很直接docker network create mynet docker run -d --name app1 --network mynet nginx docker run -d --name app2 --network mynet nginx # app2 里直接用容器名访问 app1 docker exec app2 curl http://app1app1这个名字能被解析是因为 Docker 在自定义网络里内置了一个嵌入式 DNS 服务器监听在127.0.0.11:53容器内的 DNS 查询会被转发到它它再把容器名解析成对应的 IP。这个机制是自定义网络独有的默认 bridge 没有。自定义网络还支持网络别名--network-alias可以让一个容器在同一网络里有多个名字方便做灰度或者服务分组。另外自定义网络之间的容器默认是隔离的除非显式把容器接入多个网络否则跨网络不通这对划分服务边界很有用。3.3 host 模式去掉网络隔离--network host让容器直接使用宿主机的网络命名空间容器里看到的网卡、IP、端口就是宿主机的。它没有独立的 IP也不做端口映射容器里监听 80 就是宿主机监听 80。docker run -d --network host nginx # 直接访问宿主机 80 端口即可 curl http://localhosthost 模式的好处是没有 NAT 开销网络性能最好适合对延迟敏感或者需要处理大量连接的服务。代价是失去了端口隔离同一宿主机上两个容器不能监听同一个端口而且容器里的网络配置会直接影响宿主机风险更高。它只在 Linux 上原生支持其他平台上的行为要单独确认。3.4 none 模式彻底断网--network none给容器一个只有 loopback 的网络没有任何外部连通性。它适合跑纯计算任务、或者需要自己手动配置网络的场景。容器起来后ip addr只能看到lo想联网得自己往里加网卡一般用不上但排查网络问题时可以用它来排除网络因素干扰。3.5 container 模式共享另一个容器的网络--network container:xxx让新容器复用另一个已存在容器的网络命名空间。两个容器共享 IP、端口空间和网卡相当于在同一网络栈里跑两个进程。docker run -d --name base nginx docker run -d --network container:base --name sidecar alpine sleep 3600 # sidecar 里能看到 base 的网络两者 localhost 互通这个模式在 sidecar 场景里很常见比如一个容器跑主服务另一个容器跑日志采集或者网络代理共享网络后可以直接通过localhost通信省去网络配置。但要注意共享网络意味着端口冲突两个容器不能监听同一端口。把这五种模式放在一起对比差异会更清楚模式独立 IP自动 DNS端口隔离典型场景默认 bridge有无有临时调试自定义 bridge有有有生产服务互访host无不适用无高性能、端口敏感none仅 lo无有纯计算、手动配网container共享共享无sidecar、共享网络栈4. docker network 命令的实操细节4.1 创建网络时那些值得设置的参数docker network create除了给个名字还有几个参数在实际项目里很有用docker network create \ --driver bridge \ --subnet 172.20.0.0/16 \ --gateway 172.20.0.1 \ --ip-range 172.20.5.0/24 \ mynet--subnet指定网段避免和宿主机已有网段冲突。这点很重要如果自定义网段和公司内网或者宿主机路由表里的网段重叠会出现路由混乱表现为时通时不通。--gateway指定网关一般不用改。--ip-range限定容器 IP 的分配范围方便和静态分配配合。还有一个--internal参数创建出来的网络没有对外网关容器只能在这个网络内部通信不能访问外网。适合做纯内部的服务网络安全性更好。4.2 把运行中的容器接入或断开网络容器跑起来之后网络不是一成不变的。docker network connect和disconnect可以动态调整# 把已运行的容器接入另一个网络 docker network connect mynet app1 # 断开 docker network disconnect mynet app1 # 强制断开容器内还有连接时 docker network disconnect -f mynet app1一个容器可以同时接入多个网络每个网络里有一个 IP。这在需要跨网络边界的场景很有用比如一个容器既要和前端网络通信又要和数据库网络通信就可以同时接入两个网络充当桥梁。但要注意多网络接入后容器内的默认路由只会指向其中一个网络跨网络访问时可能需要指定源地址或者调整路由。4.3 inspect 网络看什么docker network inspect mynet输出的信息量很大重点看这几块docker network inspect mynetSubnet和Gateway确认网段和网关排查网段冲突时必看。Containers当前接入这个网络的所有容器以及它们在这个网络里的 IP 和 MAC。排查“谁在这个网络里”时直接看这里。Options创建时传的参数比如com.docker.network.bridge.name可以指定网桥名字。如果怀疑 DNS 解析有问题可以进容器看/etc/resolv.conf自定义网络里应该指向127.0.0.11。如果这里指向的是宿主机的 DNS 或者别的地址说明容器没接入自定义网络或者 DNS 配置被覆盖了。4.4 清理无用网络的正确姿势长期运行的环境里会积累一堆没用的网络docker network prune可以清理没有被任何容器使用的网络docker network prune # 跳过确认 docker network prune -f但要注意prune 会删掉所有没容器接入的自定义网络如果某个网络是给未来容器预留的会被一起删掉。更稳妥的做法是先docker network ls看一遍确认哪些确实没用了再删。删除单个网络用docker network rm mynet如果还有容器接入会报错需要先断开或者删容器。5. 容器名互访背后的 DNS 机制5.1 127.0.0.11 这个地址是怎么来的在自定义网络里容器内的/etc/resolv.conf通常长这样nameserver 127.0.0.11 options ndots:0127.0.0.11是 Docker 嵌入式 DNS 服务器的地址它只在容器的网络命名空间内可达。容器发起 DNS 查询时请求先到127.0.0.11Docker 的 DNS 组件查自己维护的记录如果查的是同一网络里某个容器的名字或者别名直接返回对应 IP如果不是就转发给宿主机配置的上游 DNS。这个机制解释了为什么容器名解析只在同一自定义网络内有效。DNS 记录是按网络维护的跨网络的容器名查不到因为 DNS 服务器根本不知道另一个网络里的容器。5.2 容器名、别名和网络作用域一个容器在不同网络里可以有不同的名字。--name是容器名全局唯一--network-alias是网络内的别名只在指定网络里生效可以重复。docker run -d --name app1 --network mynet --network-alias api nginx docker run -d --name app2 --network mynet --network-alias api nginx上面两个容器在mynet里都有api这个别名DNS 查询api会返回两个 IP相当于一个简单的负载均衡。这个特性在做无状态服务的横向扩展时挺方便不用额外部署服务发现组件。但要注意别名的解析顺序和缓存。Docker 的 DNS 对别名返回多个 A 记录客户端怎么选取决于自己的解析策略不保证轮询。如果需要严格的负载均衡还是得上专门的服务发现或者反向代理。5.3 解析失败时的排查顺序容器名解析不了按这个顺序查基本能定位确认两个容器是否在同一自定义网络docker network inspect mynet看Containers列表。确认容器内/etc/resolv.conf指向127.0.0.11。在容器内直接nslookup app1或getent hosts app1看解析结果。如果解析返回了 IP 但连不上问题就不在 DNS而在网络连通性或目标端口。检查是否有--dns参数覆盖了默认 DNS或者容器内应用用了自己的 DNS 缓存。我遇到过一次解析失败最后发现是容器启动时用了--dns 8.8.8.8把默认的127.0.0.11覆盖了导致容器名完全解析不了。这个参数在需要指定外部 DNS 时才会用但会牺牲容器名解析能力用之前要想清楚。6. 端口映射与容器直连的取舍6.1 -p 和 --expose 的区别-p是端口映射把宿主机端口转发到容器端口外部可访问。--expose只是声明容器暴露了某个端口不实际做映射主要用于文档和docker ps显示对网络连通性没有直接影响。# 映射外部可访问 docker run -d -p 8080:80 nginx # 仅声明不映射 docker run -d --expose 80 nginx容器之间互访时用的是容器内端口和-p映射的宿主机端口无关。比如容器 B 访问容器 A 的 80 端口直接http://app1:80不需要 A 做任何端口映射。这一点新手最容易绕进去。6.2 什么时候必须用端口映射端口映射只在一种情况下必须用从宿主机外部包括宿主机本身通过 localhost访问容器。比如你在浏览器里访问跑在容器里的 Web 服务或者宿主机上的某个进程要连容器里的数据库这时候需要-p。容器之间的通信只要在同一网络里一律走容器 IP 和容器内端口不需要映射。把这条原则记住能避免大量不必要的端口暴露减少安全面。6.3 端口冲突与随机映射宿主机端口是独占的两个容器映射同一个宿主机端口会冲突第二个启动会报port is already allocated。解决办法要么换端口要么用随机映射# 宿主机端口随机用 docker port 查实际映射 docker run -d -P nginx docker port container_id-P会把镜像里EXPOSE声明的端口全部随机映射到宿主机高位端口。适合本地开发时不想手动指定端口的场景但生产环境还是显式指定更可控。7. 几类典型通信故障的排查链路7.1 容器间 ping 不通的完整排查假设 app1 和 app2 在同一自定义网络app2 ping app1 不通。按这个链路走第一步确认网络接入。docker network inspect mynet看两个容器是否都在Containers里。如果不在说明启动时没指定--network或者接的是别的网络。第二步确认 IP 和路由。进 app2 执行ip route看默认路由和网段是否和 app1 一致。如果路由表里没有 app1 所在网段说明网络配置有问题。第三步确认 DNS。getent hosts app1看能否解析。解析不了回到上一节的 DNS 排查。第四步确认连通性。ping app1_ip和curl app1_ip:port分别测。ping 通但端口不通问题在目标服务没监听或者监听地址不对比如只监听了 127.0.0.1。第五步检查宿主机防火墙。iptables -L -n看 FORWARD 链有没有 DROP 规则。有些系统的防火墙默认会拦转发流量导致容器间不通。7.2 能 ping 通但端口连不上这种情况最常见的原因是目标服务只监听了127.0.0.1。容器内的服务如果绑定在 loopback 上只有容器自己能访问其他容器通过容器 IP 访问会被拒绝。解决办法是让服务监听0.0.0.0。以常见的应用为例很多框架默认监听127.0.0.1需要显式改成0.0.0.0。这个改动在容器化时是必须的但经常被忽略因为本地直接跑的时候127.0.0.1没问题一进容器就出问题。另一个原因是目标端口没在容器内真正监听。用docker exec app1 netstat -tlnp或者ss -tlnp确认监听状态别只看 Dockerfile 里的EXPOSE那只是声明。7.3 跨宿主机通信的边界单机上的容器通信靠 Docker 自己的网络机制就够了。一旦涉及多台宿主机上的容器互访Docker 默认的 bridge 网络就无能为力了因为容器 IP 只在单机内有效跨主机不可路由。跨主机通信需要额外的方案比如把容器网络接入一个覆盖网络overlay或者用宿主机端口映射加外部负载均衡。overlay 网络通过docker network create --driver overlay创建需要集群模式支持它会在多台主机之间建立隧道让容器像在同一网络里一样通信。这块内容展开会很长这里只点出边界单机 bridge 解决不了跨主机别在这上面浪费时间。8. 我在实际项目里踩过的几个坑第一个坑是网段冲突。有次自定义网络用了172.17.0.0/16和默认 bridge 网段重叠结果容器时通时不通排查了半天才发现是路由冲突。后来养成习惯自定义网络一律用172.20以后的网段避开默认范围。第二个坑是 DNS 覆盖。前面提过--dns参数会覆盖默认的127.0.0.11导致容器名解析失效。这个参数不是不能用但用之前要确认是否真的需要以及是否接受失去容器名解析的代价。第三个坑是容器重建后 IP 变化。早期用默认 bridge 加硬编码 IP容器一重建 IP 就变配置全失效。后来全部改成自定义网络加容器名访问这个问题彻底消失。容器名是稳定的IP 不是这个认知转变很关键。第四个坑是防火墙。某次在开启了严格防火墙策略的机器上容器间怎么都不通最后发现是 FORWARD 链默认策略是 DROPDocker 的规则没生效。这种情况需要检查iptables的 FORWARD 链策略确保 Docker 的规则被正确插入。第五个坑是--network host下的端口冲突。host 模式下容器直接用宿主机端口两个容器监听同一端口直接冲突而且报错信息不一定直观。用 host 模式前一定要规划好端口占用。这些坑的共同点是它们都不在 Docker 的文档显眼位置而是要在实际环境里撞过才知道。理解容器网络的底层模型比记住一堆命令更能帮你快速定位问题。命令会变模型不会。