📝 术语别名
  • NIC: Network Interface Card, 物理网卡, 真正的硬件;
  • net_device: Linux 内核里描述"一张网卡"的统一抽象, 虚拟和物理共用同一个数据结构;
  • 虚拟网卡: 没有对应物理硬件、纯软件模拟的 net_device, 例如 lo / veth / tap / tun / bridge;
  • tap / tun: 给用户态进程收发包用的虚拟网卡, VM 和 VPN 的标配;
  • netns: Linux network namespace, 内核提供的网络栈隔离机制;
  • veth pair: virtual ethernet pair, 一对虚拟网卡, 一端发, 另一端收, 是连接两个 netns 的通道;
  • bridge: 内核虚拟二层交换机, 把若干网卡拉到同一广播域;
  • DNAT / SNAT: destination / source NAT, iptables 用来改包的 dst/src IP, 是端口映射和"出口走主机 IP"的核心;

前置 A: 物理网卡 (NIC) 与虚拟网卡到底是什么

聊 Docker 网络之前必须先把"网卡"这个词拆开, 否则后面 veth, tap, bridge 这些会一片混乱;

物理网卡 (NIC)

物理网卡就是字面意义上的硬件, 一块 PCIe / USB / on-board 设备, 它干的事情非常底层:

  • 把内存里的字节序列编码成线路上的电信号 / 光信号 (发);
  • 把线路上的信号解码成字节, 通过 DMA 写进内存, 再触发中断告诉 CPU “有包来了” (收);
  • 自带一个出厂烧死的 MAC 地址, 用来在二层网络里被定位;
  • 现代网卡还会做一堆 offload: checksum 算硬件里、TCP segmentation offload (TSO)、receive side scaling (RSS) 多队列分流等等;

操作系统层面, Linux 用一个叫 struct net_device 的内核数据结构来代表"一张网卡"; 物理网卡有一个驱动, 驱动负责把 net_device 上的发包请求翻译成具体硬件操作;

虚拟网卡

关键洞察: struct net_device 不一定要后面挂硬件;

只要写一段内核代码, 实现 net_device 要求的几个回调 (发包时怎么处理、收包时怎么调用协议栈), 就凭空造出一张"网卡"; 这种没有物理硬件背书、纯软件模拟的 net_device 就是虚拟网卡;

对内核的 TCP/IP 协议栈来说, 虚拟网卡和物理网卡没有任何区别:

  • 都能分配 IP 地址;
  • 都能加路由表条目;
  • 都能跑 ARP, ICMP, TCP, UDP;
  • 都能被 iptables / tcpdump 看到;

唯一的区别在于: 发包的时候, 包没去到线上, 而是被另一段内核代码拦下来转手处理掉了;

类比一下: 协议栈是水管, net_device 是水龙头, 龙头后面是真水管 (物理网卡) 还是另一根管子 (虚拟网卡), 协议栈不关心也看不见;

Linux 里常见的虚拟网卡种类

类型 干嘛用 举例
lo (loopback) 自发自收, 每个 netns 必有一张 127.0.0.1 走的就是 lo
veth 一对网卡像虚拟双绞线, 一端发另一端收 连接 container netns 和宿主
tap 给用户态进程一个收发包接口, 可以读到完整以太网帧 VM 的"虚拟网卡"通常是一张 tap
tun 类似 tap 但只到 IP 层, 用户态读到的是 IP 包不带二层头 OpenVPN, WireGuard 客户端
bridge 内核虚拟二层交换机, 自己也是一种 net_device docker0, br0
bond 把多张物理网卡聚合成一张, 做负载/冗余 服务器双网口绑定
vlan 给物理网卡打 VLAN tag 后派生出的子接口 eth0.100 是 eth0 的 VLAN 100
dummy 永远收不到包, 拿来挂 IP 做配置占位 一些虚拟路由方案会用
📝 关键直觉

Linux 网络栈的强大之处就在于: 一切网络设备都被抽象成 net_device, 物理和虚拟混在一起用, 协议栈一视同仁; 所以容器、VM、VPN、SDN, 底子上都是在玩 net_device 的不同拼装方式;

前置 B: 看懂 ifconfig / ip a 的输出

