FinOps有望降低企业50%+的云成本! 了解详情
写点什么

Kubernetes 网络分析之 Flannel

  • 2020-02-13
  • 本文字数:0 字

    阅读完需:约 1 分钟

Kubernetes网络分析之Flannel

Flannel 是 cereos 开源的 CNI 网络插件,下图 flannel 官网提供的一个数据包经过封包、传输以及拆包的示意图,从这个图片里面里面可以看出两台机器的 docker0 分别处于不同的段:10.1.20.1/24 和 10.1.15.1/24 ,如果从 Web App Frontend1 pod(10.1.15.2)去连接另一台主机上的 Backend Service2 pod(10.1.20.3),网络包从宿主机 192.168.0.100 发往 192.168.0.200,内层容器的数据包被封装到宿主机的 UDP 里面,并且在外层包装了宿主机的 IP 和 mac 地址。这就是一个经典的 overlay 网络,因为容器的 IP 是一个内部 IP,无法从跨宿主机通信,所以容器的网络互通,需要承载到宿主机的网络之上。


1538040889434021198.png


flannel 的支持多种网络模式,常用用都是 vxlan、UDP、hostgw、ipip 以及 gce 和阿里云等,vxlan 和 UDP 的区别是 vxlan 是内核封包,而 UDP 是 flanneld 用户态程序封包,所以 UDP 的方式性能会稍差;hostgw 模式是一种主机网关模式,容器到另外一个主机上容器的网关设置成所在主机的网卡地址,这个和 calico 非常相似,只不过 calico 是通过 BGP 声明,而 hostgw 是通过中心的 etcd 分发,所以 hostgw 是直连模式,不需要通过 overlay 封包和拆包,性能比较高,但 hostgw 模式最大的缺点是必须是在一个二层网络中,毕竟下一跳的路由需要在邻居表中,否则无法通行。


在实际的生产环境总,最常用的还是 vxlan 模式,我们先看工作原理,然后通过源码解析实现过程。


安装的过程非常简单,主要分为两步:


第一步安装 flannel,


yum install flannel 或者通过 kubernetes 的 daemonset 方式启动,配置 flannel 用的 etcd 地址


第二步是配置集群网络,


col 1col 2
1curl -L http:``//etcdurl:2379/v2/keys/flannel/network/config -XPUT -d value="{\"Network\":\"172.16.0.0/16\",\"SubnetLen\":24,\"Backend\":{\"Type\":\"vxlan\",\"VNI\":1}}"


然后启动每个节点的flanned程序。  
复制代码


工作原理:


1、容器的地址如何分配:


Docker 容器启动时候通过 docker0 分配 IP 地址,flannel 为每个机器分配一个 IP 段,配置在 docker0 上面,容器启动后就在本段内选择一个未占用的 IP,那么 flannel 如何修改 docker0 网段的呢?


先看一下 flannel 的启动文件 /usr/lib/systemd/system/flanneld.service


col 1col 2


1


2


3


4


5 | [Service]


Type=notify


EnvironmentFile=/etc/sysconfig/flanneld


ExecStart=/usr/bin/flanneld-start $FLANNEL_OPTIONS


ExecStartPost=/opt/flannel/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/docker


文件里面指定了 flannel 环境变量和启动脚本和启动后执行脚本 ExecStartPost 设置的 mk-docker-opts.sh,这个脚本的作用是生成/run/flannel/docker,文件内容如下:


col 1col 2


1


2


3


4 | DOCKER_OPT_BIP=``"--bip=10.251.81.1/24"


DOCKER_OPT_IPMASQ=``"--ip-masq=false"


DOCKER_OPT_MTU=``"--mtu=1450"


DOCKER_NETWORK_OPTIONS=``" --bip=10.251.81.1/24 --ip-masq=false --mtu=1450"


而这个文件又被 docker 启动文件/usr/lib/systemd/system/docker.service 所关联,


col 1col 2


1


2


3


4


5 | [Service]


Type=notify


NotifyAccess=all


EnvironmentFile=-/run/flannel/docker


EnvironmentFile=-/etc/sysconfig/docker


这样便可以设置 docker0 的网桥了。


在开发环境中,有三台机器,分别分配了如下网段:


