前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >K8s面试系列: Flannel VS Calico 基于 L2 与 L3 的 CNI 之战(一)

K8s面试系列: Flannel VS Calico 基于 L2 与 L3 的 CNI 之战(一)

作者头像
山河已无恙
发布2025-02-25 20:49:37
发布2025-02-25 20:49:37
8500
代码可运行
举报
文章被收录于专栏:山河已无恙
运行总次数:0
代码可运行

写在前面

  • 博文内容涉及一些名词的前置知识点讲解,VETH,Bridge,IPIP,VXLAN 等
  • Calico 跨主机 Pod 之间链路跟踪
  • 以及Calico CNI kube-proxy 使用 iptables 的 链路跟踪
  • 篇幅问题,Flannel 部分放到下一篇讲解
  • 理解不足小伙伴帮忙指正

在童年期,我们更多是处于认知,而不是意欲的状态。—— 《作为意欲和表象的世界》第二卷第三十一章


K8s 的环境中。 CNI 常用的有 FlannelCalico ,今天我们来看看 FlannelCalico 是如何实现跨主机容器组网以及服务发布负载网络策略的。

简单总结:

在跨主机的容器网络方案中,Flannel 是使用 VXLAN 后端的覆盖网络(Overlay Network),基于L3构建的L2,可以单纯理解为通过三层UDP构建隧道传递以太网帧,而 Calico 是一个纯三层的路由网络模型(Route-based Network),利用Linux 内核技术 iptables路由表IPIPBGP协议来模拟传统的路由组网方案。

只有 Calico 支持ACL网络策略,可以配置 Pod 级别的 白名单黑名单,基于 iptables 实现。

名词解释:

在讨论下面两个 CNI 前,我们需要先了解一些基本名词

VETH Pair(虚拟网卡对):

VETH(Virtual Ethernet)对 是一种成对的网络设备,它们总是成对出现,一端连着网络命名空间(例如一个容器),另一端连着另一个网络命名空间。两个设备同时 UP 同时 DOWN,彼此之间通过内核软链接实现(类似虚拟的网线),常用于两个命名空间通信,或者一个网络命名空间和网桥通信。

docker 在默认网络驱动(桥接)的情况下,即经典容器组网,veth 对 +网桥 模式,基于这个特性实现单机 docker 的多个容器组网,安装 docker 会默认创建一个 桥接设备 docker0

容器AB 之间的通信即 容器 A网桥 docker0网桥 docerk0 在到容器 B,网桥这里可以理解为一个交换机,可以实现子网中任意网段互通。 容器到 网桥的连接即通过 veth 对 实现,一端在容器对应网络命名空间,一端在网桥。

容器 A 尝试与容器 B 通信时,它发送的数据包首先会被发送到与容器 A 相连的 VETH 设备的 docker0 网桥一端。

容器和主机之间通信也是同理,容器网络命名空间和主机的根网络命名空间之间通过网桥通信。

网桥(Bridge):

上面我们有讲到网桥设备,它充当着交换机的角色,负责将数据包从一个端口转发到另一个端口。

当数据包到达网桥时,网桥会根据目标IP地址,转发到对应的 端口,所以 在安装 docker 的时候,会默认创建一个 docker0 的网桥,同时需要修改内核参数,开启 ipv4 转发

桥接设备(Bridge Device)是在网络层次结构中工作的二层设备(Data Link Layer),它主要用于连接多个网络设备。桥接设备通过学习和转发数据帧的方式,将连接到它的网络设备组成一个共享的以太网段,使得这些设备可以直接通信。桥接设备工作在数据链路层(第二层),它不涉及 IP 地址或路由。基于 MAC 地址构建地址转发表

VETH对 + 网桥 组网 Demo

看一个实际的 Demo,使用Linux内部网桥实用程序创建网桥(vnet-br0),创建红色绿色两个网络名称空间。为redgreen命名空间创建两个veth虚拟网卡对,将veth对的一端连接到特定的命名空间,另一端连接到内部网桥,确保红色绿色命名空间中的接口可以于网桥(vnet-bro)与内部和外部网络通信。

创建两个网络命名空间,创建网桥vnet-br0

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip netns add red
┌──[root@liruilongs.github.io]-[~]
└─$ip netns add green

用于在 Linux 上创建一个名为 vnet-br0桥接设备

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip link add vnet-br0 type bridge

添加虚拟网卡对eth0-r 和 veth-reth0-g 和 veth-g

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip link add eth0-r type veth peer name veth-r
┌──[root@liruilongs.github.io]-[~]
└─$ip link add eth0-g type veth peer name veth-g

把两个虚拟网卡对中的一端放到上面创建的网络命名空间

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip link set eth0-r netns red
┌──[root@liruilongs.github.io]-[~]
└─$ip link set eth0-g netns green

然后将虚拟网卡对的另一端连接到vnet-br0桥。

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip link set veth-r master vnet-br0
┌──[root@liruilongs.github.io]-[~]
└─$ip link set veth-g master vnet-br0

查看根网络命名空间的桥接设备类型的网络设备(桥接表)。

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip link show type bridge
5: vnet-br0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 62:2b:41:f9:39:b3 brd ff:ff:ff:ff:ff:ff

查看桥接设备(vnet-br0)关联的网络设备。

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip link show master vnet-br0
6: veth-r@if7: <BROADCAST,MULTICAST> mtu 1500 qdisc noop master vnet-br0 state DOWN mode DEFAULT group default qlen 1000
    link/ether 62:2b:41:f9:39:b3 brd ff:ff:ff:ff:ff:ff link-netns red