ifconfig 是老一辈工具, 现在 Linux 标准做法是 ip 命令族 (ip addr, ip link, ip route), 输出更全, 但内容含义一致, 这里都讲;

示例输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
inet6 ::1/128 scope host

2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP
link/ether 08:00:27:1a:2b:3c brd ff:ff:ff:ff:ff:ff
inet 192.168.1.42/24 brd 192.168.1.255 scope global enp0s3
inet6 fe80::a00:27ff:fe1a:2b3c/64 scope link

3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
link/ether 02:42:ed:5f:88:c9 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0

5: vethXXXXXXX@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP
link/ether 6a:9c:fe:21:33:b1 brd ff:ff:ff:ff:ff:ff link-netnsid 0

字段含义逐项拆

  • 1:, 2: … — 接口索引, 内核给的连续 ID, 用来在内部相互引用;
  • <UP,BROADCAST,LOWER_UP,...>flags, 几个常见值:
    • UP: 管理员把这张网卡置为启用 (ip link set up);
    • LOWER_UP: 物理层有载波, 线插着且对端在线 (拔了网线就没了);
    • BROADCAST / MULTICAST: 支持广播 / 组播;
    • PROMISC: 混杂模式, 收所有包不过滤 (tcpdump 抓包会临时开);
    • NO-CARRIER: 接口 UP 但物理层没信号 (空 bridge 经常这样);
  • mtu 1500Maximum Transmission Unit, 这张网卡一次能发的最大包字节数; 以太网默认 1500, loopback 65536, VXLAN 因为多了封装头通常调到 1450;
  • qdisc fq_codelqueueing discipline, 这张网卡的发包排队算法, 一般默认 fq_codelpfifo_fast;
  • state UP / DOWN / UNKNOWN — 当前可用状态;
  • link/ether 08:00:27:1a:2b:3cMAC 地址, link/loopback 的 MAC 全 0 因为没意义;
  • brd ff:ff:ff:ff:ff:ff — 广播 MAC;
  • inet 192.168.1.42/24 — IPv4 地址 + 子网掩码长度;
  • inet6 fe80::.../64 — IPv6 地址, fe80:: 开头是 link-local;
  • scope global / host / link — 地址作用域, host 只在本机有效, link 在本链路有效, global 可以路由到外面;
  • master docker0 — 这张网卡挂在 docker0 这个 bridge 上 (类比"插在某个交换机上");
  • vethXXX@if4 — 这是 veth pair 一端, @if4 表示对端的接口索引是 4;
  • link-netnsid 0 — 对端在 netns id 0 里 (说明这张 veth 跨 netns);

Linux 接口命名套路

接口名前缀就藏着它的来源, 看名字就知道是什么:

前缀 含义
lo loopback, 回环, 每个 netns 一张
eth0 (老风格) 以太网, BIOS 时代命名, 现代 systemd 默认不用了
enp0s3, eno1, ens3 Predictable Network Interface Names, 基于 PCI 总线/插槽位置, 重启后名字稳定
wlp3s0, wlan0 无线网卡
docker0, br-XXXX Docker 创建的 bridge
vethXXXX veth pair 一端
tun0, tap0 VPN / VM 用的 tun/tap 设备
vxlan.calico, flannel.1 overlay 网络的 VXLAN 接口
wg0 WireGuard VPN
📝 排查习惯

看到一个不认识的接口名, 先看前缀; 看到 <...> 里的 flags 缺 LOWER_UP 八成是物理层断了; 看到一张 veth 没 master, 八成是没挂上 bridge, 这是 Docker 出毛病常见现象;

前置 C: VM 和 Container 的网卡设计差在哪

VM 和 Container 都给应用一个"独立网络"的错觉, 但底层手法完全不同, 理解这点有助于看懂为什么 Docker 选用 netns + veth, 而不是抄 VM 那套;

VM 的网卡 (经过 hypervisor)

VM 里跑的是完整一套独立的 guest OS, guest 内核有自己的协议栈, 它认得的"网卡"必须看起来像真硬件; hypervisor 提供这层假象, 主流有三种做法, 性能/复杂度递增:

