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,无法从跨宿主机通信,所以容器的网络互通,需要承载到宿主机的网络之上。

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地址
第二步配置集群网络
curl -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
[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,文件内容如下:
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所关联,
[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封包转发呢?这是每个机器上面路由设定的。
#ip route show dev flannel.
10.254.50.0/ via 10.254.50.0 onlink
10.254.60.0/ via 10.254.60.0 onlink
可以看到每个主机上面都有到另外两台机器的路由,这个路由是onlink路由,onlink参数表明强制此网关是“在链路上”的(虽然并没有链路层路由),否则linux上面是没法添加不同网段的路由。这样数据包就能知道,如果是容器直接的访问则交给flannel.1设备处理。
flannel.1这个虚拟网络设备将会对数据封包,但下面一个问题又来了,这个网关的mac地址是多少呢?因为这个网关是通过onlink设置的,flannel会下发这个mac地址,查看一下arp表
# ip neig show dev flannel.
10.254.50.0 lladdr ba::0e:7b:: PERMANENT
10.254.60.0 lladdr :f3:c8:b2:6e:f0 PERMANENT
可以看到这个网关对应的mac地址,这样内层的数据包就封装好了
还是最后一个问题,外出的数据包的目的IP是多少呢?换句话说,这个封装后的数据包应该发往那一台机器呢?难不成每个数据包都广播。vxlan默认实现第一次确实是通过广播的方式,但flannel再次采用一种hack方式直接下发了这个转发表FDB
# bridge fdb show dev flannel.
:f3:c8:b2:6e:f0 dst 10.100.139.246 self permanent
ba::0e:7b:: 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征询,如果尝试
/proc/sys/net/ipv4/neigh/$NIC/ucast_solicit
此时后会向用户空间发送arp征询
/proc/sys/net/ipv4/neigh/$NIC/app_solicit
之前版本的flannel正是利用这个特性,设定
# cat /proc/sys/net/ipv4/neigh/flannel./app_solicit
从而flanneld便可以获取到内核发送到用户空间的L3MISS,并且配合etcd返回这个IP地址对应的mac地址,设置为reachable。从分析可以看出,如果flanneld程序如果退出后,容器之间的通信将会中断,这里需要注意。Flannel的启动流程如下图所示:

Flannel启动执行newSubnetManager,通过他创建后台数据存储,当前有支持两种后端,默认是etcd存储,如果flannel启动指定“kube-subnet-mgr”参数则使用kubernetes的接口存储数据。
具体代码如下:
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
func init() {
backend.Register("vxlan", New)
如果是udp
func init() {
backend.Register("udp", New)
}
其它也是类似,将构建方法都注册到一个map里面,从而根据etcd配置的网络模式,设定启用对应的网络管理器。
3、注册网络
RegisterNetwork,首先会创建flannel.vxlanID的网卡,默认vxlanID是1.然后就是向etcd注册租约并且获取相应的网段信息,这样有个细节,老版的flannel每次启动都是去获取新的网段,新版的flannel会遍历etcd里面已经注册的etcd信息,从而获取之前分配的网段,继续使用。
最后通过WriteSubnetFile写本地子网文件,
# cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.254.0.0/
FLANNEL_SUBNET=10.254.44.1/
FLANNEL_MTU=
FLANNEL_IPMASQ=true
通过这个文件设定docker的网络。细心的读者可能发现这里的MTU并不是以太网规定的1500,这是因为外层的vxlan封包还要占据50 Byte。
当然flannel启动后还需要持续的watch etcd里面的数据,这是当有新的flannel节点加入,或者变更的时候,其他flannel节点能够动态更新的那三张表。主要的处理方法都在handleSubnetEvents里面
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().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().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里面任何主机的添加和删除都可以被其它节点所感知到,从而更新本地内核转发表。
作者:陈晓宇
来源:宜信技术学院
Kubernetes网络分析之Flannel的更多相关文章
- 手把手教你构建 Kubernetes 1.8 + Flannel 网络(一)
一.环境说明 操作系统:CentOS7 Kubernetes版本:v1.8.4 Docker版本:v17.06-ce Flannel 版本: flannel-v0.9.1 二.Ntp 服务器配置 ...
- Kubernetes网络方案 Flannel和calico
摘抄某博客 1. Flannel Flannel是为kubernetes设计的一个非常简洁的多节点三层网络方案,解决不同host上的容器互联问题,原理是为每个host分配一个subnet,容器从此 ...
- 007.Kubernetes二进制部署Flannel
一 部署flannel 1.1 安装flannel kubernetes 要求集群内各节点(包括 master 节点)能通过 Pod 网段互联互通.flannel 使用 vxlan 技术为各节点创建一 ...
- kubernetes网络之Flannel
简介 Flannel是CoreOS团队针对Kubernetes设计的一个网络规划服务,简单来说,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址. 在默认的Dock ...
- Kubernetes网络之Flannel工作原理
目录 1.Docker网络模式 1.1 bridge网络的构建过程 1.2 外部访问 2.Kubernetes网络模式 2.1 同一个Pod中容器之间的通信 2.2 不同Pod中容器之间的通信 2.3 ...
- Kubernetes网络插件Flannel的三种工作模式
跨主机通信的一个解决方案是Flannel,由CoreOS推出,支持3种实现:UDP.VXLAN.host-gw 一.UDP模式(性能差) 核心就是通过TUN设备flannel0实现(TUN设备是工作在 ...
- kubernets controller 和 CRD的扩展
sample git repo 各个组件开发指导 operator 介绍 此图来自谷歌员工的实践介绍 client-go的使用和源码分析 (dlv) p pods *k8s.io/api/core/v ...
- kubernetes 内网节点部署笔记(一)
在Centos7上部署kubernetes时,碰到很多坑,特别在摸拟在内网部署时,有来自GFW的障碍,有来自Firewalld的阻塞,反正是各种不服,终于慢慢理顺了思路,自己记录一下,防止遗忘. 环境 ...
- Ubuntu上手动安装Kubernetes
背景 两台Ubuntu16.04服务器:ip分别为192.168.56.160和192.168.56.161.. Kubernetes版本:1.5.5 Docker版本:1.12.6 etcd版本:2 ...
随机推荐
- Django之FBV和CBV的用法
FBV FBV,即 func base views,函数视图,在视图里使用函数处理请求. 以用户注册代码为例, 使用两个函数完成注册 初级注册代码 def register(request): &qu ...
- Git学习-安装与创建本地仓库
前言 以前公司进行项目的版本管理都是用svn,最近公司推荐用git,把svn给关闭了.没办法,拿来主义.网上一番猛操作,勉强也能用了,给自己做个笔记. git安装 因为我用的是windos系统,在Wi ...
- 用Python分析2000款避孕套,得出这些有趣的结论
到现在为止,我们的淘宝教程已经写到了第四篇,前三篇分别是: 第一篇:Python模拟登录淘宝,详细讲解如何使用requests库登录淘宝pc端. 第二篇:淘宝自动登录2.0,新增Cookies序列化, ...
- Docker Compose基本使用-使用Compose启动Tomcat为例
场景 Docker-Compose简介与Ubuntu Server 上安装Compose: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/deta ...
- Linux防火墙管理
1.临时关闭防火墙 systemctl stop firewalld 2.查看防火墙运行状态 firewall-cmd --state 3.开启防火墙 systemctl start firewall ...
- 第一次作业:使用Packet Tracer分析HTTP包
0 个人信息 张樱姿 201821121038 计算1812 1 实验目的 熟练使用Packet Tracer工具.分析抓到的HTTP数据包,深入理解:HTTP协议,包括语法.语义.时序. 2 实验内 ...
- DirectX12 3D 游戏开发与实战第二章内容
矩阵代数 学习目标 理解矩阵及其相关运算的定义 探究为何能把向量和矩阵的乘法视为一种线性组合 学习单位矩阵.转置矩阵.行列式以及矩阵的逆等概念 逐步熟悉DirectXMath库中提供的关于矩阵计算的类 ...
- PHP的跨域问题
服务端的代码 public function test(){ header("Access-Control-Allow-Origin: http://cnblogs.com"); ...
- Java中自定义注解类,并加以运用
在Java框架中,经常会使用注解,而且还可以省很多事,来了解下自定义注解. 注解是一种能被添加到java代码中的元数据,类.方法.变量.参数和包都可以用注解来修饰.注解对于它所修饰的代码并没有直接的影 ...
- java架构之路-(mysql底层原理)Mysql事务隔离与MVCC
上几篇博客我们大致讲了一下mysql的底层结构,什么B+tree,什么Hash需要回行啊,再就是讲了mysql优化的explain,这次我们来说说mysql的锁. mysql锁 锁从性能上分为乐观锁( ...