8: veth-g@if9: <BROADCAST,MULTICAST> mtu 1500 qdisc noop master vnet-br0 state DOWN mode DEFAULT group default qlen 1000
    link/ether be:a3:9a:1c:a1:06 brd ff:ff:ff:ff:ff:ff link-netns green

根据输出,有两个网络设备与 vnet-br0 桥接设备关联:

veth-r@if7:这是一个虚拟网络设备(veth pair),它与 vnet-br0 桥接设备关联。它的状态是 DOWN,表示当前处于未激活状态。它的 MAC 地址为 62:2b:41:f9:39:b3。此设备属于 red 网络命名空间。

veth-g@if9:这是另一个虚拟网络设备(veth pair),也与 vnet-br0 桥接设备关联。它的状态是 DOWN,表示当前处于未激活状态。它的 MAC 地址为 be:a3:9a:1c:a1:06。此设备属于 green 网络命名空间。

激活桥接对应的网络设备

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip link set vnet-br0 up
┌──[root@liruilongs.github.io]-[~]
└─$ip link set veth-r up
┌──[root@liruilongs.github.io]-[~]
└─$ip link set veth-g up

激活 网络命名空间中的回环地址和对应的虚拟网卡对

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec red ip link set lo up
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec red ip link set eth0-r up
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec green ip link set lo up
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec green ip link set eth0-g up

ip link 确认设备状态

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 00:0c:29:93:51:67 brd ff:ff:ff:ff:ff:ff
    altname enp3s0
5: vnet-br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 62:2b:41:f9:39:b3 brd ff:ff:ff:ff:ff:ff
6: veth-r@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master vnet-br0 state UP mode DEFAULT group default qlen 1000
    link/ether 62:2b:41:f9:39:b3 brd ff:ff:ff:ff:ff:ff link-netns red
8: veth-g@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master vnet-br0 state UP mode DEFAULT group default qlen 1000
    link/ether be:a3:9a:1c:a1:06 brd ff:ff:ff:ff:ff:ff link-netns green

进入网络命名空间 shell 环境,分配 IP, 给 red 命名空间分配IP 地址 192.168.20.2/24

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec red bash
┌──[root@liruilongs.github.io]-[~]
└─$ip address add 192.168.20.2/24 dev eth0-r

查看路由信息

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip r
# 对于目标网络 192.168.20.0/24 的数据包,它们将使用 eth0-r 设备进行本地通信。
192.168.20.0/24 dev eth0-r proto kernel scope link src 192.168.20.2
┌──[root@liruilongs.github.io]-[~]
└─$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
7: eth0-r@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ca:b0:b2:80:25:43 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.20.2/24 scope global eth0-r
       valid_lft forever preferred_lft forever
    inet6 fe80::c8b0:b2ff:fe80:2543/64 scope link
       valid_lft forever preferred_lft forever
┌──[root@liruilongs.github.io]-[~]
└─$exit
exit

对另一个命名空间操作,给 green 命名空间分配IP 地址 192.168.20.3/24

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec green bash
┌──[root@liruilongs.github.io]-[~]
└─$ip address add 192.168.20.3/24 dev eth0-g
┌──[root@liruilongs.github.io]-[~]
└─$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
9: eth0-g@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 36:5e:d9:8d:04:a8 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.20.3/24 scope global eth0-g
       valid_lft forever preferred_lft forever
    inet6 fe80::345e:d9ff:fe8d:4a8/64 scope link
       valid_lft forever preferred_lft forever
┌──[root@liruilongs.github.io]-[~]
└─$ip r
192.168.20.0/24 dev eth0-g proto kernel scope link src 192.168.20.3
┌──[root@liruilongs.github.io]-[~]
└─$exit
exit

两个命名空间之间的连通性测试

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec green bash
┌──[root@liruilongs.github.io]-[~]
└─$ping -c 2 192.168.20.2
PING 192.168.20.2 (192.168.20.2) 56(84) bytes of data.
64 bytes from 192.168.20.2: icmp_seq=1 ttl=64 time=0.252 ms
64 bytes from 192.168.20.2: icmp_seq=2 ttl=64 time=0.047 ms

--- 192.168.20.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1034ms
rtt min/avg/max/mdev = 0.047/0.149/0.252/0.102 ms

┌──[root@liruilongs.github.io]-[~]
└─$exit
exit

我们在上面把两个命名空间的 veth pair 都配置到了网桥,所以可以直接通信。

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec red bash
┌──[root@liruilongs.github.io]-[~]
└─$ping -c 2 192.168.20.3
PING 192.168.20.3 (192.168.20.3) 56(84) bytes of data.
64 bytes from 192.168.20.3: icmp_seq=1 ttl=64 time=0.241 ms
64 bytes from 192.168.20.3: icmp_seq=2 ttl=64 time=0.129 ms

--- 192.168.20.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1038ms
rtt min/avg/max/mdev = 0.129/0.185/0.241/0.056 ms
┌──[root@liruilongs.github.io]-[~]
└─$exit
exit

将 IP 192.168.20.1/24 分配给根网络命名空间中的 vnet-br0 桥接口,以允许来自红色和绿色名称空间的外部通信,它将成为该网络的默认网关(这里很重要哦)

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip address add 192.168.20.1/24 dev vnet-br0