方式 实现 性能 适用
Emulated NIC hypervisor 用软件假装一块 e1000/rtl8139, guest 加载真驱动跟它讲话 慢, 每个 register 访问都要 trap 到 host 兼容性最好, 默认选项
Paravirtualized (virtio-net) guest 装 virtio 驱动, host/guest 之间共享 ring buffer 直接交换 packet, 跳过完整硬件模拟 快得多 现代 Linux VM 默认
SR-IOV / PCIe passthrough 物理网卡的 Virtual Function 直接挂给 guest, 中间没 hypervisor 经手 接近裸机 高性能场景, 需要硬件支持

不管哪种, host 侧通常还要有一个与之配对的设备把流量送出去, 标配组合是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Guest VM                 Host (Linux + KVM/QEMU)
┌──────────────┐ ┌─────────────────────────┐
│ guest OS │ │ │
│ ┌────────┐ │ │ ┌──────┐ │
│ │协议栈 │ │ │ │ tap0 │ (虚拟网卡) │
│ └───┬────┘ │ │ └──┬───┘ │
│ │ │ ←──→ │ │ │
│ ┌───▼────┐ │ virtio │ ┌──▼─────┐ │
│ │ eth0 │◄─┼─ ring │ │ br0 │ (bridge) │
│ │(virtio)│ │ shared │ └──┬─────┘ │
│ └────────┘ │ buffer │ │ │
└──────────────┘ │ ┌──▼──┐ │
│ │ eth0│ (物理 NIC) │
│ └─────┘ │
└─────────────────────────┘

guest 视角看自己有一张 eth0, host 视角看多了一张 tap0, 两边由 hypervisor (qemu-kvm) 在内核 ring buffer 上 backed; 然后 host 把 tap0 挂到 bridge 上, bridge 再连物理网卡 — 流量就通了;

关键点: VM 的 guest 内核完全独立, 它不知道自己跑在虚拟里, 因此必须通过"模拟一张物理网卡"来骗它;

Container 的网卡 (共享 host kernel)

Container 没有 guest OS, 直接跑在 host kernel 之上; 这意味着根本不需要骗它"这是真硬件", 内核就一个, container 看到的"网卡" 就是 host 内核里的一个 net_device 而已, 区别只是在不同 netns 视图里;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Container netns                 Host root netns
┌──────────────┐ ┌─────────────────────────┐
│ │ │ │
│ ┌────────┐ │ │ ┌──────┐ │
│ │协议栈 │ │ ← 同一个内核 │ │ veth │ (host 端) │
│ └───┬────┘ │ │ └──┬───┘ │
│ │ │ │ │ │
│ ┌───▼────┐ │ veth pair │ ┌──▼─────┐ │
│ │ eth0 │◄─┼────────────────┼─►│docker0 │ (bridge) │
│ │(veth) │ │ (内核内部转发) │ └──┬─────┘ │
│ └────────┘ │ │ │ │
└──────────────┘ │ ┌──▼──┐ │
│ │ eth0│ (物理 NIC) │
│ └─────┘ │
└─────────────────────────┘

container 内的 eth0 就是 veth pair 的一端, 没有任何 hypervisor 模拟层, 没有 ring buffer 中转, 包从 container 协议栈下来直接被内核挪给 host 端 veth, 整个过程没出过内核;

二者直接对比

维度 VM Container
隔离层级 整套独立 guest OS + 独立内核 同一 host kernel + netns 视图隔离
"网卡"本质 hypervisor 模拟的硬件 (或直通物理 VF) host 内核里的一个虚拟 net_device
协议栈 guest 跑自己的, host 跑自己的, 各自完整 只有一份协议栈 (host kernel), 多套配置 (多 netns)
数据路径 guest 协议栈 → 模拟设备 → ring buffer → host tap → bridge → 物理网卡 container 协议栈 → veth → bridge → 物理网卡
性能开销 emulated 高, virtio 中, SR-IOV 几乎无 极低 (无模拟无 trap)
启动时间 秒到几十秒 (整套 OS 启动) 毫秒到秒
隔离强度 强, guest 内核漏洞不影响 host 弱, 共享 kernel, 一个 kernel CVE 全完
典型场景 跨 OS, 不信任的 workload, 强隔离 微服务, CI, 高密度部署
📝 核心区别一句话

