本文主要是介绍第九课 k8s网络CNI插件学习-Flannel网络插件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
第九课 k8s网络CNI插件学习-Flannel网络插件
tags:
- k8s网络
- Vxlan
- IPIP
- IPAM
文章目录
- 第九课 k8s网络CNI插件学习-Flannel网络插件
- 第一节 Flannel学习准备
- 1.1 Flannel环境
- 1.2 CNI的设计介绍
- 第二节 Flannel的UDP模式
- 2.1 UDP的模式环境
- 2.2 UDP的模式介绍
- 2.3 同一节点不同pod内的通信
- 2.4 不同节点不同pod内的通信
- 第三节 Flannel的Vxlan模式
- 3.1 VxLAN基础介绍
- 3.2 VxLAN模型介绍
- 3.3 Flannel VxLAN模式
- 3.4 VxLAN的linux模拟实验
- 3.5 物理网络中的VxLAN常用实现
- 第四节 Flannel的其他模式
- 4.1 Flannel的IPIP模式
- 4.2 Flannel HOST-GW模式
- 第五节 Flannel IP 地址管理 (IPAM)
- 5.1 Flannel IP地址管理
- 5.2 Flannel启动过程分析
第一节 Flannel学习准备
1.1 Flannel环境
- CentOS 7 内核升级 [升级前请务必确认自己环境,商业环境慎用!]
- Linux的kernel需要在4.4+以上,才能提供稳定的ipvlan方案。
- 升级kernel:升级为最新版本的kernel(可以指定升级到稳定版本)
uname -r
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
yum --enablerepo=elrepo-kernel install kernel-ml-devel kernel-ml -y
grub2-set-default 0
reboot
uname -r
- 安装k8s集群。至少在1.18.0版本之上。
## 指定版本安装1.19.0 现阶段最稳定
sudo yum install -y kubelet-1.19.0 kubeadm-1.19.0 kubectl-1.19.0
sudo systemctl restart kubelet && systemctl enable kubelet
# 初始化集群
kubeadm init \
--apiserver-advertise-address=192.168.44.129 \
--image-repository registry.aliyuncs.com/google_containers \
--kubernetes-version v1.19.0 \
--service-cidr=10.96.0.0/12 \
--pod-network-cidr=10.244.0.0/16
# 集群快照创建,每次网络试验后还原一下。防止一些环境残留。
# 先下载,需要修改不同的backend进行实验https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
- 其他工具eNSP、wireshark和virtualbox。
yum -y install bridge-utils
- 配置不同的backend选择不同Flannel模式,默认是VxLNA模式
---
kind: ConfigMap
apiVersion: v1
metadata:name: kube-flannel-cfgnamespace: kube-systemlabels:tier: nodeapp: flannel
data:cni-conf.json: |{"name": "cbr0","cniVersion": "0.3.1","plugins": [{"type": "flannel","delegate": {"hairpinMode": true,"isDefaultGateway": true}},{"type": "portmap","capabilities": {"portMappings": true}}]}net-conf.json: |{"Network": "10.244.0.0/16","Backend": {"Type": "vxlan" # backend:ipip,vxlan,udp,host-gw#}}
1.2 CNI的设计介绍
- 虚拟网桥:创建一个虚拟网卡对(veth pair),一头在容器内,一头在宿主机的root namespace内。这样一来,容器内发出的网络数据包,可以通过网桥进入宿主机网络栈,而发往容器的网络数据包也可以经过网桥进入容器。
- 多路复用:使用一个中间网络设备,暴露多个虚拟网卡接口,容器网卡都可以接入这个中间设备,并通过mac地址/IP地址来区分packet应该转发给哪一个容器设备。
- 硬件交换:还有个“比较直接”的方法就是为每个Pod分配一个虚拟网卡,这样一来,Pod与Pod之间的连接关系就会变的非常清晰,因为近乎物理机之间的通信基础。如今大多数网卡都支持SR-IOV功能,该功能将单一的物理网卡虚拟成多个VF接口,每个VF接口都有单独虚拟PCle通道,这些虚拟PCle通道共用物理网卡的PCle通道。
- CNI设计思路。
-
- 建立完整的网络通路。1.使用虚拟网桥实现。2.使用多路复用实现。3.使用SRIOV by pass内核。
-
- 对地址的管理,回收。
-
- 任何一种CNI都有两个重要目录,它们是CNI的一种标准。
cd /etc/cni/net.d/ # 配置目录后面会讲解
cd /opt/cni/bin/ # CNI的一些二进制工具文件
第二节 Flannel的UDP模式
2.1 UDP的模式环境
- UDP 模式:第一步我们需要在 Flannel 的配置文件中指定 Backend type 为 upd,可以直接修改 Flannel 的方式实现:
# kubectl edit cm kube-flannel-cfg -n kube-system 或者部署时候修改
apiVersion: v1
data:cni-conf.json: |{"cniVersion": "0.2.0","name": "cbr0","plugins": [{"type": "flannel","delegate": {"hairpinMode": true,"isDefaultGateway": true}},{"type": "portmap","capabilities": {"portMappings": true}}]}net-conf.json: |{{"Network": "10.244.0.0/16","Backend": {"Type": "udp" # 修改后端类型为 UDP}}
- 第二步:需要将 Backend 的类型更改为 udp,采用 UDP 模式时后端默认为端口为 8285,即 Flanneld 的监听端口。当采用 UDP 模式时,Flanneld 进程在启动时会通过打开 /dev/net/tun 的方式生成一个 TUN 设备,TUN 设备可以简单理解为 Linux 当中提供的一种内核网络与用户空间通信的一种机制,即应用可以通过直接读写 TUN 设备的方式收发 RAW IP 包。所以我们还需要将宿主机的 /dev/net/tun 文件挂载到容器中去:
# kubectl edit ds kube-flannel-ds-amd64 -n kube-system
......volumeMounts:- mountPath: /run/flannelname: run- mountPath: /etc/kube-flannel/name: flannel-cfg # 添加这个- mountPath: /dev/netname: tun
......
volumes:
- hostPath:path: /run/flanneltype: ""name: run
- hostPath:path: /etc/cni/net.dtype: ""name: cni
- hostPath: # 添加这个path: /dev/net # 挂载宿主机的 /dev/net/tun 文件type: ""name: tun
- 保存pod自动重启, 后查看一下damonset的pod的日志。
apiVersion: apps/v1
kind: Deployment
metadata:name: nettools
spec:selector:matchLabels:app: nettoolsreplicas: 3template:metadata:labels:app: nettoolsspec:containers:- name: nettoolsimage: burlyluo/nettoolbox:v3ports:- containerPort: 80
2.2 UDP的模式介绍
- UDP 是最开始支持的最简单的但是却是性能最差的一种方式,因此基本上在正式使用的时候不会使用这种方式,不过该方式由于非常简单所有可以有助于我们来理解容器跨主机网络通信的实现原理,所以我们先来和大家了解下 UDP 方式的实现方式
- UDP是与Docker网桥模式最相似的实现模式。不同的是,UDP模式在虚拟网桥基础上引入了TUN设备(flannel0)。TUN设备的特殊性在于它可以把数据包转给创建它的用户空间进程,从而实现内核到用户空间的拷贝。在Flannel中,flannel0由flanneld进程创建,因此会把容器的数据包转到flanneld,然后由flanneld封包转给宿主机发向外部网络。
- UDP转发的过程为:Node1的Pod-1发起的IP包(目的地址为Node2的Pod-2)通过容器网关发到cni0,宿主机根据本地路由表将该包转到flannel0,接着发给flanneld。Flanneld根据目的容器容器子网与宿主机地址的关系(由etcd维护)获得目的宿主机地址,然后进行UDP封包,转给宿主机网卡通过物理网络传送到目标节点。在UDP数据包到达目标节点后,根据对称过程进行解包,将数据传递给目标Pod。
- UDP模式使用了Flannel自定义的一种包头协议,实现三层网络Overlay网络处理跨主通信的问题。但是由于数据在内核和用户态经过了多次拷贝:容器是用户态,cni0和flannel0是内核态,flanneld是用户态,最终又要通过内核将数据发到外部网络,因此性能损耗较大,对于有数据传输有要求的在线业务并不适用。
- 查看flanneld该用户空间的进程:端口号使用8285.
netstat -ulnp | grep flanneld
- 每台主机的flanneld都监听着8285端口,所以flanneld只要把UDP发给其它Node的8285端口就可。然后该Node的flanneld再把IP包发送给它所管理的TUN设备flannel0,flannel0再发给cni0最后由cni0网桥发给对应的Pod。
- 同一节点同一pod内的容器。一个容器直接使用另外一个已经存在容器的网络配置:IP信息和网络端口等所有网络相关的信息都是共享的。需要注意的是:这两个容器的计算和存储资源还是隔离的。kubernetes的pod就是用这个实现的。同一个pod中的容器共一个network namespace,container网络模式用于容器和容器直接频繁交流的情况。
2.3 同一节点不同pod内的通信
- 通常情况下,在Flannel上解决同节点Pod之间的通信依赖的是Linux Bridge和我们在Docker中不同的是,在Kubernetes Flannel的环境中使用的Linux Bridge为cni0,而不是原来的docker0。可通过
brctl show
查看对应的Linux Bridge的bridge name和interfaces。
# node查看网桥对应的cni0对应的网卡
brctl show
kubectl exec -it nettools-66b897f8d7-4s8r7 bash
ethtool -S eth0 # 可以查看网卡对端的索引 peer_ifindex: 7
- 过程:我们知道用户空间是没有办法进行数据报文的封装的。从一个pod的容器的用户空间出来送到cni0内核空间在送到另外一个pod的容器的用户空间。
- Veth Pair的Linux的网卡点对点实验实现通信。
# 对于此种模式我们普通Linux中 在之前环境做过
# 创建 namespace
ip netns a ns1
ip netns a ns2# 创建一对 veth-pair veth0 veth1
ip l a veth0 type veth peer name veth1# 将 veth0 veth1 分别加入两个 ns
ip l s veth0 netns ns1
ip l s veth1 netns ns2# 给两个 veth0 veth1 配上 IP 并启用
ip netns exec ns1 ip a a 10.1.1.2/24 dev veth0
ip netns exec ns1 ip l s veth0 upip netns exec ns2 ip a a 10.1.1.3/24 dev veth1
ip netns exec ns2 ip l s veth1 up# veth0 ping veth1
ip netns exec ns1 ping 10.1.1.3
ip netns exec ns1 tcpdump -n -e -i veth0 # 抓包看下mac和ns1、ns2是否相同
ip netns exec ns1 ifconfig
ip netns exec ns2 ifconfig
- 容器内抓包查看数据包的过程。重点看下mac。
# 一个窗口
kubectl exec -it nettools-66b897f8d7-4s8r7 bash
tcpdump -n -e -i eth0
# 另外一个窗口ping 10.244.2.5
- 查看对应的MAC地址和不同ns中的MAC。
# cni0 就相当于交换机
brctl showmacs cni0
# 端口 mac 是否是本地
port no mac addr is local? ageing timer1 22:1e:0e:52:04:13 yes 0.001 22:1e:0e:52:04:13 yes 0.001 56:84:3b:af:c5:43 no 0.822 be:51:1d:3e:59:85 yes 0.002 be:51:1d:3e:59:85 yes 0.00
- Veth Pair的Linux的通过网桥模拟cni0交换实现ns通信。
# 此种Bridge模式把相应的peer建立在pod和bridge之间。Linux中实现:
# network topo:
10.1.1.2 10.1.1.3[ns1] [ns2]| |-- [br0] --
#
# 创建ns
ip netns a ns1
ip netns a ns2
# 首先创建 bridge br0
ip l a br0 type bridge
ip l s br0 up # 然后创建两对 veth-pair
ip l a veth0 type veth peer name br-veth0
ip l a veth1 type veth peer name br-veth1# 分别将两对 veth-pair 加入两个 ns 和 br0
ip l s veth0 netns ns1
ip l s br-veth0 master br0
ip l s br-veth0 upip l s veth1 netns ns2
ip l s br-veth1 master br0
ip l s br-veth1 up# 给两个 ns 中的 veth 配置 IP 并启用
ip netns exec ns1 ip a a 10.1.1.2/24 dev veth0
ip netns exec ns1 ip l s veth0 up
ip netns exec ns2 ip a a 10.1.1.3/24 dev veth1
ip netns exec ns2 ip l s veth1 up
# ping 测:
ip netns exec ns1 ping 10.1.1.3
brctl show
ip netns exec ns1 arp -n
# 注意一:容器以特权模式运行是可以修改内核参数的,而它们和宿主机共用一个内核。这就是很多容器需要以特权模式运行的原因。
# 注意二: 因为同节点不同Pod之间的通信走二层(只涉及mac层面的交换),暂不涉及三层路由相关原理。
# 注意三: 查看和清除arp
ip netns exec ns1 arp -n
ip netns exec ns1 arp -d 10.1.2.2
2.4 不同节点不同pod内的通信
- TAP And TUN设备:
- tap/tun 提供了一台主机内用户空间的数据传输机制。它虚拟了一套网络接口,这套接口和物理的接口无任何区别,可以配置 IP,可以路由流量,不同的是,它的流量只在主机内流通。
- 作为网络设备,tap/tun 也需要配套相应的驱动程序才能工作。tap/tun 驱动程序包括两个部分,一个是字符设备驱动,一个是网卡驱动。这两部分驱动程序分工不太一样,字符驱动负责数据包在内核空间和用户空间的传送,网卡驱动负责数据包在 TCP/IP 网络协议栈上的传输和处理。
- tun是三层设备,其封装的外层是IP头。
- tap是二层设备,其封装的外层是以太网帧(frame)头。
- tun是PPP点对点设备,没有MAC地址。
- tap是以太网设备,有MAC地址tap比tun更接近于物理网卡,可以认为,tap设备等价于去掉了硬件功能的物理网卡。
- 如何去区分这样的2层的TAP和3层的TUN设备呢?
- tap/tun 有些许的不同,tun 只操作三层的 IP 包,而 tap 操作二层的以太网帧。
- 在veth pair的实验中,每一个veth peer设备可以看做成一个tap设备,此时处理的时候,其主要是在处理2层的MAC地址层的数据包。
- 其中在Flannel的UDP Mode中的flannel0就是一个TUN设备,
- 通过
ip -d link show flannel0
查看. - 通过抓包可以看出,此时处理的是一个RAW格式数据包,但无2层MAC信息。只有3层的IP信息。
tcpdump -i flannel0 -w flannel.cap
- 通过
- tap/tun 有些许的不同,tun 只操作三层的 IP 包,而 tap 操作二层的以太网帧。
# 当采用UDP模式时,flanneld进程在启动时会通过打开/dev/net/tun的方式生成一个TUN设备,TUN设备可以简单理解为Linux当中提供的一种内核网络与用户空间(应用程序)通信的一种机制,即应用可以通过直接读写tun设备的方式收发RAW IP包。
# 集群中查看flanneld的进程 运行在用户空间
netstat -aulnp | grep flanneld
# 可以看到flannel0它是tun设备
ip -d link show flannel0
- 不同节点不同pod内的通信总体流程图如下。
- 过程如下:
- 第一步:在node1节点中发出 ICMP 请求报文,此时通过pod内的路由表匹配到应该将该 IP 包发送到 node1节点上网关,也就是node1节点的cni0网桥上。
- 第二步:根据宿主机上面的路由规则了,只能匹配到10.244.0.0/16 对应的这条路由规则,这个时候内核将 RAW IP 包发送给flannel0设备。
- 第三步:flannel0 设备它是一个 TUN 设备(Tunnel 设备)。在 Linux 中,TUN 设备是一种工作在三层(Network Layer)的虚拟网络设备,TUN 设备的功能非常简单,即:在操作系统内核和用户应用程序之间传递 IP 包。由于 flannel0 是一个 TUN 设备,发送给 flannel0 接口的 RAW IP 包将被 Flanneld 进程接收到,然后在原有的基础上进行 UDP 封包。
- 第四步:然后发送到node2 节点上的 Flanneld 进行UDP 包解包后得到 RAW IP 包,解包后的 RAW IP 包匹配到node2 节点上的路由规则(10.244.2.0/24),内核将 RAW IP 包发送给 cni0 设备。
- 第五步:cni0 将 IP 包转发给连接在 cni0 网桥上的 pod-b,这样就完成了这个通信过程。
- 还有关键的就是 UDP 封包发送到我们的目标 IP 10.244.2.123 这个容器所在的节点,但是是如何知道这个节点的呢?
- 这个就需要了解一个非常重要的概念子网(Subnet),Flannel 管理的容器网络,一台宿主机上的所有容器,都属于该宿主机被分配的一个子网,比如我们这里的 ydzs-node1 节点的子网是 10.244.1.0/24(10.244.1.1-10.244.1.254),pod-a 的 IP 地址是 10.244.1.236;ydzs-node2 节点的子网是 10.244.2.0/24(10.244.2.1-10.244.2.254),pod-b 的 IP 地址是 10.244.2.123,这些子网信息是当 Flanneld 进程在启动时通过 api-server 保存到 etcd 当中,所以在发送报文时可以通过目的地址 10.244.2.123 匹配到对应的子网是 10.244.2.0/24,这个时候查询 etcd 得到这个子网对应的宿主机的 IP 地址 10.151.30.23,也就是 ydzs-node2 节点。
# 三层数据转发时,源ip和目的ip不会变化。但是源MAC和目的MAC每一跳都会变化。
# 一边ping测 一边抓包 主要看目的MAC的变化
kubectl exec -it nettools-66b897f8d7-4s8r7 -- tcpdump -n -e -i eth0
kubectl exec -it nettools-66b897f8d7-4s8r7 -- ping 10.244.1.8
# 发现此时数据包被发送到当前pod所在节点的cni0这张网卡上,也就意味着数据包到达ROOT NS中.
# 根据路由规则 报文将要从flannel0这个接口发送出去,而flannel0是由flanneld该进程在启动时候创建的一个tun设备:所以看到的数据包是RAP IP Data。
route -n
# 到node节点上出接口flannel0接口上抓包 发现这个包没有mac地址 只有RAW Packeet Data
tcpdump -n -e -i flannel0 -w flannel0.cap
kubectl exec -it nettools-66b897f8d7-4s8r7 -- ping 10.244.1.8
# flanneld把数据包源ip封装为node节点的ens33的ip和mac(默认路由的地址来作为缺省封装的外层地址)
# flanneld在启动时会将该节点的网络信息通过api-server保存到etcd当中,故在发送报文时可以通过查询etcd得到10.244.1.8这个Pod的IP属于的Node。
tcpdump -i ens33 -w ens33.cap # 通过wireshark打开过滤udp的报文 这里已经封装为UDP格式
kubectl exec -it nettools-66b897f8d7-4s8r7 -- ping 10.244.1.8
# 抓到的UDP的包需要哦通过wireshark的解码功能获得原始报文。解析成IPV4
- 我们可以明显看出来数据包是通过 tun 设备从内核态复制到用户态的应用中的,然后再通过用户态复制到内核态,仅一次网络传输就进行了两次用户态和内核态的切换,显然这种效率是不会很高的,由于低效率所以这种方式基本上不是呀,要提高效率最简单的方式就是把封包解包这些事情都交给内核去干好了,事实上 Linux 内核本身也提供了比较成熟的网络**封包解包(隧道传输)**实现方案
VXLAN
,Flanneld 也实现了基于VXLAN
的方案,该方案在我们日常使用的时候也是最普遍的。
第三节 Flannel的Vxlan模式
3.1 VxLAN基础介绍
-
VXLAN ( Virtual eXtensible Local Area Network,虚拟扩展局域网),是由IETF定义的NVO3 ( Network Virtualization over Layer 3 )标准技术之一,是对传统VLAN协议的一种扩展。VXLAN的特点是将L2的以太帧封装到UDP报文(即L2 over L4 )中,并在L3网络中传输。
-
VXLAN本质上是一种隧道技术,在源网络设备与目的网络设备之间的IP网络上,建立一条逻辑隧道,将用户侧报文经过特定的封装后通过这条隧道转发。从用户的角度来看,接入网络的服务器就像是连接到了一个虚拟的二层交换机的不同端口上(可把蓝色虚框表示的数据中心VXLAN网络看成一个二层虚拟交换机),可以方便地通信。
-
VXLAN已经成为当前构建数据中心的主流技术,是因为它能很好地满足数据中心里虚拟机动态迁移和多租户等需求。
-
VxLAN 数据报文结构.
- 内层报文:通信的虚拟机双方要么直接使用 IP 地址,要么通过 DNS 等方式已经获取了对方的 IP 地址,因此网络层地址已经知道。同一个网络的虚拟机需要通信,还需要知道对方虚拟机的 MAC 地址,vxlan 需要一个机制来实现传统网络 ARP 的功能
- vxlan 头部:只需要知道 VNI,这一般是直接配置在 vtep 上的,要么是提前规划写死的,要么是根据内部报文自动生成的,也不需要担心
- UDP 头部:最重要的是源地址和目的地址的端口,源地址端口是系统生成并管理的,目的端口也是写死的,比如 IANA 规定的 4789 端口,这部分也不需要担心
- IP 头部:IP 头部关心的是 vtep 双方的 IP 地址,源地址可以很简单确定,目的地址是虚拟机所在地址宿主机 vtep 的 IP 地址,这个也需要由某种方式来确定
- MAC 头部:如果 vtep 的 IP 地址确定了,MAC 地址可以通过经典的 ARP 方式来获取,毕竟 vtep 网络在同一个三层,经典网络架构那一套就能直接用了
3.2 VxLAN模型介绍
- 点对点VxLAN实现过程。
# 节点一:
ip link add vxlan0 type vxlan id 5 dstport 4789 remote 172.12.1.12 local 172.12.1.11 dev ens33
ip addr add 10.20.1.2/24 dev vxlan0
ip link set vxlan0 up
# 节点二:
ip link add vxlan0 type vxlan id 5 dstport 4789 remote 172.12.1.11 local 172.12.1.12 dev ens33
ip addr add 10.20.1.3/24 dev vxlan0
ip link set vxlan0 up
# ping测抓包
tcpdump -i vxlan0 -w vxlan0.cap
tcpdump -i ens33 -w ens33.cap
ping 10.1.20.3
- 组播VxLAN实现过程
# muticast vxlan配置:
# 节点一
ip link add vxlan0 type vxlan id 6 dstport 4789 group 239.1.1.1 dev ens33
ip addr add 10.20.1.2/24 dev vxlan0
ip link set vxlan0 up# 节点二
ip link add vxlan0 type vxlan id 6 dstport 4789 group 239.1.1.1 dev ens33
ip addr add 10.20.1.3/24 dev vxlan0
ip link set vxlan0 up# 这里最重要的参数是 group 239.1.1.1 表示把 vtep 加入到这个多播组。关于多播的原理和使用不是这篇文章的重点,这里选择的多播 IP 地址也没有特殊的含义,关于多播的内容可以自行了解。
# 分析这个模式下 vxlan 通信的过程:
ip link add vxlan0 type vxlan id 6 dstport 4789 group 239.1.1.1 dev ens33
# 在配置完成之后,vtep 通过 IGMP 加入同一个多播网络 239.1.1.1。# 1.发送 ping 报文到 10.20.1.3,查看路由表,报文会从 vxlan0 发出去
# 2.内核发现 vxlan0 的 IP 是 10.20.1.2/24,和目的 IP 在同一个网段,所以在同一个局域网,需要知道对方的 MAC 地址,因此会发送 ARP 报文查询
# 3.ARP 报文源 MAC 地址为 vxlan0 的 MAC 地址,目的 MAC 地址为全 1 的广播地址
# 4.vxlan 根据配置(VNI 6)添加上头部
# 5.因为不知道对方 vtep 在哪台主机上,根据配置,vtep 会往多播地址 239.1.1.1 发送多播报文
# 6.多播组中所有的主机都会受到这个报文,内核发现是 vxlan 报文,会根据 VNI 发送给对应的 vtep
# 7.vtep 去掉 vxlan 头部,取出真正的 ARP 请求报文。同时 vtep 会记录 <源 MAC 地址 - vtep 所在主机 IP 地址> 信息到 fdb 表中
# 8.如果发现 ARP 不是发送给自己的,直接丢弃;如果是发送给自己的,则生成 ARP 应答报文
# 9.应答报文目的 MAC 地址是发送方 vtep 的 MAC 地址,而且 vtep 已经通过源报文学习到了 vtep 所在的主机,因此会直接单播发送给目的 vtep。因此 vtep 不需要多播,就能填充所有的头部信息
# 10.应答报文通过 underlay 网络直接返回给发送方主机,发送方主机根据 VNI 把报文转发给 vtep,vtep 解包取出 ARP 应答报文,添加 arp 缓存到内核。并根据报文学习到目的 vtep 所在的主机地址,添加到 fdb 表中
# 11.vtep 已经知道了通信需要的所有信息,后续 ICMP 的 ping 报文都是单播进行的
3.3 Flannel VxLAN模式
- 第一步:我们使用node1上的pod1 去ping node2上的pod2。对于pod1要去的目的地址10.244.1.5 和自己10.244.0.3并不是同一个网段,我们需要根据容器内路由表发送数据包到 cni0。(这里和UDP模式一样的)。
- 第二步:到达 cni0 当中的 IP 包通过匹配节点 node1 当中的路由表发现通往目的容器的 IP 包应该交给 flannel.1 接口。
- 第三步:flannel.1 作为一个 VTEP 设备,收到报文后将按照 VTEP 的配置进行封包,通过node1 节点上的 arp 和转发表得知目的容器ip属于节点 node2,并且会将node2 节点对应的 VTEP 设备的 MAC 地址,根据 flannel.1 设备创建时的设置的参数(VNI、local IP、Port)进行 VXLAN 封包。
- 第四步:通过节点 node2 跟 node1 之间的网络连接,VXLAN 包到达 node2 的 eth0 接口,通过端口 8472,VXLAN 包被转发给 VTEP 设备 flannel.1 进行解包。
- 第五步 :解封装后的 IP 包匹配节点 node2 当中的路由表(10.244.2.0),内核将 IP 包转发给cni0, cni0将 IP 包转发给连接在 cni0 上的 pod-b
# 最后面是- 说明是运行在内核空间的
[root@k8s-master01 ~]# netstat -ulnp | grep 8472
udp 0 0 0.0.0.0:8472 0.0.0.0:* -
# 抓包看一下
tcpdump -n -e -i flannel.1# 内部源mac和目的mac是两个节点上的flannel.1的mac
# 那么对于node1上的这个VTEP flannel.1它现在知道它所在宿主机上的Outer的S_IP和S_MAC。也知道Inner的S_IP和S_MAC 和 D_IP和D_MAC。但是唯独不知道的是D_IP。也就是说不知道10.244.1.0这个地址下的flannel.1在哪一个节点上。
# 在Linux内核里面,网桥设备进行转发的依据来自FDB的转发数据库。这个flannel网桥对应的FDB信息,就是flannel进程维护的。
# 由fdb转发信息,我们可以得出10.244.1.0此vtep所在node
# 然后此时外部的IP信息为:S_IP:172.12.1.11 D_IP:172.12.1.12
# 封装时包通过fbd表获取对端的vtep在哪个节点上 知道对端mac就能知道对端ip
bridge fdb show | grep 72:2a:c9:ed:0a:b3
- VXLAN的核心在于在三层网络的基础上构建了二层网络,使分布在不同节点上的所有容器在这个虚拟二层网络下自由通信。二层虚拟网络通过VXLAN在宿主机上创建的VTEP设备(flannel.1)实现,flannel.1和flanneld一样负责封包解包工作,不同的是flannel.1的封解包对象是二层数据帧,在内核中完成。
3.4 VxLAN的linux模拟实验
# 1.环境配置VxLAN Tunnel:
yum -y install bridge-utils
# 节点一的配置:
# 创建接口vxlan0 type为vxlan,vni为20.
ip link add vxlan_docker type vxlan id 20 remote 172.12.1.12 dstport 4789 dev ens33
ip link set vxlan_docker up
# 使用brctl 添加interface vxlan0 到bridge docker0上,连接docker0和vxlan的接口,这样数据包从docker0上来以后,就可以被vxlan封装了。
brctl addif docker0 vxlan_docker
brctl show# 节点二的配置:
ip link add vxlan_docker type vxlan id 20 remote 172.12.1.11 dstport 4789 dev ens33
ip link set vxlan_docker up
brctl addif docker0 vxlan_docker
brctl show# 在节点一上创建容器:
docker run --name vxlan-c01 -td burlyluo/nettoolbox
# 在节点二上创建容器:
docker run --name vxlan-c01 -td burlyluo/nettoolbox
docker run --name vxlan-c02 -td burlyluo/nettoolbox# ping测 抓包:
docker exec -it vxlan-c01 bash
3.5 物理网络中的VxLAN常用实现
- 看下第八课的实验即可。
第四节 Flannel的其他模式
4.1 Flannel的IPIP模式
- flannel的backends的说明地址:https://github.com/flannel-io/flannel/blob/master/Documentation/backends.md
- 这里有一个字段
DirectRouting
直接路由默认为fasle,*如果主机的节点在同一网段时,设置为true, 使用host-gw用来提供高性能的使用场景。overlay的网络都可以设置这个字段。 - Linux内核转发
IP forwarding
实验演示,看下第二课七八节即可。
net-conf.json: |{"Network": "10.244.0.0/16","Backend": {"Type": "vxlan",# backend:ipip,vxlan,udp,host-gw#"Directrouting": true # host-gw不支持这个选项}}
4. 看看Flannel中又是如何去调用IPIP模块去实现Overlay网络的,主要介绍跨节点通信的原理。
- 数据包从Pod中出来到ROOT NS中, 进入宿主机路由表查询,通过flannel.ipip接口发出到另外主机的flannel.ipip接口。
- 数据包是一个RAW IP Data,所以此时仅仅是做RAW格式数据包转化,并且指导内核做IPIP封装。可以在flannel.ipip接口抓包的看下结果。
- 因为也是内核的通信,封装层数较少,header头部较小,所以一般情况下它比vxlan的性能要优一些。(只是性能上,还有一些还有很多维度的比较)
4.2 Flannel HOST-GW模式
- 手工实现不同host上的不同网段的Docker容器之间互通。
docker run --name c1 -td burlyluo/nettoolbox
docker network create -d bridge --subnet 172.18.0.0/16 br_net18
docker run --name c2 --network br_net18 -td burlyluo/nettoolbox
# 此时做ping测试:
# 在c1上pingc2,此时不通:
# 原因是数据从c1容器出来以后达到172.12.1.11的ROOT NS中以后,此时查看172.12.1.11节点上的路由表:
route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.12.1.2 0.0.0.0 UG 100 0 0 ens33
10.244.0.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.244.1.0 172.12.1.12 255.255.255.0 UG 0 0 0 ens33
172.12.1.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0# 此时没有到172.18.0.0/16该网络的具体路由,所以此时会匹配到默认路由,会被送到网关,网关上可能没有其路由,从而造成该数据报文被丢弃。 # 可通过抓包观察
# 所以我们想要和对端的c2容器172.18.0.2/16互通的话,需要做相应的路由。
# 我们在172.12.1.11节点上需要做172.18.0.0/16的路由:
# route add -h 需要net-tools的库:yum -y install net-tools
# route add -net 172.18.0.0/16 gw 172.12.1.12
route -n ## 此时查看路由表:
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.12.1.2 0.0.0.0 UG 100 0 0 ens33
10.244.0.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.244.1.0 172.12.1.12 255.255.255.0 UG 0 0 0 ens33
172.12.1.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
172.18.0.0 172.12.1.12 255.255.0.0 UG 0 0 0 ens33 # 匹配到这一跳路由
# 这样数据包就能被转发到172.12.1.12节点上,现在我们到172.12.1.12上观察数据包转发过程:
route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.12.1.2 0.0.0.0 UG 100 0 0 ens33
10.244.0.0 172.12.1.11 255.255.255.0 UG 0 0 0 ens33
10.244.1.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
172.12.1.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33
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 br-f858947429bb # 该条路由信息是交换的路由信息。但是此时是走三层,走交换肯定不行。
172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-f858947429bb
# 所以需要手工添加路由信息:
route add -net 172.17.0.0/16 gw 172.12.1.11
route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.12.1.2 0.0.0.0 UG 100 0 0 ens33
10.244.0.0 172.12.1.11 255.255.255.0 UG 0 0 0 ens33
10.244.1.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
172.12.1.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33
172.17.0.0 172.12.1.11 255.255.0.0 UG 0 0 0 ens33 # 此时查询路由表。匹配到这里。下一跳是172.12.1.11.至此数据包回到了节点172.12.1.11上
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 br-f858947429bb
# 此时做ping测试:
# 使用172.12.1.11节点上的c1去ping 172.12.1.12上的c2容器:
docker exec -it c1 bash ## 这里实际上使用到了Linux内核的路由转发功能,所以:需要开启内核转发能力:echo 1 > /proc/sys/net/ipv4/ip_forward
ping 172.18.0.2
- Flannel HOST-GW模式主要是看路由表,会把节点别的节点路由信息加到当前的主机上。这种方式性能是最高的, 但是节点需要维护路由表需要在同一网段。
第五节 Flannel IP 地址管理 (IPAM)
5.1 Flannel IP地址管理
-
搜一下
network-attach-defination k8s
,可以看到一些自定义网络CNI的标准。NAD主要包含用什么CNI和CNI中用什么IPAM这俩个大块。- CNI官方文档:https://www.cni.dev/plugins/current/
-
在电信行业比较好的提供方案的公司,当然也包括网络方案。
https://www.robin.io/
,可以去查一下它的IPAM的文档https://docs.robin.io/
-
它可以让我们了解自己做CNI或者自己做IPAM需要遵循的标准。
https://docs.robin.io/platform/5.3.7/manage_network.html
-
常用CNI的说明地址:https://www.cni.dev/docs/
-
dhcp 的IPAM用到的不多,需要在每个node节点上启动dhcp的client去发dhcp的地址。
-
host-local ip的IPAM地址储存在本地的文件中也会同步给api server。这里
cd /var/lib/cni/networks/cbr0
可以看到ip -
static ip的IPAM需要配置固定的ip,有个固定的访问端点。
# Demo for the CNI Config:
cat 00-multus.conf
{"cniVersion": "0.3.1","kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig","name": "multus-cni-network","type": "multus","confDir": "/etc/cni/net.d","binDir": "/opt/cni/bin","logFile": "/var/log/multus.log","logLevel": "debug","multusNamespace": "kube-system","capabilities": {"portMappings": true},"delegates": [{"cniVersion": "0.3.1","name": "calico","plugins": [{"datastore_type": "kubernetes", # this parameter.||||||kubernetes or etcd's address."ipam": {"type": "whereabouts"},"kubernetes": {"kubeconfig": "/etc/cni/net.d/calico-kubeconfig"},"log_level": "info","mtu": 1500,"nodename": "k8s-1","policy": {"type": "k8s"},"type": "calico"},{ "type": "tuning", "sysctl": { "net.ipv6.conf.all.accept_ra": "0", "net.ipv6.conf.default.accept_ra": "0", "net.ipv6.conf.lo.accept_ra": "0", "net.ipv6.conf.all.accept_dad": "0", "net.ipv6.conf.default.accept_dad": "0", "net.ipv6.conf.all.ndisc_notify": "1", "net.ipv6.conf.default.ndisc_notify": "1" } }, {"capabilities": {"portMappings": true},"snat": true,"type": "portmap"}]}]
}
- 一些个CNI格式的CNI定义的字段的说明:github.com/containernetworking/cni/blob/master/SPEC.md
ipMasq
: 做地址伪装的ipam
: 字典包含很多字段
- 模拟ipam产生ip的过程。
cd /opt/cni/bin/
# 模拟ipam的过程
echo '{ "cniVersion": "0.3.1", "name": "examplenet", "ipam": { "type": "host-local", "ranges": [ [{"subnet": "203.0.113.0/24"}], [{"subnet": "2001:db8:1::/64"}]], "dataDir": "/tmp/cni-example" } }' | CNI_COMMAND=ADD CNI_CONTAINERID=example CNI_NETNS=/dev/null CNI_IFNAME=dummy0 CNI_PATH=. ./host-local
- 上面用到了whereabouts 的IPAM
- git地址: github.com/k8snetworkplumbingwg/whereabouts
- 说明文档:dougbtv.com/nfvpe/2019/11/27/whereabouts-a-cluster-wide-cni-ipam-plugin/
- 把地址存在etcd中,避免了地址冲突的问题。
- pod中默认只有一个网卡,可以使用多网卡的cni实现pod内多网卡。
- multus-cni: github.com/k8snetworkplumbingwg/multus-cni/blob/master/docs/how-to-use.md
- 一般情况下会用到ipvlan和macvlan的技术:cizixs.com/2017/02/17/network-virtualization-ipvlan/
5.2 Flannel启动过程分析
# Flannel 过程分析:
# 0.Flanneld启动的时候,Flannel会拉取pod的CIDR,from apiServer。然后写在subnet.env 文件中:
cat /run/flannel/subnet.env # 1.此时Kubernetes调用container runtime通过CRI plugin# 2.然后CRI plugin创建network namespace,然后依据CNI 的config file调用CNI plugin。
# CNI config file 为:
cat /etc/cni/net.d/10-flannel.conflist # network namespace 放在目录: /var/run/docker/netns/下:
cd /var/run/docker/netns
ls
docker ps
# # 我们可以通过次查看各自Pod所在的NS。实际上是pause的网络的命名空间 共享的
for i in $(docker ps |awk -F " " '{print $1}'|grep -v CONTAINER);do echo $i;docker inspect $i|grep SandboxKey;done # 3.Flannel CNI Plugin配置和调用Bridge CNI plugin,从这里我们可以看出,Flannel实际上设计了一个二层调用,去调用下层的Bridge。
# cat /var/lib/cni/flannel/b75233adc93b128d2d49ec0ed6e1919825f5c0062251d88aac3bad129e72df33 | python -m json.tool
# 查看该调用Bridge的详情:
{"cniVersion": "0.3.1","hairpinMode": true, "ipMasq": false, "ipam": {"routes": [{"dst": "10.244.0.0/16" # 路由信息,到10.244.0.0/16的目的网络}],"subnet": "10.244.1.0/24", # 使用的子网,采用的每一个节点上有一个单独的24位掩码的子网。"type": "host-local" # 使用的IPAM的类型。通常cni.dev中介绍有三种:1.static。2.dhcp。3.host-local},"isDefaultGateway": true, # 是否是默认路由。"isGateway": true, "mtu": 1500, # 本环境是host-gw的环境,所以mtu是1500,在overlay的网络中,此时应该是小于1500的。比如1450"name": "cbr0", # name:实际上我们通常使用的是cni0"type": "bridge" # type
}
# 4.然后Bridge Plugin会按照上边的配置文件创建cni0的bridge。此时会创建veth pair,一段在Pod所在的ns,一端在cni0上,然后调用host-local 的IPAM。# 5.host-local的IPAM形式:
"ipam": {"routes": [{"dst": "10.244.0.0/16" # 路由信息,到10.244.0.0/16的目的网络}],"subnet": "10.244.1.0/24", # 使用的子网,采用的每一个节点上有一个单独的24位掩码的子网。"type": "host-local" # 使用的IPAM的类型。通常cni.dev中介绍有三种:1.static。2.dhcp。3.host-local},# host-local的IPAM此时如果有Pod创建的话,会返回一个ip地址,和gateway信息给到Pod中。
# IP地址信息以文件的形式存储在本地的文件中:
cd /var/lib/cni/nmworks/cni0/
ls
这篇关于第九课 k8s网络CNI插件学习-Flannel网络插件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!