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

Docker网络模式与容器互通:从原理到排障

Docker网络模式与容器互通:从原理到排障 ★ FEATURED ARTICLE
先抛个观点Docker里的网络问题十有八九不是你的应用代码写错了而是没搞清楚数据包是怎么在容器之间、容器与宿主机之间“漂移”的。容器网络和容器间网络互通看似是Docker的“进阶内容”实际上只要你会装Docker、会跑容器就一定绕不开端口映射、网络模式、互通配置这些事。这篇把Docker网络模式、自定义网络、跨容器打通一次性讲透照着命令敲一遍基本能应付日常开发和部署里的绝大多数网络场景。我见过太多人卡在同一道坎上容器能启动、日志也正常但外面就是访问不到或者两个容器之间怎么都Ping不通。原因往往不是复杂的高深配置而是对Docker的网络模型没有一个整体印象。本文适合刚学Docker不久、对bridge和host一知半解的人也适合已经写了好几个服务但被跨主机通信折腾过的开发者。咱们从最底层的“一个容器怎么拿到IP”开始聊起再逐步讲到自定义网络、多网络连通和排障方法全程用命令做演示你可以直接照着敲。1. 先搞懂基础一个容器是怎么“上网”的1.1 容器网络最小模型veth pair、docker0和Network NamespaceDocker容器没有独立的物理网卡它靠的是Linux内核的Network Namespace来做网络隔离。每个容器都有自己的Network Namespace里面有自己的网卡、路由表、iptables规则但对外通信必须借道宿主机。这句话听起来抽象拆开来看就很好懂。你在机器上跑一条docker run -d --name web -p 8080:80 nginx后Docker实际做了这么几件事创建一个新的Network Namespace相当于给容器一个独立的“网络房间”。在宿主机和这个房间之间拉一根虚拟网线专业术语叫veth pair一端在宿主机上另一端在房间里。把房间那端的网卡配上IP地址让容器认为自己有一块正常的网卡。把宿主机这端的veth口塞进一个叫docker0的Linux网桥里。docker0是什么它就是一台虚拟交换机默认IP段一般是172.17.0.0/16网关是172.17.0.1。你在宿主机上执行ip addr show docker0能直接看到这个网桥设备。所有默认bridge模式启动的容器都会通过veth pair接到这台“交换机”上彼此之间二层可达借助三层路由和NAT规则出外网。所以“容器连接网络”这件事本质上就是容器网卡 → veth pair → docker0网桥 → 宿主机路由转发 → 外部网络。这个过程并不复杂但它是后面所有网络模式的基础。日常大家最常用的bridge模式底子就是这个模型只是不同的bridge网络互相隔离而已。1.2 通过命令看清容器网络的真实样子很多教程喜欢直接给结论但我想说一句真心话容器网络这块不动手看一眼永远只能背结论。下面这几条命令基本是必看的建议实际操作的时候每条都执行一遍。# 查看宿主机上的网桥和veth接口 ip a # 查看docker0网桥的IP和网段 ip addr show docker0 # 查看一个容器的网络详情 docker inspect web --format {{.NetworkSettings.IPAddress}} # 进入容器的Network Namespace查看内部网卡 nsenter -t $(docker inspect -f {{.State.Pid}} web) -n ip addr我最早接触到这套命令时最大的感受是容器里看到的eth0ifXX和宿主机上看到的vethXXXX其实是同一条虚拟网线的两端。对着看一次就彻底明白了“容器和宿主机共用一个内核网络栈但隔离在各自Namespace里”这句话是什么意思。如果想看端口映射的底层实现还得去看NAT规则iptables -t nat -L -n | grep 8080正常情况下你会看到一条DNAT规则把访问宿主机8080端口的数据包改写目标地址为容器IP的80端口。这就是-p 8080:80的底层真相不是Docker“自动转发”而是它替你维护了iptables规则。1.3 容器之间怎么通信三种最常用的方式同一台宿主机上两个容器要互通主流做法有三种我从最推荐到底层最原始依次说。第一种是自定义bridge网络 容器名互访。这也是生产环境最推荐的用法。创建一个自定义bridge网络之后同一个网络里的容器可以直接用容器名做DNS解析比如在容器A里执行ping myapp就能解析到容器B的IP。这个方式的好处是IP变了也不影响容器重建之后名字不变通信照常。默认的docker0网桥是不支持容器名DNS解析的这一点很多人踩过坑——在默认bridge网络里你只能用--link别的容器而且--link本质上只是往/etc/hosts写了一条静态记录容器重建后记录不会自动更新非常脆弱。第二种是直接通过IP互访。既然同一个bridge网络里的容器都连着同一个网桥那么它们之间二层互通只要网络没隔离直接用IP Ping肯定通。但问题是容器IP在重建后会变脚本里写死IP就非常难受所以IP互访只适合临时验证。第三种是用--networkcontainer:另一个容器两个容器共享同一个Network Namespace。这种情况下它们连IP都一样天然互通还省了端口映射的麻烦。适合一些需要共享网络栈的场景比如Sidecar模式抓包调试但正常业务里用得不多。2. Docker网络模式逐个拆解选对的模式少走一半弯路2.1 五种内置网络模式的真实使用场景Docker官方把网络模式分为bridge、host、none、container、overlay这几种。每次有人问我“该选哪种”我都是按下面的逻辑给建议不是看谁功能多而是看你的部署需求。先看一张核心对比表后面再逐个展开。网络模式底层实现容器是否独立IP容器间互访方式适用场景缺点bridge默认Linux网桥 veth pair是同网络内IP/容器名互访跨网络需connect单机开发、默认部署端口映射有性能损耗默认bridge无DNS解析host直接使用宿主机网络栈否就是宿主IP直接用宿主端口性能敏感、需要固定端口的服务无隔离端口冲突风险高none只有loopback网卡否无离线任务、安全隔离容器完全不能对外通信container复用另一个容器的网络栈共享IP本机和共享对象互通Sidecar模式、调试抓包网络耦合强容器生命周期绑定overlayVXLAN隧道跨主机是DNS名称跨主机互访跨主机集群、Swarm、K8s配置略复杂有额外封装开销这张表建议收藏或者截图存一下选模式之前拿出来扫一遍基本不会选错。2.2 默认bridge和自定义bridge差距不只是“名字好听”很多人以为默认的bridge网络和自定义bridge网络是同一个东西这是很大的误区。默认bridge确实也是网桥但功能和体验上差了一大截。关键差距有三个。第一自定义bridge支持内置DNS解析容器之间可以直接用服务名通信默认bridge不行只能靠静态的--link。第二自定义bridge里容器可以动态加入和退出互访不受影响默认bridge则是绑死在一台上退出再重建就得换IP。第三默认bridge和自定义bridge之间默认隔离而多个自定义bridge之间如果不做connect也不能通。所以只要不是一次性跑个临时容器我强烈建议一律用自定义bridge。创建自定义bridge网络非常简单命令就一条docker network create -d bridge my-net跑起来之后你在宿主机上会看到多了一个网桥名字是一串哈希前缀一般叫br-xxxxx。再启动容器时指定网络就能享受容器名DNS解析的能力。docker run -d --name app1 --network my-net nginx docker run -d --name app2 --network my-net nginx docker exec app1 ping app2这里ping app2能通就是内置DNS的功劳。你可以在容器里看/etc/resolv.conf会看到一个127.0.0.11的地址这是Docker自带的嵌入式DNS服务器专门负责容器名到IP的解析。2.3 host和none模式什么时候用host模式是最“暴力”的一种容器不创建自己的Network Namespace直接复用宿主机的网络栈。启动容器后你在容器里看到的IP就是宿主机的IP服务监听0.0.0.0:8080就等于宿主机监听8080。好处是网络延迟最低数据包不经过NAT和网桥适合对性能要求很高、讲究稳定连接的服务比如某些视频转码、高性能缓存之类的中间件。坏处也很明显没有隔离容器里改路由表、防火墙规则都可能影响宿主机端口冲突更常见两个容器都要用80端口就起不来必须手动协调。none模式则相反容器只有loopback没有物理网卡也没有IP彻底“断网”。这种模式不是没用而是用得很专一。比如某些离线批处理任务、需要强制隔离高安全风险的容器用none是最干净的。还有一种情况是机器本身没配网络但你不想让容器自作主张去连也可以用none。还有一个经常被忽略的是container模式它可以让新容器直接复用现有容器的网络栈。比如调试某容器网络时我经常起一个带curl、tcpdump的临时容器通过container模式共享目标容器的网络直接看到的就是目标容器的网络状态非常方便。3. 自定义网络实操从创建到跨网络互通3.1 自己规划网段为什么不要偷懒用默认网段自定义bridge网络的另一个好处是可以自己规划IP段。默认docker0是172.17.0.0/16如果你所在的局域网刚好也用了172.17.0.0这个段就有了网段冲突风险。虽然实际冲突概率不低但很多人根本没意识到这个问题。创建时可以这样指定子网和网关避免和现有网络撞车docker network create -d bridge \ --subnet172.28.0.0/16 \ --gateway172.28.0.1 \ my-net172.28.0.0/16理论上可用的地址是65534个对于一个普通的开发环境绰绰有余。如果你只需要几十个容器也可以把子网缩到/24比如172.28.1.0/24可用地址从.1到.254一共254个规划更清晰。给容器指定固定IP也有用比如某些旧服务把IP写死在配置里没法改成域名那就可以用docker run -d --name app1 \ --network my-net \ --ip 172.28.0.10 \ nginx这里我多说一句插件地址池的事。很多人在同一台机器上建了一堆网络每个都不指定子网最终都落在172.17、172.18、172.19这种相邻网段上表面上没冲突但Docker埋点监控、服务发现之类的工具默认扫描整个私有段时很容易被这些“幽灵网段”干扰。养成建网就划段的习惯后面维护能省很多事。3.2 容器动态加入网络一台容器接入多个网络生产里经常会遇到这种需求一套业务的前端容器需要同时连接“供外部访问的网络”和“连接数据库的网络”但这两个网络本身不希望完全互通。Docker支持一个容器同时加入多个自定义网络做法就是反复执行docker network connect。# 先建两个网络 docker network create -d bridge front-net docker network create -d bridge back-net # 启动一个容器先加入front-net docker run -d --name app \ --network front-net \ nginx # 再把同一个容器也接入back-net docker network connect back-net app连接成功后容器app在front-net和back-net里各有一个IP对外表现为“这个容器和两个网络的容器都互通”。如果你进去看路由表会看到两条默认路由或者由各网络网关决定的转发路径。这种模式的典型价值在于隔离和互通并存。比如数据库容器只在back-net里外部网络访问不到业务容器只要同时挂在两个网络上它既能访问外部入口流量也能安全地连数据库而数据库不会暴露给前端网络。这一点用默认bridge是很难做到的。3.3 真正的“网络打通”跨网络跨主机要注意什么单机上的网络打通相对简单只要docker network connect把容器拽进另一个网络就行。但跨主机互通就完全不是一回事了需要引入overlay网络底层用VXLAN把多台机器上的容器网络封装成一张大二层网络。最简单的跨主机overlay演示可以借用Docker自带的Swarm模式。后面我会用两条Docker主机来演示Swarm集群下的overlay网络你不用完全理解VXLAN的每个细节但要知道它做的事情数据包从容器A出网被打上VXLAN标签通过宿主机的物理网卡发送到对端宿主机对端解封装后再交给容器B。表现为容器A和容器B的IP虽然在各自宿主机上但彼此能直接通信像是连了同一台交换机。# 第一台主机初始化为管理节点 docker swarm init --advertise-addr 192.168.1.10 # 第二台主机加入集群join命令从第一台的输出里复制 docker swarm join --token ... 192.168.1.10:2377 # 在管理节点上创建overlay网络 docker network create -d overlay --subnet10.10.0.0/16 my-overlay # 把服务部署进这个overlay网络跨主机自动调度 docker service create --name demo --network my-overlay --replicas 2 nginx跨主机通信用overlay是标准方案。但需要注意VXLAN会引入额外的封装开销带宽敏感场景下要考虑MTU和GSO等调优这对初学者来说不是一天能弄明白的。所以我的建议是如果你的服务规模还没到跨多台主机的程度优先把单机的bridge网络吃透别一上来就上overlay反而容易被各种问题绊住。3.4 打通记录iptables和路由转发是绕不开的暗礁自定义网络和跨网络打通之后最常见的问题集中在防火墙和内核转发上。Docker本身会在iptables里维护大量规则包括NAT规则、FORWARD链规则。如果宿主机开启了firewalld或者ufw容易和Docker的规则冲突导致容器出网失败或外部访问不通。最典型的现象是容器能Ping通同一个bridge里的其它容器但Ping不通宿主机局域网里的其它机器。原因多半是宿主机没开IP转发或者iptables的FORWARD链默认策略是DROP。检查方法很简单# 查看IP转发是否开启1为开启 sysctl net.ipv4.ip_forward # 临时开启 sysctl -w net.ipv4.ip_forward1 # 永久开启 echo net.ipv4.ip_forward 1 /etc/sysctl.conf sysctl -p同时看一下iptablesiptables -L FORWARD -n如果看到FORWARD链策略是DROP而Docker又没插入允许规则那跨网段的容器通信就会断。这时要么把策略改成ACCEPT要么保留Docker自己的规则。我的经验是只要不是对安全要求极其苛刻的环境尽量让Docker自己管理iptables别手动往FORWARD链里加太激进的DROP规则否则“网络打不通”会成为Docker里的头号玄学问题。4. 常见问题排查与避坑网络不通别慌按顺序查4.1 容器网络检查五步法接到一个“容器网络不通”的问题我基本按五步定位从底层到上层挨个排除。这五个步骤不仅是解决问题的路径也是理解Docker网络结构的绝佳框架。第一步看网卡和IP在宿主机上ip a看看容器对应的veth接口是否在宿主机上再到容器内部看IP确认容器有没有拿到正确的IP。有些情况是自定义网络建好了但子网配得太小容器无法获得地址这种情况排查起来最扎心。第二步看路由表在容器里执行ip route看默认路由是否指向正确的网关。如果默认路由丢了容器只能和自己网段里的机器通信出不了网。第三步看iptables规则重点看iptables -t nat -L -n里有没有容器的DNAT规则以及FORWARD链的ACCEPT情况。端口映射不生效十有八九问题就在这里。第四步看DNS解析默认bridge不支持容器名解析指望ping 容器名之前先确认是不是自定义网络。另外检查/etc/resolv.conf是否指向127.0.0.11如果系统的DNS被外部覆盖了容器名解析也会失效。第五步看负载均衡和代理层当你确认容器本身网络没问题但通过宿主机端口访问不到时就得看外部有没有负载均衡、防火墙、安全组之类的因素介入。很多“Docker网络不通”其实是云平台安全组没放行端口和Docker半毛钱关系没有。4.2 高频故障场景速查表下面这些是我在实操中反复见过的典型场景整理成表格方便你遇到问题时对着查。现象常见原因解决方法容器Ping不通宿主机局域网机器内核IP转发未开启开启net.ipv4.ip_forward1容器间Ping不通但在同一自定义网络容器所属网络不同或防火墙策略阻塞确认两个容器都在同一网络检查FORWARD链使用容器名Ping不通用默认bridge网络未启用内置DNS改用自定义bridge网络或使用IP互访宿主机访问容器端口不通映射端口未监听、iptables规则被清检查容器进程监听状态确认NAT规则存在容器IP访问正常域名访问不通DNS解析失败或代理配置异常检查resolv.conf和DNS设置跨主机容器互通失败overlay网络未创建或join失败检查Swarm集群状态重新构建overlay网络4.3 独家避坑心得这些坑我真的踩过第一个坑是docker0的IP和公司内网IP冲突。有次部署某系统容器死活连不上外部数据库排查了很久才发现原来公司办公网正好用了172.17.0.0这个网段数据包全被Docker的NAT规则截走了。解决方案也很简单把docker0的网段改掉即可。改动方式是在Docker守护进程配置文件里加上bip: 192.168.250.1/24改完重启Docker就行。第二个坑是重启容器后IP变了导致硬编码IP失效。用默认bridge跑一些老服务时容器每次重建IP都会变服务注册表里记录的IP还是旧的导致调用失败。后来我统一改成自定义bridge服务间用容器名通信才彻底解决了这个痛点。第三个坑是手动清iptables导致整个Docker网络瘫痪。有人觉得Docker生成的iptables规则太多了顺手执行iptables -F清理一遍结果容器全部无法访问外部网络。Docker的NAT规则和FORWARD规则是它运行的前提不要手动清空。真想调整策略也要用iptables -t nat -D精准删掉某条规则。第四个坑是端口映射时宿主机的端口已经被占用。启动容器时报bind: address already in use这不是Docker的问题是宿主机端口被别的进程占用了。用ss -lntp | grep 8080看一下占用情况最直接。别盲目改容器端口先确认宿主机端口可用。4.4 网络性能调优的几点经验网络通了以后还有很多人关心性能问题。单机bridge模式因为有NAT和网桥转发肯定比host模式慢一点但大多数业务根本感觉不到差异。如果真到了需要抠性能的时候有三个方向可以试。第一是尽量少做端口映射内部服务之间的调用直接用容器网络访问IP或服务名不要每个容器都映射出好几个端口。第二是如果对时延极敏感考虑host模式但要注意端口规划。第三是按需调整MTU。如果你发现跨主机overlay网络下大包传输速度异常慢很可能是VXLAN叠加后的MTU问题把宿主机网卡MTU调小或者给overlay网络指定合适的MTU值都能改善。另外容器网络性能受宿主机CPU影响也不小尤其是使用无状态NAT时。对性能有要求的环境记得给宿主机留足CPU别把负载跑满再去怪Docker的网络慢。结尾写到这里Docker网络的主线内容基本都覆盖了最后再分享一点我个人在实际操作中的体会。网络这块东西光靠看文章远不如自己动手敲一遍命令。我建议你搭一个尽量简单的环境一台虚拟机装好Docker然后按顺序做四个实验——先跑两个默认bridge容器用IP Ping通再建一个自定义bridge网络用容器名互访接着把一个容器connect到两个网络里观察多IP状态最后有条件的话再搭两台机器跑一个overlay。走完这四个实验Docker网络的大多数坑你都会提前踩一遍后续看问题会快很多。还有一个小技巧是排查容器网络问题的时候不要只盯着容器本身。虽然问题都叫“容器网络不通”但很多时候根因在宿主机路由、iptables、DNS甚至外面的防火墙里。养成从物理层一步步往上看的习惯比记住任何一份速查表都管用。网络调试的本质就是不断缩小“数据包到底在哪个环节丢了”的范围剩下的只是时间问题。
阅读完成 · 觉得有帮助?
咨询建站