host-139.245 10.254.44.1/24


host-139.246 10.254.60.1/24


host-139.247 10.254.50.1/24


2、容器如何通信


上面介绍了为每个容器分配 IP,那么不同主机上面的容器如何通信呢,我们用最常见的 vxlan 举例,这里有三个关键点,一个路由,一个 arp,一个 FDB。我们按照容器发包的过程,逐一分析一下上面三个元素的作用,首先容器出来的数据包会经过 docker0,那么下面是直接从主机网络出去,还是通过 vxlan 封包转发呢?这是每个机器上面路由设定的,


col 1col 2


1


2


3 | ``#ip route show dev flannel.1


10.254.50.0/24 via 10.254.50.0 onlink


10.254.60.0/24 via 10.254.60.0 onlink


可以看到每个主机上面都有到另外两台机器的路由,这个路由是 onlink 路由,onlink 参数表明强制此网关是“在链路上”的(虽然并没有链路层路由),否则 linux 上面是没法添加不同网段的路由。这样数据包就能知道,如果是容器直接的访问则交给 flannel.1 设备处理。


flannel.1 这个虚拟网络设备将会对数据封包,但下面一个问题又来了,这个网关的 mac 地址是多少呢?因为这个网关是通过 onlink 设置的,flannel 会下发这个 mac 地址,查看一下 arp 表


col 1col 2


1


2


3 | # ip neig show dev flannel.1


10.254.50.0 lladdr ba:10:0e:7b:74:89 PERMANENT


10.254.60.0 lladdr 92:f3:c8:b2:6e:f0 PERMANENT


可以看到这个网关对应的 mac 地址,这样内层的数据包就封装好了


还是最后一个问题,外出的数据包的目的 IP 是多少呢?换句话说,这个封装后的数据包应该发往那一台机器呢?难不成每个数据包都广播。vxlan 默认实现第一次确实是通过广播的方式,但 flannel 再次采用一种 hack 方式直接下发了这个转发表 FDB


col 1col 2


1


2


3 | # bridge fdb show dev flannel.1


92:f3:c8:b2:6e:f0 dst 10.100.139.246 self permanent


ba:10:0e:7b:74:89 dst 10.100.139.247 self permanent


这样对应 mac 地址转发目标 IP 便可以获取到了。


这里还有个地方需要注意,无论是 arp 表还是 FDB 表都是 permanent,它表明写记录是手动维护的,传统的 arp 获取邻居的方式是通过广播获取,如果收到对端的 arp 相应则会标记对端为 reachable,在超过 reachable 设定时间后,如果发现对端失效会标记为 stale,之后会转入的 delay 以及 probe 进入探测的状态,如果探测失败会标记为 Failed 状态。之所以介绍 arp 的基础内容,是因为老版本的 flannel 并非使用我上面的方式,而是采用一种临时的 arp 方案,此时下发的 arp 表示 reachable 状态,这就意味着,如果在 flannel 宕机超过 reachable 超时时间的话,那么这台机器上面的容器的网络将会中断,我们简单回顾试一下之前(0.7.x)版本的做法,容器为了为了能够获取到对端 arp 地址,内核会首先发送 arp 征询,如果尝试


col 1col 2
1/proc/sys/net/ipv4/neigh/$NIC/ucast_solicit


此时后会向用户空间发送 arp 征询


col 1col 2
1/proc/sys/net/ipv4/neigh/$NIC/app_solicit


之前版本的 flannel 正是利用这个特性,设定


col 1col 2


1


2 | # cat /proc/sys/net/ipv4/neigh/flannel.1/app_solicit


3


从而 flanneld 便可以获取到内核发送到用户空间的 L3MISS,并且配合 etcd 返回这个 IP 地址对应的 mac 地址,设置为 reachable。从分析可以看出,如果 flanneld 程序如果退出后,容器之间的通信将会中断,这里需要注意。Flannel 的启动流程如下图所示:


1538041061165425030.png


Flannel 启动执行 newSubnetManager,通过他创建后台数据存储,当前有支持两种后端,默认是 etcd 存储,如果 flannel 启动指定“kube-subnet-mgr”参数则使用 kubernetes 的接口存储数据。