VM 给的是"假硬件", container 给的是"独立视图的同一个内核"; 这就是为什么 Docker 网络栈的全部技巧都是在 net_device + netns + veth + bridge 之间排列组合, 你下面看到的所有模式都不出这个圈;

前置 D: Linux network namespace

Docker 网络的本质是对内核 network namespace 的封装, 没有 namespace 就没有容器网络可言;

network namespace 是 Linux 提供的一种隔离手段, 每个 netns 拥有它自己的一整套网络栈:

  • 自己的 network interface (网卡列表);
  • 自己的 IP 地址和路由表;
  • 自己的 ARP 邻居表;
  • 自己的 iptables / nftables 规则视图;
  • 自己的端口空间 (一个 netns 占了 80 端口不影响另一个 netns 也开 80);
  • 自己的 socket 列表;

但底下复用的是宿主机同一个 Linux kernel, 所以 namespace 之间通信不走真实网络, 全靠内核内部转发;

每启动一个 container, Docker 就为它新建一个 netns; docker exec 进容器时, 你看到的 ip a, route -n, netstat 都是这个 netns 视角下的结果, 跟宿主机是隔离的;

前置 E: veth pair + bridge 是怎么把两个 netns 连起来的

两个 netns 想互通, 内核给的最常见工具组合是 veth pair + bridge:

  • veth pair: 创建出来就是两张虚拟网卡, 一张丢进 container netns, 另一张留在宿主 netns; 这两张网卡像一根虚拟双绞线 — 一端发什么, 另一端就收到什么;
  • bridge: 宿主侧那一端不能孤零零悬着, 要插到一个虚拟交换机 (bridge) 上, 这样多个容器的"宿主端 veth" 都挂在同一台 bridge 上, 就形成了一个虚拟二层网络, 容器之间可以互相 ping;
1
2
3
4
5
6
7
8
9
10
11
12
13
         宿主机 root netns                     │  Container A netns

┌─────────────────────────┐ │
│ docker0 (bridge) │ │
│ 172.17.0.1/16 │ │
└────┬───────────┬────────┘ │
│ │ │
veth-A0 veth-B0 │
│ │ │
│ └──────► [veth-B1] ──────► Container B netns (172.17.0.3)
│ │
└──────────────────► [veth-A1] eth0 ────► (172.17.0.2)

所有 Docker 网络模式都是这两个原语的不同组合, 看完这张图剩下的章节就只是排列组合;

Docker 网络模式总览

Docker 内置六种网络驱动, 各自解决不同的隔离/连通性需求:

驱动 是否独立 netns 是否独立 IP 跨主机 典型用途
bridge (默认) ✅ (172.17.0.0/16) 单机多容器, 默认场景
host ❌ (复用宿主) ❌ (用宿主 IP) 极致性能, 牺牲隔离
none ✅ (空白) 完全离线容器 / 自定义网络
container ❌ (复用别的容器) sidecar / Pod 模型
macvlan ✅ (LAN 同段) 容器要像物理机一样直挂 LAN
ipvlan 同 macvlan, 但共享 MAC
overlay 多机集群 (Swarm/K8s)

下面逐个展开;

模式 1: bridge (默认模式)

docker run 不加 --network 时走的就是这条; 启动流程:

  1. 宿主机 boot 时 Docker daemon 创建一个名为 docker0 的 bridge, 默认网段 172.17.0.0/16;
  2. 创建容器时, Docker 新建一个 netns, 拉一对 veth, 一端塞进容器叫 eth0, 另一端在宿主, 名字像 vethXXXX@if5, 然后挂到 docker0;
  3. 给容器 eth0 分一个 172.17.0.X, 把默认网关指向 docker0 的地址 172.17.0.1;
  4. 在宿主机的 iptables 里加一条 SNAT 规则, 容器出网时把源 IP 改成宿主 IP, 这样 LAN/公网就能正常回包;
1
2
3
4
5
6
7
8
Container (172.17.0.2)                 Host
│ │
│ eth0 ──► veth-host ──► docker0 ──► eth0 (LAN)
│ │
│ ┌────── iptables ──┘
│ │ SNAT: 172.17.0.2 → 192.168.x.y
▼ ▼
send pkt 出去前改 src IP

端口映射 -p 的本质

