天天看點

k8s與網絡--Flannel源碼分析源碼分析

之前在 k8s與網絡--Flannel解讀 一文中,我們主要講了Flannel整體的工作原理。今天主要針對Flannel v0.10.0版本進行源碼分析。首先需要了解三個比較重要的概念:

  • 網絡(Network):整個叢集中配置設定給 flannel 要管理 的網絡位址範圍
  • 子網(Subnet):flannel 所在的每台主機都會管理 network 中一個子網,子網的掩碼和範圍是可配置的
  • 後端(Backend):使用什麼樣的後端網絡模型,比如預設的 udp,還是 vxlan 等

源碼分析

整體的代碼組織如下:

k8s與網絡--Flannel源碼分析源碼分析

除了可執行檔案的入口 main.go之外,有backend,network,pkg和subnet這麼幾個代碼相關的檔案夾。

  • network主要是iptables相關。主要是供main函數根據設定進行MasqRules和ForwardRules規則的設定。
  • pkg主要是抽象封裝的ip功能庫。
  • backed 主要是後端實作,目前支援 udp、vxlan、host-gw 等。
k8s與網絡--Flannel源碼分析源碼分析
  • subnet 子網管理。主要支援etcdv2和k8s兩種實作。
k8s與網絡--Flannel源碼分析源碼分析

啟動參數

name 預設值 說明
etcd-endpoints http://127.0.0.1 :4001, :2379 etcd終端節點清單
etcd-prefix /coreos.com/network etcd 字首
etcd-keyfile SSL key檔案
etcd-certfile SSL certification 檔案
etcd-cafile SSL Certificate Authority 檔案
etcd-username 通過BasicAuth通路etcd 的使用者名
etcd-password 通過BasicAuth通路etcd 的密碼
iface 完整的網卡名或ip位址
iface-regex 正規表達式表示的網卡名或ip位址
subnet-file /run/flannel/subnet.env 存放運作時需要的一些變量 (subnet, MTU, ... )的檔案名
public-ip 主機IP
subnet-lease-renew-margin 60分鐘 在租約到期之前多長時間進行更新
ip-masq false 是否為覆寫網絡外部的流量設定IP僞裝規則
kube-subnet-mgr 是否使用k8s作為subnet的實作方式
kube-api-url "" Kubernetes API server URL ,如果叢集内部署,則不需要設定,做好rbac授權即可
kubeconfig-file kubeconfig file 位置,如果叢集内部署,則不需要設定,做好rbac授權即可
healthz-ip 0.0.0.0 要監聽的healthz伺服器的IP位址
healthz-port 要監聽的healthz伺服器的端口,0 表示停用

分析

從main函數開始分析,主要步驟如下:

1. 校驗subnet-lease-renew-margin

if opts.subnetLeaseRenewMargin >= 24*60 || opts.subnetLeaseRenewMargin <= 0 {
 log.Error("Invalid subnet-lease-renew-margin option, out of acceptable range")
 os.Exit(1)
 }           

需要小于等于24h,大于0。

2. 計算去使用哪一個網絡接口

假如主機有多個網卡,flannel會使用哪一個?

這就和咱們前面提到的iface和iface-regex兩個參數有關。這兩個參數每一個可以指定多個。flannel将按照下面的優先順序來選取:

1) 如果”–iface”和”—-iface-regex”都未指定時,則直接選取預設路由所使用的輸出網卡

2) 如果”–iface”參數不為空,則依次周遊其中的各個執行個體,直到找到和該網卡名或IP比對的執行個體為止

3) 如果”–iface-regex”參數不為空,操作方式和2)相同,唯一不同的是使用正規表達式去比對

最後,對于叢集間互動的Public IP,我們同樣可以通過啟動參數”–public-ip”進行指定。否則,将使用上文中擷取的網卡的IP作為Public IP。

外部接口的定義如下:

type ExternalInterface struct {
 Iface *net.Interface
 IfaceAddr net.IP
 ExtAddr net.IP
}           

3.建立SubnetManager

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 := ReadSubnetFromSubnetFile(opts.subnetFile)

 return etcdv2.NewLocalManager(cfg, prevSubnet)
}           

子網管理器負責子網的建立、更新、添加、删除、監聽等,主要和 etcd 打交道,定義:

type Manager interface {
 GetNetworkConfig(ctx context.Context) (*Config, error)
 AcquireLease(ctx context.Context, attrs *LeaseAttrs) (*Lease, error)
 RenewLease(ctx context.Context, lease *Lease) error
 WatchLease(ctx context.Context, sn ip.IP4Net, cursor interface{}) (LeaseWatchResult, error)
 WatchLeases(ctx context.Context, cursor interface{}) (LeaseWatchResult, error)

 Name() string
}           
  • RenewLease 續約。在lease到期之前,子網管理器調用該方法進行續約。
  • GetNetworkConfig 擷取本機的subnet配置,進行一些初始化的工作。

4. 擷取網絡配置

config, err := getConfig(ctx, sm)
 if err == errCanceled {
 wg.Wait()
 os.Exit(0)
 }           