192.168.20.1配置为绿色和红色命名空间中的默认网关。将所有目标不在本地网络中的数据包发送到该网关进行进一步路由。

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec red bash
┌──[root@liruilongs.github.io]-[~]
└─$route add default gw 192.168.20.1
┌──[root@liruilongs.github.io]-[~]
└─$exit
exit
代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec green bash
┌──[root@liruilongs.github.io]-[~]
└─$route add default gw 192.168.20.1
┌──[root@liruilongs.github.io]-[~]
└─$exit
exit

即对应的网络命名空间内的流量都路由到网关,即网桥设备。

NAT 表中添加一个规则,将源 IP 地址为 192.168.20.0/24 的数据包进行源地址转换 (Source NAT),即 SNAT,否则可以出去但是找不到回家的路。

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$iptables -s 192.168.20.0/24 -t nat -A POSTROUTING -j MASQUERADE

根命名空间做内网和公网地址Ping

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ping 192.169.26.149 -c 3
PING 192.169.26.149 (192.169.26.149) 56(84) bytes of data.
64 bytes from 192.169.26.149: icmp_seq=1 ttl=128 time=199 ms
64 bytes from 192.169.26.149: icmp_seq=2 ttl=128 time=199 ms
64 bytes from 192.169.26.149: icmp_seq=3 ttl=128 time=216 ms

--- 192.169.26.149 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 199.020/204.755/215.909/7.888 ms
┌──[root@liruilongs.github.io]-[~]
└─$ping baidu.com -c 3
PING baidu.com (39.156.66.10) 56(84) bytes of data.
64 bytes from 39.156.66.10 (39.156.66.10): icmp_seq=1 ttl=128 time=11.9 ms
64 bytes from 39.156.66.10 (39.156.66.10): icmp_seq=2 ttl=128 time=11.9 ms
64 bytes from 39.156.66.10 (39.156.66.10): icmp_seq=3 ttl=128 time=12.1 ms

--- baidu.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 11.919/11.999/12.142/0.100 ms

在主机系统上启用IPV4转发以允许外部通信。执行该命令后,系统将开启 IP 转发功能,允许数据包在不同的网络接口之间进行转发。

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1

在两个命名空间中做内网 ping 测试

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec green ping baidu.com -c 3
PING baidu.com (110.242.68.66) 56(84) bytes of data.
64 bytes from 110.242.68.66 (110.242.68.66): icmp_seq=1 ttl=127 time=20.5 ms
64 bytes from 110.242.68.66 (110.242.68.66): icmp_seq=2 ttl=127 time=20.0 ms
64 bytes from 110.242.68.66 (110.242.68.66): icmp_seq=3 ttl=127 time=20.3 ms

--- baidu.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 20.031/20.261/20.475/0.181 ms
代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec red ping baidu.com -c 3
PING baidu.com (110.242.68.66) 56(84) bytes of data.
64 bytes from 110.242.68.66 (110.242.68.66): icmp_seq=1 ttl=127 time=20.2 ms
64 bytes from 110.242.68.66 (110.242.68.66): icmp_seq=2 ttl=127 time=20.3 ms
64 bytes from 110.242.68.66 (110.242.68.66): icmp_seq=3 ttl=127 time=20.1 ms

--- baidu.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 20.085/20.197/20.278/0.082 ms

在两个命名空间中做内网 ping 测试

代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec red ping 192.168.26.149 -c 3
PING 192.168.26.149 (192.168.26.149) 56(84) bytes of data.
64 bytes from 192.168.26.149: icmp_seq=1 ttl=64 time=0.241 ms
64 bytes from 192.168.26.149: icmp_seq=2 ttl=64 time=0.110 ms
64 bytes from 192.168.26.149: icmp_seq=3 ttl=64 time=0.075 ms

--- 192.168.26.149 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2074ms
rtt min/avg/max/mdev = 0.075/0.142/0.241/0.071 ms
代码语言:javascript
代码运行次数:0
复制
┌──[root@liruilongs.github.io]-[~]
└─$ip netns exec green ping 192.168.26.149 -c 3
PING 192.168.26.149 (192.168.26.149) 56(84) bytes of data.
64 bytes from 192.168.26.149: icmp_seq=1 ttl=64 time=0.258 ms
64 bytes from 192.168.26.149: icmp_seq=2 ttl=64 time=0.097 ms
64 bytes from 192.168.26.149: icmp_seq=3 ttl=64 time=0.094 ms

--- 192.168.26.149 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2043ms
rtt min/avg/max/mdev = 0.094/0.149/0.258/0.076 ms
┌──[root@liruilongs.github.io]-[~]
└─$

拓扑图

到这里,我们实现和两个网络命名空间彼此通信,并且和根命名空间通信,同时可以和公网通信,实际上上面讲的也就是 docker 中容器的经典组网模型(veth pair + Bridge)

简单回顾一下我们干了什么:

  • 在主机的根网络命名空间中创建一个 Linux 网桥,创建两个 Linux 网络命名空间
  • 创建两个 veth pair,将其中一个端口连接到根命名空间中的网桥上,另一个端口放置在目标命名空间中。
  • 在目标命名空间中配置 IP 地址,并将该端口启动起来。
  • 在根命名空间中启用 IP 转发功能(通过设置 net.ipv4.ip_forward=1),给网桥分配IP地址,同时在命名空间配置默认网关地址为网桥地址。
  • 配置 NAT 规则 SNAT,将目标命名空间中的流量转发的源IP地址转化为根命名空间中的IP地址。
  • 目标命名空间中的流量将通过默认网关走网桥IP地址转发到根命名空间中,并通过根命名空间中的网络设备连接到互联网。

IPIP

