
如果你刚接触 Docker跑第一个容器时其实感觉不到网络的存在。直到你发现容器里的服务宿主机访问不到、两个容器互相 ping 不通、或者 MySQL 容器换了端口映射还是连不上——这种时候你才意识到Docker 的网络模式不是知道名字就行的入门知识而是实打实的排障技能。我见过太多同事被 docker 网络坑怕了一说网络模式就只知道 bridge、host、none却说不清什么时候该用哪个也不知道自定义网络才是容器互联的正确打开方式。这篇内容就围绕 docker 网络模式、配置和实际排错展开把底层原理、选择逻辑、配置步骤和docker网络不通的完整排查链路梳理清楚适合正在学习 Docker、或者部署应用时遇到连接问题的朋友直接拿来对照。1. 先讲透底层逻辑网络命名空间与 veth 对是怎么撑起容器网络的1.1 每个容器其实住在一套独立的小隔间里要理解 Docker 网络模式不能只看表面命令得从 Linux 的 network namespace网络命名空间说起。简单讲网络命名空间就是 Linux 内核提供的一套隔离墙每个命名空间里有自己独立的网卡、路由表、防火墙规则和 socket 列表。一个容器本质上就是一个被塞进独立命名空间里的进程组。你在宿主机上用ip addr看不到容器内网卡在容器里也看不到宿主机的物理网卡就是因为两边各自住在完全隔离的网络空间里。这就很像 VMware Workstation 里的虚拟网络编辑功能。玩过 VMware 的朋友应该有印象虚拟网络编辑器里有仅主机模式和NAT 模式两种网络形态。仅主机模式相当于虚拟机和宿主机组了一个封闭小局域网外部完全访问不进来NAT 模式则是虚拟机躲在宿主机后面通过宿主机做地址转换去访问外网外部要主动找虚拟机还得靠端口映射。Docker 的 bridge 模式跟 VMware 的 NAT 很像而 host 模式则相当于虚拟机直接共享宿主机网卡。这个类比一出来很多抽象名词就容易落地了。1.2 报文从容器到外部世界的完整路径Docker 默认的 bridge 网络里核心组件是docker0这个 Linux 网桥以及每创建一个容器都会生成的一对 veth pair虚拟网线。一对 veth pair 的两端一端连在容器内部的 eth0 上另一端像插头一样插在 docker0 网桥上所以宿主机上会看到一堆vethxxx开头的网卡。假设容器内有个应用监听 8080 端口外部请求映射到宿主机的 18800 端口。数据流是这样的请求先到宿主机网卡和 iptables 的 PREROUTING 链因为容器的端口映射-p 18800:8080在 Docker 初始化时写了 DNAT 规则所以目标地址会被改写为容器的 IP 和 8080 端口。改写后报文从 docker0 桥转发通过 veth peer 进入容器命名空间最终被容器内进程接住。容器主动访问外网时走反向路径源地址在 POSTROUTING 链经过 MASQUERADE 伪装变成宿主机 IP 出去回来的时候再由内核自动把连接关联到容器的私网地址上。分析网络故障时脑子里要有这条完整链路。我实测过很多次所谓docker网络不通绝大多数不是 Docker 网桥坏了而是中间某个环节被干扰——比如防火墙拦了转发链、iptables 规则被覆盖、或者 Docker 服务启动前已经存在同名网桥。链路不清晰排查就全凭感觉效率极低。1.3 一张表看懂默认网络和它们干的事网络名称类型默认存在主要用途bridge桥接网络是容器默认接入的网络支持端口映射适合单机多容器互通host主机网络是容器共享宿主机网络栈无独立 IP 和端口映射性能最好none无网络是完全隔离网络容器里只有 lo 回环口适合跑离线备份等任务container容器共享网络否新容器复用另一个容器的网络栈常用于 sidecar 场景overlay覆盖网络否Swarm 模式下跨主机的容器互通需额外初始化 Swarmdocker network ls列出来的结果里bridge、host、none是 Docker 自己创建的三个内建网络它们有一个共同点是删除不掉也不建议直接改配置。而自定义的 bridge 或 overlay 网络才是日常配置的重点。2. bridge、host、none 三种基础模式的真实使用场景2.1 为什么默认是 bridge它好在哪创建容器时如果不带--network参数Docker 会默认把容器接入名为 bridge 的网桥网络。这个桥接网络本身走的是 Linux bridge NAT 方案容器有自己独立的 IP通常 172.17.0.x对外通信需要经过宿主机地址转换。这样设计的好处是隔离性不错多个容器之间默认不互通早期版本其实可以互通后来出于安全考虑加了限制每个容器都像隐藏在宿舍楼里的一间寝室只有开了端口映射这条门禁外面的人才能进得来。举一个实战场景你在宿主机上部署 Nginx 容器里面监听 80想从外部浏览器访问得执行docker run -d -p 80:80 nginx。如果不加端口映射外部访问宿主机的 80 端口是找不到任何服务的因为容器里的 80 和宿主机的 80 是两套隔离空间。初学阶段最容易犯的错就是容器启动成功后在宿主机直接 curl 容器 IP 的端口看起来好像没通误以为是 Docker 问题实际上端口映射和数据链路设计上就没打算让你这么访问。2.2 host 模式什么时候必须用host 模式只需要在docker run时加--network host容器就不再拥有独立网络栈而是直接使用宿主机内核网络协议栈。此时容器里的进程监听的端口就是宿主机的端口没有 NAT、没有端口映射、没有转发链路性能损耗几乎为零。什么时候用 host我自己的标准是服务本身对延迟和吞吐敏感、且端口不跟宿主机已有进程冲突的场景。比如跑一些需要高并发转发性能的网关类中间件或者要拿容器内工具直接抓取宿主机网卡上的流量包host 模式会省心得多。但代价也很明显——容器的隔离性几乎在这层被打破Dockerfile 里写的EXPOSE在 host 下不生效端口冲突也要自己管理你启动两个都监听 3306 的 host 模式容器后一个必然失败。有个细节容易被忽略host 模式下容器内看到的 IP 就是宿主机 IPlocalhost也是同一回事。所以在容器里连接宿主机上的 Redis、MySQL直接写127.0.0.1就能通不用像 bridge 模式那样查ip addr show docker0去拿网关地址。2.3 none 模式完全断网的意义--network none创建的容器内部只有一块回环网卡没有 eth0。看起来什么都不能干但在某些需要严格控制网络暴露的场景下反而是优势。比如一次性执行的离线计算任务、数据导出脚本根本不需要联网或者安全要求高的场景跑一些不允许对外通信的批处理用 none 模式从网络层面就切断了所有外部访问路径连 DNS 解析都省了。实际排障里还有个使用技巧当容器里网络栈被弄坏、或者想在不影响现有配置的前提下快速测试一个进程能否独立存活可以先用 none 模式跑起来看看这比费劲去调整 bridge 上的 iptables 规则快得多。2.4 模式选择对比清单模式隔离性性能端口映射典型用途bridge中中支持默认 Web 应用、数据库容器、微服务host低高无网络服务、抓包工具、性能敏感进程none高无网络无离线计算、隔离测试、安全约束切记host 和 none 模式下-p、--expose这些参数无效。这个坑我见过不止一次有人明明加了-p 8080:80和--network host访问不进去查了半天才发现参数被忽略了。3. 自定义网络容器互联和固定 IP 的正确答案3.1 为什么--link是过时方案自定义网络是正路老教程里经常见到--link db:mysql这种用法它本质上是早期 Docker 在同一个默认 bridge 网段里维护静态 DNS 映射。但它作用有限--link依赖容器创建顺序不能跨宿主机不支持动态重启后自动更新解析官方文档也已经明确标注为 legacy过时特性。现代 Docker 推荐的做法是自己创建 networks。核心原因是自定义 bridge 网络自带 DNS 解析只要两个容器在同一个自定义网络里容器之间就可以直接用容器名互访比如curl http://web:8080Docker 内置 DNS 会自动解析到对应容器的 IP。这个特性太实用了完全不用关心容器 IP 是 172.18.0.2 还是 172.18.0.5——容器重建、迁移后 IP 会变但容器名不会DNS 解析保证服务总能找到彼此。3.2 创建和接入自定义网络的完整命令我的习惯是先规划网段再创建网络# 创建名为 app-net 的自定义网络子网 172.28.0.0/24网关 172.28.0.1 docker network create \ --driver bridge \ --subnet172.28.0.0/24 \ --gateway172.28.0.1 \ app-net创建业务容器接入网络时可以同时指定固定 IPdocker run -d \ --name mysql8 \ --network app-net \ --ip 172.28.0.10 \ -e MYSQL_ROOT_PASSWORD123456 \ mysql:8.0另外常用的操作是docker network connect它允许一个容器同时加入多个网络。像数据库容器一开始在默认 bridge后续想加入 app-net 跟业务容器互通可以执行docker network connect app-net mysql8这时候容器里会多出一块网卡两个网段同时可达。这个能力还是很有用的尤其是灰度升级、临时联调的场景。查看网络里接入了哪些容器# 查看 app-net 的完整配置和连接端点 docker network inspect app-net用过几次inspect后就会意识到所有网络问题的第一现场都应该从这里开始看。比如容器明明启动了但Containers列表里没有它说明容器创建时网络没接上再看一下IPAM.Config里的 Subnet能判断固定 IP 是否冲突。3.3 配置自定义网络时容易踩的暗坑不要在创建网络时把 subnet 和宿主机现有网段搞成重叠比如宿主机是 192.168.1.0/24你再指定 192.168.1.0/24 的 Docker 子网路由会直接乱套几乎必然导致容器和宿主机互相访问不了。固定 IP 不设 subnet 时有些版本的 Docker 会报错所以docker network create创建包含固定 IP 需求的网络时subnet 一定要显式写上。自定义 bridge 默认开了iptables管理如果宿主机又装了 firewalld 并启动了 masquerade经常出现容器能访问外网但跨主机容器不通的怪现象需要逐个检查链的 DROP 规则。值得单独强调的是 DNS 配置。默认情况下容器接入自定义网络Docker 会在容器内部的 /etc/resolv.conf 写入 127.0.0.11 这个内嵌 DNS 服务。它负责把容器名解析成 IP同时也做上游转发。可如果你在docker run时手动指定了--dns比如指向公司内部的 DNS 服务器等于覆盖了 Docker 内置解析器容器名之间的互访就直接失效了。排查时可以进容器执行cat /etc/resolv.conf看到 127.0.0.11 一切正常若是别的 IP就要想想是不是手动 dns 参数配过头了。4. Docker Compose 里的网络声明和跨主机场景4.1 compose 文件到底怎么写网络Docker Compose 是编排利器项目里经常要用它一次性拉起来 web 服务、数据库、Redis 等一系列容器。Compose 里网络配置最基础的写法是version: 3.9 services: app: image: myapp:latest networks: - app-net mysql: image: mysql:8.0 networks: - app-net networks: app-net: driver: bridge只要把两个服务声明在同一个自定义网络app-net下Compose 创建项目时就会自动建好这个网络并且每个容器都可以通过服务名直接访问彼此。这个网络名称在项目运行时实际会带上项目前缀比如myproject_app-net如果觉得默认命名太长可以在 networks 里用name: app-net显式固定。Compose 的项目结构还有个细节如果别的 compose 项目想复用同一个网络就要声明外部网络。services: app: networks: - shared-net networks: shared-net: external: true name: app-net这样声明后跨项目共享网络就打通了。像是微服务拆分成多个 compose 文件部署时这种外部网络配置几乎是必需品否则服务之间看到的容器名解析会失灵或者在默认网络上互相 ping 不通。4.2 compose 里指定固定 IP 和 hosts 映射有些老系统因为连接串缓存了 IP 地址或者出于基础设施安全要求需要固定内网地址Compose 里也可以写services: mysql: networks: app-net: ipv4_address: 172.28.0.10 networks: app-net: ipam: config: - subnet: 172.28.0.0/24注意 ipam 的配置必须定义在 networks 顶级下面不能写在 service 里。配置完可以执行docker compose config校验一下内容这个命令会输出解析好的完整配置非常直观比自己对着 yaml 找错快得多。另外如果你需要容器额外解析一个自定义域名到另一个容器可以在 service 里加extra_hosts: - db.internal:172.28.0.10这相当于往容器 /etc/hosts 里写一条静态记录。它跟 Docker DNS 的优先级问题要注意/etc/hosts里的记录通过系统解析时会优先使用。调试某些习惯了固定域名连接的程序时这个用法能救人一命。4.3 跨主机通信swarm 模式 overlay 网络的取舍Compose 处理单机绰绰有余跨主机就得用 Kubernetes 或 Swarm 里那套 overlay 网络。以 Swarm 为例初始化集群后可以创建 overlay 网络让分布在不同物理机上的服务通过 VXLAN 隧道互相通信。docker swarm init docker network create -d overlay --attachable crm-net--attachable这个参数很有意思它允许普通容器非 service手动 attach 到 overlay 网络上做临时调试非常方便。不过要泼一盆冷水在单台服务器上部署的场景overlay 网络完全是多余的别为了追求先进而引入它多一层 VXLAN 隧道就多一层排障复杂度而且 overlay 模式下默认的容器间 mDNS、广播包支持并不完美很多应用协议在 overlay 上会有限制。5. docker网络不通的完整排查链路一条命令一条命5.1 从容器内部往外ping先分清是容器不通还是外部不通乱成一团的网络问题最忌讳线上抓瞎乱改。我的排查流程永远从分层定位开始确认容器本身状态docker ps -a看看容器是否在 running退出码有没有异常最近一次启动日志是否正常。进入容器测试本机回环docker exec -it container bash之后执行ping 127.0.0.1或curl localhost:端口。回环不通说明应用层或容器进程有问题这时再查网络都是白费力气。测试同网络内其他容器ping 另一个容器名能通说明网络内部链路没问题问题出在出网环节。测试宿主机网关在容器里 ping 宿主机的网桥 IP默认是ip addr show docker0里的 172.x.x.1。这步能判断 veth 和 bridge 转发链路是否正常。测试外网ping 8.8.8.8某些地区可能不响应可以换ping 114.114.114.114。能通说明 NAT 出口没问题不通则重点检查宿主机的 iptables FORWARD 链和 ip_forward 开关。这套链路走完问题基本能缩小到某个具体环节了。我用这招解决过不少看起来是 Docker 问题的故障最后发现是容器里根证书过期、应用没监听正确端口这类非网络故障。5.2 宿主机上的三步检查ip_forward、iptables、firewalld如果容器内上网失败回到宿主机从上往下查。先看内核转发开关sysctl net.ipv4.ip_forward # 期望输出 net.ipv4.ip_forward 1如果输出是 0Docker 容器所有出网请求都会被丢弃。可以直接写入配置文件echo net.ipv4.ip_forward1 /etc/sysctl.conf sysctl -p然后检查 firewalld 是否在运行时把 docker0 放在 trust 之外。在 RHEL/CentOS 系系统上firewalld 和 Docker 共存经常导致转发被 DROP。保险的做法是把 docker 相关接口加入 trust 区域firewall-cmd --permanent --zonetrusted --add-interfacedocker0 firewall-cmd --reload最后看 iptables 的 FORWARD 链有没有 ACCEPT 策略。执行iptables -L FORWARD -n如果第一条不是 ACCEPT 或 DROP 在 ACCEPT 之前且带有关联状态为 established/related就需要按 Docker 网络规则重新调整。注意很多人机器上有 K8s 或别的网管工具它们的 iptables 规则经常在 Docker 启动后被覆盖这种冲突属于经典疑难杂症。5.3 端口映射单独排查宿主机访问容器服务失败容器能通外网但宿主机访问容器端口就是不行——这种也算高发问题。按顺序排查# 1. 查看端口映射是否真实存在 docker port container # 2. 在宿主机上测试映射端口 curl http://127.0.0.1:映射端口 # 3. 查看 DNAT 规则是否被其他规则覆盖 iptables -t nat -L DOCKER -n如果docker port显示空的说明容器启动时没加-p或--network host映射自然不存在。如果docker port有内容但宿主机访问不通看 NAT 表里 DOCKER 链中 DNAT 规则是否还在。常见情况是用户用 ufw 或 firewalld 清空过规则Docker 生成的 DOCKER 链没了这时重启 Dockersystemctl restart docker但注意重启 Docker 会重建 iptables 规则也会把正在运行的容器停掉所以业务高峰期别乱重启。另一个安全做法是手动重建缺失的规则iptables -t nat -A DOCKER -p tcp --dport 18800 -j DNAT --to-destination 172.17.0.2:8080这属于应急手段能临时恢复服务但还是建议通过调整防火墙配置来根治避免每次都被同样的规则清空坑到。5.4 几个典型症状的快速对照表现象优先检查项大概率根因容器 ping 网关都不通veth 状态、docker0 是否存在网桥被删或 Docker 服务异常能 ping 通网关但上不了网ip_forward、iptables FORWARDNAT 或转发链被禁用两个容器互相找不到是否在同一网络、DNS 配置不在同一自定义网络/手动 dns 覆盖宿主机访问端口失败docker port、NAT DOCKER 链映射丢失或防火墙拦截跨主机容器不通overlay 网络状态、VXLAN 端口Swarm 或防火墙隧道端口未放行5.5 Docker Desktop 场景下 Windows/macOS 的特别提醒平时在开发机上用 Docker Desktop 的朋友会碰到一个和 Linux 服务端不太一样的点容器中的 localhost 指向的并不是宿主机。因为在 Windows/macOS 中Docker Desktop 其实是跑在一个轻量虚拟机里的容器看到的 localhost 是虚拟机内部而外部浏览器要访问宿主机映射端口时正常通过-p 8080:80就能访问 localhost:8080但容器内访问宿主机上的服务比如连接宿主机上的 Redis时不能写127.0.0.1要写host.docker.internal。这个域名是 Docker Desktop 内置的专用于容器访问宿主机。Linux 原生 Docker 没有这个域名除非你手动在容器里加 hosts 条目。我在新同事的电脑上看到过不少次类似的困惑容器里启动成功浏览器打不开宿主机映射端口最后发现是 Windows 防火墙把 Docker 使用的 Hyper-V 虚拟网卡挡了。解决方式很简单在 Windows 防火墙高级安全里放行 Docker Desktop 相关进程的出入站规则或者临时关闭防火墙验证——如果关闭后一切正常那基本就是防火墙拦截确认无疑。这也是开发机上面最容易被忽略的隐藏坑。6. 我的随身配置清单和几点经验沉淀最后分享一份我长期在用、直接抄作业即可的 Docker 网络配置清单按场景分类单机跑常规业务服务使用自定义 bridge 网络子网规划按项目名分网段例如 user-service 用 172.28.0.0/24order-service 用 172.29.0.0/24容器之间用容器名互访不要依赖固定 IP。需要和宿主机共享端口、追求性能使用 host 模式但要提前统一管理端口清单避免容器进程和宿主机进程抢端口。强隔离离线任务使用 none 模式完全不暴露网络。多 compose 项目间共享网络需要共享的网络统一放到独立的shared-net里在 compose 文件里声明为external: true。所有服务建议显式声明 networks不依赖默认 bridge。显式声明的好处是后期维护时一眼能看清服务间的网络关系而且重启容器也不用担心默认网段拥堵或冲突。还有两个不常被文档强调但实战中很重要的心得。第一个是永远不要在容器运行中途修改 network mode除非你完全清楚自己在干什么。容器创建后网络模式就固定了切换模式必须重新创建容器。所以很多我明明改对了参数为什么还连不上的怪问题根子在于旧的孤儿容器还占着同名的端口或 IP新容器启动失败或网络冲突。排查时用docker ps -a把所有旧容器清理干净再启动能避免大量无效的抓狂。第二个是保留一份干净的端口映射记录。用docker ps --format table {{.Names}}\t{{.Ports}}定期导出端口分配情况放到项目文档里。项目大了之后业务容器数据库监控一堆容器同时跑端口冲突会成为常态。提前做端口资产管理网络排障的时间能省下一大半。Docker 网络模式这个东西入门时觉得复杂其实底层就那几个概念命名空间隔离、veth 连接、网桥转发、iptables 规则。把这些链路理顺多练几次创建网络、配置 Compose、跑通容器通-网关通-外网通-端口映射通的完整链路后面几乎不会再出现让你束手无策的网络故障。如果哪天又踩到新坑优先从docker network inspect和iptables -L -n两个命令开始答案通常就在输出里。