1. 先把 Docker 网络这回事想明白:docker0、veth 与网段划分
很多朋友用 Docker 跑起来第一个容器的时候,心里其实都有个疑问:为什么我什么都没配置,容器就能上网?为什么我docker run -p 8080:80之后,浏览器里敲localhost:8080就能访问到容器里的 nginx?
我当年学 Docker 的时候,也经历了一段"能用但不知道为啥能用"的阶段。后来翻了源码和内核文档才算彻底把这块梳理清楚。这里我不打算贴一堆底层代码,而是用一套比较好理解的方式把核心机制讲明白。
1.1 Docker 默认安装后到底创建了什么东西
Docker 安装完成后,只要守护进程启动,它就会在宿主机上创建一个名为docker0的虚拟网桥。这个网桥你可以直接理解成一台"虚拟交换机",所有默认网络模式下的容器,都有一根"虚拟网线"插在这台交换机上。在 Linux 宿主机上执行ip addr show docker0,你通常能看到类似这样的信息:
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 172.17.0.1/16 brd 172.17.0.255 scope global docker0这个172.17.0.1/16就是 Docker 默认的桥接网段,宿主机自己是172.17.0.1,容器启动后会被分配172.17.0.2、172.17.0.3这样的地址,依此类推。
那容器里的网卡又是怎么来的?这里就得提到 Linux 的 veth pair(虚拟以太网对)机制。veth 永远是成对出现的,就像一根网线的两头:一头插在docker0网桥上,叫vethxxx;另一头被放进了容器的网络命名空间里,叫eth0。容器里看到的是网卡,宿主机上看到的是一个以veth开头的虚拟接口。
我打个比方:docker0是一台交换机,每个容器是一台独立主机,veth pair 就是一根根网线。容器之间要通信,数据包先通过自己那根网线到达交换机,再由交换机转发给目标容器。这个模型虽然简单,但足够帮助我们理解后续所有网络模式的行为差异。
1.2 容器访问外网的路径与 NAT 的角色
容器被分配了172.17.0.x这样的私有地址,外界路由根本不知道怎么把这个网段的数据包送到容器里。那容器是怎么上外网的呢?靠的是 iptables 的 NAT 规则。
数据包的路径大概是这样的:容器里的进程发起一个访问百度的请求,源地址是172.17.0.2:56789,目标地址是百度服务器的 IP。这个数据包经过docker0网桥,进入宿主机的网络协议栈,然后 iptables 的 POSTROUTING 链里有一条 MASQUERADE 规则,会把源地址改写为宿主机网卡的 IP,比如192.168.1.100:xxxxx。百度服务器收到请求后,响应数据包回给宿主机,宿主机再根据 NAT 表项把数据包原路拆包、还原成给172.17.0.2:56789的响应,最终送回容器。
这个机制和家用路由器上网的逻辑几乎一模一样:内网设备共享一个公网出口。理解了这个模型,你就知道——默认 bridge 模式下,外网是访问不到容器的,因为外面根本没有通往172.17.0.x网段的路由。所以 Docker 才提供了-p端口映射。
端口映射的原理也简单:-p 8080:80会在宿主机的 iptables 里添加一条 DNAT 规则,把发往宿主机8080端口的数据包,目标地址改写成172.17.0.2:80,然后数据包进入docker0,被转发给容器。这就是为什么你访问localhost:8080就能打到容器里的 80 端口,本质上是一个"目标地址转换"。
我在用 Docker 的早期阶段,经常有人问我:能不能让容器直接用宿主机的 IP 对外提供服务,不走端口映射?有,但那是另一种网络模式,后面我会细讲。
2. bridge 模式的实际使用:默认网桥与自定义网桥的差异
bridge 是 Docker 的默认网络模式,也是日常使用里出现频率最高的模式。很多人从入门到熟练,一直用默认的bridge网络,没有接触过自定义网桥,直到某一天遇到了"容器之间连不上"的问题,才开始意识到这里面的门道。
2.1 默认 bridge 网络的几个"隐藏限制"
默认的 bridge 网络(也就是docker0)在功能上其实是够用的,特别是只跑一两个容器的场景。但当你开始用docker-compose编排多个服务、容器之间需要互相调用的时候,默认网桥的局限性就会暴露出来。
最大的问题在于默认 bridge 网络不支持容器名自动 DNS 解析。举个例子:你跑了一个 MySQL 容器和一个后端服务容器,后端服务想过配置项去连接 MySQL,连接地址你会怎么写?如果你写172.17.0.2:3306,那一旦 MySQL 容器重建,IP 可能变成了172.17.0.3,后端服务的配置就要跟着改,非常麻烦。
更合理的写法是用容器名,比如mysql:3306。但在默认 bridge 网络下,这种写法是行不通的——Docker 自带的嵌入式 DNS 服务器只对自定义网络生效,默认网络里的容器只能靠 IP 互访。
另外还有一个限制,默认 bridge 网络下,容器之间虽然可以互通,但如果你想通过--link参数来建立别名连接,Docker 官方也已经明确说过这个机制是遗留方案,不推荐新项目使用。--link本质上只是往容器的/etc/hosts里写了一条 IP 和容器名的映射,并不是真正的服务发现。
2.2 自定义 bridge 网络:建议你从今天就开始用
针对默认网桥的这些痛点,Docker 提供了自定义网络的能力。创建一条自定义网桥只需要一条命令:
docker network create --driver bridge --subnet 172.28.0.0/16 --gateway 172.28.0.1 my-net如果你不对--subnet做指定,Docker 会从本机可用网段里自动挑一个,避免和已有的172.17.0.0/16网段冲突。我习惯手动指定,这样在做防火墙策略、固定容器 IP 规划的时候心里有数。
创建好之后,启动容器时用--network参数指定即可:
docker run -d --name mysql --network my-net -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0 docker run -d --name backend --network my-net my-backend-image在自定义 bridge 网络中,backend容器里直接可以用mysql作为主机名访问 MySQL,因为 Docker 的内置 DNS 会自动解析容器名。这种基于 DNS 的服务发现机制非常稳,容器重建后只要名字不变,其他服务无需任何改动就能继续工作。
还有个容易忽略的点:在同一个自定义 bridge 网络里的容器,它们之间的通信是直接通过docker0这类网桥转发的,不会有 iptables 的过滤规则干扰,因此不需要-p端口映射也能互相访问。-p映射只服务于"从宿主机或外部访问容器"的场景。
我强烈建议:只要你的服务编排里存在容器间互访的需求,一律使用自定义 bridge 网络。别再用默认网桥加--link的老办法去填坑了。自定义网络既支持域名解析,又能随时动态增删容器,还天然支持docker network connect把一个容器同时挂到多个网络上。比如某个容器既要访问前端网关网络,又要访问后端数据库网络,一条docker network connect my-net2 my-container就能搞定,非常灵活。
2.3 一个 docker-compose 场景下的 bridge 配置示例
在实际项目中,我们一般不会一条条docker run去起容器,而是用 docker-compose。用 compose 时,自定义 network 的概念同样适用,而且语法更简洁:
version: "3.8" services: web: image: nginx:1.24 ports: - "80:80" networks: - frontend - backend app: image: my-app:latest networks: - backend db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: secret networks: - backend networks: frontend: driver: bridge backend: driver: bridge这个配置里web同时挂了两个网络,可以访问app和db;app和db在backend网络内互通;frontend网络里只有web,和外界隔着端口映射。这种分区思路在稍微大一点的架构里很有用,它让流量路径变得清晰,也方便按网络粒度做安全隔离。
3. host 与 none 模式:什么时候直接用宿主机网络最合适
两条命令就能切换模式:
docker run --network host nginx docker run --network none alpine但“能用”和“用得对”是两码事。host 模式省去了所有 NAT、端口映射、网桥转发的开销,性能接近原生进程,看起来非常诱人。但它的代价也很明显——容器失去了网络隔离性,容器里启动的进程直接监听宿主机的端口。
3.1 host 模式适合的业务场景与不适合的场景
我先说适合的场景。最简单的例子:一个只在本机提供服务、不需要对外部网络做任何隔离的指标收集器,比如 node-exporter,它监听 9100 端口收集宿主机监控指标。如果你用 bridge 模式加-p 9100:9100,也没问题,但从语义上 host 模式更贴合“这个容器就是宿主机的一部分”的感觉。再比如一些对网络延迟极度敏感的应用,像高性能缓存、实盘行情推送这类,多一层 NAT 可能多零点几毫秒延迟,虽然单次看起来微不足道,但在高频调用的场景下会被放大。
此外,host 模式还有一个实用价值:容器内可以通过宿主机的网络栈访问宿主机的服务。举个例子,你在宿主机上装了 Consul,监听 8500,容器里想连localhost:8500。如果走 bridge 模式,容器里的localhost是容器自己,需要改成宿主机 IP 才能访问,而宿主机 IP 在跨环境时往往不是固定的。用 host 模式,localhost就是宿主机本机,配置可以写死,避免了环境迁移时改配置的麻烦。
不适合的场景也很明显。第一,如果你需要同时跑多个使用相同端口的容器,host 模式会直接端口冲突,压根起不来;第二,host 模式没有独立的网络命名空间,容器里的网络栈和宿主机完全共享,因此 Docker 集群管理、防火墙规则隔离这类基于网络命名的功能会受到很大限制。所以不要习惯性用 host 模式跑业务服务,特别是在生产环境里,一旦多个服务端口撞车,排查起来会相当头疼。
3.2 none 模式:真正的“断网容器”与它的价值
很多人第一次看到 none 模式会困惑:完全没有网络,那这个容器能干什么?
实际上 none 模式的应用场景比想象中要多。一个是离线任务的执行容器——你把一批数据文件挂载进去,容器里的脚本只做计算和文件处理,不需要任何网络能力。此时给容器完整的网络栈反而增加了攻击面。另一个场景是安全敏感的批处理任务:比如一个容器专门处理密钥生成、敏感文件加解密,让它完全没有网络接口,即使容器被攻破,攻击者也无法从容器内部发起网络请求,信息泄露面会被压缩得很小。
还有一种用法是配合docker network connect来使用:先用--network none启动一个容器,后面在需要的时候手动把它连接到某个自定义网络。这种“先隔离、后按需接入”的方式在一些安全要求较高的 CI/CD 流水线里很常见,容器启动时不需要网络,等到执行部署步骤时才临时连接网络完成推送。
不过要提醒一句:none 模式下容器里如果跑了一些依赖网络初始化的服务(比如等待网卡就绪的框架),可能会启动失败。排障时第一件事就是看容器日志,别光盯着网络模式的配置看。
4. container 模式与网络栈共享:sidecar 容器的正确打开方式
--network container:容器名这种模式,官方文档描述是"复用另一个容器的网络栈"。什么意思?就是新容器和指定容器共享同一个 Network Namespace,这个新容器里没有自己的 IP 地址,它看到的路由表、网卡、iptables 规则和那个容器完全一样。
4.1 为什么 sidecar 架构会用到 container 模式
最经典的场景是日志采集器。比如我们有一个主容器运行业务应用,业务日志写到本地文件,旁边需要一个 filebeat 或 fluentd 容器把这些日志采集走。如果用 bridge 模式,filebeat 要通过网络访问主容器,依赖端口映射和网络配置;如果用container:模式,两个容器共享 localhost,filebeat 直接访问主容器的本地端口即可,逻辑上简洁非常多。
实际部署的时候,顺序是这样的:
docker run -d --name app my-app docker run -d --name logger --network container:app my-logger此时logger容器里的网络栈就是app容器的网络栈,所以logger容器里用localhost访问到的端口,和app容器里访问到的一模一样。这种方案在 Kubernetes 里其实也被大量使用——K8s 的 Pod 中多个容器共享同一个网络命名空间,本质上就是这种设计思路。
还有一种场景是调试容器:主容器镜像里比较精简,没有curl、tcpdump这些排查工具。你可以启动一个附带有全套工具的调试容器,用--network container:app让它直接观察主容器的网络流量,而不需要进入主容器里安装任何包。这保持了主容器镜像的干净整洁,也方便在故障时快速介入。
4.2 container 模式容易踩的坑
这个模式的坑主要是生命周期管理。容器之间的网络共享不是"跟随"关系——如果主容器被删掉或重启,那个共享网络栈的 sidecar 容器也会发生异常,因为它的 Network Namespace 已经不存在了,进程的网络行为会变得不可预测。实际镜像重启之后,你得把 sidecar 容器一起重启,否则它可能一直处于僵死状态。
另外,共享网络栈的两个容器不能监听相同的端口,这和 host 模式的限制是一样的。如果你两个容器内部都默认监听 8080,那么第二个容器启动时必然会报端口已被占用,这种报错很容易掩盖在镜像日志里,排查起来会有迷惑性。
我在实际运维中看到的另一个问题是:有些人会把主容器的网络模式设置为container:另一个容器,然后再把第三个容器也设为container:同一个容器,形成一条很长的依赖链。一旦中间某个容器出问题,整个网络的依赖关系会变得很难捋清。所以我建议大家只把 container 模式用在真正的 sidecar 场景上——主容器独立运行,sidecar 只负责“搭车”,不要形成多级链式依赖。
5. macvlan 模式:让容器拥有“真实在网”的 IP 地址
如果你想让容器像一个独立的物理设备一样直接出现在局域网里,有自己的 IP、MAC 地址,可以被局域网里的其他机器直接访问,那 macvlan 模式几乎是唯一的选择。它的原理是给宿主机物理网卡创建多个带独立 MAC 地址的虚拟子接口,每个子接口绑定一个容器。这样,局域网交换机看到的就是一个个“虚拟出来的物理主机”。
5.1 macvlan 的创建方式与配置细节
先看命令。假设宿主机网卡是eth0,网关是192.168.1.1,准备给容器分配192.168.1.100到192.168.1.200这个地址段:
docker network create -d macvlan \ --subnet=192.168.1.0/24 \ --gateway=192.168.1.1 \ --ip-range=192.168.1.100/28 \ -o parent=eth0 \ macnet这里说几个容易踩的细节。
--ip-range不是必填项,但如果不写,Docker 会认为整个--subnet网段都可以用来分配 IP。假设你的局域网里已经有一堆设备占用了192.168.1.2~192.168.1.99的地址,那么 DHCP 分配时 Docker 可能把 IP 分配给别人已经占用的地址,造成冲突。所以我把--ip-range写成一个比较小的子网段192.168.1.100/28,只在这个范围内做自动分配,从根上避免冲突。
-o parent=eth0这一项指定的是宿主机物理网卡接口名。如果宿主机有多个网卡,你一定要看清楚实际用于局域网通信的是哪一块,填错了容器网络会完全不可用。
创建好网络后,启动容器:
docker run -d --name app1 --network macnet nginx容器启动后,你可以进容器里看一下 IP,它会显示为192.168.1.x,此时局域网里的其他机器直接往这个 IP 访问即可,完全不需要在宿主机上做端口映射。
5.2 macvlan 的几个奇怪限制
macvlan 用起来确实很爽,但有三个限制我希望大家心里有数。
第一个是宿主机与 macvlan 容器之间默认无法互通。比如你在宿主机上执行ping 192.168.1.100,大概率是不通的。原因涉及 Linux 数据包绕行顺序的问题——一般需要手动为父接口添加一个同网段的macvlan子接口才能让宿主机和容器互通,但在不少网络环境下配置起来比较麻烦。所以 macvlan 的定位是“宿主机与外界都直接访问容器”的场景,而不是“宿主机内部访问容器”的场景。
第二个限制是无线网卡环境下 macvlan 并不适用。无线网卡在 Linux 驱动层面一般不允许创建多个 MAC 地址的子接口,或者创建了以后无法正常收发数据。如果你是在笔记本上做实验,插网线通常没问题,用 Wi-Fi 则会出现容器无法上网的情况。所以 macvlan 更适合在物理机或者有有线网卡直通的服务器环境里使用。
第三个限制是云服务器基本用不了 macvlan。云厂商的虚拟网络一般会做多种安全检查,macvlan 的虚拟 MAC 很容易被底层网络策略拦截。如果你在云主机上尝试跑 macvlan,容器多半无法上网。遇到这种情况,可以直接放弃 macvlan,改用 bridge 加端口映射,或者走 overlay 网络方案。
6. 跨主机通信与 overlay 模式:当容器不止跑在一台机器上
单机环境下的网络模式讲完了,但现实中服务迟早要跨主机部署。两台物理机,各跑一部分容器,它们之间怎么互通?最简单粗暴的方案是:容器用 bridge 模式,然后把端口映射到宿主机 IP,另一台机器通过宿主机 IP 加端口访问。这种做法在服务数量少的时候是可行的,但一旦容器数量上来了,端口管理会变成噩梦。更优雅的方案是 Docker 原生的 overlay 网络。
6.1 overlay 网络的基本工作机制
overlay 网络依赖 Docker Swarm 模式或者 Docker 内置的 key-value 存储(或者外部的 Consul、etcd)来同步网络状态。它做的事情本质上是在多台宿主机之间搭了一条“叠加网络”:每个容器仍然有自己网段内的虚拟 IP,比如10.0.0.2、10.0.0.3,但数据包在跨宿主机传输时会被封装一层新的包头,走 UDP 协议在宿主机之间的物理网络上传输。对应用层来说,它们就像连在同一个局域网里一样,可以不关心对方到底在哪台机器上。
初始化 Swarm 集群之后,创建一个 overlay 网络:
docker swarm init docker network create -d overlay --attachable my-overlay这里的--attachable允许非 Swarm 服务的普通容器也连接到这个 overlay 网络。不加这个参数,只有通过docker service启动的容器才能使用它。
6.2 overlay 模式适合与不适合的场景
overlay 网络最大的价值是让跨主机部署的服务编排变得简单。容器之间用服务名互访,Docker 内置 DNS 会跨主机解析,这比维护一堆真实 IP 和端口映射省心太多了。很多人在生产环境中结合 Traefik 或 Nginx 作为入口网关,网关容器通过 overlay 网络直接发现后端服务,后端扩容时不需要修改任何网关配置。
但 overlay 网络也有性能开销。因为每个数据包都要额外封装一层,CPU 消耗比 bridge 模式高一些。如果你的业务对网络性能非常敏感,还是老老实实给容器用 host 模式,或者使用类似 SR-IOV 的方案直通网卡。另一个开销是部署复杂度:你得保证所有宿主机之间的端口连通性(VXLAN 默认使用 UDP 4789 端口),还要把防火墙策略调对,否则会出现很诡异的“容器能创建但跨主机访问不通”的问题。
我自己经历过一次生产事故:两台宿主机之间的 overlay 网络工作正常,但第三台新加的宿主机上容器始终无法和集群里的其他容器通信。排查了半天,发现是那台新机器的安全组规则没有放行 UDP 4789 端口。这个问题不遂,就是典型的“物理网络通、封装隧道不通”的案例。
7. 网络排查基本功:不猜不蒙,从 inspect 开始
讲了这么多模式,最后必须补上排查手段。很多朋友容器网络出问题的时候,第一反应是重启容器,然后看日志,再不行就重启 Docker 服务。这种排查方式太漫无目的了。我个人的排查顺序是固定的,稳定性高很多。
7.1 docker network inspect 是排障入口
不管问题描述得多么玄乎,第一步永远是看网络详情。执行:
docker network inspect <网络名>这个命令会输出该网络的名称、驱动类型、网段、网关、已连接容器的列表(包括容器 IP 和 MAC 地址)。通过它你能快速确认:
- 容器到底在哪个网络里?
- 容器的 IP 是否在预期网段?
- 网关是否正常存在?
如果容器不在这个网络里,比如你启动容器时忘了加--network参数,容器悄悄跑到了默认 bridge 里,那它和自定义网络里的容器当然互相不通。这类问题通过 inspect 一眼就能定位。
7.2 容器内连通性测试的正确工具与姿势
确认网络归属后,下一步测试连通性。通常我会进入容器里做三件事:
docker exec -it <容器名> sh # 容器内依次执行: ping <目标IP> nc -vz <目标IP> <目标端口> cat /etc/resolv.confping测试三层连通性,nc -vz测试具体的 TCP 端口是否可达,cat /etc/resolv.conf检查容器内 DNS 配置是否异常。这里有个细节:很多精简镜像里没有ping和nc,你可以在容器里装一下,或者直接启动一个自带工具的调试容器来测。比如:
docker run -it --rm --network container:<目标容器> nicolaka/netshootnetshoot 镜像里集成了 tcpdump、iperf、nc、dig 等一整套网络排查工具,是我非常推荐的调试神器。配合--network container:模式,可以直接复用目标容器的网络栈,排查起来很方便。
还有一个常见误区:容器里ping不通一个域名,不一定就是网络问题。有可能是容器里的 DNS 解析失败,也可能是镜像里没有配置/etc/resolv.conf,又或者是宿主机所在环境对 ICMP 协议做了限制。这种情况下不要只用 ping 下结论,直接用nc -vz或curl测试 TCP 端口会更可靠。
7.3 一个典型的“容器网络不通”排查案例
我之前帮人排查过一个问题:某个容器可以访问外网,但无法访问同宿主机另一个容器。很多人第一反应是防火墙问题,但我按上面这套流程走下来,很快就发现了真相。
第一步docker network inspect发现两个容器分别属于两个不同的自定义网络——启动时一个用了net-a,另一个忘了指定网络,跑到了默认 bridge。两个网络网段不同,互相不认识,当然不通。解决方式就是执行docker network connect net-a <另一个容器>,把容器挂到目标网络上,问题立刻消失。
另一个更隐蔽的案例:容器能通,但访问速度非常慢。排查时发现宿主机上 iptables 规则里有一条 FORWARD 链 DROP 策略,一些未知连接被丢掉后重传,造成大面积延迟。这种情况靠容器内工具很难发现,需要在宿主机上用iptables -L FORWARD -v查看规则计数,确认是否有异常丢包。所以我的经验是:容器内排查解决“能不能通”的问题,宿主机的规则和网络栈排查解决“为什么慢”“为什么偶尔断”的问题。
8. Windows Docker Desktop 用户的特殊网络注意事项
很多朋友是在 Windows 上通过 Docker Desktop 学习或开发,这和使用 Linux 宿主机的体验还是有差别的。Docker Desktop 底层通过 WSL2 虚拟化了一个轻量级 Linux 环境,所以前面讲的所有网络模式,其实都是作用在那台虚拟 Linux 主机上的,而不是直接作用在 Windows 宿主机上。
8.1 端口映射的“假象”与 host 模式的实际含义
在 Windows 上,你执行:
docker run -d -p 8080:80 nginx然后浏览器访问localhost:8080可以正常打开页面。这是因为 Docker Desktop 把 WSL2 虚拟机的端口转发到了 Windows 宿主机上,让你感觉容器好像直接跑在 Windows 里。但当你在 Windows 的 WSL2 虚拟机里还有其他服务监听同一端口时,就要小心端口占用和转发冲突。
更需要注意的是host网络模式。在 Windows 的 Docker Desktop 环境下,--network host里的 “host” 指的是 WSL2 虚拟机,不是 Windows 宿主机。也就是说,容器监听8080,你会发现在 Windows 本机上访问localhost:8080并不一定通——它只监听了 WSL2 虚拟机内部的 8080 端口。这个坑经常让从 Linux 切到 Windows 的开发者一脸懵。
8.2 Windows Docker Desktop 网络配置的几个实用技巧
如果你在 Windows 上使用 Docker Desktop,有三个小技巧比较实用。
第一,容器间互访时,尽量使用自定义 network 加容器名,而不是依赖localhost。在 WSL2 环境下,容器 A 要访问容器 B,用docker network create建立自定义桥接网络后,A 里直接写 B 的容器名即可。
第二,WSL2 的虚拟交换机网段和 Windows 宿主机的局域网网段可能是隔离的。如果你希望局域网里的其他设备访问 Windows 上运行着的容器,不仅要做-p端口映射,还要确保 Windows 防火墙放行了对应端口。WSL2 的端口转发是自动的,但防火墙很容易拦掉外部访问。
第三,如果 Docker Desktop 的网络异常,比如容器全部无法上网,我通常先试重启 Docker Desktop 服务,而不是折腾 WSL2 的网络配置。在 Windows 的 Docker 体系里,很多诡异网络问题通过wsl --shutdown之后重新启动 Docker Desktop 就能解决。这种操作看上去简单粗暴,但确实有效,因为 WSL2 虚拟网络栈在长时间休眠或切换 WiFi 后容易“状态错乱”。
9. 一个过来人的选型建议与个人经验总结
关于 Docker 网络模式的选型,我给自己的项目定过一套简单实用的原则,这里分享给大家参考。
单机部署、容器数量不多、服务之间有互访需求,无脑选自定义 bridge 网络。配合容器名做 DNS 解析,后续维护成本非常低。需要对外提供访问的容器,通过-p映射端口,同时注意端口别和宿主机已有服务冲突。
需要极致网络性能、或者容器必须要用宿主机的网络端口做广播、组播这类操作,再考虑 host 模式。不过要记住它的隔离性差,使用前确认好端口占用。
sidecar 扩展容器、调试容器,用 container 模式共享目标容器的网络栈,这种方式既省资源又逻辑清晰。
容器需要直接暴露在局域网里,网络环境是物理机,可以用 macvlan。无线网卡和云服务器环境建议直接放弃这个方案。
多机编排、需要跨主机服务发现,把 Docker Swarm 或者 Kubernetes 用起来,配合 overlay 网络,配合内置 DNS,体验会顺畅很多。
关于排障,我也越来越觉得:网络问题一定要一步一步来,先确认网络归属,再测三层连通性,再测四层端口,最后查宿主机规则。每一步都有对应工具和命令,不要跳步。很多你以为的“灵异问题”,最后查出来都是某个小细节没对上。
如果让我说一个最值得养成的习惯,那就是:从写第一行 docker run 开始,所有需要容器间通信的服务,一律指定自定义网络并显式声明网络名。等容器数量多了、架构复杂了,你会发现这个习惯能帮你省下大量排查时间。网络模式的切换和调整本身并不难,难的是在一开始就选对方向。