Docker Swarm 之 网络(Overlay)

摘要

Overlay 简介

  • 在 Docker Swarm 中,overlay 网络 是一种分布式网络驱动,用于将集群中不同主机上的容器连接到同一个逻辑网络中,就像它们在同一台主机上一样。

  • 当你使用 Docker Swarm 部署服务时,Swarm 会自动使用 overlay 网络来连接不同节点上的容器,实现服务发现和负载均衡,保证容器间的通信安全(通过加密)。

  • overlay 网络特点

    • 跨主机通信:容器无论在哪个节点上,都可以使用 overlay 网络进行通信。
    • 内置服务发现:容器之间可以通过服务名称直接通信。
    • 支持加密:Swarm 的 overlay 网络支持数据加密,提高安全性。
    • 自动配置:Swarm 会自动为 overlay 网络分配子网、管理 IP 等。

Swarm 中的 overlay 网络

  • 当我们初始化Swarm 时,Swarm 会自动创建两个network,一个是 bridge network:docker_gwbridge ,一个是 overlay network:ingress

1
2
3
4
5
6
7
docker network ls
NETWORK ID NAME DRIVER SCOPE
6b7aadbbd180 bridge bridge local
5ddadf5d0608 docker_gwbridge bridge local
21c6f5b1bedd host host local
idx465x3jg68 ingress overlay swarm
a770c5ad4b13 none null local

docker_gwbridge

  • 查看 docker_gwbridge 详情,其网段为 172.18.0.0/16,网关为 172.18.0.1,内部有一个容器 ingress-sbox

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
docker inspect docker_gwbridge
[
{
"Name": "docker_gwbridge",
"Id": "5ddadf5d06086fcdad5890b8d59edcca4b1293bde23a26f1968fd6114fcaec93",
"Created": "2025-06-08T07:29:52.119033035-04:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"ingress-sbox": {
"Name": "gateway_ingress-sbox",
"EndpointID": "4764160a1b048da6d325a2f14165a981a446892ea3b4ebb12e20ee689fdac397",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.enable_icc": "false",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.name": "docker_gwbridge"
},
"Labels": {}
}
]
  • 先来看这个网段 172.18.0.0/16,我们查看主机的网络和路由表,可以看到 261: docker_gwbridge,其IP地址为 172.18.0.1,所以这里我们就可以知道 docker_gwbridge 就是连接到261: docker_gwbridge这块网卡上的,另外当前还有一个263: veth3176100@if262虚拟网络接口也连接到261: docker_gwbridge上,通过路由表我们得知其最终连接到2: enp0s5上,也就是这台主机的网卡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
ip a
## 输出结果
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp0s5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:1c:42:49:12:82 brd ff:ff:ff:ff:ff:ff
inet 10.211.55.10/24 brd 10.211.55.255 scope global noprefixroute enp0s5
valid_lft forever preferred_lft forever
inet6 fdb2:2c26:f4e4:0:21c:42ff:fe49:1282/64 scope global dynamic noprefixroute
valid_lft 2591886sec preferred_lft 604686sec
inet6 fe80::21c:42ff:fe49:1282/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:5c:31:cd:30 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:5cff:fe31:cd30/64 scope link
valid_lft forever preferred_lft forever
261: docker_gwbridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:fe:e3:ca:f7 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 brd 172.18.255.255 scope global docker_gwbridge
valid_lft forever preferred_lft forever
inet6 fe80::42:feff:fee3:caf7/64 scope link
valid_lft forever preferred_lft forever
263: veth3176100@if262: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker_gwbridge state UP group default
link/ether 96:4b:c1:d9:83:c5 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::944b:c1ff:fed9:83c5/64 scope link
valid_lft forever preferred_lft forever

# 查看路由表
route -n
## 输出结果,其目的地址最终都会转到网关 10.211.55.1 上
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.211.55.1 0.0.0.0 UG 100 0 0 enp0s5
10.211.55.0 0.0.0.0 255.255.255.0 U 100 0 0 enp0s5
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker_gwbridge

# 查看docker_gwbridge网桥设备信息,可以看到其挂载了一个虚拟网卡 veth3176100
brctl show docker_gwbridge
bridge name bridge id STP enabled interfaces
docker_gwbridge 8000.0242fee3caf7 no veth3176100
  • 按理说263: veth3176100@if262虚拟网络接口应该对应到一个容器上,那么接下来我们就看一看这个容器 ingress-sbox,当前docker中并没有这个容器,那么这个容器在哪里呢?docker创建的容器都会有一个网络命名空间,其保存在宿主机的/var/run/docker/netns/