ipip:即IPv4 in IPv4,没什么好说的 在IPv4报文的基础上封装一个IPv4报文,是 Linux L3隧道的一种,底层实现原理都基于tun设备

看一个 ipip隧道通信 Demo

通过 Linux 上的两个 network namespace 来模拟两个机器节点,每个 network namespce 是一个独立的网络栈

要使用ipip隧道,首先需要内核模块ipip.ko的支持。通过lsmod|grep ipip查看内核是否加载,若没有则用modprobe ipip加载,正常加载应该显示

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ lsmod | grep ipip
liruilonger@cloudshell:~$ sudo modprobe ipip
liruilonger@cloudshell:~$ modprobe ipip
liruilonger@cloudshell:~$ lsmod | grep ipip
ipip                   16384  0
ip_tunnel              28672  1 ipip
tunnel4                16384  1 ipip

加载ipip内核模块后,就可以创建隧道了。方法是先创建一个tun设备,然后将该tun设备绑定为一个ipip隧道。ipip隧道网络拓扑如图

在这里插入图片描述

这里我们用两个 Linux network namespace 来模拟 ,创建两个网络命名空间,同时配置两个 veth pair,一端放到命名空间

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns add ns1
liruilonger@cloudshell:~$ sudo ip netns add ns2
liruilonger@cloudshell:~$ sudo ip link add v1 netns ns1  type veth peer name v1-P
liruilonger@cloudshell:~$ sudo ip link add v2 netns ns2  type veth peer name v2-P

确认创建的 veth pair

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default 
    link/ether 0a:10:50:88:eb:09 brd ff:ff:ff:ff:ff:ff link-netnsid 0
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default 
    link/ether 02:42:f9:2d:29:3e brd ff:ff:ff:ff:ff:ff
4: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
5: v1-P@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 76:81:b0:33:e4:2b brd ff:ff:ff:ff:ff:ff link-netns ns1
6: v2-P@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether aa:a6:ac:15:b1:64 brd ff:ff:ff:ff:ff:ff link-netns ns2

另一端放到 根网络命名空间,同时两个Veth-pair 配置不同网段IP启动。

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip addr add 10.10.10.1/24 dev v1-P
liruilonger@cloudshell:~$ sudo ip link set v1-P up
liruilonger@cloudshell:~$ sudo ip addr add 10.10.20.1/24 dev v2-P
liruilonger@cloudshell:~$ sudo ip link set v2-P up

命名空间一端的同样配置IP 并启用

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ip addr add 10.10.10.2/24 dev v1
liruilonger@cloudshell:~$ sudo ip netns exec ns2 ip addr add 10.10.20.2/24 dev v2
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ip link set v1 up
liruilonger@cloudshell:~$ sudo ip netns exec ns2 ip link set v2 up

确定设备在线

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default 
    link/ether 0a:10:50:88:eb:09 brd ff:ff:ff:ff:ff:ff link-netnsid 0
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default 
    link/ether 02:42:f9:2d:29:3e brd ff:ff:ff:ff:ff:ff
4: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
5: v1-P@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 76:81:b0:33:e4:2b brd ff:ff:ff:ff:ff:ff link-netns ns1
6: v2-P@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether aa:a6:ac:15:b1:64 brd ff:ff:ff:ff:ff:ff link-netns ns2
liruilonger@cloudshell:~$

调整内核参数,开启 ipv4 转发

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ cat /proc/sys/net/ipv4/ip_forward
1

这个时候,Linux 网络命名空间中的 v1 和 v2 veth 任然不通,应为是在两个不同的网段。

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec ns1 route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.10.10.0      0.0.0.0         255.255.255.0   U     0      0        0 v1

查看路由信息,没有通向 10.10.20.0/24网段的路由

所以我们在 ns1 里面配置一条路由,通向 10.10.20.0 的访问路由到 10.10.10.1 网关,实际上是 veth pair 的另一端。

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec ns1 route add -net 10.10.20.0 netmask 255.255.255.0 gw 10.10.10.1

再查看路由表

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec  ns1 route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.10.10.0      0.0.0.0         255.255.255.0   U     0      0        0 v1
10.10.20.0      10.10.10.1      255.255.255.0   UG    0      0        0 v1
liruilonger@cloudshell:~$ 

同理,也给ns2配上通往10.10.10.0/24网段的路由。

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec ns2 route add -net 10.10.10.0 netmask 255.255.255.0 gw 10.10.20.1
liruilonger@cloudshell:~$ sudo ip netns exec  ns2 route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.10.10.0      10.10.20.1      255.255.255.0   UG    0      0        0 v2
10.10.20.0      0.0.0.0         255.255.255.0   U     0      0        0 v2
liruilonger@cloudshell:~$ 

这时候我们在 ns1 和 ns2 之间做ping 测试,正常通信

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ping -c 3  10.10.20.2                                                
PING 10.10.20.2 (10.10.20.2) 56(84) bytes of data.
64 bytes from 10.10.20.2: icmp_seq=1 ttl=63 time=0.092 ms
64 bytes from 10.10.20.2: icmp_seq=2 ttl=63 time=0.057 ms
64 bytes from 10.10.20.2: icmp_seq=3 ttl=63 time=0.053 ms

