Flannel源码解读
Table of Contents
版本
flannel-0.7.1
源码目录说明
1 | ├── backend/ # 后端实现,目前支持 udp、vxlan、hostgw 等 |
flannel可以为容器提供网络服务。
其模型为全部的容器使用一个network,然后在每个host上从network中划分一个子网subnet。
为host上的容器创建网络时,从subnet中划分一个ip给容器。
其采用目前比较流行的no server的方式,即不存在所谓的控制节点,而是每个host上的flanneld从一个etcd中获取相关数据,然后声明自己的子网网段,并记录在etcd中。
其他的host对数据转发时,从etcd中查询到该子网所在的host的ip,然后将数据发往对应host上的flanneld,交由其进行转发。
根据kubernetes的模型,即为每个pod提供一个ip。
flannel的模型正好与之契合。
三大核心概念
- network 负责网络的管理(以后的方向是多网络模型,一个主机上同时存在多种网络模式),根据每个网络的配置调用 subnet;
- subnet 负责和 etcd 交互,把 etcd 中的信息转换为 flannel 的子网数据结构,并对 etcd 进行子网和网络的监听;
- backend 接受 subnet 的监听事件,负责增删相应的路由规则
main()函数
- 新建一个SubnetManager sm,负责与etcd交互
- 新建一个NetworkManager nm,并执行其run(),负责调用SubnetManager sm
1 | func main() { |
NetworkManager
NetworkManager 负责网络的管理,根据 etcd 中的网络配置host主机的网络信息,其中的bm backend.Manager是其核心部分。
1 | type Manager struct { |
实例化一个NetworkManager
1 | func NewNetworkManager(ctx context.Context, sm subnet.Manager) (*Manager, error) { |
NetworkManager.Run()
对于每一个网络n,启动一个groutine执行 func (m * Manager) runNetwork(n * Network)
见/flannel-0.7.1/network/manager.go
1 | func (m *Manager) Run(ctx context.Context) { |
看func (m * Manager) runNetwork
1 | func (m *Manager) runNetwork(n *Network) { |
runOnce()
func (n * Network) runOnce 是NetworkManager中最核心的函数,其核心逻辑如下:
- 读取 etcd 中的值,根据获得的值做一些初始化的工作,retryInit()
- 调用 backend 的 Run 函数,让 backend 在后台运行,n.bn.Run(ctx)
- 监听子网在 etcd 中的变化,执行对应的操作
1 | func (n *Network) runOnce(extIface *backend.ExternalInterface, inited func(bn backend.Network)) error { |
- retryInit()
1 | func (n *Network) retryInit() error { |
init()函数中需要注意的是be.RegisterNetwork(n.ctx, n.Name, n.Config)
subnet分配子网
分配子网的核心在/subnet/local_manager.go的 AcquireLease()。
会通过各个backend网络的RegisterNetwork()函数进行调用。
1 | func (m *LocalManager) AcquireLease(ctx context.Context, network string, attrs *LeaseAttrs) (*Lease, error) { |
运行真正的网络插件backend
目前支持的backend类型有allpc,awsvpc,gce,hostgw,udp和vxlan。
从上面可以知道在NetworkManager的函数runOnce()中会运行真正的网络插件backend,以hostgw为例子,见/backend/hostgw/network.go
hostgw 模式下,每台主机上的 flannel 只负责路由表的维护就行了,当发现 etcd 中有节点信息变化的时候就随时更新自己主机的路由表项。
func (n * network) Run总结:
- 监听 etcd 中的子网信息,有event发生的时候调用对应的处理函数。
- 处理函数handleSubnetEvents()只负责两种事件:子网被添加和子网被删除
1 | func (n *network) Run(ctx context.Context) { |
处理函数handleSubnetEvents()
func (n * network) handleSubnetEvents的注意点:为了容错,添加和删除路由出错的时候只是记一条 log,然后就跳过。
在极端的情况下,会出现本地的路由表项和 etcd 中数据不一致的情况
触发添加或删除的具体逻辑如下:
- EventAdded:
- 先是创建一个路由对象,查找本地路由比对,
- 如果目的地址一样但网关不一样,删了重建
- 如果已经有重复的则不做任何操作
- 如果没有这条路由则通过RouteAdd添加到本地路由表中并通过addToRouteList写到本地缓存中
- EventRemoved:
- 直接删除本地路由表和本地缓存
1 | func (n *network) handleSubnetEvents(batch []subnet.Event) { |
backend中的公共interface定义
见/backend/common.go
1 | /* |
运行flannel
- flanneld网络使用etcd来保证一致性,所以需要先配置etcd集群
- 设置网段
1 | # etcdctl set /coreos.com/network/config '{ "Network": "10.1.0.0/16" }' |
- 运行flannel,每个节点都要执行
1 | # flanneld -etcd-endpoints=http://192.168.91.200:2379,http://192.168.91.201:2379,http://192.168.91.202:2379,http://192.168.91.203:2379 -etcd-prefix="/coreos.com/network">> /var/log/flanneld.log 2>&1 & |

- Flanneld网络会自动给每个节点分配一个网段以保证Docker容器在整个集群内的IP唯一,所以需要把一开始就已经存在的Docker网桥删除掉
1 | # iptables -t nat -F |

- 重启docker服务
1 | # source /run/flannel/subnet.env |