具体代码如下:


col 1col 2


1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20 | func newSubnetManager() (subnet.Manager, error) {


``if opts.kubeSubnetMgr {


``return kube.NewSubnetManager(opts.kubeApiUrl, opts.kubeConfigFile)


``}


``cfg := &etcdv2.EtcdConfig{


``Endpoints: strings.Split(opts.etcdEndpoints, ``","``),


``Keyfile: opts.etcdKeyfile,


``Certfile: opts.etcdCertfile,


``CAFile: opts.etcdCAFile,


``Prefix: opts.etcdPrefix,


``Username: opts.etcdUsername,


``Password: opts.etcdPassword,


``}


``// Attempt to renew the lease for the subnet specified in the subnetFile


``prevSubnet := ReadCIDRFromSubnetFile(opts.subnetFile, ``"FLANNEL_SUBNET"``)


``return etcdv2.NewLocalManager(cfg, prevSubnet)


``}


通过 SubnetManager,结合上面介绍部署的时候配置的 etcd 的数据,可以获得网络配置信息,主要指 backend 和网段信息,如果是 vxlan,通过 NewManager 创建对应的网络管理器,这里用到简单工程模式,首先每种网络模式管理器都会通过 init 初始化注册,


如 vxlan


col 1col 2


1


2 | func init() {


``backend.Register(``"vxlan"``, New)


如果是 udp


col 1col 2


1


2


3 | ``func init() {


``backend.Register(``"udp"``, New)


``}


其它也是类似,将构建方法都注册到一个 map 里面,从而根据 etcd 配置的网络模式,设定启用对应的网络管理器。


第三步是注册网络


RegisterNetwork,首先会创建 flannel.vxlanID 的网卡,默认 vxlanID 是 1.然后就是向 etcd 注册租约并且获取相应的网段信息,这样有个细节,老版的 flannel 每次启动都是去获取新的网段,新版的 flannel 会遍历 etcd 里面已经注册的 etcd 信息,从而获取之前分配的网段,继续使用。


最后通过 WriteSubnetFile 写本地子网文件,


col 1col 2


1


2


3


4


5 | ``# cat /run/flannel/subnet.env


FLANNEL_NETWORK=10.254.0.0/16


FLANNEL_SUBNET=10.254.44.1/24


FLANNEL_MTU=1450


FLANNEL_IPMASQ=``true


通过这个文件设定 docker 的网络。细心的读者可能发现这里的 MTU 并不是以太网规定的 1500,这是因为外层的 vxlan 封包还要占据 50 Byte。


当然 flannel 启动后还需要持续的 watch etcd 里面的数据,这是当有新的 flannel 节点加入,或者变更的时候,其他 flannel 节点能够动态更新的那三张表。主要的处理方法都在 handleSubnetEvents 里面


col 1col 2


1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27


28


29


30


31


32


33


34


35


36


37


38


39


40


41


42


43


44


45


46


47


48


49


50


51 | ``func (nw *network) handleSubnetEvents(batch []subnet.Event) {


``. . .


``switch event.Type {``//如果是有新的网段加入(新的主机加入)


``case subnet.EventAdded:


``. . .``//更新路由表


if err := netlink.RouteReplace(&directRoute); err != nil {


``log``.Errorf(``"Error adding route to %v via %v: %v"``, sn, attrs.PublicIP, err)


``continue


``}


//添加arp表


log``.V(2).Infof(``"adding subnet: %s PublicIP: %s VtepMAC: %s"``, sn, attrs.PublicIP, net.HardwareAddr(vxlanAttrs.VtepMAC))


``if err := nw.dev.AddARP(neighbor{IP: sn.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {


``log``.Error(``"AddARP failed: "``, err)


``continue


``}


``//添加FDB表


``if err := nw.dev.AddFDB(neighbor{IP: attrs.PublicIP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {


``log``.Error(``"AddFDB failed: "``, err)


``if err := nw.dev.DelARP(neighbor{IP: event.Lease.Subnet.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {


``log``.Error(``"DelARP failed: "``, err)


``}


``continue


``}``//如果是删除实践


``case subnet.EventRemoved:


//删除路由


``if err := netlink.RouteDel(&directRoute); err != nil {


``log``.Errorf(``"Error deleting route to %v via %v: %v"``, sn, attrs.PublicIP, err)


``} ``else {


``log``.V(2).Infof(``"removing subnet: %s PublicIP: %s VtepMAC: %s"``, sn, attrs.PublicIP, net.HardwareAddr(vxlanAttrs.VtepMAC))


``//删除arp if err := nw.dev.DelARP(neighbor{IP: sn.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {


``log``.Error(``"DelARP failed: "``, err)


``}


``//删除FDB


``if err := nw.dev.DelFDB(neighbor{IP: attrs.PublicIP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil {


``log``.Error(``"DelFDB failed: "``, err)


``}


``if err := netlink.RouteDel(&vxlanRoute); err != nil {


``log``.Errorf(``"failed to delete vxlanRoute (%s -> %s): %v"``, vxlanRoute.Dst, vxlanRoute.Gw, err)


``}


``}


``default``:


``log``.Error(``"internal error: unknown event type: "``, ``int``(event.Type))


``}


``}


``}


这样 flannel 里面任何主机的添加和删除都可以被其它节点所感知到,从而更新本地内核转发表。


本文转载自宜信技术学院网站。


原文链接:http://college.creditease.cn/detail/176


2020-02-13 21:48639

评论

发布
暂无评论
发现更多内容

【Flutter 专题】90 图解 Dart 单线程实现异步处理之 Future (一)

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 7月日更

领域驱动设计到底在讲什么?

escray

学习 极客时间 7月日更 如何落地业务建模

Kats-Facebook最新开源的时序分析工具

好孩子

Javascript 的工作原理:引擎、运行时和调用堆栈概述

devpoint

JavaScript V8 7月日更

🏆【CI/CD技术专题】「Maven插件Docker系列」使用Maven插件构建Docker镜像的方法

洛神灬殇

Docker maven 7月日更 Plugin

Hadoop 入门教程

若尘

大数据 hadoop

公司内部使用的数仓开发规范

白程序员的自习室

数据仓库 开发规范 数仓规范 7月日更

暑假期间快手将重点整治平台:短视频平台如何完善内容审核机制

石头IT视角

PowerShell 正则表达式

耳东@Erdong

PowerShell 7月日更

你以为的你以为未必是你以为的

Bruce Talk

敏捷 随笔 Agile 引导和教练

模块一作业

Always

架构实战营

Apache Flink 漫谈系列 —— 概述

云祁

flink 7月日更

性能框架哪家强—JMeter、K6、locust、FunTester横向对比

FunTester

性能测试 接口测试 测试框架 测试开发

网络攻防学习笔记 Day70

穿过生命散发芬芳

网络攻防 7月日更

我为什么要学习业务建模?

escray

学习 极客时间 7月日更 如何落地业务建模

从明天起开始认真更新了

IT蜗壳-Tango

7月日更

架构训练营模块 1 作业 - 1班助教

听闻

【LeetCode】基于时间的键值存储Java题解

Albert

算法 LeetCode 7月日更

Go 学习笔记之 结构体

架构精进之路

Go 语言 7月日更

网络攻防学习笔记 Day71

穿过生命散发芬芳

网络攻防 7月日更

Redis - Cluster - 源码阅读(二)

旺仔大菜包

redis

全面了解Java并发编程基础!超详细!

程序员的时光

Java 并发编程

只更新代码,然后发布版本:基于 Serverless Devs 原子化操作阿里云函数计算

Serverless Devs

Linux之find命令的参数详解

入门小站

Linux

密码你真的了解吗

卢卡多多

7月日更

京东智造云:在世界人工智能大会上,听到的工业智能生长的声音

脑极体

实时音视频技术全栈攻略|寻找C站宝藏

liuzhen007

音视频 7月日更

性能测试框架对比初探

FunTester

性能测试 接口测试 测试框架 测试开发

模块八作业

Presley

架构实战营 模块八课后作业

iProcess

架构实战营

在线脑图思维导图生成工具

入门小站

工具

  • 需要帮助,请添加网站小助手,进入 InfoQ 技术交流群
Kubernetes网络分析之Flannel_文化 & 方法_陈晓宇_InfoQ精选文章