--- 10.10.20.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2036ms
rtt min/avg/max/mdev = 0.053/0.067/0.092/0.017 ms
liruilonger@cloudshell:~$ sudo ip netns exec ns2 ping -c 3  10.10.10.2
PING 10.10.10.2 (10.10.10.2) 56(84) bytes of data.
64 bytes from 10.10.10.2: icmp_seq=1 ttl=63 time=0.042 ms
64 bytes from 10.10.10.2: icmp_seq=2 ttl=63 time=0.052 ms
64 bytes from 10.10.10.2: icmp_seq=3 ttl=63 time=0.049 ms

--- 10.10.10.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2066ms
rtt min/avg/max/mdev = 0.042/0.047/0.052/0.004 ms
liruilonger@cloudshell:~$ 

v1 和 v2 可以正常通信,即我们模拟了两个不在同一网段的 Linux 机器

创建tun设备,并设置为ipip隧道

  • 在 ns1 上面创建 tun1 设备:ip tunnel add tunl
  • 设置隧道模式为ipip:mode ipip
  • 设置隧道端点,用remotelocal表示隧道外层IP: remote 10.10.20.2 local 10.10.10.2
  • 隧道内层IP配置: ip addr add 10.10.100.10 peer 10.10.200.10 dev tunl
代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ip tunnel add tunl mode ipip remote 10.10.20.2 local 10.10.10.2
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ip link set tunl up
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ip addr add 10.10.100.10 peer 10.10.200.10 dev tunl
liruilonger@cloudshell:~$ 

原始的IP 头

  • src: 10.10.100.10
  • dst: 10.10.200.10

封装后的IP头

  • src: 10.10.10.2 | src: 10.10.100.10
  • dst: 10.10.20.2 | dst: 10.10.200.10

同样需要在 ns2 上做相同的配置

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip  netns exec ns2 ip tunnel add tunr mode ipip remote 10.10.10.2 local 10.10.20.2
liruilonger@cloudshell:~$ sudo ip netns exec ns2 ip link set tunr up
liruilonger@cloudshell:~$ sudo ip netns exec ns2 ip addr add 10.10.200.10 peer 10.10.100.10 dev tunr
liruilonger@cloudshell:~$ 

到这里 两个 tun 设备的 隧道就建立成功了,我们可以在其中一个命名空间对另一个命名空间的 tun 设备发起 ping 测试

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ping 10.10.200.10 -c 3
PING 10.10.200.10 (10.10.200.10) 56(84) bytes of data.
64 bytes from 10.10.200.10: icmp_seq=1 ttl=64 time=0.091 ms
64 bytes from 10.10.200.10: icmp_seq=2 ttl=64 time=0.062 ms
64 bytes from 10.10.200.10: icmp_seq=3 ttl=64 time=0.067 ms

--- 10.10.200.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2082ms
rtt min/avg/max/mdev = 0.062/0.073/0.091/0.012 ms
liruilonger@cloudshell:~$ 

在看一各个命名空间对应的 链接

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec ns1 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
3: v1@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 72:de:67:0b:28:e1 brd ff:ff:ff:ff:ff:ff link-netnsid 0
4: tunl@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/ipip 10.10.10.2 peer 10.10.20.2
liruilonger@cloudshell:~$

两个命名空间除了 veth-pair 对应的 veth 虚拟设备,各有个一个 tun 设备,link/ipip 中的内容表示封装后的包的两端地址,即外层IP。

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec ns2 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
3: v2@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether f2:dd:3c:7d:eb:50 brd ff:ff:ff:ff:ff:ff link-netnsid 0
4: tunr@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/ipip 10.10.20.2 peer 10.10.10.2
liruilonger@cloudshell:~$

这里还需要了解一下 VXLAN 技术

VXLAN

VXLAN 是在底层物理网络(underlay)之上使用隧道技术,依托UDP层(3 层)构建的 overlay 的逻辑网络,使逻辑网络与物理网络解耦,实现灵活的组网需求。不仅能适配虚拟机环境,还能用于容器环境

VXLAN 的工作模型,它创建在原来的 IP 网络(三层)上,只要是三层可达(能够通过 IP 互相通信)的网络就能部署 VXLAN

VXLAN 网络的每个端点都有一个 VTEP 设备,负责 VXLAN 协议报文的封包和解包,也就是在虚拟报文上封装VTEP 通信的报文头部

物理网络上可以创建多个 VXLAN 网络,可以将这些 VXLAN 网络看作一个隧道,不同节点上的虚拟机/容器能够通过隧道直连。通过VNI标识不同的VXLAN网络,使得不同的VXLAN可以相互隔离。

一个VXLAN报文需要确定两个地址信息:

  • 内层报文(对应目的虚拟机/容器)的MAC地址
  • 外层报文(对应目的虚拟机/容器所在宿主机上的 VTEP)IP地址。

如果VNI也是动态感知的,那么VXLAN一共需要知道三个信息:内部MAC、VTEP IP和VNI

一般有两种方式获得以上VXLAN网络的必要信息:

  • 多播:同一个 VXLAN 网络的VTEP加入同一个多播网络
  • 控制中心:某个集中式的地方保存所有虚拟机的上述信息,自动告知VTEP它需要的信息即可,这也是 Flannel 的方式

点对点的 VXLAN Demo

我们通过一个点对点的 VXLAN 通信Demo 来体会一下 VXLAN 网络

点对点 VXLAN 即两台机器构成一个 VXLAN 网络,每台机器上有一个 VTEP,VTEP 之间通过它们的 IP 地址进行通信。点对点 VXLAN 网络拓扑如图

只有一个机器,这里我们和上面一样使用两个 Linux netowrk namespace node1,node2 来模拟两个主机

创建两个 网络命名空间

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns add node1
liruilonger@cloudshell:~$ sudo ip netns add node2
liruilonger@cloudshell:~$  ip netns list
node2
node1

