Underlay网络是由各类物理设备构成,通过使用路由协议保证其设备之间的IP连通性的承载网络
Overlay网络是建立在Underlay网络上的逻辑网络, Overlay网络有着各种网络协议和标准,包括VXLAN、NVGRE、SST、GRE、NVO3、EVPN等
应用层
表示层
会话层
传输层
网络层
数据链路层
物理层
# 参数
-c 显示更多信息
-n 不解析名字
-v 显示详细的处理信息
-F 显示发送信息
-C 显示路由缓存
-f 清除所有网关入口的路由表
-p 与 add 命令一起使用时使路由具有永久性
add 添加一条新路由
del 删除一条路由
-net 目标地址是一个网络
-host 目标地址是一个主机
U Up表示此路由当前为启动状态
H Host,表示此网关为一主机
G Gateway,表示此网关为一路由器
R Reinstate Route,使用动态路由重新初始化的路由
D Dynamically,此路由是动态性地写入
M Modified,此路由是由路由守护程序或导向器动态修改
! 表示此路由当前为关闭状态
# 添加路由,直接使用route命令添加路由,一旦系统重启,配置的路由会消失, 在/etc/rc.local里添加
route add [-net|-host] target [netmask Nm] [gw Gw] [[dev] If]
route add -host 192.168.2.254 gw 192.168.3.254
握手
挥手
namespace
的主要作用是封装抽象,限制,隔离,使命名空间内的进程看起来拥有他们自己的全局资源time namespace
没有使用到,其他namespace都有用到namespace | 说明 |
---|---|
cgroup | 控制进程使用的资源, 限制内存, cpu等, 内核 4.6添加的, 视图像宿主机一样以根形式来呈现 |
ipc | 有自己的IPC,比如共享内存、信号量, 消息队列, posix message queues等, 隔离进程间通信 |
network | 有自己的网络设备资源,包括网络协议栈(IPV4和IPV6协议栈)、网络设备、路由表、防火墙、端口, /proc/net 目录、/sys/class/net 目录、套接字(socket)等 |
mount | 有自己的文件系统挂载点 |
pid | 有自己的进程号,使得namespace中的进程PID单独编号,隔离pid, 实际是伪装 |
time | 有自己的启动时间点信息和单调时间,比如可设置某个namespace的开机时间点为1年前启动,再比如不同的namespace创建后可能流逝的时间不一样, k8s没有使用到time namespace |
user | 有自己的用户权限管理机制, 比如独立的UID/GID(用户/用户组),使得namespace更安全 |
uts | 有自己的主机信息,包括主机名(hostname)、域名 |
# [4026534853] 中扩展中的是文件的inode号,如果两个容器进程的某个ns指向的是inode号是一样的,那就说明他们共享了同一个namespace,下面正式pause容器个业务容器共享的namespace信息, 同时这些文件都是硬链接
[root@xingguang ~]# ll /proc/4011221/ns/
total 0
lrwxrwxrwx 1 root root 0 Apr 27 11:14 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Apr 27 11:14 ipc -> ipc:[4026534853]
lrwxrwxrwx 1 root root 0 Apr 27 11:14 mnt -> mnt:[4026535201]
lrwxrwxrwx 1 root root 0 Apr 27 11:14 net -> net:[4026534856]
lrwxrwxrwx 1 root root 0 Apr 27 11:14 pid -> pid:[4026535347]
lrwxrwxrwx 1 root root 0 Apr 27 11:14 pid_for_children -> pid:[4026535347]
lrwxrwxrwx 1 root root 0 Apr 27 11:14 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Apr 27 11:14 uts -> uts:[4026535346]
[root@xingguang ~]# ll /proc/4010952/ns/
total 0
lrwxrwxrwx 1 root root 0 Apr 27 11:14 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Apr 24 18:16 ipc -> ipc:[4026534853]
lrwxrwxrwx 1 root root 0 Apr 27 11:14 mnt -> mnt:[4026534851]
lrwxrwxrwx 1 root root 0 Apr 24 18:16 net -> net:[4026534856]
lrwxrwxrwx 1 root root 0 Apr 27 11:14 pid -> pid:[4026534854]
lrwxrwxrwx 1 root root 0 Apr 27 11:14 pid_for_children -> pid:[4026534854]
lrwxrwxrwx 1 root root 0 Apr 27 11:14 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Apr 27 11:14 uts -> uts:[4026534852]
ip netns
进入名称空间后可以用宿主机上的所有命令# 查看程序的network的namespace的inode号
readlink /proc/1570493/ns/net
net:[4026532056]
# /var/run/netns/目录是是ip netns ls显示所有的network namespace, add mynet会在这个目录下生成一个挂载点
ip netns add mynet
# 查看有哪些network namespace
ip netns ls
# 当有了mynet名称空间,再进入网络名称空间,当进入mynet名称空间后,执行echo $$ 可以查看到这个名称空间在宿主机上的进程号, 也可以ip netns exec mynet echo $$获得pid
ip netns exec mynet bash
echo $$
# 查看回环网卡,发现还是DOWN状态
ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noqueue state DOWN 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
# ping 127.0.0.1 是不通的
# 将down状态设置为up, 把设备打开后才可以通
ip link set dev lo up
# 获取namespace名字
ip netns identify ${pid}
# 同样unshare也可以创建并进入新的network namespace,但是namespace没有名字, 进入后执行echo $$也可以得到pid,unshare
unshare -n bash
# 删除network namespace,但是他只是移除了这个network namespace对应的挂载点,只要里面还有进程运行着,这个namespace就会一直存在(之前的Linux内核版本)
ip netns delete mynet
ip netns
等操作, 我们在创建一个新的名称空间后,得到一个pid, 当退出名称空间后,名称空间并不会删除, 当打开/proc/$$/ns/
目录下的文件的时候,只要文件描述符保持open状态, 对应的namespace就会一直存在, 哪怕是namespace中的进程终止了(Linux内核黑科技)nsenter
命令也可以这样执行# 如下操作就是保持文件描述符处于open状态
# 创建一个新文件
touch /var/run/netns/mynet
# 创建名称空间并进入, unshare很像clone方法(netns其实就是实现clone方法),区别在于unshare作用在一个已存在的进程上,而clone会创建一个新的进程
unshare -n bash
echo $$
# 获得namespace的ID
readlink /proc/$$/ns/net
# 绑定挂载把当前进程的 network namespace 文件挂载mynet文件上,通过查看文件inode和namespace的ID是一样的,就算退出名称空间,文件的inode号也不会发生变化
mount -B /proc/$$/ns/net /var/run/netns/mynet
ll -i /var/run/netns/mynet
# 取消挂载, 如果/proc/self/ns/net目录
umount /var/run/netns/mynet
veth pair
设备总是成对的, 常用于跨network namespace之间通讯, 其原理就是将veth pair
的两端放在不同的namespace里, 任意一端接收数据,都会在另一端转发出去
veth pair
设备在转发过程中不会篡改数据包中的内容mtu
(Maximum Transmission Unit)是最大传输单元,用来通知对方所能接受数据服务单元的最大尺寸,说明发送方能够接受的有效载荷大小# 创建veth pair,名字分别为veth0和veth1
ip link add veth0 type veth peer name veth1
# 查看,veth1@veth0, veth0@veth1很好的诠释了他们之间的关系,但是他们却是DOWN状态,mtu是1500, veth0和veth1设备都没有绑定ip地址
ip link list
23: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether b6:59:64:6f:de:8c brd ff:ff:ff:ff:ff:ff
24: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether ca:04:6b:9b:ae:3c brd ff:ff:ff:ff:ff:ff
# 将两块网卡设备up
ip link set dev veth0 up
ip link set dev veth1 up
# 给设备配置ip地址
ifconfig veth0 10.0.0.10/24
# 或者这样可以配ip地址
ip addr add 10.0.0.10/24 dev veth0
ip addr add 10.0.0.11/24 dev veth1
# 给另一块设备放到network namespace中, mynet是上面创建的network namespace
ip link set veth1 netns mynet
eth0
设备实际上和宿主机上的某个veth是成对的(pair)关系# 进入一个容器查看
cat /sys/class/net/eth0/iflink
21964
# 或者容器中
ip link show eth0
21963: eth0@if21964: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 00:00:00:8c:a9:66 brd ff:ff:ff:ff:ff:ff
# 或者在容器中ethtool -S eth0,列出veth pair对端的网卡, 如果容器没有这个命令可以nsenter
nsenter -t 4011221 -n ethtool -S eth0
NIC statistics:
peer_ifindex: 21964
rx_queue_0_xdp_packets: 0
rx_queue_0_xdp_bytes: 0
rx_queue_0_xdp_drops: 0
# 在这个容器所在的宿主机上
ip a | grep 21964
21964: f07cb212eb59_h@if21963: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master ovs-system state UP group default qlen 1000
# 或者
cat /sys/class/net/f07cb212eb59_h/iflink
21963
veth pair
可以让两个network namespace连接起来, 理论上k8s中的每一个pod都是独立的network namespace,这么多的名称空间,相互连接,veth pair
就捉襟见肘了,这时候就需要Bridge
了Bridge
是用于连接两个不同的局域网, 网桥是二层网络设备, 工作在数据链路层(Data Link), 主要功能是根据 MAC 地址来将数据包转发到网桥的不同端口(Port)上, Linux Bridge有多个端口, 数据可以从任何端口进来, 进来之后从哪个口出去取决于目的Mac地址, 原理和物理交换机差不多, Linux Bridge不能跨机连接网络设备# 创建bridge
[root@xingguang ~]# ip link add name br0 type bridge
[root@xingguang ~]# ip link set br0 up
[root@xingguang ~]# ip a | grep br0
25: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
# 还可以这样创建
brctl addbr br0
veth0
绑定到bridge的br0
的时候, bridge在veth0
和协议栈之间做了一次拦截,将veth0
本来要发给协议栈的数据拦截下来,全部转发给bridge, 同时bridge也可以向veth0发数据
# 根据上面创建的veth pair, veth0和veth1, 并给他们绑定相应的ip地址,如果没有创建,则创建如下(ip地址根据服务器的物理网卡eth0同一网段就行)
ip link add veth0 type veth peer name veth1
ip addr add 10.0.0.10/24 dev veth0
ip addr add 10.0.0.11/24 dev veth1
ip link set dev veth0 up
ip link set dev veth1 up
# 根据上面创建的bridge,将veth pair的一端veth0绑定到br0上
ip link set dev veth0 master br0
# 或者用brctl命令来绑定
brctl addif br0 veth0
# 查看当前网桥有哪些网路设备
bridge link
# 或者brctl查看
brctl show
bridge name bridge id STP enabled interfaces
br0 8000.000000000000 no veth0
tun/tap
设备的用处是将协议栈(处于内核空间)中部分数据包转发给用户空间的应用程序, 在操作系统内核和用户应用程序之间传递 IP 包, TUN 设备是一种虚拟网络设备tun/tap
就是虚拟网卡
从Linux角度看, tun/tap
设备是可以用文件句柄操作字符设备, 从网络虚拟化角度,他是虚拟网卡,一端连着网络协议栈,另一端连着用户态程序, 他的作用是可以TCP/IP协议栈处理好的网络包发送给任何一个使用tun/tap驱动的进程,由进程重新处理后发送到物理链路中
tun/tap
驱动程序包括两个部分,一个是字符设备驱动,一个是网卡驱动。这两部分驱动程序分工不太一样,字符驱动负责数据包在内核空间和用户空间的传送,网卡驱动负责数据包在 TCP/IP 网络协议栈上的传输和处理, 在 Linux 中,用户空间和内核空间的数据传输有多种方式,字符设备就是其中的一种, tap/tun
通过驱动程序和一个与之关联的字符设备,来实现用户空间和内核空间的通信接口, 设备文件即充当了用户空间和内核空间通信的接口。当应用程序打开设备文件时,驱动程序就会创建并注册相应的虚拟设备接口,一般以 tunX
或 tapX
命名。当应用程序关闭文件时,驱动也会自动删除 tunX
和 tapX
设备,还会删除已经建立起来的路由等信息, tap/tun
设备文件就像一个管道,一端连接着用户空间,一端连接着内核空间。当用户程序向文件 /dev/net/tun
或 /dev/tap0
写数据时,内核就可以从对应的 tunX
或 tapX
接口读到数据,反之,内核可以通过相反的方式向用户程序发送数据
# 创建tun设备并绑定网卡
ip tuntap add dev tun0 mod tun
ifconfig tun0 192.168.188.11 netmask 255.255.255.0
tun
设备, 可以通过命令 ip tunnel help
查看 IP 隧道的相关操作# 可以看到有5中隧道模式
ip tunnel help
ipip
:即 IPv4 in IPv4
,在 IPv4 报文的基础上再封装一个 IPv4 报文GRE
:即通用路由封装(Generic Routing Encapsulation
),定义了在任意一种网络层协议上封装其他任意一种网络层协议的机制,IPv4 和 IPv6 都适用sit
:和 ipip
类似,不同的是 sit
是用 IPv4 报文封装 IPv6 报文,即 IPv6 over IPv4
ISATAP
:即站内自动隧道寻址协议(Intra-Site Automatic Tunnel Addressing Protocol
),和 sit
类似,也是用于 IPv6 的隧道封装vti
:即虚拟隧道接口(Virtual Tunnel Interface
),是 cisco 提出的一种 IPsec
隧道技术ipip
需要内核模块 ipip.ko
的支持,用modprobe ipip
加载[root@xingguang ~]# modprobe ipip
[root@xingguang ~]# lsmod | grep ipip
ipip 13465 0
tunnel4 13252 1 ipip
ip_tunnel 25163 1 ipip
# 上图为ipip隧道网络拓扑, 这里先创建2个network namespace
ip netns add ns1
ip netns add ns2
# 创建2对veth pair,并将另一端挂在某个namespace下
ip link add v1 type veth peer name v1_p
ip link add v2 type veth peer name v2_p
ip link set v1 netns ns1
ip link set v2 netns ns2
# 分别给两对veth-pair端点配上ip并启用
ip addr add 10.10.10.1/24 dev v1_p
ip addr add 10.10.20.1/24 dev v2_p
ip link set v1_p up
ip link set v2_p up
# 给2个network namespace添加上ip信息,并启用
ip netns exec ns1 ip a add 10.10.10.2/24 dev v1
ip netns exec ns1 ip link set v1 up
ip netns exec ns2 ip a add 10.10.20.2/24 dev v1
ip netns exec ns2 ip link set v2 up
# Linux本身就是一台路由器,可以通过 echo 1 >>/proc/sys/net/ipv4/ip_forward 来临时打开,永久打开如下
vim /etc/sysctl.conf
net.ipv4.ip_forward=1
# 修改完后生效
sysctl -p
# 新建路由信息,让v1_p和v1_p设备互通,
ip netns exec ns1 route add -net 10.10.20.0 netmask 255.255.255.0 gw 10.10.10.1
ip netns exec ns2 route add -net 10.10.10.0 netmask 255.255.255.0 gw 10.10.20.1
# 可以在ns1中ping下
ip netns exec ns1 ping 10.10.20.2
# ns1中创建tun设备,绑定ip地址,并设置为up状态, 然后还需要设置隧道端点,用 remote 和 local 表示,这是隧道外层IP,对应的还有隧道内层IP,用 ip addr xx peer xx 配置
ip netns exec ns1 ip tunnel add tun1 mode ipip remote 10.10.20.2 local 10.10.10.2
ip netns exec ns1 ip link set tun1 up
ip netns exec ns1 ip addr add 10.10.100.10 peer 10.10.200.10 dev tun1
# ns2中创建tun设备,绑定ip地址,并设置为up状态.....
ip netns exec ns1 ip tunnel add tun2 mode ipip remote 10.10.10.2 local 10.10.20.2
ip netns exec ns1 ip link set tun2 up
ip netns exec ns1 ip addr add 10.10.200.10 peer 10.10.100.10 dev tun2
tun1(10.10.100.10)
,目标IP地址为 tun2(10.10.200.10)
, 由于 tun1 和 tun2 不在同一网段,所以会查路由表,当通过 ip tunnel
命令建立 ipip
隧道之后,会自动生成一条路由,如下,表明去往目的地 10.10.200.10
的路由直接从 tun1 出去ipip
隧道的配置,会封装上一层新的 IP 报头 ,源目的 IP 地址分别为 v1(10.10.10.2)
和 v2(10.10.20.2)
, v1 和 v2 同样不在一个网段,同样查路由表(刚才新建了路由信息),发现去往 10.10.20.0
网段可以从 10.10.10.1
网关发出去ip_forward
,相当于一台路由器,10.10.10.0
和 10.10.20.0
是两条直连路由,所以直接查表转发,从 ns1 过渡到 ns210.10.200.10
,这正是自己配置的 ipip
隧道的 tun2 地址,于是就将报文交给 tun2 设备, 至此,tun1 的 ping 请求包就成功到达 tun2# ip tunnel命令建立 ipip 隧道后,会创建的路由信息
[root@xingguang ~]# 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
10.10.200.10 0.0.0.0 255.255.255.255 UH 0 0 0 tun1
VTEP
(VXLAN Tunnel Endpoints) 将二层以太网帧封装在 UDP
中,一个 VTEP
可以被一个物理机上的所有 VM(或容器)共用,一个物理机对应一个 VTEP
。从交换机的角度来看,只是不同的 VTEP
之间在传递 UDP
数据,只需要记录与物理机数量相当的 MAC 地址表条目就可以了,一切又回到了和从前一样, VLAN tag 总共有 4 个字节, 其中有 12 bit
用来标识不同的二层网络(即 LAN ID
),故而最多只能支持2的12次方 ,即 4096
个子网的划分, 而 VXLAN 的报文 Header 预留了 24 bit
来标识不同的二层网络(即 VNI
,VXLAN Network Identifier),即 3 个字节,可以支持 2的24次方个子网
VTEP
时候VNI
字段,用来定义VXLAN网络中不同的租户, 此外,还包含VXLAN Flags
(8比特,取值为00001000)和两个保留字段(分别为24比特和8比特)VXLAN Header
和原始以太帧一起作为UDP的数据了, 这就是MAC in UDP
的说法来源, UDP Header
中,目的端口号VXLAN Port, 也就是接收方VTEP设备的端口, 源端口号(UDP Src. Port)是原始以太帧通过哈希算法计算后的值UDP Header
, 就会根据VXLAN头部的VNI把原始报文发送到目的容器DATA
、传输层头部信息 、IP Header
、 MAC Header
, 这里将这些原始报文信息用Original L2 Frame
表示
IP Header
是已经知道的, 但是MAC Header
的目的MAC地址是不知道的, 需要VXLAN的机制来实现ARP(地址解析, 根据IP来获得MAC)的功能Original L2 Frame
报文增加VXLAN Header
、UDP Header
、Outer IP Header
、Outer MAC Header
报文(VXLAN网络特性, 会封装报文), 会根据Outer IP Header
、Outer MAC Header
报文来确定源和目的VTEP设备的IP地址和MAC地址, 根据UDP Header
来确定源和目的VTEP设备端口, 从而准确发送给目的VTEP设备
UDP Header
中最重要的是VTEP设备的端口, 源端口是由系统生成并管理的, 目的端口是IANA(Internet Assigned Numbers Authority, 互联网号码分配局)分配的4789固定端口Outer IP Header
中VTEP设备源地址可以用很简单的方式确定, 目的地址是VM(或容器)所在宿主机VTEP的IP地址,却需要由某种方式来确定UDP Header
中的VNI也是动态感知的, 该怎么获取VNI信息Original L2 Frame
、VXLAN Header
, 这时目的VTEP设备会检查VXLAN Header
报文中的VNI
信息, 以及Original L2 Frame
中的MAC Header
中源和目的MAC地址, 从而确认接收方VM(或容器)与本VTEP设备相连后,拆除VXLAN Header
,将Original L2 Frame
交给接收方VM(或容器)# 创建vxlan1设备,也可以说成创建VTEP设备
[root@xingguang ~]# ip link add vxlan1 type vxlan id 4 dstport 4789 remote 192.168.8.101 local 192.168.8.100 dev eth0
# 查看vxlan1信息
[root@xingguang ~]# ip -d link show vxlan1
26: vxlan1: <BROADCAST,MULTICAST> mtu 1450 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 6a:46:a4:02:08:64 brd ff:ff:ff:ff:ff:ff promiscuity 0
vxlan id 4 group 239.1.1.1 dev eth0 srcport 0 0 dstport 4789 ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
# 如果要删除使用通用的删除接口方式
[root@xingguang ~]# ip link delete vxlan1
# 为vxlan1网卡配置IP地址,并改为UP状态
ip addr add 10.20.1.2/24 dev vxlan1
ip link set vxlan1 up
# 上面2步执行成功后,路由表多了下面的内容, 所有目的地址是10.20.1.0/24网段的包要经过vxlan1转发
[root@xingguang ~]# ip route
10.20.1.0/24 dev vxlan1 proto kernel scope link src 10.20.1.2
# 同时,vxlan1 fdb表项中的内容, 这个表项的意思是说,默认的而 vtep 对端地址为 192.168.8.101,换句话说,如果接收到的报文添加上 vxlan 头部之后都会发到 192.168.8.101
[root@xingguang ~]# bridge fdb
00:00:00:00:00:00 dev vxlan1 dst 192.168.8.101 via enp0s8 self permanent
# 同时在另外一台服务器192.168.8.101上也进行相同的配置,要保证VNI也是相同的也是4,dstport相同也是4789,并修改vtep的地址和remote IP地址到相应的值,然后测试两台vtep是否通, 在192.168.8.100上ping
[root@xingguang ~]# ping 10.20.1.3
vxlan1
,类型为 vxlan
的网络 interface,后面是 vxlan interface 需要的参数:
local
参数含义是相同的,二选一即可virtualbox
本身就支持多播,不需要额外设置, 但是不是所有的网络都支持多播, 再加上多播方式带来的报文浪费,实际生产中VXLAN的多播模式也很少被采用[root@xingguang ~]# ip link add vxlan1 type vxlan id 4 group 239.1.1.1 dstport 4789 dev eth0
# 查看vxlan1信息
[root@xingguang ~]# ip -d link show vxlan1
26: vxlan1: <BROADCAST,MULTICAST> mtu 1450 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 6a:46:a4:02:08:64 brd ff:ff:ff:ff:ff:ff promiscuity 0
vxlan id 4 group 239.1.1.1 dev eth0 srcport 0 0 dstport 4789 ageing 300 udpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
# 查看vxlan1的转发表,这个规则是在创建vxlan1接口就会自动创建,和点对点的VXLAN不一样, dst地址变成了多播地址239.1.1.1,而不是VTEP地址
[root@xingguang ~]# bridge fdb show dev vxlan1
00:00:00:00:00:00 dst 239.1.1.1 via eth0 self permanent
# 为vxlan1网卡配置IP地址,并改为UP状态
ip addr add 10.20.1.2/24 dev vxlan1
ip link set vxlan1 up
# 上面2步执行成功后,路由表多了下面的内容, 所有目的地址是10.20.1.0/24网段的包要经过vxlan1转发
[root@xingguang ~]# ip route
10.20.1.0/24 dev vxlan1 proto kernel scope link src 10.20.1.2
# 和点对点模式一样,对所有需要通信的服务器节点上进行以上配置,然后ping VTEP IP地址,验证是否可以互相通信
[root@xingguang ~]# ping 10.20.1.3
上面命令是多播模式的VXLAN, 创建一个vxlan1的interface, 它使用在eth0的多播组239.1.1.1, 初始化时没有转发表
配置完成后, VTEP通过 IGMP(Internet Group Management Protocol, 因特网组管理协议,是TCP/IP协议族中负责IP组播成员管理的协议,用来在接收者和与其直接相邻的组播路由器之间建立、维护组播组成员关系) 加入同一个多播网络239.1.1.1
VXLAN Header
, 取出真正的ARP请求报文, 同时VTEP会记录MAC地址和IP地址信息, 到FDB表(二层MAC地址表)中, 这便实现了一次学习的过程, 如果发现报文ARP不是发送给自己的, 则直接丢弃, 如果是, 则生成ARP应答报文underlay
网络直接返回给发送方主机,发送方主机根据 VNI 把报文转发给 VTEP,VTEP解包取出 ARP 应答报文,添加 ARP 缓存中, 并根据报文学习到目的 VTEP所在的主机地址,添加到FDB表中overlay
网络构建,但是通信的双方只有一个VTEP,在实际的生产中,每台主机上都有几十台甚至上百台的虚拟机或者容器需要通信,Linux网桥可以连接多快虚拟网卡, 因此能够把这些通信实体组织起来, 在 Linux 中把同一个网段的 interface 组织起来正是网桥(bridge,或者 switch,这两个名称等价)的功能,因此该如何用网桥把多个虚拟机或者容器放到同一个VXLAN Overlay 网络中?veth pair
的其中一端# 前面的多播VXLAN创建过程略,创建bridge,up并绑定到bridge0上
ip link add bridge0 type bridge
ip link set vxlan0 master bridge0
ip link set vxlan0 up
ip link set bridge0 up
# 创建network namespace和一对veth pair, 并veth pair其中一端绑定到网桥,另一端放在network namespace并绑定IP地址(也是容器的IP地址)
ip netns add container1
ip link add veth0 type veth peer name veth1
ip link set dev veth0 master bridge0
ip link set dev veth0 up
# 配置容器内部的网络和IP
ip link set dev veth1 netns container1
ip netns exec container1 ip link set lo up
ip netns exec container1 ip link set veth1 name eth0
ip netns exec container1 ip addr add 10.20.1.2/24 dev eth0
ip netns exec container1 ip link set eth0 up
# 其他的host也用同样的方法配置VXLAN网络
VXLAN Header
, 最后通过多播的方式查询通讯对端的MAC地址remote
或者 group
参数就行# 这个在192.168.8.100上创建
[root@xingguang ~]# ip link add vxlan0 type vxlan id 4 dstport 4789 dev eth0
192.168.8.101
和 192.168.8.102
,相当于手动维护了一个 VTEP 的多播组, 在所有的节点的 VTEP 上更新对应的 FDB 表项,就能实现 overlay 网络的连通, 整个通信流程和多播模式相同,唯一的区别是,VTEP 第一次会给所有的组内成员发送单播报文,当然也只有一个 VTEP 会做出应答, 这个过程当然可以用一些自动化工具实现[root@xingguang ~]# bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 192.168.8.101
[root@xingguang ~]# bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 192.168.8.102
# 换成nolearning参数,这个参数告诉VTEP不要通过收到的报文学习FDB表内容,因为我们自己进行维护,添加规则
[root@xingguang ~]# ip link add vxlan0 type vxlan id 4 dstport 4789 dev nolearning
# 在原来基础上添加FDB规则, 说明: 52:5e:55:58:9a:ab这个MAC地址是对端192.168.8.101宿主机上的VTEP的mac地址,通过ip -d link show vxlan0 可获得, 其他同理
[root@xingguang ~]# bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 192.168.8.101
[root@xingguang ~]# bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 192.168.8.102
[root@xingguang ~]# bridge fdb append 52:5e:55:58:9a:ab dev vxlan0 dst 192.168.8.101
[root@xingguang ~]# bridge fdb append d6:d9:cd:0a:a4:28 dev vxlan0 dst 192.168.8.102
IP 和 MAC
关系,在接收到容器发来的 ARP 请求时可以直接作出应答。这样的话,我们只需要更新 VTEP interface 上 ARP 表项就行了# 这条命令和上部分相比多了 proxy 参数,这个参数告诉 VTEP 承担 ARP 代理的功能, 如果收到 ARP 请求,如果自己知道结果就直接作出应答
[root@xingguang ~]# ip link add vxlan0 type vxlan id 4 dstport 4789 dev eth0 nolearning proxy
# 和上面一样,需要手动更新FDB表
[root@xingguang ~]# bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 192.168.8.101
[root@xingguang ~]# bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 192.168.8.102
[root@xingguang ~]# bridge fdb append 52:5e:55:58:9a:ab dev vxlan0 dst 192.168.8.101
[root@xingguang ~]# bridge fdb append d6:d9:cd:0a:a4:28 dev vxlan0 dst 192.168.8.102
# 还需要为 VTEP 添加 ARP 表项,所有要通信容器的 IP - MAC二元组都要加进去,这个在所有要通信的节点都需要加
[root@xingguang ~]# ip neigh add 10.20.1.2 lladdr 52:5e:55:58:9a:ab dev vxlan0
[root@xingguang ~]# ip neigh add 10.20.1.3 lladdr d6:d9:cd:0a:a4:28 dev vxlan0
自维护ARP表, 虽然可以减少多余报文, 但还有问题: 为了能够让所有的容器正常工作,所有可能会通信的容器都必须提前添加到 ARP 和 FDB表项中, 但并不是网络上所有的容器都会互相通信,所以添加的有些表项(尤其是 ARP 表项)是用不到的
Linux 提供了另外一种方法,内核能够动态地通知节点要和哪个容器通信,应用程序可以订阅这些事件,如果内核发现需要的 ARP 或者 FDB 表项不存在,会发送事件给订阅的应用程序,这样应用程序从中心化的控制拿到这些信息来更新表项,做到更精确的控制, 要收到 L2(FDB )miss,必须要满足几个条件:
# 每个宿主机都创建,l2miss:如果设备找不到 MAC 地址需要的 VTEP 地址,就发送通知事件, l3miss:如果设备找不到需要 IP 对应的 MAC 地址,就发送通知事件
[root@xingguang ~]# ip link add vxlan0 type vxlan id 4 dstport 4789 dev eth0 nolearning proxy l2miss l3miss
# ip monitor 命令能做到这点,监听某个 interface 的事件, 如果从当前节点容器中 ping 另外一个节点的容器,就先发生 l3 miss,这是 l3miss 的通知事件
[root@xingguang ~]# ip monitor all dev vxlan0
[nsid current]miss 10.20.1.3 STALE
# l3miss 是说这个 IP 地址,VTEP 不知道它对应的 MAC 地址,因此要手动添加 arp 记录, nud reachable 参数意思是,这条记录有一个超时时间,系统发现它无效一段时间会自动删除, 这样的好处是,不需要手动去删除它,删除后需要通信内核会再次发送通知事件, nud 是 Neighbour Unreachability Detection 的缩写, 当然根据需要这个参数也可以设置成其他值,比如 permanent,表示这个记录永远不会过时,系统不会检查它是否正确,也不会删除它,只有管理员也能对它进行修改
[root@xingguang ~]# ip neigh replace 10.20.1.3 lladdr b2:ee:aa:42:8b:0b dev vxlan0 nud reachable
# 这时候还是不能正常通信,接着会出现 l2miss 的通知事件, 这个事件是说不知道这个容器的 MAC 地址在哪个节点上,所以要手动添加 fdb 记录. 最后在通信的另一台机器上执行相应的操作,就会发现两者能 ping 通了
[root@xingguang ~]# ip monitor all dev vxlan0
[nsid current]miss lladdr b2:ee:aa:42:8b:0b STALE
[root@xingguang ~]# bridge fdb add b2:ee:aa:42:8b:0b dst 192.168.8.101 dev vxlan0