k8s與網路--Flannel原始碼分析
前言
之前在k8s與網路--Flannel解讀一文中,我們主要講了Flannel整體的工作原理。今天主要針對Flannel v0.10.0版本進行原始碼分析。首先需要理解三個比較重要的概念:
- 網路(Network):整個叢集中分配給 flannel 要管理的網路地址範圍
- 子網(Subnet):flannel 所在的每臺主機都會管理 network 中一個子網,子網的掩碼和範圍是可配置的
- 後端(Backend):使用什麼樣的後端網路模型,比如預設的 udp,還是 vxlan 等
原始碼分析
整體的程式碼組織如下:
除了可執行檔案的入口 main.go之外,有backend,network,pkg和subnet這麼幾個程式碼相關的資料夾。
- network主要是iptables相關。主要是供main函式根據設定進行MasqRules和ForwardRules規則的設定。
- pkg主要是抽象封裝的ip功能庫。
- backed 主要是後端實現,目前支援 udp、vxlan、host-gw 等。
- subnet 子網管理。主要支援etcdv2和k8s兩種實現。
啟動引數
name | 預設值 | 說明 |
---|---|---|
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 | false | 是否使用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 | 0 | 要監聽的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做一些工作。