使用 veth 对 来建立通信(VETH1,VETH2),配置 IP

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip link add veth1  netns node1  type veth peer name veth2 netns node2
liruilonger@cloudshell:~$ sudo ip netns exec node1 ip addr add  192.168.1.2/24 dev veth1
liruilonger@cloudshell:~$ sudo ip netns exec node2 ip addr add  192.168.1.3/24 dev veth2
liruilonger@cloudshell:~$ sudo ip netns exec node1 ip link set dev veth1 up
liruilonger@cloudshell:~$ sudo ip netns exec node2 ip link set dev veth2 up

两个命名空间都开启 ipv4 转发,ping 测试

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec node1 sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1
liruilonger@cloudshell:~$ sudo ip netns exec node2 sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1
liruilonger@cloudshell:~$ sudo ip netns  exec node1 ping -c 3 192.168.1.3
PING 192.168.1.3 (192.168.1.3) 56(84) bytes of data.
64 bytes from 192.168.1.3: icmp_seq=1 ttl=64 time=0.066 ms
64 bytes from 192.168.1.3: icmp_seq=2 ttl=64 time=0.047 ms
64 bytes from 192.168.1.3: icmp_seq=3 ttl=64 time=0.038 ms

--- 192.168.1.3 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2088ms
rtt min/avg/max/mdev = 0.038/0.050/0.066/0.011 ms
liruilonger@cloudshell:~$

模拟好了环境,在 node1 命名空间 使用ip link命令创建VXLAN接口

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec node1 ip link add vxlan0 type vxlan id 42 dstport 4789 remote 192.168.1.3 local 192.168.1.2 dev veth1

在名为 node1 的网络命名空间中创建了一个名为 vxlan0 的 VXLAN 接口。

  • 它使用 VXLAN 标识 VID 42
  • 指定了 VXLAN 流量使用的目标端口号,通常使用 UDP 端口 4789
  • 并设置了远程端点的 IP 地址为 192.168.1.3
  • 本地端点的 IP 地址为 192.168.1.2
  • 该 VXLAN 接口使用veth1作为底层网络接口
代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo  ip netns exec node1 ip -d link show  dev veth1
2: veth1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether ea:d1:4d:8c:c0:9a brd ff:ff:ff:ff:ff:ff link-netns node2 promiscuity 0 minmtu 68 maxmtu 65535
    veth addrgenmode eui64 numtxqueues 2 numrxqueues 2 gso_max_size 65536 gso_max_segs 65535

为刚创建的VXLAN网卡配置IP地址 172.17.1.2/24 并启用它

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec node1 ip addr add 172.17.1.2/24 dev vxlan0
liruilonger@cloudshell:~$ sudo ip netns exec node1 ip link set vxlan0 up

执行成功后会发现路由表项多了下面的内容,所有目的地址172.17.1.0/24 网段的包要通过 vxlan0(172.17.1.2) 转发

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec node1 ip route
172.17.1.0/24 dev vxlan0 proto kernel scope link src 172.17.1.2
192.168.1.0/24 dev veth1 proto kernel scope link src 192.168.1.2

vxlan0 的 FDB 地址转发表项中的内容如下

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec node1 bridge fdb
33:33:00:00:00:01 dev veth1 self permanent
01:00:5e:00:00:01 dev veth1 self permanent
33:33:ff:8c:c0:9a dev veth1 self permanent
00:00:00:00:00:00 dev vxlan0 dst 192.168.1.3 via veth1 self permanent

默认的VTEP对端地址为192.168.1.3。换句话说,原始报文经过vxlan0后会被内核添加上VXLAN头部,而外部UDP头的目的 IP 地址会被冠上192.168.1.3。这里的IP地址即为我们上面的配置的 远程端点 的 IP

在另一个命名空间也进行相同的配置,配置项这里不做说明

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec node2 bash
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# ip link add vxlan0 type vxlan id 42 dstport 4789 remote 192.168.1.2 local 192.168.1.3 dev veth2
代码语言:javascript
代码运行次数:0
复制
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# ip -d link show  dev vxlan0
3: vxlan0: <BROADCAST,MULTICAST> mtu 1450 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 5a:46:75:cd:ce:9e brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65535
    vxlan id 42 remote 192.168.1.2 local 192.168.1.3 dev veth2 srcport 0 0 dstport 4789 ttl auto ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
代码语言:javascript
代码运行次数:0
复制
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# ip addr add 172.17.1.3/24 dev vxlan0
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# ip link set vxlan0 up
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# ip route
172.17.1.0/24 dev vxlan0 proto kernel scope link src 172.17.1.3
192.168.1.0/24 dev veth2 proto kernel scope link src 192.168.1.3
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# bridge fdb
33:33:00:00:00:01 dev veth2 self permanent
01:00:5e:00:00:01 dev veth2 self permanent
33:33:ff:6d:21:bb dev veth2 self permanent
00:00:00:00:00:00 dev vxlan0 dst 192.168.1.2 via veth2 self permanent

测试两个命名空间中 veth 对应的 VTEP 内的 vxlan 设备的连通性

在 node2 对应的网络命令空间 ping node1 的 vxlan IP 地址

代码语言:javascript
代码运行次数:0
复制
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# ping -c 3 172.17.1.2
PING 172.17.1.2 (172.17.1.2) 56(84) bytes of data.
64 bytes from 172.17.1.2: icmp_seq=1 ttl=64 time=0.088 ms
64 bytes from 172.17.1.2: icmp_seq=2 ttl=64 time=0.062 ms
64 bytes from 172.17.1.2: icmp_seq=3 ttl=64 time=0.063 ms