1
2
3
4
cd /var/run/docker/netns/
ls
## 输出,在这里我们还真的发现了与这个容器名称类似的网络命名空间,容器名称是中划线,网络命名空间名称是下划线
1-idx465x3jg ingress_sbox
  • 进入这个网络,我们就找到与宿主机上的虚拟网络接口对应的容器网络接口了:262: eth1@if263

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nsenter --net=ingress_sbox ip a
## 输出
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
259: eth0@if260: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 02:42:0a:00:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.0.0.2/24 brd 10.0.0.255 scope global eth0
valid_lft forever preferred_lft forever
262: eth1@if263: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet 172.18.0.2/16 brd 172.18.255.255 scope global eth1
valid_lft forever preferred_lft forever
  • 这里还有一个网络接口 259: eth0@if260,它又是与谁对接的呢?别着急,我们接着往下看。

ingress

  • 查看 ingress 详情,其网段为 10.0.0.0/24,网关为 10.0.0.1,内部有一个容器 ingress-sbox,另外其有一个Peers属性,内部包含了集群中所有的节点IP,所以从这里也能大概猜出这个网络是负责节点间通信的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
docker network inspect ingress
## 输出
[
{
"Name": "ingress",
"Id": "idx465x3jg682fmceumsio297",
"Created": "2025-06-08T07:29:51.734714967-04:00",
"Scope": "swarm",
"Driver": "overlay",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "10.0.0.0/24",
"Gateway": "10.0.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": true,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"ingress-sbox": {
"Name": "ingress-endpoint",
"EndpointID": "fd05086c5104e28c75dbed3e3b308236aaa0e87b698dad6186c23c81755bb009",
"MacAddress": "02:42:0a:00:00:02",
"IPv4Address": "10.0.0.2/24",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.driver.overlay.vxlanid_list": "4096"
},
"Labels": {},
"Peers": [
{
"Name": "4969a4611607",
"IP": "10.211.55.10"
},
{
"Name": "ae3756658a26",
"IP": "10.211.55.14"
},
{
"Name": "f12309731131",
"IP": "10.211.55.13"
},
{
"Name": "de5000b11067",
"IP": "10.211.55.12"
},
{
"Name": "377001904d63",
"IP": "10.211.55.11"
}
]
}
]
  • 我们还是先来看这个网关10.0.0.1,在哪呢?宿主机的网络设备中并没有,所以它应该是docker创建的,我们还是要从/var/run/docker/netns中查看一下,这里还有一个名称为 1-idx465x3jg 的网络命名空间,我们进去看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nsenter --net=/var/run/docker/netns/1-idx465x3jg ip a
## 输出
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
258: vxlan0@if258: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UNKNOWN group default
link/ether 32:c9:a4:01:8a:19 brd ff:ff:ff:ff:ff:ff link-netnsid 0
2: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 32:c9:a4:01:8a:19 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.1/24 brd 10.0.0.255 scope global br0
valid_lft forever preferred_lft forever
260: veth0@if259: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UP group default
link/ether 8a:d3:52:ac:7d:cd brd ff:ff:ff:ff:ff:ff link-netnsid 1
  • 在这里我们找到了2: br0,其IP地址为10.0.0.1,所以它就是我们要找的网关。其上面还挂载了两个网络设备,一个是 260: veth0@if259,这个就是与ingress_sbox259: eth0@if260对应的网络接口 ,另一个是 258: vxlan0@if258,其基于vxlan协议,负责集群跨主机通信。

overlay总结

  • docker_gwbridge中的容器ingress-sbox,其有两块网卡,一块对接宿主机上的 261: docker_gwbridge,另一块对接/var/run/docker/netns/1-idx465x3jg中的 2: br0

  • 实际上Swarm中的所有容器都有两个网卡,一块对接宿主机上的 261: docker_gwbridge,另一块对接/var/run/docker/netns/1-idx465x3jg中的 2: br0

  • 当请求到达宿主机时,会通过enp0s5转发到docker_gwbridge,然后先被转到ingress-sbox容器,然后再经过其转发到br0网关,再由它负责查找目标容器。如果目标容器不在本节点,则通过vxlan0网络接口转发到其它节点进行查找,中间经过一系列的网络地址转换。

  • 当你通过docker network create --driver overlay my-network创建一个overlay网络时,Docker会创建一个类似“ingress”的网络结构(新的br0),如果不指定ip段,其ip段会从10.0.1.0/24开始,依次递增一个网段。但会共用docker_gwbridge

查看overlay网络中的负载均衡

  • 启动一个service