docker run -p 8080:80 看起来是"把容器 80 暴露到宿主 8080", 实现上是 iptables 的 DNAT:

1
2
3
4
5
6
7
LAN 来包: dst = 192.168.x.y:8080

▼ (iptables PREROUTING DNAT)
改写为: dst = 172.17.0.2:80

▼ (走 docker0 转给容器)
容器收到, 看到的就是 8080 → 80 的目的地址翻译

回包反向走 SNAT, 让 LAN 客户端以为自己一直在跟宿主 IP 聊天; 这就是为什么 docker run -p 必须由 daemon 来注入 iptables, 而不是容器自己能搞定;

默认 bridge 的坑: 容器名 DNS 不通

默认 docker0 上的容器只能通过 IP 互相访问, 不能用容器名; 想用 ping container_b 这种就得用 user-defined bridge (下一个章节);

模式 2: host 模式

docker run --network host:

  • 不创建新的 netns, 容器进程直接复用宿主 root netns;
  • 容器看到的 eth0, IP, 路由表, 全是宿主的;
  • 容器开 LISTEN 0.0.0.0:80 就直接占用宿主的 80 端口;
优点 缺点
没有 NAT, 没有 veth, 性能最高 没有网络隔离, 端口冲突, 安全风险大
容器内程序看到的网络拓扑 = 宿主拓扑, 调试方便 跨容器协调端口很麻烦

适合场景: 高吞吐网络应用 (DPDK, 数据库 replica, 监控 agent), 或者明确不需要隔离的系统级容器;

模式 3: none 模式

docker run --network none:

  • 容器有独立 netns, 但里面只有 lo 一张 loopback 网卡, 没有任何对外连通性;
  • 容器内进程能跑, 但 curl 谁都不通, ping 8.8.8.8 也不通;

适合场景:

  • 完全离线计算 (纯 CPU 跑加密/分析, 不允许偷数据出去);
  • 自己手动用 ip netns 搭复杂网络拓扑, 不想要 Docker 默认那套;

模式 4: container 模式 (共享 netns)

docker run --network container:<name>:

  • 新容器不创建自己的 netns, 直接进入指定容器的 netns;
  • 两个容器看到一模一样的网卡 / IP / 端口, 它们之间通过 localhost 互访;

这其实就是 Kubernetes Pod 模型的底层原理: 一个 Pod 里多个 container 共享 netns, 主容器开 80, sidecar 用 localhost:80 就能直接连;

1
2
3
4
5
6
7
8
9
10
Pod / Container Group
┌────────────────────────────────────┐
│ shared netns │
│ eth0: 10.244.1.5 │
│ │
│ ┌─────────┐ ┌──────────────┐ │
│ │ app │ │ envoy sidecar│ │
│ │ :8080 │◄───│ localhost:8080│ │
│ └─────────┘ └──────────────┘ │
└────────────────────────────────────┘

模式 5: user-defined bridge (推荐生产用)

docker network create my-net 创建一个 user-defined bridge, 然后 docker run --network my-net ...;

虽然底层和默认 docker0 都是 bridge driver, 但有一系列重要差异, Docker 官方明确推荐生产环境用 user-defined bridge:

特性 默认 docker0 user-defined bridge
容器名 DNS 互访 ❌ 只能用 IP ✅ 内置 DNS, 容器名直接当 hostname
网络可隔离 ❌ 所有容器共用 docker0 ✅ 不同 user-defined network 之间默认隔离
动态加入/退出 容器启停才行 docker network connect/disconnect 在线切换
自定义子网 ❌ 固定 ✅ 可指定 subnet/gateway
📝 实战习惯
  • 一个 docker-compose 项目自动创建一个 user-defined bridge, 项目内的服务用 service 名字互相调用, 不要硬编码 IP;
  • 不同业务/环境之间用不同的 user-defined network, 保证网络层面就是隔离的;

模式 6: macvlan / ipvlan (容器 = LAN 设备)

前面所有 bridge 模式, 容器对外都是宿主在做 NAT, LAN 上别的机器看到的是宿主 IP; 有时候这不够, 比如:

  • 老系统认 MAC 地址做 license;
  • 监控/分析需要直接接收 LAN 二层广播 (DHCP, ARP, multicast);
  • 想让容器拥有 LAN 上一个独立 IP, 跟物理机平起平坐;