--- 172.17.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2035ms
rtt min/avg/max/mdev = 0.062/0.071/0.088/0.012 ms
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger#

上面即为点对点组网模型 VXLAN 网络

抓包测试:

node1 上发起 ping

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec node1  bash
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# ping -c 5 172.17.1.3
PING 172.17.1.3 (172.17.1.3) 56(84) bytes of data.
64 bytes from 172.17.1.3: icmp_seq=1 ttl=64 time=0.053 ms
64 bytes from 172.17.1.3: icmp_seq=2 ttl=64 time=0.058 ms
64 bytes from 172.17.1.3: icmp_seq=3 ttl=64 time=0.059 ms
64 bytes from 172.17.1.3: icmp_seq=4 ttl=64 time=0.064 ms
64 bytes from 172.17.1.3: icmp_seq=5 ttl=64 time=0.065 ms

--- 172.17.1.3 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4115ms
rtt min/avg/max/mdev = 0.053/0.059/0.065/0.004 ms
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger#

查看 node2 上配置的 vxlan 设备信息

代码语言:javascript
代码运行次数:0
复制
liruilonger@cloudshell:~$ sudo ip netns exec node2 bash
。。。。。。。。。。。。。。。
3: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether 5a:46:75:cd:ce:9e brd ff:ff:ff:ff:ff:ff
    inet 172.17.1.3/24 scope global vxlan0
       valid_lft forever preferred_lft forever
    inet6 fe80::5846:75ff:fecd:ce9e/64 scope link
       valid_lft forever preferred_lft forever

指定对应的设备信息抓包

代码语言:javascript
代码运行次数:0
复制
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger# tcpdump -i vxlan0
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on vxlan0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
^C07:40:51.689496 IP6 fe80::5846:75ff:fecd:ce9e > ff02::2: ICMP6, router solicitation, length 16
07:40:56.278116 IP 172.17.1.2 > 172.17.1.3: ICMP echo request, id 7030, seq 1, length 64
07:40:56.278143 IP 172.17.1.3 > 172.17.1.2: ICMP echo reply, id 7030, seq 1, length 64
07:40:57.321585 IP 172.17.1.2 > 172.17.1.3: ICMP echo request, id 7030, seq 2, length 64
07:40:57.321610 IP 172.17.1.3 > 172.17.1.2: ICMP echo reply, id 7030, seq 2, length 64
07:40:58.345557 IP 172.17.1.2 > 172.17.1.3: ICMP echo request, id 7030, seq 3, length 64
07:40:58.345582 IP 172.17.1.3 > 172.17.1.2: ICMP echo reply, id 7030, seq 3, length 64
07:40:59.369546 IP 172.17.1.2 > 172.17.1.3: ICMP echo request, id 7030, seq 4, length 64
07:40:59.369575 IP 172.17.1.3 > 172.17.1.2: ICMP echo reply, id 7030, seq 4, length 64
07:41:00.393571 IP 172.17.1.2 > 172.17.1.3: ICMP echo request, id 7030, seq 5, length 64
07:41:00.393600 IP 172.17.1.3 > 172.17.1.2: ICMP echo reply, id 7030, seq 5, length 64
07:41:01.417503 ARP, Request who-has 172.17.1.2 tell 172.17.1.3, length 28
07:41:01.417543 ARP, Request who-has 172.17.1.3 tell 172.17.1.2, length 28
07:41:01.417599 ARP, Reply 172.17.1.3 is-at 5a:46:75:cd:ce:9e (oui Unknown), length 28
07:41:01.417594 ARP, Reply 172.17.1.2 is-at c6:b1:87:67:d9:e4 (oui Unknown), length 28

15 packets captured
15 packets received by filter
0 packets dropped by kernel
root@cs-1080702884152-ephemeral-ne4n:/home/liruilonger#

上面即为一个点对点的 VXLAN 通信 ,回到我们今天要讲的 Flannel 和 Calico,我们先来看一下 Calico

Calico

Calico第 3 层工作,并依赖于 Linux 路由表和 iptables 来移动数据包。

使用 Calico 时 Pod 之间的通信方式

Pod 之间的通信当使用 Calico 作为 CNI 时,每个 Pod 都会被分配一个唯一的 IP 地址,并且这些 IP 地址将用于在 Pod 之间进行通信。

Pod 通信本质上是 不同机器上的两个 network namespace 通信, network namespace 通过 veth pair 会在容器内部和宿主机映射一对虚拟网卡(veth pair),在部署的好的 K8s 集群中,可以在节点上看到好多虚拟网卡,这些就是 veth pair宿主机的虚拟网卡。

Calico 使用 Linux 内核的网络堆栈来实现网络功能(宿主机的 calico 组件的 Felix 程序会在内核的路由表里面写入数据,注明这个IP出去时下一跳地址和进来时的由那个网卡解析), 同时路由程序会获取ip变换,通过 BPG 路由协议扩散到其他宿主机上,这里也包括使用代理 ARP 来处理 Pod 到节点的 ARP 请求。

宿主机,也就是工作节点,可以看做是一个路由器。pod 可以看做是连接到路由器上的网络终端。

这里创建两个 Pod ,简单分析一下,编写 YAML 文件通过拓扑分布约束调度在不同的节点。

