kube-proxy ipvs 模式源碼分析

前幾篇文章已經(jīng)分析了 service 的原理以及 kube-proxy iptables 模式的原理與實(shí)現(xiàn)囚灼,本篇文章會(huì)繼續(xù)分析 kube-proxy ipvs 模式的原理與實(shí)現(xiàn)设拟。

ipvs

ipvs (IP Virtual Server) 是基于 Netfilter 的,作為 linux 內(nèi)核的一部分實(shí)現(xiàn)了傳輸層負(fù)載均衡所坯,ipvs 集成在LVS(Linux Virtual Server)中,它在主機(jī)中運(yùn)行挂捅,并在真實(shí)服務(wù)器集群前充當(dāng)負(fù)載均衡器芹助。ipvs 可以將對(duì) TCP/UDP 服務(wù)的請(qǐng)求轉(zhuǎn)發(fā)給后端的真實(shí)服務(wù)器,因此 ipvs 天然支持 Kubernetes Service闲先。ipvs 也包含了多種不同的負(fù)載均衡算法状土,例如輪詢、最短期望延遲伺糠、最少連接以及各種哈希方法等蒙谓,ipvs 的設(shè)計(jì)就是用來(lái)為大規(guī)模服務(wù)進(jìn)行負(fù)載均衡的。

ipvs 的負(fù)載均衡方式

ipvs 有三種負(fù)載均衡方式训桶,分別為:

關(guān)于三種模式的原理可以參考:LVS 配置小結(jié)累驮。

上面的負(fù)載均衡方式中只有 NAT 模式可以進(jìn)行端口映射,因此 kubernetes 中 ipvs 的實(shí)現(xiàn)使用了 NAT 模式渊迁,用來(lái)將 service IP 和 service port 映射到后端的 container ip 和container port慰照。