這個配置主要是管理網絡的配置,需要在flannel啟動之前寫到etcd中。例如:

{
 "Network": "10.0.0.0/8",
 "SubnetLen": 20,
 "SubnetMin": "10.10.0.0",
 "SubnetMax": "10.99.0.0",
 "Backend": {
 "Type": "udp",
 "Port": 7890
 }
}           

/coreos.com/network/config 儲存着上面網絡配置資料。

詳細解讀一下:

  • SubnetLen表示每個主機配置設定的subnet大小,我們可以在初始化時對其指定,否則使用預設配置。在預設配置的情況下,如果叢集的網絡位址空間大于/24,則SubnetLen配置為24,否則它比叢集網絡位址空間小1,例如叢集的大小為/25,則SubnetLen的大小為/26
  • SubnetMin是叢集網絡位址空間中最小的可配置設定的subnet,可以手動指定,否則預設配置為叢集網絡位址空間中第一個可配置設定的subnet。
  • SubnetMax表示最大可配置設定的subnet
  • BackendType為使用的backend的類型,如未指定,則預設為“udp”
  • Backend中會包含backend的附加資訊,例如backend為vxlan時,其中會存儲vtep裝置的mac位址

5. 建立backend管理器,然後使用它來建立backend并使用它注冊網絡,然後執行run方法

bm := backend.NewManager(ctx, sm, extIface)
 be, err := bm.GetBackend(config.BackendType)
 if err != nil {
 log.Errorf("Error fetching backend: %s", err)
 cancel()
 wg.Wait()
 os.Exit(1)
 }

 bn, err := be.RegisterNetwork(ctx, config)
 if err != nil {
 log.Errorf("Error registering network: %s", err)
 cancel()
 wg.Wait()
 os.Exit(1)
 }

...

log.Info("Running backend.")
 wg.Add(1)
 go func() {
 bn.Run(ctx)
 wg.Done()
 }()           

backend管理器

type manager struct {
 ctx context.Context
 sm subnet.Manager
 extIface *ExternalInterface
 mux sync.Mutex
 active map[string]Backend
 wg sync.WaitGroup
}           

主要是提供了GetBackend(backendType string) (Backend, error)方法,根據配置檔案的設定backend标志,生産對應的backend。

此處注意

go func() {
 <-bm.ctx.Done()

 // TODO(eyakubovich): this obviosly introduces a race. // GetBackend() could get called while we are here. // Currently though, all backends' Run exit only // on shutdown

 bm.mux.Lock()
 delete(bm.active, betype)
 bm.mux.Unlock()

 bm.wg.Done()
 }()
           

在生産backend以後,會啟動一個協程,在flanneld退出運作之前,将會執行激活的backend map中删除操作。

最後run方法:

func (n *RouteNetwork) Run(ctx context.Context) {
 wg := sync.WaitGroup{}

 log.Info("Watching for new subnet leases")
 evts := make(chan []subnet.Event)
 wg.Add(1)
 go func() {
 subnet.WatchLeases(ctx, n.SM, n.SubnetLease, evts)
 wg.Done()
 }()

 n.routes = make([]netlink.Route, 0, 10)
 wg.Add(1)
 go func() {
 n.routeCheck(ctx)
 wg.Done()
 }()

 defer wg.Wait()

 for {
 select {
 case evtBatch := <-evts:
 n.handleSubnetEvents(evtBatch)

 case <-ctx.Done():
 return
 }
 }
}           

run方法中主要是執行:

  • subnet 負責和 etcd 互動,把 etcd 中的資訊轉換為 flannel 的子網資料結構,并對 etcd 進行子網和網絡的監聽;
  • backend 接受 subnet 的監聽事件,并做出對應的處理。

事件主要是subnet.EventAdded和subnet.EventRemoved兩個。

添加子網事件發生時的處理步驟:檢查參數是否正常,根據參數建構路由表項,把路由表項添加到主機,把路由表項添加到自己的資料結構中。

删除子網事件發生時的處理步驟:檢查參數是否正常,根據參數建構路由表項,把路由表項從主機删除,把路由表項從管理的資料結構中删除

6. 其他

除了上面的核心的邏輯,還有一些iptables規則和SubnetFile相關的操作。

// Set up ipMasq if needed if opts.ipMasq {
 go network.SetupAndEnsureIPTables(network.MasqRules(config.Network, bn.Lease()))
 }

 // Always enables forwarding rules. This is needed for Docker versions >1.13 (https://docs.docker.com/engine/userguide/networking/default_network/container-communication/#container-communication-between-hosts) // In Docker 1.12 and earlier, the default FORWARD chain policy was ACCEPT. // In Docker 1.13 and later, Docker sets the default policy of the FORWARD chain to DROP. go network.SetupAndEnsureIPTables(network.ForwardRules(config.Network.String()))           

可以看出主要是調用了network檔案裡的SetupAndEnsureIPTables方法。

PS

在Docker 1.13及更高版本中,Docker設定了FORWARD的預設政策是drop,是以需要flannel做一些工作。

本文轉自SegmentFault-

k8s與網絡--Flannel源碼分析

繼續閱讀