代码语言:javascript
代码运行次数:0
复制
apiVersion: apps/v1
kind:Deployment
metadata:
name:demo-deployment
labels:
    app:os
spec:
replicas:2
selector:
    matchLabels:
      app:os
template:
    metadata:
      labels:
        app:os
    spec:
      containers:
      -name:centos
        image:centos:latest
        args:
         -tail
         --f
         -/dev/null
      topologySpreadConstraints:
        -maxSkew:1
          topologyKey:kubernetes.io/hostname
          whenUnsatisfiable:ScheduleAnyway
          labelSelector:
            matchLabels:
              app:os

应用之后,查看 Pod 信息

代码语言:javascript
代码运行次数:0
复制
┌──[root@vms100.liruilongs.github.io]-[~/docker]
└─$kubectl get pods -n demo -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP              NODE                          NOMINATED NODE   READINESS GATES
demo-deployment-6cbdbd86d5-fbt9d   1/1     Running   0          17m   10.244.169.66   vms105.liruilongs.github.io   <none>           <none>
demo-deployment-6cbdbd86d5-nm467   1/1     Running   0          16m   10.244.38.174   vms103.liruilongs.github.io   <none>           <none>
┌──[root@vms100.liruilongs.github.io]-[~/docker]
└─$

分别调度到了不同节点:

  • vms105.liruilongs.github.io : demo-deployment-6cbdbd86d5-fbt9d
  • vms103.liruilongs.github.io : demo-deployment-6cbdbd86d5-nm467

进入容器查看 Pod IP 信息

demo-deployment-6cbdbd86d5-fbt9d Pod 对应 IP 为 :10.244.169.66, 生成的 veth pair容器侧的虚拟网卡为 eth0@if16

代码语言:javascript
代码运行次数:0
复制
┌──[root@vms100.liruilongs.github.io]-[~/docker]
└─$kubectlexec -it demo-deployment-6cbdbd86d5-fbt9d -n demo -- bash
[root@demo-deployment-6cbdbd86d5-fbt9d /]# 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
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
4: eth0@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
    link/ether 42:17:dd:38:6a:10 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.244.169.66/32 scope global eth0
       valid_lft forever preferred_lft forever
[root@demo-deployment-6cbdbd86d5-fbt9d /]# exit
exit

demo-deployment-6cbdbd86d5-nm467 Pod 对应 IP 为 10.244.38.174 ,生成的 veth pair容器侧的虚拟网卡为 eth0@if34

代码语言:javascript
代码运行次数:0
复制
┌──[root@vms100.liruilongs.github.io]-[~/docker]
└─$kubectlexec -it demo-deployment-6cbdbd86d5-nm467 -n demo -- bash
[root@demo-deployment-6cbdbd86d5-nm467 /]# 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
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
4: eth0@if34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
    link/ether 36:a2:81:c4:84:f9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.244.38.174/32 scope global eth0
       valid_lft forever preferred_lft forever
[root@demo-deployment-6cbdbd86d5-nm467 /]# exit
exit

进入 demo-deployment-6cbdbd86d5-nm467 Pod 简单做 ping 测试,来看一下这个 ICMP 包是如何出去的。

代码语言:javascript
代码运行次数:0
复制
┌──[root@vms100.liruilongs.github.io]-[~/ansible]
└─$kubectl exec -it demo-deployment-6cbdbd86d5-nm467 -n demo -- bash
[root@demo-deployment-6cbdbd86d5-nm467 /]# ping -c 3  10.244.169.66
PING 10.244.169.66 (10.244.169.66) 56(84) bytes of data.
64 bytes from 10.244.169.66: icmp_seq=1 ttl=62 time=0.497 ms
64 bytes from 10.244.169.66: icmp_seq=2 ttl=62 time=0.460 ms
64 bytes from 10.244.169.66: icmp_seq=3 ttl=62 time=0.391 ms

--- 10.244.169.66 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2044ms
rtt min/avg/max/mdev = 0.391/0.449/0.497/0.047 ms

当前容器 IP 为 10.244.38.174 , ping 侧的容器 IP 为 10.244.169.66,不在同一个网络内,所以当前容器会在路由表获取一下跳地址.

查看容器路由信息

代码语言:javascript
代码运行次数:0
复制
[root@demo-deployment-6cbdbd86d5-nm467 /]# ip route
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link

第一条为一条默认路由,所有目标地址不在本地子网的流量都会通过这个路由转发到下一跳地址。

下一跳地址为 169.254.1.1 ,这是预留的本地 IP 网段,这里的容器里的路由规则在所有的容器都是一样的,不需要动态更新.

这条路由表明,169.254.1.1 是一个直接可达的地址,属于 eth0 接口,使用 Calico 时,169.254.1.1Calico 默认的虚拟网关地址(Link-local Gateway)

容器会查询下一跳 168.254.1.1MAC 地址,这个 ARP 请求(查找目标设备的 MAC 地址)会如何发出?

这里通过 veth pair 发出,容器内部的虚拟网卡eth0@if34 发到宿主节点的对应的虚拟网卡cali7a4b00317e6 宿主机上的 veth 设备代理 ARP 请求并响应。容器中没有复杂的路由规则,所有非本地流量都通过 169.254.1.1 路由到宿主机

如何确定一对 veth pair 虚拟网卡?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-12-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 山河已无恙 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在前面
  • 名词解释:
    • VETH Pair(虚拟网卡对):
    • 网桥(Bridge):
    • IPIP
    • VXLAN
    • 点对点的 VXLAN Demo
  • Calico
    • 使用 Calico 时 Pod 之间的通信方式
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档