NAT 模式下的工作流程如下所示:

   +--------+
   | Client |
   +--------+
     (CIP)       <-- Client's IP address
       |
       |
  { internet }
       |
       |
     (VIP)       <-- Virtual IP address
  +----------+
  | Director |
  +----------+
     (PIP)       <-- (Director's Private IP address)
       |
       |
     (RIP)       <-- Real (server's) IP address
 +-------------+
 | Real server |
 +-------------+

其具體流程為:當(dāng)用戶發(fā)起一個(gè)請(qǐng)求時(shí),請(qǐng)求從 VIP 接口流入琉朽,此時(shí)數(shù)據(jù)源地址是 CIP毒租,目標(biāo)地址是 VIP,當(dāng)接收到請(qǐng)求后拆掉 mac 地址封裝后看到目標(biāo) IP 地址就是自己箱叁,按照正常流程會(huì)通過(guò) INPUT 轉(zhuǎn)入用戶空間墅垮,但此時(shí)工作在 INPUT 鏈上的 ipvs 會(huì)強(qiáng)行將數(shù)據(jù)轉(zhuǎn)到 POSTROUTING 鏈上,并根據(jù)相應(yīng)的負(fù)載均衡算法選擇后端具體的服務(wù)器耕漱,再通過(guò) DNAT 轉(zhuǎn)發(fā)給 Real server算色,此時(shí)源地址 CIP,目標(biāo)地址變成了 RIP螟够。

ipvs 與 iptables 的區(qū)別與聯(lián)系

區(qū)別

  • 底層數(shù)據(jù)結(jié)構(gòu):iptables 使用鏈表灾梦,ipvs 使用哈希表
  • 負(fù)載均衡算法:iptables 只支持隨機(jī)峡钓、輪詢兩種負(fù)載均衡算法而 ipvs 支持的多達(dá) 8 種;
  • 操作工具:iptables 需要使用 iptables 命令行工作來(lái)定義規(guī)則若河,ipvs 需要使用 ipvsadm 來(lái)定義規(guī)則能岩。

此外 ipvs 還支持 realserver 運(yùn)行狀況檢查、連接重試萧福、端口映射拉鹃、會(huì)話保持等功能。

聯(lián)系

ipvs 和 iptables 都是基于 netfilter內(nèi)核模塊鲫忍,兩者都是在內(nèi)核中的五個(gè)鉤子函數(shù)處工作膏燕,下圖是 ipvs 所工作的幾個(gè)鉤子函數(shù):

interactions between netfilter and LVS

關(guān)于 kube-proxy iptables 與 ipvs 模式的區(qū)別,更多詳細(xì)信息可以查看官方文檔:https://github.com/kubernetes/kubernetes/blob/master/pkg/proxy/ipvs/README.md悟民。

ipset

IP sets 是 Linux 內(nèi)核中的一個(gè)框架坝辫,可以由 ipset 命令進(jìn)行管理。根據(jù)不同的類型逾雄,IP set 可以以某種方式保存 IP地址阀溶、網(wǎng)絡(luò)、(TCP/UDP)端口號(hào)鸦泳、MAC地址、接口名或它們的組合永品,并且能夠快速匹配做鹰。

根據(jù)官網(wǎng)的介紹,若有以下使用場(chǎng)景:

  • 在保存了多個(gè) IP 地址或端口號(hào)的 iptables 規(guī)則集合中想使用哈希查找;
  • 根據(jù) IP 地址或端口動(dòng)態(tài)更新 iptables 規(guī)則時(shí)希望在性能上無(wú)損鼎姐;
  • 在使用 iptables 工具創(chuàng)建一個(gè)基于 IP 地址和端口的復(fù)雜規(guī)則時(shí)覺得非常繁瑣钾麸;

此時(shí),使用 ipset 工具可能是你最好的選擇炕桨。

ipset 是 iptables 的一種擴(kuò)展饭尝,在 iptables 中可以使用-m set啟用 ipset 模塊,具體來(lái)說(shuō)献宫,ipvs 使用 ipset 來(lái)存儲(chǔ)需要 NAT 或 masquared 時(shí)的 ip 和端口列表钥平。在數(shù)據(jù)包過(guò)濾過(guò)程中,首先遍歷 iptables 規(guī)則姊途,在定義了使用 ipset 的條件下會(huì)跳轉(zhuǎn)到 ipset 列表中進(jìn)行匹配涉瘾。

kube-proxy ipvs 模式

kube-proxy 的 ipvs 模式是在 2015 年由 k8s 社區(qū)的大佬 thockin 提出的(Try kube-proxy via ipvs instead of iptables or userspace),在 2017 年由華為云團(tuán)隊(duì)實(shí)現(xiàn)的(Implement IPVS-based in-cluster service load balancing)捷兰。前面的文章已經(jīng)提到了立叛,在kubernetes v1.8 中已經(jīng)引入了 ipvs 模式。

kube-proxy 在 ipvs 模式下自定義了八條鏈贡茅,分別為 KUBE-SERVICES秘蛇、KUBE-FIREWALL其做、KUBE-POSTROUTING、KUBE-MARK-MASQ赁还、KUBE-NODE-PORT妖泄、KUBE-MARK-DROP、KUBE-FORWARD秽浇、KUBE-LOAD-BALANCER浮庐,如下所示:

NAT 表:

Filter 表:

此外,由于 linux 內(nèi)核原生的 ipvs 模式只支持 DNAT柬焕,不支持 SNAT审残,所以述召,在以下幾種場(chǎng)景中 ipvs 仍需要依賴 iptables 規(guī)則:

  • 1居灯、kube-proxy 啟動(dòng)時(shí)指定 –-masquerade-all=true 參數(shù)娶吞,即集群中所有經(jīng)過(guò) kube-proxy 的包都做一次 SNAT庭敦;
  • 2冯事、kube-proxy 啟動(dòng)時(shí)指定 --cluster-cidr= 參數(shù)败潦;
  • 3嗡贺、對(duì)于 Load Balancer 類型的 service讶隐,用于配置白名單赎懦;
  • 4雀鹃、對(duì)于 NodePort 類型的 service,用于配置 MASQUERADE励两;
  • 5黎茎、對(duì)于 externalIPs 類型的 service;

但對(duì)于 ipvs 模式的 kube-proxy当悔,無(wú)論有多少 pod/service傅瞻,iptables 的規(guī)則數(shù)都是固定的。

ipvs 模式的啟用

1盲憎、首先要加載 IPVS 所需要的 kernel module

$ modprobe -- ip_vs
$ modprobe -- ip_vs_rr
$ modprobe -- ip_vs_wrr
$ modprobe -- ip_vs_sh
$ modprobe -- nf_conntrack_ipv4
$ cut -f1 -d " "  /proc/modules | grep -e ip_vs -e nf_conntrack_ipv4

2嗅骄、在啟動(dòng) kube-proxy 時(shí),指定 proxy-mode 參數(shù)

--proxy-mode=ipvs

(如果要使用其他負(fù)載均衡算法饼疙,可以指定 --ipvs-scheduler= 參數(shù)溺森,默認(rèn)為 rr)

當(dāng)創(chuàng)建 ClusterIP type 的 service 時(shí),IPVS proxier 會(huì)執(zhí)行以下三個(gè)操作:

  • 確保本機(jī)已創(chuàng)建 dummy 網(wǎng)卡宏多,默認(rèn)為 kube-ipvs0儿惫。為什么要?jiǎng)?chuàng)建 dummy 網(wǎng)卡?因?yàn)?ipvs netfilter 的 DNAT 鉤子掛載在 INPUT 鏈上伸但,當(dāng)訪問(wèn) ClusterIP 時(shí)肾请,將 ClusterIP 綁定在 dummy 網(wǎng)卡上為了讓內(nèi)核識(shí)別該 IP 就是本機(jī) IP,進(jìn)而進(jìn)入 INPUT 鏈更胖,然后通過(guò)鉤子函數(shù) ip_vs_in 轉(zhuǎn)發(fā)到 POSTROUTING 鏈铛铁;
  • 將 ClusterIP 綁定到 dummy 網(wǎng)卡隔显;
  • 為每個(gè) ClusterIP 創(chuàng)建 IPVS virtual servers 和 real server,分別對(duì)應(yīng) service 和 endpoints饵逐;