1
docker service create --name my-nginx --replicas 5 --publish 80:80 nginx
  • 查看ingress_sboxiptables数据链中的 mangle 表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
nsenter --net=/var/run/docker/netns/ingress_sbox iptables -nvL -t mangle
## 输出结果,这里看到 PREROUTING 链中有一条监听80端口的规则,其被打了Mark标记: 0x105,换算为10进制:261
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 MARK tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 MARK set 0x105

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 MARK all -- * * 0.0.0.0/0 10.0.0.33 MARK set 0x105

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
  • 通过ipvsadm查看负载均衡信息

ipvsadm 是 Linux 下管理 IPVS(IP Virtual Server)负载均衡器的命令之一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 如果没有ipvsadm,则安装
dnf install ipvsadm -y
# 查看ipvs信息
nsenter --net=/var/run/docker/netns/ingress_sbox ipvsadm -Ln
# -L:表示列出当前 IPVS 的规则和状态(List)。
# -n:表示以数字方式显示地址和端口,而不进行 DNS 解析或端口名解析(即:IP 和端口号以数字显示,更直观,也更快)。
## 输出,可以看到 FWM 261 rr,这里rr表示轮询
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
FWM 261 rr
-> 10.0.0.34:0 Masq 1 0 0
-> 10.0.0.35:0 Masq 1 0 0
-> 10.0.0.36:0 Masq 1 0 0
-> 10.0.0.37:0 Masq 1 0 0
-> 10.0.0.38:0 Masq 1 0 0
## 输出解释
# Prot: 协议(TCP/UDP),这里是 FWM,表示Firewall Mark(防火墙标记)模式
# LocalAddress:Port: 本地地址和端口号,这里是 261 ,这是防火墙标记值(mark 值),就是上面看到的那个16进制 0x105
# Scheduler: 调度算法,这里是 rr,表示轮询(round-robin),调度算法还有 wrr(加权轮询)、lc(最少连接)等
# RemoteAddress:Port: 远程地址和端口号,这里是 10.0.0.34:0,表示负载均衡到的第一个节点,后面同理
# Forward: 转发策略,这里是 Masq,表示将负载均衡到的节点的IP地址转换成宿主机的IP地址,即 Masquerade(伪装)。转发方式(如:Masq、Tunnel、Direct Route)
# Weight: 权重,这里是 1,表示负载均衡到的节点的权重,默认为 1
# ActiveConn: 当前活动连接数,这里是 0
# InActConn: 当前不活动连接数(等待关闭的连接),这里是 0

VXLAN是什么?

  • VXLAN(Virtual Extensible LAN)是 Cisco 公司开发的一种虚拟局域网(VLAN)技术,它可以将多个 VLAN 逻辑分组,并使用单个物理网络进行管理。VXLAN 的主要作用是提高网络性能和扩展性。

  • 它本质上是一种 网络封装协议(overlay protocol),用来在已有的 IP 网络之上,构建二层(L2)虚拟网络。

为什么需要 VXLAN?

  • 在传统数据中心或云计算中,经常有这样的需求:

    • 跨不同物理网络或子网,部署在不同服务器上的虚拟机或容器,要能像在同一个二层网络里一样直接通信。
    • VLAN(802.1Q)提供的二层隔离能力只有 12 bit VLAN ID(最多 4096 个 VLAN),在大型数据中心远远不够用。
  • 数据中心想要更好的弹性、跨区域部署、容器编排、大规模租户隔离。

VXLAN 核心原理

  • VXLAN 通过封装的方式,把二层以太网帧包在 UDP 数据报里,在三层 IP 网络中传递。

  • 封装格式大致是:

1
2
3
4
5
6
7
8
9
+-------------------------+
| 外层IP头 (IP Header) |
+-------------------------+
| 外层UDP头 (UDP Header) |
+-------------------------+
| VXLAN头 (VXLAN Header) |
+-------------------------+
| 内层二层帧 (Ethernet) |
+-------------------------+
  • 外层 IP/UDP 用于三层传输

  • 内层保留原本的二层以太网帧(如 MAC 地址)

  • VXLAN 头部里面包含了一个 VNI (VXLAN Network Identifier):24 bit,可支持 1600万个虚拟网络

  • 简单示意图

1
2
3
4
5
6
7
VM1 (10.1.1.1) ——> VTEP1 ——> Underlay IP网络 ——> VTEP2 ——> VM2 (10.1.1.2)

VTEP1 封装:
内层以太网帧 + VXLAN头 (VNI) + UDP + IP

VTEP2 解封装:
去掉外层头部,还原原始二层帧