本文已参与「新人创作礼」活动,一起开启掘金创作之路
本文前半部分主要介绍容器,镜像,命名空间等基本概念,熟悉的同学可以直接跳过;主要是想介绍同一台宿主机上的不同容器间的网络通讯以及跨宿主机容器之间的网络通讯,kubernetes社区两个重要的跨主机网络通讯项目flannel和Calico,对比两者之间的优缺点,希望给架构师同学在方案设计阶段提供一点参考。
一、什么是容器?
- 现实生活中的容器是用来装东西的,方便管理和运输,软件领域借鉴了这个思想
- 有一个大家熟悉的场景,一台PC上安装了Win10,管理员创建了两个账号,vpclub和vphonor,用户vpclub登录后,看到的是vpclub的桌面,自己的文件以及自己安装的应用程序Word,相应的vphonor登录后,看到的桌面、文件和应用程序则不同,vphonor安装了WPS而不是Word,这就类似两个Win10容器,它们分别“装”了属于两个用户的办公环境,两个人共享Win10操作系统
- 容器(Linux Container简称LXC)类似这个办公场景,不同的是LXC“装”的是应用程序所依赖的运行环境,它是一种轻量级的操作系统层虚拟化技术,目的是隔离应用程序的运行环境,从而保证了软件交付过程中开发、测试、生产环境的强一致性
- 容器技术基于Namespace和Cgroup两大机制来实现,Namespace负责隔离,Cgroup(Linux Control Group)负责资源使用限制
- 理解了容器,容器镜像是什么,就比较简单了,可以认为容器镜像 image是存储在U盘或光盘上的Win10操作系统;容器 Container是运行在电脑上的WIN10,容器是运行态(动态),容器镜像是存储文件(静态);一个容器镜像可以多次运行,从而创建多个容器,它们之间是1:n的关系;容器镜像有版本控制,即tag,类似Win7,Win8,Win10
- 现在大家习惯了先交付容器镜像,然后再运行它创建容器,但是历史上是先有容器技术,容器镜像得益于dotCloud公司的创新,即大家熟悉的 Docker。容器经常会被拿来和大家熟悉的虚拟机对比,如下图所示:
图1 虚拟机和容器运行环境对比
思考
- 容器具有敏捷和高性能的优点,在运行Win10的电脑上切换一个账号比启动Win10耗时要少的多
- 但容器的缺点是隔离不彻底,试想:
- 在容器中执行top命令,会看到什么?
- 如果容器A中的应用程序修改了系统时间或者优化了内核参数,那容器B中的应用程序会怎样?
- 如果容器A中的应用程序需要运行在Linux4.x内核上,而容器B中的应用程序兼容Linux内核3.x以下版本,那HostOS又该如何处理?
- 虚拟机具有完全隔离的优势,拥有独立的操作系统,但是在网络性能等方面又会带来15%左右的损耗。图1中右半边部分和你在其它地方看到的图略微有一点区别,原因是容器的运行并不依赖DockerEngine,容器是直接运行在HostOS上的,DockerEngine提供的是容器操作和管理界面,帮助用户启动和管理容器;容器的本质是进程,只不过DockerEngine在创建容器进程的时候,会调用操作系统接口,做一些命名空间(Namespace)和Cgroup方面的设置。
二、什么是命名空间?
- 班级聚会上叫一声“刘伟”,和在车站叫一声“刘伟”,效果是不一样的,原因就是范围不同,湖北的刘伟和山西的刘伟,虽然名字相同,但是命名空间一个是湖北一个是山西
- 班级聚会或者车站这样的范围就可以理解为命名空间;程序员经常会遇到Namespace这个词,例如XML中的命名空间,C++代码中的Namespace,它们的作用是类似的;Linux内核里面实现了七种不同类型的namespace,如下所示:
| 名称 | 宏定义 | 隔离的内容 |
|---|---|---|
| Mount | CLONE_NEWNS | 为进程提供了一个文件层次视图 since Linux 2.4.19 |
| UTS | CLONE_NEWUTS | 隔离主机名 since Linux 2.6.19 |
| IPC | CLONE_NEWIPC | 隔离进程间通讯所需的资源( System V IPC, POSIX message queues),同一个IPC名字空间内的进程可以彼此看见,允许进行交互,不同空间进程无法交互 since Linux 2.6.19 |
| PID | CLONE_NEWPID | 隔离进程号,同一个进程,在不同的命名空间进程号不同 since Linux 2.6.24 |
| Network | CLONE_NEWNET | 为进程提供一个完全独立的网络协议栈视图,包括网络设备接口,IPv4和IPv6协议栈,IP路由表,防火墙规则,sockets等,和独立的系统一样 since Linux 2.6.24 |
| User | CLONE_NEWUSER | 用于隔离用户 started in Linux 2.6.23 and completed in Linux 3.8 |
| Cgroup | CLONE_NEWCGROUP | 最新加入的命名空间,用于隔离资源限制信息;之前版本在一个容器里查看 /proc/$PID/cgroup,是可以看到整个宿主机的cgroup 信息的 since Linux 4.6 |
- 有了Network Namespace做隔离,Linux容器能看见的网络,就是被隔离在它自己的 Network Namespace 中的“网络栈”,包括了网卡(Interface)、回环设备(Loopback)、路由表(Routing Table)和 iptables 规则等,这些要素就构成了一个容器发起和响应网络请求的基本环境。
- 如果一个容器不需要独立的“网络栈”,和宿主机上的其它进程使用相同的“网络栈”,即共用宿主机的网络栈,可以在启动参数中指定(-net=host),不开启Network Namespace,直接使用宿主机网络栈的方式,虽然可以为容器提供良好的网络性能,但不可避免地引入端口冲突等共享网络资源的问题,所以在大多数情况下,容器进程会开启Network Namespace, 即拥有属于容器自己的IP地址和端口。
- 那不同容器之间如何通讯呢?每个容器都拥有自己的IP地址(网络栈),其实可以把每一个容器看做一台轻量化的虚拟主机。
三、同一台宿主机上的容器间网络通讯
- 现实世界中如果想要实现两台主机之间的通信,最直接简单的办法,就是用一根网线把它们连接起来;而如果你想要实现多台主机之间的通信,那就需要把它们用网线连接在一台交换机上。
- 在Linux世界中谁来承担交换机的职责?虚拟网线又是谁?承担起虚拟交换机作用的网络设备,是网桥(Bridge),它是一个工作在数据链路层(Data Link)的设备,主要功能是根据 MAC 地址学习来将数据包转发到网桥的不同端口(Port)上。Docker会默认在宿主机上创建一个名叫 docker0 的网桥,凡是连接在 docker0 网桥上的容器,就可以通过它来进行通信,那容器是如何“连接”到 docker0 网桥上的?
- 这就要使用一种名叫 Veth Pair 的虚拟设备了,类似一个网线的两端用水晶接头链接两张网卡,Veth Pair虚拟设备的特点是,总是以两张虚拟网卡(Veth Peer)的形式成对出现,从其中一个“网卡”发出的数据包,可以直接出现在与它对应的另一张“网卡”上,哪怕这两个“网卡”在不同的Network Namespace里。 下面通过实验来说明两个容器间的通讯过程,首先登录虚拟机Node1,查看网络设备:
1、第一次查看宿主机网络信息
[root@node1 ~]# yum install -y net-tools
[root@node1 ~]# yum install -y bridge-utils
[root@node1 ~]# iptables -t raw -A OUTPUT -p icmp -j TRACE
[root@node1 ~]# iptables -t raw -A PREROUTING -p icmp -j TRACE
[root@node1 ~]# ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:55:85:c8:d8 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@node1 ~]# ip a
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:55:85:c8:d8 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
[root@node1 ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02425585c8d8 no
接下来在 Node1 上启动容器 busybox-1,并查看容器网络信息:
[root@node1 ~]# docker run -it --name busybox-1 busybox /bin/sh
/ # ip a
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:02
inet addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
/ # route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 172.17.0.1 0.0.0.0 UG 0 0 0 eth0
172.17.0.0 * 255.255.0.0 U 0 0 0 eth0
- 这里可以看到容器 busybox-1 里有一个eth0网卡,它正是一个 Veth Pair虚拟网线在容器里的一端,通过route命令查看 busybox-1 容器的路由表,可以看到这个 eth0 网卡是这个容器里的默认路由设备,所有对 172.17.0.0/16 网段的请求,会被交给 eth0 来处理,而 Veth Pair虚拟网线的另一端,则插在宿主机的docker0网桥上。
2、第二次查看宿主机网络信息
[root@node1 ~]# ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:55:85:c8:d8 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
veth6993911: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
ether a2:ae:6b:4a:28:2f txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@node1 ~]# ip a
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:55:85:c8:d8 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
5: veth6993911@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether a2:ae:6b:4a:28:2f brd ff:ff:ff:ff:ff:ff link-netnsid 0
[root@node1 ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02425585c8d8 no veth6993911
- 这里会看到,比第一次的网络信息多出了一个网卡veth6993911,它就是容器busybox-1 容器对应的 Veth Pair 虚拟网线在宿主机上的另一端,并且通过 brctl show 的输出可以看到这一端被“插”在了docker0网桥上。
在Node1上启动容器 busybox-2,并查看容器网络信息:
[root@node1 ~]# docker run -it --name busybox-2 busybox /bin/sh
/ # ip a
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:03
inet addr:172.17.0.3 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
/ # route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 172.17.0.1 0.0.0.0 UG 0 0 0 eth0
172.17.0.0 * 255.255.0.0 U 0 0 0 eth0
- 这里可以看到容器 busybox-2 里同样有一个eth0网卡,它正是一个Veth Pair虚拟网线在容器里的一端,通过route命令查看busybox-2容器的路由表,可以看到这个 eth0 网卡是这个容器里的默认路由设备,所有对 172.17.0.0/16 网段的请求,会被交给 eth0 来处理,而 Veth Pair虚拟网线的另一端,则插在宿主机的docker0网桥上。
3、第三次在宿主机上查看网络信息
[root@node1 ~]# ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:55:85:c8:d8 txqueuelen 0 (Ethernet)
RX packets 6 bytes 250 (250.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 6 bytes 404 (404.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
veth6993911: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
ether a2:ae:6b:4a:28:2f txqueuelen 0 (Ethernet)
RX packets 3 bytes 167 (167.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 4 bytes 244 (244.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
vethc4fefc2: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
ether d2:8c:37:56:58:1b txqueuelen 0 (Ethernet)
RX packets 3 bytes 167 (167.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 4 bytes 244 (244.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@node1 ~]# ip a
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:55:85:c8:d8 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
5: veth6993911@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether a2:ae:6b:4a:28:2f brd ff:ff:ff:ff:ff:ff link-netnsid 0
7: vethc4fefc2@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether d2:8c:37:56:58:1b brd ff:ff:ff:ff:ff:ff link-netnsid 1
[root@node1 ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02425585c8d8 no veth6993911
vethc4fefc2
- 发现比第二次的网络信息多出了一个网卡vethc4fefc2,它就是容器busybox-2容器对应的 Veth Pair 虚拟网线在宿主机上的另一端,并且通过 brctl show 的输出可以看到docker0网桥上多了一个网卡。
4、在busybox-1容器里ping一下busybox-2的IP 172.17.0.3,如下所示:
[root@node1 ~]# docker exec -it busybox-1 /bin/sh
/ # ping -c 4 172.17.0.3
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.086 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.059 ms
64 bytes from 172.17.0.3: seq=2 ttl=64 time=0.053 ms
64 bytes from 172.17.0.3: seq=3 ttl=64 time=0.053 ms
--- 172.17.0.3 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.053/0.062/0.086 ms
- 发现容器busybox-1和busybox-2已经相互连通,即同一宿主机上的两个容器默认就是相互连通的。
- 这其中的原理是,当在busybox-1 容器里访问busybox-2 容器的IP地址(如ping 172.17.0.3)的时候,这个目的IP地址会匹配到busybox-1容器里的第二条路由规则,这条路由规则的网关(Gateway)是 0.0.0.0,即这是一条直连规则,凡是匹配到这条规则的IP包,经过本机的eth0网卡,通过二层网络直接发往目的主机。
- 而要通过二层网络到达 busybox-2容器,就需要有172.17.0.3 这个IP地址对应的 MAC 地址,所以busybox-1 容器的网络协议栈,就需要通过eth0网卡发送一个ARP广播,查找IP地址对应的MAC地址。busybox-1的eth0网卡通过Veth Pair虚拟网线接在了宿主机的 docker0网桥上,即该网桥的“从设备”,从设备会被“剥夺”调用网络协议栈处理数据包的资格,从而“降级”成为网桥上的一个端口,这个端口唯一的作用,就是接收流入的数据包,然后把这些数据包交给对应的网桥,docker0 网桥在收到这些 ARP 请求之后,就会扮演二层交换机的角色,把 ARP 广播转发到其它被“插”在 docker0 上的虚拟网卡上,也就是说同样连接在 docker0网桥上的busybox-2容器的网络协议栈就会收到这个 ARP 请求,将自己的MAC地址回复给busybox-1容器。有了这个目的MAC地址,busybox-1容器的eth0 网卡就可以将数据包发出去。
- 根据 Veth Pair虚拟网线的原理,这个数据包会立刻出现在宿主机上的veth6993911虚拟网卡上,docker0继续扮演二层交换机的角色,负责转发,即docker0网桥根据数据包的目的MAC地址(也就是busybox-2容器的MAC地址),在它的MAC地址表里查到对应的端口(Port)为:vethc4fefc2,然后把数据包发往这个端口,这个端口正是busybox-2 容器“插”在 docker0 网桥上的虚拟网线的一端,数据包就进入到了busybox-2 容器的Network Namespace里,从busybox-2容器角度看,就是eth0 网卡上出现了流入的数据包,busybox-2的网络协议栈负责对流入的数据包进行处理,并将响应返回给busybox-1。 以上就是同一个宿主机上的不同容器通过 docker0 网桥进行通信的流程,如下所示:
图2 同一台宿主机上连个容器之间的网络通讯
想必你已经留意到,在实验开始的时候,打开了iptables的 TRACE 功能,打开/var/log/messages文件(也可以通过journalctl命令查看),数据包传输的日志如下:
Feb 1 19:07:05 node1 NetworkManager[790]: <info> [1612264025.4833] device (veth51ae339): carrier: link connected
Feb 1 19:07:05 node1 NetworkManager[790]: <info> [1612264025.4840] device (docker0): carrier: link connected
Feb 1 19:07:11 node1 kernel: device veth04f5890 entered promiscuous mode
Feb 1 19:07:11 node1 kernel: docker0: port 2(veth04f5890) entered forwarding state
Feb 1 19:07:11 node1 kernel: docker0: port 2(veth04f5890) entered forwarding state
Feb 1 19:07:11 node1 kernel: docker0: port 2(veth04f5890) entered disabled state
Feb 1 19:07:11 node1 NetworkManager[790]: <info> [1612264031.3853] manager: (veth61feeb4): new Veth device (/org/freedesktop/NetworkManager/Devices/12)
Feb 1 19:07:11 node1 NetworkManager[790]: <info> [1612264031.3859] manager: (veth04f5890): new Veth device (/org/freedesktop/NetworkManager/Devices/13)
Feb 1 19:07:11 node1 containerd: time="2021-02-02T19:07:11.402192245+08:00" level=info msg="starting signal loop" namespace=moby path=/run/containerd/io.containerd.runtime.v2.task/moby/e0d54e3b3e9847f666d1d1245c6ddacd30b19e5964ccda667875159979e6eac1 pid=9111
Feb 1 19:07:11 node1 kernel: IPVS: Creating netns size=2200 id=5
Feb 1 19:07:11 node1 kernel: IPVS: ftp: loaded support on port[0] = 21
Feb 1 19:07:11 node1 kernel: eth0: renamed from veth61feeb4
Feb 1 19:07:11 node1 kernel: docker0: port 2(veth04f5890) entered forwarding state
Feb 1 19:07:11 node1 kernel: docker0: port 2(veth04f5890) entered forwarding state
Feb 1 19:07:11 node1 NetworkManager[790]: <info> [1612264031.5184] device (veth04f5890): carrier: link connected
Feb 1 19:07:20 node1 kernel: docker0: port 1(veth51ae339) entered forwarding state
Feb 1 19:07:26 node1 kernel: docker0: port 2(veth04f5890) entered forwarding state
与之类似,宿主机上的进程访问该宿主机上的容器的IP地址时,请求的数据包先根据路由规则到达docker0网桥,然后被转发到对应的Veth Pair设备,最后出现在容器里,这个过程的示意图如下所示:
图3 宿主机和容器之间的网络通讯
在宿主机node1上查看路由信息,即对 172.17.0.0/16网段的请求,会被交给docker0来处理:
[root@node1 log]# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default gateway 0.0.0.0 UG 100 0 0 ens33
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.66.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33
同样地,当一个容器试图连接到其它主机时,比如:ping www.baidu.com, 它发出的请求数据包,首先经过 docker0 网桥出现在宿主机上。然后根据宿主机的路由表里的默认路由规则(192.168.66.0/24 via ens33)),对www.baidu.com 的访问请求就会交给宿主机的ens33处理,这个数据包就会经宿主机的ens33网卡转发到宿主机网络上,最终到达 www.baidu.com 对应的主机上,当然,这个过程的实现要求这台宿主机和目标主机是连通的,这个过程的示意图,如下所示:
图4:容器和宿主机外其它主机的通信
- 从上面的实验相信大家已经明白,当遇到容器连不通“外网”的时候,应该先测试 docker0 网桥能不能ping通,然后检查和docker0和Veth Pair设备相关的iptables规则是不是正常,基本就能够找到问题的答案了。
- 不过在最后一个“Docker容器连接其它主机”的例子里,你可能已经联想到了这样一个问题:如果在另外一台宿主机(比如:192.168.66.13)上,也有一个 Docker 容器busybox-3,那busybox-1 容器又该如何访问它呢?这就是容器的“跨主机通信”问题。
- 在宿主机安装Docker后的默认配置中,宿主机上的docker0网桥,和其它宿主机上的 docker0 网桥,没有任何关系,它们之间也没办法连通,连接在这些网桥上的容器,自然也没办法进行通信了。 如果通过软件的方式,创建一个整个集群中所有宿主机“公用”的网桥,然后把集群里的所有容器都连接到这个网桥上,不就可以互通了吗?整个集群里的容器网络类似下图所示:
图5:OverlayNetwork覆盖网络示意图
- 在已有的宿主机网络上,通过软件构建一个覆盖在已有宿主机网络之上的、可以把所有容器连通在一起的虚拟网络,这种技术就被称为:Overlay Network(覆盖网络)。
- Overlay Network 本质是由每台宿主机上的一个“特殊网桥”共同组成。比如,当Node-1上的busybox-1要访问Node-2上的busybox-3 的时候,Node-1上的“特殊网桥”在收到数据包之后,能够通过某种方式,把数据包发送到正确的宿主机,比如Node-2上,Node-2上的“特殊网桥”在收到数据包后,也能够通过某种方式,把数据包转发给正确的容器,比如 busybox-3。
- 又或者每台宿主机上不需要有一个特殊的网桥,而仅仅通过某种方式配置宿主机的路由表,就能够把数据包转发到正确的宿主机上,这就是接下来要分享的内容。
四、容器“跨主机通信”的原理分析,即Flannel项目介绍
- CoreOS公司推出的Flannel项目是一个容器网络框架,真正提供容器网络功能的,是Flannel的后端实现,Flannel支持三种后端实现:UDP、VXLAN和host-gw,三种不同的后端实现,代表了三种容器跨主机网络的主流实现方法。
- UDP模式是Flannel项目最早支持的一种方式,也是性能最差的一种方式,目前这个模式已经被弃用,Flannel之所以开始就选择UDP模式,是因为这种模式是最直接、也是最容易理解的容器跨主机网络实现方案,接下来从UDP模式开始,分析容器“跨主机网络”的实现原理。
1、Flannel-UDP模式
- 实验环境准备(这里只是给出简单的说明,详细的部署请参考官方文档):
- 下载Flanneld,mk-docker-opts.sh,安装到Node-1和Node-2节点上
- Flanneld运行需要root权限
- Node-1和Node-2上正确安装Docker,mk-docker-opts.sh会修改docker0网桥配置
- Flannel依赖etcd,用于存储子网信息,请提前准备好etcd的地址和证书
- 在etcd中配置网络参数:set /coreos.com/network/config '{"Network":"172.8.0.0/16", "Backend":{"Type": "udp"} }'
实验环境如下图所示:
图6:flannel UDP模式下容器跨主机通信
- 宿主机Node-1上有一个容器busybox-1,它的 IP 地址是 172.8.1.2,对应的docker0网桥的地址是172.8.1.1/24
- 宿主机Node-2上有一个容器busybox-4,它的 IP 地址是 172.8.2.3,对应的docker0 网桥的地址是172.8.2.1/24,下面通过busybox-1访问busybox-4的网络通信,分析flannel项目UDP模式的工作原理。
- busybox-1 容器里的进程发起的IP包,其源地址就是172.8.1.2,目的地址就是172.8.2.3,显然目的地址172.8.2.3不在Node-1的docker0 网桥的网段里,所以这个IP包会从docker0 网桥流出,出现在宿主机上,交给默认路由规则处理,即这个IP包的下一个目的地,就取决于宿主机上的路由规则了,flanneld进程在宿主机Node-1上添加加一条路由规则:
[root@node1 ~]# ip route
default via 192.168.1.1 dev ens33 proto static metric 100
172.8.0.0/16 dev flannel0 proto kernel scope link src 172.8.1.0 metric 100
172.8.1.0/24 dev docker0 proto kernel scope link src 172.8.1.1 metric 100
192.168.1.0/24 dev ens33 proto kernel scope link src 192.168.1.2 metric 100
- 上述IP包只能匹配到第二条172.8.0.0/16路由规则,从而进入到一个叫作 flannel0 的设备中,路由规则里的flannel0设备就是前面提到过的“特殊网桥”,它是一个Linux TUN(Tunnel)设备,工作在三层(Network Layer)的虚拟网络设备,在操作系统内核和用户应用程序之间传递IP包,如下图所示:
图7:flannel0 工作示意图
- 当操作系统将一个IP包发送给flannel0设备之后,flannel0就会把这个IP包,交给创建这个设备的应用程序,也就是Flanneld进程,也就是说flanneld通过TUN:flannel0接管了从docker0网桥流出的数据包,这一步数据从内核态流向用户态。
- flanneld拿到这个IP包,发现目的地址是172.8.2.3,目的地址所在的子网是172.8.2.0/24,flanneld从etcd中查找到该子网所在的宿主机是Node-2,即192.168.1.3,就把它发送给了 Node-2宿主机。
- 这里提到的子网,是Flannel项目里一个非常重要的概念,Flanneld为每台宿主机分配一个子网,例如Node-1对应的子网是172.8.1.0/24,Node-2对应的子网是172.8.2.0/24,这些数据被保存在Etcd中,在宿主机之间共享网络信息。
[root@node1 ~]# etcdctl get /coreos.com/network/subnets/172.8.2.0-24
{"PublicIP":"192.168.1.3"}
- 找到目标宿主机是Node-2,Node-1和Node-2是互通的,flanneld把busybox-1发给 busybox-4的IP包封装在一个UDP包里,发送给Node-2上的flanneld(端口8285),显然这个UDP包的源地址,就是Node-1的地址192.168.1.2,而目的地址则是Node-2的地址192.168.1.3。
- Node-2上监听8285端口的进程也是flanneld,flanneld解析从Node-1发过来的UDP包,得到busybox-1发送给busybox-4的原IP包,直接把这个IP包发送给它所管理的 TUN 设备,即Node-2宿主机上的flannel0设备,IP包进入了内核态,Linux内核网络栈负责处理这个IP 包,通过Node-2的路由表来寻找这个IP包的下一步流向,Node-2宿主机上的路由表和Node-1宿主机上的路由表类似,这个IP包的目的地址是172.8.2.3,Linux内核按照路由规则,把这个IP包转发给Node-2上的docker0网桥,docker0网桥会扮演二层交换机的角色,将数据包发送给正确的端口,通过Veth Pair虚拟网线设备进入到busybox-4的Network Namespace 里。
- 现在回头看看图6是不是更清晰了呢?从busybox-4返回数据包给busybox-1的流程正好相反,Node-2上的flanneld负责UDP封包,Node-1上的flanneld负责解包。
- 从上述跨宿主机容器间网络通讯,可以看出,除了UDP封包解包外,还有一个关键的环节是,容器通信所依赖的docker0网桥的地址范围必须是flanneld为宿主机分配的子网,flanneld通过Docker Daemon守护进程启动参数bip传递子网信息,如下所示:
[root@node1 ~]# FLANNEL_SUBNET=172.8.1.1/24
[root@node1 ~]# dockerd --bip=$FLANNEL_SUBNET ... ... ...
思考:UDP是无连接、无序、低可靠的传输协议,通过UDP转发的容器间通讯可靠吗?欢迎在留言区和大家过过招
- 前面提到Flannel UDP模式性能比较差,主要原因有两点:
- flanneld进行UDP封装(Encapsulation)和解封装(Decapsulation)的过程,是在用户态完成的,在Linux操作系统中,这些上下文切换和用户态操作的代价是比较高的。
- 由于使用了TUN设备(flannel0),在发送IP包和接收IP的过程中,各有三次用户态和内核态之间的数据拷贝
- 第一次,容器进程到docker0网桥,即从用户态进入内核态
- 第二次,TUN设备(flannel0)到flanneld进程,即从内核态到用户态
- 第三次,flanneld完成UDP封包后通过宿主机的ens33发出去,即从用户态进入内核态
- 所以现在UDP模式已经被弃用,VXLAN模式成为Flannel支持的主流的容器网络方案,VXLAN,即 Virtual Extensible LAN(虚拟可扩展局域网),是Linux内核中的一个模块,属于一种网络虚似化技术,VXLAN是在内核态实现封装和解封装的动作,从而通过与TUN相似的“隧道”机制,构建出覆盖网络(Overlay Network)。
2、Flannel-VXLAN 模式
- Flannel VXLAN模式的设计思路是:在现有的三层网络之上,“覆盖”一层虚拟的、由内核VXLAN模块负责维护的二层网络,使得连接在这个VXLAN二层网络上的主机之间,像在同一个局域网(LAN)里一样互通。实际上这些主机可能分布在不同的宿主机上,甚至是分布在不同的物理机房里。
- 为了能够在二层网络上创建“隧道”,VXLAN会在宿主机上设置一个特殊的网络设备VTEP作为“隧道”的两端,VTEP即:VXLAN Tunnel End Point(虚拟隧道端点),VTEP 设备的作用,和UDP模式中的flanneld进程非常相似,只不过VTEP进行封装和解封装的对象,是二层数据帧(Ethernet frame)而不是IP包;而且VTEP封装和解封装的动作,全部是在内核里完成的,性能更好。
- VXLAN隧道是个逻辑概念,即两个VTEP之间的点到点的逻辑隧道,VTEP为数据帧封装VXLAN头,UDP头和IP头,通过VXLAN隧道将封装后的报文传递到远端VTEP,远端VTEP设备对其进行解封装。
Flannel VXLAN模式通讯流程图如下图所示:
图8:Flannel VXLAN模式通信流程图
- 每台宿主机上的flanneld进程会负责维护VXLAN所需的VTEP设备,即上图中的flannel.1的设备,它既有IP地址,又有MAC地址,flanneld会将flannel.1的MAC地址写入etcd中;flanneld进程也会监听Etcd的EventAdded事件,当获取一台宿主机添加到Etcd的消息后,它会在本机执行三个动作:
- 路由信息,例如发往172.8.2.0/24的包都通过VTEP设备flannel.1设备发出
- ARP信息,例如172.8.2.0网关的MAC地址是XX:XX:XX:XX:XX:XX
- Fdb信息,例如MAC地址是XX:XX:XX:XX:XX:XX的包,都通过VXLAN发往目的地址192.168.1.3,即宿主机Node-2
可以通过ip命令查看ARP记录表:
[root@node1 ~]# ip neigh show dev flannel.1
172.8.2.0 lladdr 02:42:ac:11:00:02 PERMANENT
172.8.3.0 lladdr 02:42:ac:11:00:03 STALE
- 与Flannel UDP模式的流程类似,当busybox-1发出的IP包,目的地址是172.8.2.3,首先会出现在docker0网桥,然后被路由到本机flannel.1设备进行处理,即到了“隧道”的入口。
- 为了将IP包封装并发送到正确的宿主机,VXLAN就需要找到这条“隧道”的出口,即目的VTEP设备,先查路由表,172.8.2.3的网关是172.8.2.0,即Node-2上的flannel.1,进一步查ARP表,172.8.2.0的MAC地址是XX:XX:XX:XX:XX:XX,确定了目的VTEP是谁,Linux内核就可以进行二层封包动作了,二层帧如下图所示:
图9:容器二层帧结构
- 这样的数据包,对于宿主机网络显然是没有意义的,目的VTEP设备的MAC地址是躲在某一台宿主机里面的,宿主机之间发送的包格式应该类似下面的一个帧结构:
图10:宿主机二层帧结构
- 那把图9的帧结构放到图10帧结构中的Data位置,类似特洛伊木马,让宿主机的数据帧“载着”容器的数据帧,通过宿主机的ens33网卡发送出去,是不是就可以了?目的宿主机收到了这样一个数据帧,应该交给谁处理呢?显然这里还是缺少了一点什么。
- 为了把特洛伊木马顺利送到目的地,把希腊战士释放出来,Linux 内核会在容器数据帧前面,加上一个特殊的VXLAN头,用来表示这个“乘客”实际上是一个VXLAN要使用的数据帧,需要交给VTEP设备来处理,而这个VXLAN头里有一个重要的标志叫作VNI,它是VTEP 设备识别某个数据帧是不是应该归自己处理的重要标识,在Flannel中,VNI的默认值是1,这也是为什么宿主机上的VTEP设备都叫作flannel.1的原因,这里的“1”,其实就是VNI的值。
接着,Linux 内核会把这个数据帧封装进一个UDP包里发出去,数据帧如下图所示:
图11:Flannel VXLAN模式的UDP数据帧
- 跟Flannel UDP模式类似,宿主机以为自己的flannel.1设备只是在向另外一台宿主机的 flannel.1设备,发起了一次普通的UDP通信,然而这个特洛伊木马里面是一个完整的二层数据帧。
- 那宿主机的MAC地址和IP又是怎么确定的呢?flannel.1设备只知道另一端的flannel.1设备的MAC地址,并不知道对应的宿主机地址是什么,要这个UDP包发给目的容器所在的宿主机,需要flannel.1设备扮演一个“网桥”的角色,在二层网络进行 UDP 包的转发。而在Linux内核里面,“网桥”设备进行转发的依据,保存在 FDB(Forwarding Database) 转发数据库中,正是前面提到的flanneld 三个动作中的最后一个,可以通过bridge fdb命令查看,如下所示:
[root@node1 ~]# bridge fdb show flannel.1
00:50:56:e8:d5:18 dev flannel.1 dst 192.168.1.3 self permanent
- 这条记录的意思是,MAC地址是00:50:56:e8:d5:18的二层数据帧,应该通过flannel.1设备发往IP地址为192.168.1.3的主机,正是宿主机Node-2。
- UDP包要发往的目的宿主机确定了,接下来就是一个正常的宿主机网络上的封包工作,UDP包是一个四层数据包,Linux内核会在它前面加上一个IP头,组成一个IP包,在这个IP头里,会填上前面通过FDB查询出来的目的主机的IP地址,即Node-2的IP地址192.168.1.3,Linux内核再在这个IP包前面加上二层数据帧头,并把Node-2的MAC地址填进去,特洛伊木马终于建成了:
图12:Flannel VXLAN模式的宿主机通信数据帧
- 这个数据帧经过宿主机网络来到Node-2的ens33网卡,Node-2的内核网络栈会发现这个数据帧里有VXLAN Header,并且VNI=1,于是Linux内核会对它进行拆包,拿到里面的内部数据帧,然后根据VNI的值,把它交给Node-2上的flannel.1设备;flannel.1设备则会进一步拆包,取出容器IP包,IP包通过docker0网桥进入busybox-4容器的Network Namespace里,实现busybox-1和busybox-4的跨主机通信。
- Flannel VXLAN模式组建的Overlay Network覆盖网络,其实就是一个由不同宿主机上的 VTEP设备,也就是flannel.1设备组成的虚拟二层网络;Flannel VXLAN和Flannel UDP两种模式的实现原理都可以称为隧道机制
- Docker overlay network 和 Weave的两种模式(sleeve 和 fastdp)的实现原理类似。
思考:由于增加了额外的头部信息,所以Payload是有损耗的,有更好的方法么?
3、Flannel的host-gw模式
- Flannel的host-gw模式则是一种纯三层(Pure Layer 3)网络方案,如下图所示:
图13:Flannel host-gw模式
- 和前面的隧道方案一样,Flannel子网和主机的信息保存在Etcd中,flanneld进程WACTH 这些数据的变化,然后实时更新宿主机路由;当设置Flannel使用host-gw模式后,flanneld会在宿主机Node-1上添加一条路由规则:
[root@node1 ~]# ip route
default via 192.168.1.1 dev ens33 proto static metric 100
172.8.1.0/24 dev docker0 proto kernel scope link src 172.8.1.1
192.168.1.0/24 dev ens33 proto kernel scope link src 192.168.1.2 metric 100
172.8.2.0/24 via 192.168.1.3 dev ens33
- 这条路由规则的含义是:目的IP地址属于172.8.2.0/24网段的IP包,经过本机的ens33设备发出去,并且它下一跳地址是192.168.1.3(即:via 192.168.1.3),所谓下一跳地址就是:如果IP包从主机A发到主机B,需要经过路由设备X的中转,那么X的IP地址就应该配置为主机A的下一跳地址。
- 这里还是继续使用容器busybox-1发送数据包给busybox-4的例子来分析,这个下一跳地址正是目的宿主机Node-2,那么当IP包从网络层进入链路层封装成帧的时候,ens33设备就会使用下一跳地址对应的MAC地址,作为该数据帧的目的MAC地址,即Node-2的MAC地址,这个数据帧就会从Node-1通过宿主机的二层网络顺利到达Node-2, Node-2的内核网络栈从二层数据帧里拿到IP包后,会发现这个IP包的目的IP地址是172.8.2.3,即 busybox-4的IP地址。
- Node-2上的路由表和Node-1上的类似,根据路由规则,该目的地址会匹配到第二条路由规则(也就是172.8.2.0对应的路由规则),从而进入docker0网桥,进而进入到 busybox-4 容器。
- 上面的例子说明host-gw模式的工作原理,其实就是将每个宿主机上的Flannel子网(Flannel Subnet,比如:172.8.1.0/24)的“下一跳”,设置成该子网对应的宿主机的IP地址,也就是说“宿主机”(Host)充当容器跨主机通信路径里的“网关”(Gateway),这也正是“host-gw”的含义。
- Flannel host-gw模式下,容器跨主机通信的过程免除了额外的封包和解包带来的性能损耗,实际的测试,host-gw的网络性能损失大约在10%左右,而其它基于VXLAN“隧道”机制的网络方案,网络性能损失大约在20%~30%左右。
- 通过上面的分析,可以很容易理解Flannel host-gw模式正常工作的关键,在于IP包在封装成帧发送出去的时候,会使用路由表里的“下一跳”来设置目的MAC地址,即它会经过二层网络到达目的宿主机,也就是说Flannel host-gw模式要求宿主机之间必须是二层连通的。
- 现实中,宿主机之间二层不连通的情况也是广泛存在的,比如宿主机分布在不同的子网(VLAN)里,但是在一个集群里接受Kubernetes的调度,宿主机之间必须可以通过IP地址进行通信,也就是说三层互通但是二层不可达。
思考:
在大规模集群中,路由表规模变大,会带来什么问题?
所有容器三层互通,可能产生什么问题?
五、容器的跨主机通信网络插件 Calico 介绍
说到三层网络解决方案,不得不提“龙头”级的项目Calico,Calico项目提供的网络解决方案与Flannel host-gw模式,几乎是完全一样的,即Calico 也会在每台宿主机上,添加一个格式如下所示的路由规则:
<目的容器IP地址段> via <网关的IP地址> dev ens33
- 网关的IP地址,正是目的容器所在宿主机的IP地址。
- 不同于Flannel通过Etcd和宿主机上的flanneld来维护路由信息的方法,Calico项目使用了一个“重型武器”来自动地在整个集群中分发路由信息,这个**“重型武器”就是BGP**。
- BGP(Border Gateway Protocol)即:边界网关协议名,它是一个Linux内核原生就支持的、专门用在大规模数据中心里维护不同的“自治网络”之间路由信息的、无中心的路由协议,如下图所示:
图14:BGP协议工作示意图
- 上图中有两个自治系统(Autonomous System,简称为AS):AS1和AS2,一个自治系统指的是一个组织管辖下的所有IP网络和路由器的集合,可以是一个小公司里的所有主机和路由器,也可以是一个城域网中所有IP设备和路由器的集合,正常情况下,自治系统之间不会有任何“来往”,但是如果两个自治系统里的主机,要通过IP地址直接进行通信,就必须使用路由器把这两个自治系统连接起来。
- 比如AS1里面的主机10.10.0.2,要访问AS2 里面的主机172.17.0.3的话,它发出的IP包,就会先到达自治系统AS1上的路由器Router1,此时Router1的路由表里,有这样一条规则,即目的地址是172.17.0.2包,应该经过Router 1的C接口发往网关Router2,即自治系统AS2上的路由器,所以IP包就会到达Router2上,然后经过Router2的路由表,从B接口出来到达目的主机172.17.0.3;反过来,如果主机172.17.0.3要访问10.10.0.2,那么这个IP包在到达Router2之后,就不知道如何转发,因为在Router2的路由表里,并没有关于AS1自治系统的任何路由规则,这时候网络管理员就应该给Router2添加一条路由规则,比如:目标地址是10.10.0.2的IP包,应该经过Router2的C接口,发往网关Router1。
- 上面负责把自治系统连接在一起的路由器,我们就把它称为边界网关,它跟普通路由器的不同之处在于,它的路由表里拥有其他自治系统里的主机路由信息。
- 上面的原理,理解起来应该很容易,毕竟路由器本身的主要作用,就是连通不同的网络。但是,设想一下现在的网络拓扑结构非常复杂,每个自治系统都有成千上万个主机、无数个路由器,甚至是由多个公司、多个网络提供商、多个自治系统组成的复合自治系统,这时候,如果还要依靠人工来对边界网关的路由表进行配置和维护,那是绝对不现实的。这种情况下就需要BGP大显身手了,在使用了BGP之后,在每个边界网关上都会运行着一个小程序,它会将各自的路由表信息,通过TCP传输给其它的边界网关,其它边界网关上的这个小程序,会对收到的这些数据进行分析,然后将需要的信息添加到自己的路由表里,图2中Router2的路由表里,就自动出现10.10.0.2和10.10.0.3对应的路由规则了。
- BGP就是在大规模网络中实现节点路由信息共享的一种协议。BGP的这个能力,正好可以取代Flanneld进程维护主机上路由表的功能,并且BGP这种原生就是为大规模网络环境而实现的协议,其可靠性和可扩展性,远超Flannel项目的实现。
- 在理解了BGP之后,Calico项目的架构就容易理解了,它由三个部分组成:
- Calico的CNI 插件,这是Calico与Kubernetes对接的部分,CNI插件的工作原理这里就不再展开了。
- Felix,它是一个DaemonSet,负责在宿主机上插入路由规则,以及维护Calico所需的网络设备等工作。
- BIRD,它就是BGP的客户端,专门负责在集群里分发路由规则信息。
Calico 新版本中,把Felix 和 BIRD 放在一个 DaemonSet 类型的Pod中运行
并且增加了typha服务,作为 k8s-apiserver和Calico之间的桥梁,官方建议在节点数大于50的情况下使用,可以减小对k8s的调用压力
- 除了对路由信息的维护方式之外,Calico项目与Flannel的host-gw模式的另一个不同之处,就是它不会在宿主机上创建任何网桥设备,如下图所示:
图15:Calico BGP模式
- Calico的CNI插件会为每个容器设置一个Veth Pair设备,然后把其中的一端放置在宿主机上,名字以cali作为前缀,此外由于Calico没有使用CNI的网桥模式,所以Calico的CNI插件还需要在宿主机上为每个容器的Veth Pair设备配置一条路由规则,用于接收传入的IP 包,比如宿主机Node-2上的busybox-4对应的路由规则,如下所示:
172.8.2.3 dev cali51ae339 scope link
- 即:发往172.8.2.3的IP包应该进入cali51ae339设备。
- 这样的设计方案,使得容器发出的IP包会经过Veth Pair设备出现在宿主机上,然后宿主机网络栈就会根据路由规则的下一跳IP地址,把它们转发给正确的网关,后续流程就跟 Flannel host-gw 模式完全一致了;最核心的“下一跳”路由规则,就是由Calico的Felix进程负责维护的,这些路由规则信息,则是通过BGP Client也就是BIRD组件,通过BGP协议得到的。
- Calico 项目实际上将集群里的所有节点,都当作是边界路由器来处理,它们一起组成了一个全连通的网络,互相之间通过BGP协议交换路由规则,这些节点称为BGP Peer,需要注意的是,Calico维护的网络在默认配置下,是一个被称为“Node-to-Node Mesh”的模式。即每台宿主机上的BGP Client都需要跟其他所有节点的BGP Client进行通信以便交换路由信息,随着节点数量N的增加,这些连接的数量就会以N²的规模指数级增长,从而给集群本身的网络带来巨大的压力,所以Node-to-Node Mesh模式一般推荐用在少于100个节点的集群里,而在更大规模的集群中,需要用到的一个叫作Route Reflector的模式,在这种模式下,Calico会指定一个或者几个的节点,来负责跟所有节点建立BGP连接,从而学习到全局的路由规则,其它节点只需要跟这几个节点交换路由信息,就可以获得整个集群的路由规则了,这些负责收集路由规则的节点,就是所谓的Route Reflector节点,它们实际上扮演了“中间代理”的角色,从而把BGP连接的规模控制在N的数量级上。
思考:如果网络中有不支持BGP协议的路由器设备,怎么办?
- 此外,Flannel host-gw模式最主要的限制,就是要求集群宿主机之间是二层连通的,这个限制对于Calico同样存在,为了解决这个问题,Calico开发了IPIP模式,如下图所示:
图16:Calico IPIP模式
Calico IPIP模式下,Felix进程在Node-1上添加的路由规则,会稍微不同,如下所示:
172.8.2.0/24 via 192.168.2.3 tunl0
- 尽管这条规则的下一跳地址仍然是Node-2的IP地址,但负责将IP包发出去的设备,变成了tunl0,是一个IP隧道(IP tunnel)设备,IP包进入IP隧道设备之后,就会被Linux内核的IPIP 驱动接管,IPIP驱动会将这个IP包直接封装在一个宿主机网络的IP包中,如下所示:
图17:Calico IPIP模式数据包
- 经过封装后的IP包的目的地址(图17中的Outer IP Header 部分),正是原IP包的下一跳地址,即Node-2的IP地址:192.168.2.3,而原IP包本身,则会被直接封装成新IP包的Payload,从容器到Node-2的IP包,就被伪装成了一个从Node-1到Node-2的IP包。
- 宿主机之间已经使用路由器配置了三层转发,也就是设置了宿主机之间的“下一跳”,所以这个IP包在离开Node-1之后,就可以经过路由器,最终“跳”到Node-2上,Node-2的网络内核栈会使用IPIP驱动进行解包,从而拿到原始的IP包,原始IP包就会经过路由规则和Veth Pair设备到达目的容器内部。
- 从Calico的实现原理可以发现,使用IPIP模式的时候,集群的网络性能会因为额外的封包和解包工作而下降,实际测试中,Calico IPIP模式与Flannel VXLAN 模式的性能大致相当,在实际应用中,如非硬性需求,建议将所有宿主机节点放在一个子网里,避免使用IPIP模式。
- 在大规模集群里,三层网络方案在宿主机上的路由规则可能会非常多,这会导致错误排查变得困难。此外,在系统故障的时候,路由规则出现重叠冲突的概率也会变大。基于上述原因,如果是在公有云上,由于宿主机网络本身比较“直白”,推荐更加简单的Flannel host-gw 模式,在私有部署环境里,Calico项目才能够覆盖更多的场景,并为你提供更加可靠的组网方案和架构思路。
Flannel 和 Calico项目的对比:
| 维度 | Flannel-UDP | Flannel-UDP | Flannel-HGW | Calico-BGP | Calico-IPIP |
|---|---|---|---|---|---|
| 实现方式 | TUN | VXLAN | 路由 | 路由 | TUNL |
| 封包解包 | 用户态 | 内核态 | 无 | 无 | 内核态 |
| Payload | 有损失 | 有损失 | 无损失 | 无损失 | 有损失 |
| 网络性能 | C | B | A+ | A | B |
| 网络风暴 | 无 | 无 | 有 | 无 | 无 |
| 集群规模 | 大规模 | 大规模 | 小规模,需直连 | 大规模 | 大规模 |
术语:
- ARP(Address Resolution Protocol)是通过三层的IP地址找到对应的二层MAC地址的网络协议。
- VXLAN(Virtual Extensible LAN)虚拟可扩展局域网。
- VTEP(VXLAN Tunnel End Point)VXLAN隧道端点。
- 先用起来,通过操作实践认识kubernetes(k8s),积累多了自然就理解了
- 把理解的知识分享出来,自造福田,自得福缘
- 追求简单,容易使人理解,知识的上下文也是知识的一部分,例如版本,时间等
- 欢迎留言交流,也可以提出问题,一般在周末回复和完善文档
- Jason@vip.qq.com 2022-4-26