例如下面的示例:

// kube-ipvs0 dummy 網(wǎng)卡
$ ip addr
......
4: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default
    link/ether de:be:c0:73:bc:c7 brd ff:ff:ff:ff:ff:ff
    inet 10.96.0.1/32 brd 10.96.0.1 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.96.0.10/32 brd 10.96.0.10 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.97.4.140/32 brd 10.97.4.140 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    ......
    

// 10.97.4.140 為 CLUSTER-IP 掛載在 kube-ipvs0 上
$ kubectl get svc
NAME             TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
tenant-service   ClusterIP   10.97.4.140   <none>        7000/TCP   23s

// 10.97.4.140 后端的 realserver 分別為 10.244.1.2 和 10.244.1.3
$ ipvsadm -L -n
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.97.4.140:7000 rr
  -> 10.244.1.2:7000              Masq    1      0          0
  -> 10.244.1.3:7000              Masq    1      0          0

ipvs 模式下數(shù)據(jù)包的流向

clusterIP 訪問(wèn)方式

PREROUTING --> KUBE-SERVICES --> KUBE-CLUSTER-IP --> INPUT --> KUBE-FIREWALL --> POSTROUTING
  • 首先進(jìn)入 PREROUTING 鏈
  • 從 PREROUTING 鏈會(huì)轉(zhuǎn)到 KUBE-SERVICES 鏈括眠,10.244.0.0/16 為 ClusterIP 網(wǎng)段
  • 在 KUBE-SERVICES 鏈打標(biāo)記
  • 從 KUBE-SERVICES 鏈再進(jìn)入到 KUBE-CLUSTER-IP 鏈
  • KUBE-CLUSTER-IP 為 ipset 集合,在此處會(huì)進(jìn)行 DNAT
  • 然后會(huì)進(jìn)入 INPUT 鏈
  • 從 INPUT 鏈會(huì)轉(zhuǎn)到 KUBE-FIREWALL 鏈倍权,在此處檢查標(biāo)記
  • 在 INPUT 鏈處掷豺,ipvs 的 LOCAL_IN Hook 發(fā)現(xiàn)此包在 ipvs 規(guī)則中則直接轉(zhuǎn)發(fā)到 POSTROUTING 鏈
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

-A KUBE-SERVICES ! -s 10.244.0.0/16 -m comment --comment "Kubernetes service cluster ip + port for masquerade purpose" -m set --match-set KUBE-CLUSTER-IP dst,dst -j KUBE-MARK-MASQ

