资讯动态

Docker网络配置实战:驱动选型、容器互通与故障排查

发布时间:2026/9/18 4:53:38 来源:尧图企业网站定制
1. Docker 网络配置的整体设计思路与驱动选型1.1 为什么网络是新手崩的第一个点刷完 docker 安装教程、把镜像拉下来、容器也run起来了然后打开浏览器输入localhost:8080页面转圈转到超时——这大概是每个学 Docker 的人都会经历的第一次破防。我当年也是明明docker ps里容器状态写着 Up日志也打印了启动成功监听 8080可宿主机就是连不上。折腾两个小时之后才发现容器里的 8080 和宿主机的 8080 是两套完全独立的网络栈中间那根线要我自己手动搭。这就是 Docker 网络配置最容易被忽略的地方安装 docker desktop、装 ubuntu 安装 docker、装 centos7 配置网络这些步骤本身都很顺教程也不会出错真正卡人的是后面——容器跑起来了但它和外面这个世界没有任何默认的通道。容器的 IP 是私有的、容器之间的名字默认不互通、容器的端口默认不外露这三点叠加在一起新手必然懵。这篇笔记不打算把网卡、网桥、命名空间的理论从头讲一遍网上那些东西够多了。我想做的是把我实际配置 docker 网络时踩过什么、怎么排查、最后形成什么习惯写清楚。无论你是在 Windows 上用 Docker Desktop还是在 ubuntu 20.04 / ubuntu 22.04 / centos7 / rocky9 上装 docker网络这一层的逻辑是一样的只是宿主机侧的差异需要注意。看完之后你至少能做到三件事知道几种网络模式该在什么时候用、能独立排查容器连不上这类问题、能写出一个不会在生产环境翻车的 compose 网络配置。1.2 一句大白话Docker 到底给容器装了几张网卡把 Docker 的网络想成一栋公寓楼会好理解很多。宿主机是整个小区的物业docker0这个网桥就是小区中间的那条主干道每起一个容器Docker 就给它拉一根虚拟网线veth pair接到这条主干道上容器内部看到的那张网卡就是这根网线的这一头另一头插在docker0上。容器拿到的 IP默认从 172.17.0.0/16 里分相当于这栋楼的门牌号只在小区内部有效外面的人想找它必须经过物业登记端口映射。再往下一层每根网线两头其实运行在不同的网络命名空间network namespace里。命名空间的意思是容器有自己独立的一张路由表、一份/etc/resolv.conf、一套 iptables 规则和宿主机完全隔离。所以你在宿主机上ping 127.0.0.1和容器里ping 127.0.0.1命中的是两个不同的东西。理解了这一点容器访问宿主机服务该填什么地址这个经典问题就自然有答案了——填 127.0.0.1 是找不到宿主机的那指的是容器自己。Docker 起的每一张网卡、每一个网桥、每一条 iptables 规则都是守护进程在后台自动完成的。你可以用ip link show在宿主机上看到一堆vethxxxx开头的接口那些就是容器的网线头。看到它们说明网络这套机制正在正常工作。1.3 六种网络驱动别再用默认的那一个docker network ls一敲通常能看到 bridge、host、none 三个。这三个是默认存在的但真正干活的驱动远不止这些。我整理了一张选型表这张表比我第一次看官方文档时那几大段描述有用得多驱动类型典型使用场景容器 IP端口映射容器间按名字互访bridge默认最基础的单机容器有172.17.x.x需要-p不支持只能靠 IP自定义 bridge单机上多个容器协作有自定义网段需要-p支持host追求极致性能、端口固定无共用宿主机不需要也不能用直接走 localhostnone纯计算任务、不需要网络只有 lo不可用不可用container:名称共享另一个容器的网络栈与目标容器相同不可用走 localhostoverlay多台宿主机组成集群有跨主机可通需要-p或入口转发支持选型逻辑其实只有三句话。第一单机上的多个容器要互相调用一律用自定义 bridge不用默认的那个第二只有当容器需要独占宿主机端口、或者你对网络延迟极度敏感时才考虑 host 模式第三容器之间不需要网络比如纯做数据处理的批处理任务直接--network none最安全少暴露一个攻击面。提示在 Linux 上使用 host 模式时-p参数会被忽略并打印一条警告因为容器已经没有独立的网络栈了。很多人第一次遇到这个警告会以为是报错其实不是。Overlay 和 macvlan 属于进阶内容。macvlan 的用途是给容器发一个和宿主机同网段的真实 IP让它直接出现在公司局域网里适合跑一些需要被交换机层面识别的服务但它有个硬伤宿主机和 macvlan 容器之间默认不能直接通信得再补一张网卡。Overlay 则依赖 Swarm 集群单机环境用不上。小白阶段先把自定义 bridge 玩透比什么都强。2. 默认 bridge 的三个大坑与自定义网络的正确姿势2.1 坑一容器名互相 ping 不通新手最常干的事是这样的起了两个容器一个叫 web一个叫 db然后在 web 里试图ping db结果提示ping: db: Name or service not known。但换成ping 172.17.0.3就通了。这不是网络断了而是默认的 bridge 网络根本没有内置 DNS 解析服务。Docker 只在自定义网络里部署了一个内嵌的 DNS 服务器容器里/etc/resolv.conf会指向 127.0.0.11它负责把容器名、容器别名、服务名解析成对应的 IP。默认 bridge 是 Docker 早期留下的历史包袱那时还没这套机制所以只保留了通过--link写死 hosts 的老办法。解决方式很简单一行命令docker network create app-net docker run -d --name db --network app-net mysql:8.0 docker run -d --name web --network app-net nginx docker exec -it web ping db你会发现ping db直接通了。这个差异背后的逻辑是自定义网络里的每个容器都会被 DNS 服务登记一次容器名即域名。这也解释了为什么很多 compose 项目里服务名可以直接当主机名用——compose 默认会给你创建一个独立的 bridge 网络所有服务都挂在上面。2.2 坑二默认网段和公司内网撞车默认 bridge 用的 172.17.0.0/16如果你公司内网恰好也用 172.17 或者 172.16 段那么宿主机上的路由表就乱了套访问某个内网机器时包可能会被送进 docker0 网桥。表现是宿主机访问公司某个服务突然不通或者容器访问内网数据库超时。这个问题不会报错只会莫名其妙不通排查起来非常耗时间。所以我的习惯是任何正式一点的环境都不使用默认网段创建网络时显式指定一个不容易冲突的私有段。docker network create \ --driver bridge \ --subnet 10.88.0.0/16 \ --gateway 10.88.0.1 \ --opt com.docker.network.bridge.namebr-app \ app-net这里--opt com.docker.network.bridge.name是给宿主机上那张网桥起个可读的名字默认会生成br-加一串随机字符。环境多了以后br-app、br-web这种名字比一堆乱码好认太多。另外还可以用--ip-range限制容器的地址分配范围比如网段是 /16 但只想用里面的一小段给容器剩下的留给别的东西。如果宿主机上跑了几十个网络光靠每次手动指定太累可以在/etc/docker/daemon.json里配置默认地址池{ default-address-pools: [ { base: 10.200.0.0/16, size: 24 } ] }改完之后systemctl restart docker。注意这一步会重启守护进程所有容器都会停做之前先确认能接受停机。这也是为什么很多人问docker 权限错误怎么解决failed to connect to the docker api这类问题——如果 daemon.json 写错了缩进或者语法守护进程起不来客户端就会连不上那个 socket。2.3 坑三--link 已经是历史遗留江湖上还流传着--link的写法网上有些老教程还在用。我的建议是新项目一律不要用。它做的是单向的 hosts 注入A 链接 BA 能解析 BB 反过来解析不了 A还得再加一次。而且容器重启后 IP 变化会让 link 关系失效跨网络更是完全不支持。自定义网络从设计上就把这事解决了双向解析、动态更新、支持别名。想给某个容器加多个名字用--network-aliasdocker run -d --name db --network app-net \ --network-alias mysql --network-alias primary-db \ mysql:8.0这样 web 容器里ping mysql、ping primary-db、ping db都能通。这个技巧在做数据库主从切换的时候特别有用——应用里写死连primary-db切换时只需要改指向不用动应用配置。我在做 redis 主从、mysql 主从这类部署时都会加上别名后面维护省不少事。2.4 自定义网络的落地写法与可回收性创建网络还有一个容易被忽视的点清理。项目做多了docker network ls会冒出一大堆none或者没用到的网络。删除之前要确认没有容器还在用docker network inspect app-net --format {{range .Containers}}{{.Name}} {{end}} docker network rm app-net docker network pruneinspect那一行的格式化输出是我常用的一个小技巧直接列出该网络下所有容器名比翻一长串 JSON 快得多。prune会删除所有没被容器引用的网络跑之前最好先docker network ls看一眼避免删掉正在准备用的。另外一个实战习惯网络名带上项目前缀比如shop-web-net、shop-db-net而不是简单叫web-net。一台机器上同时跑三四个项目是常态命名不带前缀半年后自己都认不出来哪个是哪个。3. 从零实操搭一套可复现的容器网络环境3.1 第一步永远是先看清楚现状在动手改任何东西之前先做一次现状盘点这是我吃了亏之后养成的习惯。有一次我调了半天端口不通最后发现是前一个测试留下的容器占了同样的端口Docker 又没报错因为人家的容器还在跑。# 看看有哪些网络 docker network ls # 看某个网络的详细配置包括网关、已连接的容器 docker network inspect bridge # 看宿主机上的网桥接口和地址 ip addr show docker0 # 看当前生效的转发和 NAT 规则 sudo iptables -t nat -L DOCKER -n | head -20docker network inspect输出是 JSON信息量很大重点看三个字段Subnet网段、Gateway网关、Containers已接入的容器。如果发现某个网段的容器数量异常多就要留意是不是测试残留。在 Windows 上用 Docker Desktop 的朋友宿主机侧看不到docker0因为实际的 Linux 环境跑在 WSL2 的一个轻量虚拟机里。这种情况下排查方式不一样wsl --shutdown重启一下子系统往往能解决大部分网络抽风。顺便提一句Docker Desktop 启动时报virtualisation support not detected这类提示通常是 BIOS 里的虚拟化开关没打开或者 Windows 的 Hyper-V / WSL2 后端没有启用这跟网络配置无关但很多人会混在一起找不到北。3.2 创建一个带自定义网段的网络把前面说的要点合起来一个完整可用的网络创建命令应该长这样docker network create \ --driver bridge \ --subnet 10.88.0.0/16 \ --gateway 10.88.0.1 \ --ip-range 10.88.10.0/24 \ --opt com.docker.network.bridge.namebr-app \ app-net docker network inspect app-net参数为什么这么选--subnet用 10.88 是因为它在 RFC1918 私有地址里同时 10.88 这个段被企业内部使用的概率相对低冲突风险小。--ip-range限定在 /24意思是这个网络虽然号称 /16 大但容器只从 10.88.10.0/24 里分配地址剩下的空间留白将来需要扩展时再调整。--gateway不指定的话 Docker 会自动取网段第一个可用地址显式写出来是为了让配置文件自解释。这行命令跑完之后ip addr show br-app应该能看到一张新网桥地址是 10.88.0.1。如果用的不是 Linux 宿主机这一步看不到也没关系容器之间照样能通。3.3 端口映射 -p 的参数细节比你想的讲究-p是新手用得最多也最容易迷糊的参数。它的完整格式其实是四段ip:hostPort:containerPort/protocol。绝大多数教程只写-p 8080:80把中间的门道全省了。# 最简写法绑定到所有网卡的 8080 docker run -d -p 8080:80 nginx # 只绑定到本机回环外部机器访问不到 docker run -d -p 127.0.0.1:8080:80 nginx # 只绑定到指定网卡比如只有内网能访问 docker run -d -p 10.88.0.1:8080:80 nginx # 随机分配宿主机端口用 docker port 查看 docker run -d -P nginx绑定地址这一项在服务器上非常重要。像数据库、管理后台这类不该对外暴露的服务用127.0.0.1:3306:3306只允许本机访问是最省事的安全隔离方式。如果你在公司服务器上直接-p 3306:3306而宿主机防火墙又刚好放行了那这个数据库实际上就暴露在办公网里了——这种事我在真实环境里见过不止一次。还有两个细节值得记住。第一-p可以写多次一个容器可以映射多个端口。第二协议默认 TCP需要 UDP 时显式写-p 53:53/udpDNS 类服务经常两者都要。注意端口映射是靠宿主机 iptables 的 DNAT 规则实现的它可能绕过部分应用层防火墙的端口限制。也就是说即使系统防火墙看起来没放行 8080Docker 发布的端口仍然可能从外部访问到。发布端口前先想清楚这个服务真的需要被外面看到吗。3.4 compose 里怎么描述网络别全塞进 default用 docker compose 的人越来越多但很多 compose 文件里一个networks都没写所有服务默认挤在同一个网络里。这在开发环境没感觉一旦服务多起来数据库能被前端直接访问到隔离性为零。一个我常用的结构是这样services: gateway: image: nginx:1.25-alpine ports: - 80:80 networks: - frontend app: image: myapp:latest networks: - frontend - backend db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: change-me networks: - backend networks: frontend: driver: bridge backend: driver: bridge internal: true这里的关键在于internal: true。它表示这张网络没有对外的出口接入的容器不能访问外网也拿不到被映射到宿主机的公网入口。数据库放在这张网里即使配置写错了外面也摸不到它。这个参数我强烈建议所有涉及数据库、缓存、消息队列的 compose 项目都加上。gateway只挂在 frontenddb只挂在 backendapp两边都挂承担桥梁。这样前端服务即使被攻击也够不着数据库。网络分层的思路和微服务项目里划分内外网是同一回事只是 Docker 把这个成本降到了几行 YAML。3.5 验证连通性四个动作搞定配完网络不要直接交付按顺序跑四个验证# 1. 容器间按名字解析 docker exec -it app ping -c 2 db # 2. 容器访问外网 docker exec -it app ping -c 2 223.5.5.5 # 3. 容器访问宿主机上的服务 docker exec -it app curl -s -o /dev/null -w %{http_code}\n http://host.docker.internal:9000 # 4. 宿主机访问容器发布的服务 curl -s -o /dev/null -w %{http_code}\n http://127.0.0.1:80第 2 步用 IP 而不是域名是为了把网络通不通和DNS 好不好使分开验证这是排查顺序的基本原则——先通网络再通名字。第 3 步在 Linux 上需要额外做一步host.docker.internal这个特殊域名 Docker Desktop 自带Linux 上默认没有要在启动时手动加docker run -d --add-hosthost.docker.internal:host-gateway myapphost-gateway是 Docker 的保留值它会自动替换成宿主机在这张网桥上的地址通常就是网关 10.88.0.1。比起硬编码一个 IP这种写法在网段调整后依然有效。4. 常见问题与排查技巧实录4.1 容器连不上外网按这个顺序查容器里 ping 不通外网是高频问题排查必须讲顺序乱试只会更糊涂。第一docker exec -it 容器名 cat /etc/resolv.conf。如果显示的是一个奇怪的 IP 或者宿主机上并不存在的 DNS域名解析必然失败。自定义网络里应该是 127.0.0.11宿主机上的 DNS 配置会通过它转发。想统一指定 DNS在 daemon.json 里加dns: [223.5.5.5, 119.29.29.29]。第二docker exec -it 容器名 ping -c 2 8.8.8.8。IP 能通但域名不通那就是 DNS 问题IP 也不通往下看。第三在宿主机上sysctl net.ipv4.ip_forward。这个值必须是 1Docker 守护进程启动时会自动设置但如果之前被别的脚本改成 0容器就出不去。临时改sysctl -w net.ipv4.ip_forward1永久改写到/etc/sysctl.d/下的配置文件。第四检查 FORWARD 链。Docker 会往 iptables 的 FORWARD 链里插规则如果系统上装了 firewalld 且默认策略是 DROPDocker 的规则可能被排在后面。用sudo iptables -L FORWARD -n -v看有没有包被丢掉。这也是为什么很多人在 centos7 网络配置调好之后Docker 网络还是不通——宿主机的防火墙和 Docker 的规则打架了。4.2 宿主机访问不到容器端口八成是这三个原因如果容器日志显示服务正常但宿主机curl 127.0.0.1:端口没反应按可能性从高到低查原因一应用只监听了容器内的 127.0.0.1。这是最隐蔽的一个。有些服务默认配置里写的是bind 127.0.0.1那它只接受来自容器内部的连接端口映射过去也是空的。解决办法是改配置成0.0.0.0让服务监听所有网卡。判断方法很简单进容器里curl 127.0.0.1:端口能通但在宿主机上不通基本就是这个。原因二端口映射绑错了地址。前面说过-p 127.0.0.1:8080:80只对本机开放如果你从另一台机器访问当然不通。用docker port 容器名能看到实际绑定情况。原因三宿主机上已经有进程占了这个端口。Docker 发布端口时如果冲突会直接报错但如果是先启动容器、后来宿主机上又起了别的服务抢端口那表现就是时好时坏。sudo ss -lntp | grep 端口号一看便知。4.3 容器访问宿主机上的服务地址到底填什么这个问题几乎每个人都会问一遍因为直觉上宿主机就是127.0.0.1但容器里那个127.0.0.1是它自己。准确的答案是填宿主机在这张容器网络里的地址也就是网桥的网关地址。默认 bridge 下是172.17.0.1自定义网络下是你创建时指定的--gateway。命令行里可以用ip addr show br-app确认。但硬编码网关有个问题网络重建后网关可能变。所以更推荐两种方式。一是前面提到的--add-hosthost.docker.internal:host-gateway写一次到处能用二是在 compose 里用extra_hosts声明services: app: image: myapp:latest extra_hosts: - host.docker.internal:host-gateway还有一个前提条件宿主机上的那个服务本身必须监听0.0.0.0或者网桥地址如果它只监听127.0.0.1容器通过网桥地址也是访问不到的。这个坑和 4.2 里的原因是同一个逻辑的另一半只是方向相反。我自己就被这个坑卡过一次本地起了一个只监听回环的开发服务容器里怎么都连不上改监听地址之后立刻通了。4.4 MTU 不一致小包能通大包卡死这是最像玄学的一个问题ping能通小请求能通稍微大一点的响应就卡住或者超时。我第一次遇到时完全没头绪后来才明白是 MTU 的事。原理说一下。宿主机的网卡 MTU 一般是 1500容器网桥默认也取 1500。但如果宿主机本身在网络环境里需要通过一条封装链路出去比如某些云主机的内部网络实际可行的 MTU 会小于 1500。这时候小包正常大包被分片或者被丢弃表现就是有时候好用有时候不好用。验证方法和解决办法# 在容器里测大包 docker exec -it app ping -c 2 -s 1400 8.8.8.8 docker exec -it app ip link show eth0 # 创建网络时直接指定 MTU docker network create --opt com.docker.network.driver.mtu1400 app-net也可以全局在 daemon.json 里加mtu: 1400。具体填多少需要试从 1400 开始,能通就往下加,找到临界值再留 20 左右余量。这个参数一旦调对之前那些偶发超时会突然全部消失。4.5 一份能直接照着查的速查表现象最可能的原因快速验证容器名 ping 不通用了默认 bridgedocker network inspect bridge看是否有 DNS容器 IP 能通、域名不通DNS 配置问题cat /etc/resolv.conf应含 127.0.0.11容器完全出不了网ip_forward 为 0 或 FORWARD 被拦sysctl net.ipv4.ip_forward宿主机连不上容器端口应用只听 127.0.0.1进容器 curl 本机端口容器连不上宿主机服务填了 127.0.0.1改用网关地址或 host-gateway小包通、大包断MTU 不匹配ping -s 1400对比测试网段冲突默认 172.17 撞内网换用 10.x 段重建网络重启后配置全丢网络是临时创建的写进 compose 或用脚本固化5. 多容器协作的网络分层与生产约定5.1 按职责切网络而不是全塞一个前面提了 compose 里分层这里说说单机手写场景下的分层思路。我的习惯是按谁能访问谁来切而不是按什么服务来切。通常三张网就够了入口网只跑对外提供服务的容器比如 nginx 这类统一入口端口映射只在这张网上发生。应用网业务容器之间互调比如应用服务调缓存、调消息队列。数据网数据库、存储设成internal无外网出口。这样切的好处是出问题时影响范围清晰。前端容器被打了它够不着数据库某个业务容器需要临时访问外网抓数据只把它接到有出口的网里其他容器不受影响。代价是配置文件变长了一点容器可能同时属于两张网。但这部分成本在排查故障时能十倍地还回来。我经历过一次数据库被前端容器里的错误配置意外清库的事从那以后所有项目一律给数据网加internal。5.2 只暴露必要端口以及 DOCKER-USER 的正确用法发布端口是攻击面这句话在容器里尤其成立。我的判断标准很简单这个端口是给人访问的还是给容器访问的。给人访问的才用-p给容器访问的一律走容器网络内部的端口不发布。如果确实需要在宿主机层面统一管理进出流量Docker 提供了一条专门的链DOCKER-USER。它的好处是 Docker 升级或者重启时不会清掉你写的规则因为它排在 Docker 自己维护的规则之前。# 只允许 10.88.0.0/16 访问宿主机的 3306 sudo iptables -I DOCKER-USER -p tcp --dport 3306 ! -s 10.88.0.0/16 -j DROP这条规则的读法是往 DOCKER-USER 链头部插入一条规则目标是 tcp 协议的 3306 端口源地址不是 10.88.0.0/16 的全部丢弃。注意!的位置和-I而不是-A前者保证规则在最前面生效。改完之后一定要用外部机器实测一次别只看规则列表。5.3 跨主机通信先想清楚是不是真的需要容器跨主机通信有两个方向一是 overlay 网络配合集群二是让容器直接拿宿主机所在网段的 IPmacvlan。这两个方案我都用过也都不推荐新手一上来就碰。原因很实在overlay 需要集群环境出了问题的排查链路比单机长得多而且网络的抖动会直接影响服务发现macvlan 虽然简单但交换机的 MAC 地址表容量、宿主机与容器之间的通信限制都是容易忽略的坑。大多数中小规模的场景正确的做法其实是让容器就待在单机网络里跨主机的事交给上层的负载均衡入口去处理每台机器上的入口容器监听宿主机端口流量从上层的统一入口分发下来。这样每一层的职责都很清晰——容器网络管容器之间宿主机网络管机器之间。稳妥好排查改动成本低。顺便说一句如果是麒麟、龙芯这类国产环境的 Docker 部署网络层的逻辑完全一致只是镜像来源和基础环境需要额外确认。docker network的命令、参数、行为都不变不需要为此改变配置习惯。我个人在配置这套东西时最重要的一条体会是不要等到出问题才去理解网络。每次新起一个项目先把网络拓扑想清楚——谁需要被外面访问、谁之间要通、谁绝对不能被碰——然后落成配置文件。这个习惯养成之后你会发现自己几乎不再需要半夜爬起来查怎么容器又连不上了。最后再分享一个小技巧把常用的网络创建命令写成一个init-network.sh放在项目根目录新机器上一条命令重建整套环境比记参数靠谱得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价