前幾篇文章已經(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ù):
關(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/