// 執(zhí)行完 PREROUTING 規(guī)則,數(shù)據(jù)打上0x4000/0x4000的標(biāo)記
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000

-A KUBE-SERVICES -m set --match-set KUBE-CLUSTER-IP dst,dst -j ACCEPT

KUBE-CLUSTER-IP 為 ipset 列表:

# ipset list | grep -A 20 KUBE-CLUSTER-IP
Name: KUBE-CLUSTER-IP
Type: hash:ip,port
Revision: 5
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 352
References: 2
Members:
10.96.0.10,17:53
10.96.0.10,6:53
10.96.0.1,6:443
10.96.0.10,6:9153

然后會(huì)進(jìn)入 INPUT:

-A INPUT -j KUBE-FIREWALL

-A KUBE-FIREWALL -m comment --comment "kubernetes firewall for dropping marked packets" -m mark --mark 0x8000/0x8000 -j DROP

如果進(jìn)來(lái)的數(shù)據(jù)帶有 0x8000/0x8000 標(biāo)記則丟棄,若有 0x4000/0x4000 標(biāo)記則正常執(zhí)行:

-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE

nodePort 方式

PREROUTING --> KUBE-SERVICES --> KUBE-NODE-PORT --> INPUT --> KUBE-FIREWALL --> POSTROUTING
  • 首先進(jìn)入 PREROUTING 鏈
  • 從 PREROUTING 鏈會(huì)轉(zhuǎn)到 KUBE-SERVICES 鏈
  • 在 KUBE-SERVICES 鏈打標(biāo)記
  • 從 KUBE-SERVICES 鏈再進(jìn)入到 KUBE-NODE-PORT 鏈
  • KUBE-NODE-PORT 為 ipset 集合薄声,在此處會(huì)進(jìn)行 DNAT
  • 然后會(huì)進(jìn)入 INPUT 鏈
  • 從 INPUT 鏈會(huì)轉(zhuǎn)到 KUBE-FIREWALL 鏈当船,在此處檢查標(biāo)記
  • 在 INPUT 鏈處,ipvs 的 LOCAL_IN Hook 發(fā)現(xiàn)此包在 ipvs 規(guī)則中則直接轉(zhuǎn)發(fā)到 POSTROUTING 鏈
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

-A KUBE-SERVICES ! -s 10.244.0.0/16 -m comment --comment "Kubernetes service cluster ip + port for masquerade purpose" -m set --match-set KUBE-CLUSTER-IP dst,dst -j KUBE-MARK-MASQ

-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000

-A KUBE-SERVICES -m addrtype --dst-type LOCAL -j KUBE-NODE-PORT

KUBE-NODE-PORT 對(duì)應(yīng)的 ipset 列表:

# ipset list | grep -B 10 KUBE-NODE-PORT
Name: KUBE-NODE-PORT-TCP
Type: bitmap:port
Revision: 3
Header: range 0-65535
Size in memory: 8268
References: 0
Members:

流入 INPUT 后與 ClusterIP 的訪問(wèn)方式相同默辨。

kube-proxy ipvs 源碼分析

kubernetes 版本:v1.16

在前面的文章中已經(jīng)介紹過(guò) ipvs 的初始化了德频,下面直接看其核心方法:proxier.syncRunner。

func NewProxier(......) {
        ......
        proxier.syncRunner = async.NewBoundedFrequencyRunner("sync-runner", proxier.syncProxyRules, minSyncPeriod, syncPeriod, burstSyncs)
        ......
}