这时候用 macvlan:

1
2
3
4
5
6
Host eth0 (192.168.1.10)
├── macvlan child A → Container A: 192.168.1.50, MAC aa:bb:...01
├── macvlan child B → Container B: 192.168.1.51, MAC aa:bb:...02
└── macvlan child C → Container C: 192.168.1.52, MAC aa:bb:...03

LAN 上的别的设备看到的就是: "哦, 多了几台机器, IP 192.168.1.50/51/52"

每个容器获得一个真实 MAC 地址, 看起来就像直接插在 LAN 交换机上的物理机;

ipvlan 思路类似但不分配新 MAC, 多个容器共用宿主 MAC, 在 L3 上做隔离; 适合云上虚机环境 — 很多云的虚机不允许接口学习多个 MAC, macvlan 用不了, 这时候 ipvlan 是替代方案;

模式 独立 MAC 与宿主同网段 限制
macvlan 物理交换机要允许 promiscuous, WiFi 通常不行
ipvlan 不能在二层和宿主直接通信 (L2 mode 例外)

模式 7: overlay (跨主机)

前面六种全都是单机内的方案, 如果两台不同物理机上的容器要互通, 单机 bridge 没办法, 你需要 overlay network;

overlay 的核心思路是: 在宿主之间建一条隧道, 把容器之间的 L2 帧封进 UDP 包扔过去:

1
2
3
4
5
6
7
8
9
Container A (10.0.0.5)              Container B (10.0.0.6)
│ ▲
│ 以为自己在同一个 L2 上 │
▼ │
[Host A] [Host B]
│ ▲
│ 实际: │
│ 把 L2 frame 封进 VXLAN UDP │
└──────► UDP/4789 ──► 物理网络 ─────►───┘

实现协议是 VXLAN (RFC 7348), 默认 UDP 4789 端口; 跨主机的服务发现通常需要一个 KV 存储 (Swarm 自带, K8s 用 etcd) 来维护"哪个 IP 在哪台宿主"的映射;

overlay 几乎不会让你单独 docker run 来玩, 它跟编排器 (Docker Swarm / Kubernetes) 是绑定使用的;

容器间通信的几条路径

把上面的模式串起来看, 两个容器要互通有几条典型路径:

场景 路径
同一 user-defined bridge 容器 A eth0 → veth → bridge → veth → 容器 B eth0 (纯内核转发, 不走 NAT)
默认 docker0 不同容器 同上, 但只能用 IP, 没有 DNS
不同 user-defined network 默认隔离, 需要 docker network connect 把容器加入对方网络
host 模式 ↔ bridge 模式 通过 docker0 网关 / 宿主 IP 走
跨主机 (overlay) 容器 → overlay → VXLAN 封包 → 物理网 → 对端宿主 → 解包 → 对端容器
同 Pod (container 模式) localhost, 共享 netns, 不出网卡

排查 Docker 网络的常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 看所有网络
docker network ls

# 看某网络详情 (有哪些容器, 子网, 网关)
docker network inspect bridge

# 进容器看它的 netns 视角
docker exec -it <c> ip a
docker exec -it <c> ip route
docker exec -it <c> ss -tlnp

# 宿主侧看 iptables 规则 (DNAT/SNAT 都在这)
sudo iptables -t nat -L -n -v

# 查 veth 对应关系 (容器 eth0 对应宿主哪个 vethXXXX)
docker exec -it <c> ethtool -S eth0 | grep peer_ifindex
ip link | grep <ifindex>
📝 选型速记
  1. 单机, 一般业务 → user-defined bridge (不要用默认 docker0);
  2. 要极致网络性能 → host, 但你得接受没有隔离;
  3. 要完全离线 / 自己搭网络 → none;
  4. K8s Pod 这种共享网络的 sidecar → container;
  5. 容器要像物理机一样上 LAN → macvlan (云上用 ipvlan);
  6. 多台宿主集群联通 → overlay, 通常配合 Swarm 或 K8s;
  7. 凡是涉及端口映射 -p, 背后一定是 iptables 在做 DNAT, 别忘了去 iptables -t nat -L 验证;