proxier.syncRunner() 執(zhí)行流程:

  • 通過(guò) iptables-save 獲取現(xiàn)有的 Filter 和 NAT 表存在的鏈數(shù)據(jù)
  • 創(chuàng)建自定義鏈與規(guī)則
  • 創(chuàng)建 Dummy 接口和 ipset 默認(rèn)列表
  • 為每個(gè)服務(wù)生成 ipvs 規(guī)則
  • 對(duì) serviceMap 內(nèi)的每個(gè)服務(wù)進(jìn)行遍歷處理缩幸,對(duì)不同的服務(wù)類型(clusterip/nodePort/externalIPs/load-balancer)進(jìn)行不同的處理(ipset 列表/vip/ipvs 后端服務(wù)器)
  • 根據(jù) endpoint 列表壹置,更新 KUBE-LOOP-BACK 的 ipset 列表
  • 若為 clusterIP 類型更新對(duì)應(yīng)的 ipset 列表 KUBE-CLUSTER-IP
  • 若為 externalIPs 類型更新對(duì)應(yīng)的 ipset 列表 KUBE-EXTERNAL-IP
  • 若為 load-balancer 類型更新對(duì)應(yīng)的 ipset 列表 KUBE-LOAD-BALANCER、KUBE-LOAD-BALANCER-LOCAL表谊、KUBE-LOAD-BALANCER-FW钞护、KUBE-LOAD-BALANCER-SOURCE-CIDR、KUBE-LOAD-BALANCER-SOURCE-IP
  • 若為 NodePort 類型更新對(duì)應(yīng)的 ipset 列表 KUBE-NODE-PORT-TCP爆办、KUBE-NODE-PORT-LOCAL-TCP患亿、KUBE-NODE-PORT-LOCAL-SCTP-HASH、KUBE-NODE-PORT-LOCAL-UDP押逼、KUBE-NODE-PORT-SCTP-HASH、KUBE-NODE-PORT-UDP
  • 同步 ipset 記錄
  • 刷新 iptables 規(guī)則
func (proxier *Proxier) syncProxyRules() {
    proxier.mu.Lock()
    defer proxier.mu.Unlock()


    serviceUpdateResult := proxy.UpdateServiceMap(proxier.serviceMap, proxier.serviceChanges)
    endpointUpdateResult := proxier.endpointsMap.Update(proxier.endpointsChanges)

    staleServices := serviceUpdateResult.UDPStaleClusterIP
    // 合并 service 列表
    for _, svcPortName := range endpointUpdateResult.StaleServiceNames {
        if svcInfo, ok := proxier.serviceMap[svcPortName]; ok && svcInfo != nil && svcInfo.Protocol() == v1.ProtocolUDP {
            staleServices.Insert(svcInfo.ClusterIP().String())
            for _, extIP := range svcInfo.ExternalIPStrings() {
                staleServices.Insert(extIP)
            }
        }
    }
    ......

讀取系統(tǒng) iptables 到內(nèi)存惦界,創(chuàng)建自定義鏈以及 iptables 規(guī)則挑格,創(chuàng)建 dummy interface kube-ipvs0,創(chuàng)建默認(rèn)的 ipset 規(guī)則沾歪。

    proxier.natChains.Reset()
    proxier.natRules.Reset()
    proxier.filterChains.Reset()
    proxier.filterRules.Reset()

    writeLine(proxier.filterChains, "*filter")
    writeLine(proxier.natChains, "*nat")

      // 創(chuàng)建kubernetes的表連接鏈數(shù)據(jù)
    proxier.createAndLinkeKubeChain()

    // 創(chuàng)建 dummy interface kube-ipvs0
    _, err := proxier.netlinkHandle.EnsureDummyDevice(DefaultDummyDevice)
    if err != nil {
            ......
        return
    }

    // 創(chuàng)建默認(rèn)的 ipset 規(guī)則
    for _, set := range proxier.ipsetList {
        if err := ensureIPSet(set); err != nil {
            return
        }
        set.resetEntries()
    }

對(duì)每一個(gè)服務(wù)創(chuàng)建 ipvs 規(guī)則漂彤。根據(jù) endpoint 列表,更新 KUBE-LOOP-BACK 的 ipset 列表灾搏。

    for svcName, svc := range proxier.serviceMap {
        svcInfo, ok := svc.(*serviceInfo)
        if !ok {
            ......
        }
        
        for _, e := range proxier.endpointsMap[svcName] {
            ep, ok := e.(*proxy.BaseEndpointInfo)
            if !ok {
                klog.Errorf("Failed to cast BaseEndpointInfo %q", e.String())
                continue
            }
            ......

            if valid := proxier.ipsetList[kubeLoopBackIPSet].validateEntry(entry); !valid {
                  ......
            }
            proxier.ipsetList[kubeLoopBackIPSet].activeEntries.Insert(entry.String())
        }

對(duì)于 clusterIP 類型更新對(duì)應(yīng)的 ipset 列表 KUBE-CLUSTER-IP挫望。

        if valid := proxier.ipsetList[kubeClusterIPSet].validateEntry(entry); !valid {
            ......
        }
        proxier.ipsetList[kubeClusterIPSet].activeEntries.Insert(entry.String())
            ......
        if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP {
            ......
        }
        // 綁定 ClusterIP to dummy interface
        if err := proxier.syncService(svcNameString, serv, true); err == nil {
            // 同步 endpoints 信息
            if err := proxier.syncEndpoint(svcName, false, serv); err != nil {
                ......
            }
        } else {
                ......
        }

為 externalIP 創(chuàng)建 ipvs 規(guī)則。

        for _, externalIP := range svcInfo.ExternalIPStrings() {
            if local, err := utilproxy.IsLocalIP(externalIP); err != nil {
                ......
            } else if local && (svcInfo.Protocol() != v1.ProtocolSCTP) {
                ......
                if proxier.portsMap[lp] != nil {
                  ......
                } else {
                    socket, err := proxier.portMapper.OpenLocalPort(&lp)
                    if err != nil {
                        ......
                    }
                    replacementPortsMap[lp] = socket
                }
            } 
            ......
            if valid := proxier.ipsetList[kubeExternalIPSet].validateEntry(entry); !valid {
                        ......
            }
            proxier.ipsetList[kubeExternalIPSet].activeEntries.Insert(entry.String())
                        
            ......
            if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP {
                    ......
            }
            if err := proxier.syncService(svcNameString, serv, true); err == nil {
                ......
                if err := proxier.syncEndpoint(svcName, false, serv); err != nil {
                        ......
                }
            } else {
                    ......
            }
        }

為 load-balancer類型創(chuàng)建 ipvs 規(guī)則狂窑。

        for _, ingress := range svcInfo.LoadBalancerIPStrings() {
            if ingress != "" {
                ......
                if valid := proxier.ipsetList[kubeLoadBalancerSet].validateEntry(entry); !valid {
                        ......
                }
                proxier.ipsetList[kubeLoadBalancerSet].activeEntries.Insert(entry.String())

                if svcInfo.OnlyNodeLocalEndpoints() {
                            ......
                }
                if len(svcInfo.LoadBalancerSourceRanges()) != 0 {
                    ......
                    for _, src := range svcInfo.LoadBalancerSourceRanges() {
                        ......
                    }
                        ......
                }
                ......
                if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP {
                            ......
                }
                if err := proxier.syncService(svcNameString, serv, true); err == nil {
                    ......
                    if err := proxier.syncEndpoint(svcName, svcInfo.OnlyNodeLocalEndpoints(), serv); err != nil {
                       ......
                    }
                } else {
                    ......
                }
            }
        }

為 nodePort 類型創(chuàng)建 ipvs 規(guī)則媳板。

        if svcInfo.NodePort() != 0 {
            ......

            var lps []utilproxy.LocalPort
            for _, address := range nodeAddresses {
                ......
                lps = append(lps, lp)
            }
            for _, lp := range lps {
                if proxier.portsMap[lp] != nil {
                        ......
                } else if svcInfo.Protocol() != v1.ProtocolSCTP {
                    socket, err := proxier.portMapper.OpenLocalPort(&lp)
                    if err != nil {
                            ......
                    }
                    if lp.Protocol == "udp" {
                        ......
                    }                    
                } 
            }
            switch protocol {
            case "tcp":
                    ......
            case "udp":
                    ......
            case "sctp":
                    ......
            default:
                    ......
            }
            if nodePortSet != nil {
                for _, entry := range entries {
                    ......
                    nodePortSet.activeEntries.Insert(entry.String())
                }
            }
            if svcInfo.OnlyNodeLocalEndpoints() {
                var nodePortLocalSet *IPSet
                switch protocol {
                case "tcp":
                    nodePortLocalSet = proxier.ipsetList[kubeNodePortLocalSetTCP]
                case "udp":
                    nodePortLocalSet = proxier.ipsetList[kubeNodePortLocalSetUDP]
                case "sctp":
                    nodePortLocalSet = proxier.ipsetList[kubeNodePortLocalSetSCTP]
                default:
                        ......
                }
                if nodePortLocalSet != nil {
                    entryInvalidErr := false
                    for _, entry := range entries {
                                ......
                        nodePortLocalSet.activeEntries.Insert(entry.String())
                    }
                        ......
                }
            }
            for _, nodeIP := range nodeIPs {
                ......
                if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP {
                        ......
                }
                if err := proxier.syncService(svcNameString, serv, false); err == nil {
                    if err := proxier.syncEndpoint(svcName, svcInfo.OnlyNodeLocalEndpoints(), serv); err != nil {
                            ......
                    }
                } else {
                            ......
                }
            }
        }
    }           

同步 ipset 記錄,清理 conntrack泉哈。

    for _, set := range proxier.ipsetList {
        set.syncIPSetEntries()
    }

    proxier.writeIptablesRules()

    proxier.iptablesData.Reset()
    proxier.iptablesData.Write(proxier.natChains.Bytes())
    proxier.iptablesData.Write(proxier.natRules.Bytes())
    proxier.iptablesData.Write(proxier.filterChains.Bytes())
    proxier.iptablesData.Write(proxier.filterRules.Bytes())

    err = proxier.iptables.RestoreAll(proxier.iptablesData.Bytes(), utiliptables.NoFlushTables, utiliptables.RestoreCounters)
    if err != nil {
        ......
    }
    ......
    proxier.deleteEndpointConnections(endpointUpdateResult.StaleEndpoints)
}

總結(jié)

本文主要講述了 kube-proxy ipvs 模式的原理與實(shí)現(xiàn)蛉幸,iptables 模式與 ipvs 模式下在源碼實(shí)現(xiàn)上有許多相似之處破讨,但二者原理不同,理解了原理分析代碼則更加容易奕纫,筆者對(duì)于 ipvs 的知識(shí)也是現(xiàn)學(xué)的提陶,文中如有不當(dāng)之處望指正。雖然 ipvs 的性能要比 iptables 更好匹层,但社區(qū)中已有相關(guān)的文章指出 BPF(Berkeley Packet Filter) 比 ipvs 的性能更好隙笆,且 BPF 將要取代 iptables,至于下一步如何發(fā)展升筏,讓我們拭目以待撑柔。

參考:
http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/LVS-HOWTO.filter_rules.html
https://bestsamina.github.io/posts/2018-10-19-ipvs-based-kube-proxy-4-scaled-k8s-lb/
https://www.bookstack.cn/read/k8s-source-code-analysis/core-kube-proxy-ipvs.md
https://blog.51cto.com/goome/2369150
https://xigang.github.io/2019/07/21/kubernetes-service/
https://segmentfault.com/a/1190000016333317
https://cilium.io/blog/2018/04/17/why-is-the-kernel-community-replacing-iptables/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者仰冠。
  • 序言:七十年代末乏冀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子洋只,更是在濱河造成了極大的恐慌辆沦,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件识虚,死亡現(xiàn)場(chǎng)離奇詭異肢扯,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)担锤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門蔚晨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人肛循,你說(shuō)我怎么就攤上這事铭腕。” “怎么了多糠?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵累舷,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我夹孔,道長(zhǎng)被盈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任搭伤,我火速辦了婚禮只怎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘怜俐。我一直安慰自己身堡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布佑菩。 她就那樣靜靜地躺著盾沫,像睡著了一般裁赠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赴精,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天佩捞,我揣著相機(jī)與錄音,去河邊找鬼蕾哟。 笑死一忱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的谭确。 我是一名探鬼主播帘营,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼逐哈!你這毒婦竟也來(lái)了芬迄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤昂秃,失蹤者是張志新(化名)和其女友劉穎禀梳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肠骆,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡算途,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蚀腿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘴瓤。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖莉钙,靈堂內(nèi)的尸體忽然破棺而出廓脆,到底是詐尸還是另有隱情,我是刑警寧澤磁玉,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布狞贱,位于F島的核電站,受9級(jí)特大地震影響蜀涨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蝎毡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一厚柳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沐兵,春花似錦别垮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)烧董。三九已至,卻和暖如春胧奔,著一層夾襖步出監(jiān)牢的瞬間逊移,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工龙填, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胳泉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓岩遗,卻偏偏與公主長(zhǎng)得像扇商,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子宿礁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348