模集群Service轉(zhuǎn)發(fā)性能優(yōu)化實(shí)戰(zhàn))
kube-proxy 從 iptables 模式切到 IPVS 模式這件事在集群規(guī)模小的時(shí)候幾乎感覺(jué)不到差別但只要節(jié)點(diǎn)上 Service 數(shù)量過(guò)了幾百、Pod 副本數(shù)上千iptables 那條鏈的線性匹配開銷就會(huì)開始咬人。我最早動(dòng)這個(gè)念頭是因?yàn)橐粋€(gè)中等規(guī)模的業(yè)務(wù)集群里 Service 已經(jīng)逼近兩千節(jié)點(diǎn)上iptables-save的輸出動(dòng)輒十幾萬(wàn)行規(guī)則刷新時(shí) CPU 尖刺明顯新建連接的延遲抖動(dòng)也跟著上來(lái)了。IPVS 基于哈希表做轉(zhuǎn)發(fā)決策時(shí)間復(fù)雜度是常數(shù)級(jí)天然適合這種場(chǎng)景。這篇內(nèi)容就是把我自己從選型、改造、驗(yàn)證到踩坑的完整過(guò)程攤開講清楚適合已經(jīng)對(duì) Kubernetes 網(wǎng)絡(luò)模型有基本概念、正在被大規(guī)模 Service 轉(zhuǎn)發(fā)性能困擾、或者單純想把集群網(wǎng)絡(luò)底座升級(jí)一檔的運(yùn)維和平臺(tái)開發(fā)同學(xué)參考。哪怕你只是剛接觸 kube-proxy 的調(diào)度模式文中的原理拆解和操作步驟也能讓你照著走一遍。1. 為什么要把調(diào)度方式換成 IPVS1.1 iptables 模式在大規(guī)模集群下的真實(shí)瓶頸要理解這次改造的價(jià)值得先把 iptables 模式的工作機(jī)制說(shuō)透。kube-proxy 在 iptables 模式下會(huì)為每一個(gè) Service 生成一組規(guī)則ClusterIP 的訪問(wèn)靠KUBE-SERVICES鏈做匹配再跳到對(duì)應(yīng)的KUBE-SVC-xxx鏈最后分發(fā)到KUBE-SEP-xxx端點(diǎn)鏈。問(wèn)題在于iptables 的規(guī)則匹配本質(zhì)上是順序遍歷一條鏈上的規(guī)則越多一個(gè)數(shù)據(jù)包要走到目標(biāo)規(guī)則需要經(jīng)過(guò)的比對(duì)次數(shù)就越多。打個(gè)生活化的比方iptables 像一本沒(méi)有目錄的紙質(zhì)電話簿你要找一個(gè)聯(lián)系人只能從第一頁(yè)開始一頁(yè)頁(yè)翻。Service 有一千個(gè)平均每個(gè)包要翻五百頁(yè)才能找到對(duì)應(yīng)規(guī)則。而 IPVS 更像一個(gè)哈希索引的通訊錄你報(bào)出名字它直接算出該翻到哪一頁(yè)一步到位。這個(gè)差異在小集群里被緩存和規(guī)則命中率掩蓋了但規(guī)模一上來(lái)差距是指數(shù)級(jí)拉開的。具體到實(shí)測(cè)數(shù)據(jù)我在一個(gè) 3 節(jié)點(diǎn)、每節(jié)點(diǎn)約 1500 個(gè) Service 的環(huán)境里做過(guò)對(duì)比iptables 模式下iptables-restore單次全量同步耗時(shí)接近 4 秒期間 kube-proxy 的 CPU 占用能沖到單核 80% 以上切到 IPVS 后同樣的 Service 規(guī)模規(guī)則同步耗時(shí)降到 300 毫秒以內(nèi)CPU 峰值不到 20%。這個(gè)量級(jí)的差異直接決定了集群在頻繁擴(kuò)縮容時(shí)網(wǎng)絡(luò)平面的穩(wěn)定性。1.2 IPVS 的哈希轉(zhuǎn)發(fā)原理與適用邊界IPVSIP Virtual Server是內(nèi)核里的一套四層負(fù)載均衡實(shí)現(xiàn)它把 Service 的 ClusterIP 和端口注冊(cè)成一個(gè)虛擬服務(wù)virtual service把后端 Pod 注冊(cè)成真實(shí)服務(wù)器real server轉(zhuǎn)發(fā)時(shí)通過(guò)哈希表直接定位。kube-proxy 在 IPVS 模式下不再寫海量 iptables 規(guī)則而是通過(guò) netlink 接口調(diào)用 IPVS 的內(nèi)核模塊來(lái)維護(hù)轉(zhuǎn)發(fā)表。這里有個(gè)關(guān)鍵點(diǎn)很多人會(huì)忽略IPVS 模式并不是完全拋棄 iptables。kube-proxy 仍然會(huì)用少量 iptables 規(guī)則處理 SNAT、masquerade、NodePort 的某些邊界情況以及 IPVS 不支持的場(chǎng)景比如某些特殊協(xié)議的端口映射。所以切過(guò)去之后你依然能看到 iptables 規(guī)則只是數(shù)量從十幾萬(wàn)行驟降到幾百行這才是性能提升的來(lái)源。IPVS 的適用邊界也要說(shuō)清楚。它擅長(zhǎng)的是四層轉(zhuǎn)發(fā)對(duì)七層特性無(wú)感它依賴內(nèi)核模塊ip_vs、ip_vs_rr、ip_vs_wrr、ip_vs_sh、nf_conntrack等內(nèi)核版本太低或者裁剪過(guò)的內(nèi)核可能缺模塊。另外 IPVS 的調(diào)度算法rr、wrr、lc、sh 等決定了后端分發(fā)的均衡策略kube-proxy 默認(rèn)用 rr輪詢這個(gè)默認(rèn)值在多數(shù)場(chǎng)景夠用但如果你有長(zhǎng)連接業(yè)務(wù)可能需要考慮會(huì)話保持相關(guān)的配置。1.3 改造前必須確認(rèn)的三個(gè)前提動(dòng)手之前有三件事必須先確認(rèn)否則改到一半會(huì)卡住。第一是內(nèi)核模塊可用性。在目標(biāo)節(jié)點(diǎn)上執(zhí)行l(wèi)smod | grep ip_vs看模塊是否已加載沒(méi)有的話modprobe ip_vs試加載再確認(rèn)ip_vs_rr、ip_vs_wrr、ip_vs_sh、nf_conntrack這幾個(gè)依賴模塊都在。有些精簡(jiǎn)版系統(tǒng)鏡像會(huì)裁掉這些模塊那就得先換內(nèi)核或裝對(duì)應(yīng)模塊包這一步不通過(guò)后面全是空談。第二是kube-proxy 的部署形態(tài)。是以 DaemonSet 跑在集群里還是以系統(tǒng)服務(wù)跑在宿主機(jī)上前者改配置相對(duì)簡(jiǎn)單改 ConfigMap 重啟 Pod 即可后者要改節(jié)點(diǎn)上的配置文件并重啟服務(wù)回滾成本更高操作要更謹(jǐn)慎。兩種形態(tài)的配置路徑和生效方式完全不同搞混了會(huì)出現(xiàn)改了沒(méi)生效的迷惑現(xiàn)象。第三是集群里是否有依賴 iptables 規(guī)則做特殊攔截的組件。有些網(wǎng)絡(luò)插件或者安全組件會(huì)往 iptables 里插自定義鏈IPVS 模式下這些鏈的掛載點(diǎn)可能變化需要提前梳理。我遇到過(guò)某監(jiān)控組件依賴KUBE-SERVICES鏈做流量統(tǒng)計(jì)切 IPVS 后統(tǒng)計(jì)歸零后來(lái)改成從 IPVS 的統(tǒng)計(jì)接口取數(shù)才恢復(fù)。2. 改造方案設(shè)計(jì)與關(guān)鍵參數(shù)取舍2.1 整體切換思路先灰度后全量我的做法不是一把梭全集群切換而是先單節(jié)點(diǎn)灰度、再分批推進(jìn)。原因很直接kube-proxy 的模式切換會(huì)影響該節(jié)點(diǎn)上所有 Pod 的 Service 訪問(wèn)一旦出問(wèn)題影響面是整個(gè)節(jié)點(diǎn)而不是單個(gè)服務(wù)?;叶饶馨堰@個(gè)爆炸半徑控制住。具體節(jié)奏是挑一個(gè)非核心業(yè)務(wù)的節(jié)點(diǎn)把它上面的 kube-proxy 切成 IPVS觀察至少 24 小時(shí)重點(diǎn)看 Service 訪問(wèn)成功率、新建連接延遲、kube-proxy 自身資源占用、以及 IPVS 轉(zhuǎn)發(fā)表的條目數(shù)是否與預(yù)期 Service 數(shù)吻合。確認(rèn)穩(wěn)定后再按批次比如每次 20% 節(jié)點(diǎn)推進(jìn)每批之間留出觀察窗口。這個(gè)節(jié)奏看起來(lái)慢但比全量切換后回滾要快得多——回滾一次全集群的代價(jià)遠(yuǎn)比多等幾天大。2.2 調(diào)度算法與轉(zhuǎn)發(fā)模式的參數(shù)選擇kube-proxy 的 IPVS 配置里有幾個(gè)參數(shù)值得掰開說(shuō)。ipvs.scheduler決定調(diào)度算法默認(rèn)rr輪詢。如果你的后端 Pod 性能不均wrr加權(quán)輪詢配合權(quán)重更合適如果是需要會(huì)話保持的長(zhǎng)連接場(chǎng)景sh源地址哈希能讓同一源 IP 的請(qǐng)求固定落到同一后端。但要注意sh在 NAT 環(huán)境下可能因?yàn)樵吹刂繁桓膶懚в弥暗么_認(rèn)網(wǎng)絡(luò)路徑。ipvs.strictARP這個(gè)參數(shù)控制是否開啟嚴(yán)格 ARP 模式默認(rèn)false。當(dāng) ClusterIP 和后端 Pod 在同一網(wǎng)段、且存在 ARP 沖突風(fēng)險(xiǎn)時(shí)建議設(shè)為true避免節(jié)點(diǎn)響應(yīng)不屬于自己的 ARP 請(qǐng)求導(dǎo)致流量錯(cuò)亂。這個(gè)坑我在早期項(xiàng)目里踩過(guò)表現(xiàn)為偶發(fā)的 Service 訪問(wèn)超時(shí)抓包才定位到是 ARP 應(yīng)答搶答。ipvs.excludeCIDRs用來(lái)排除某些 CIDR 不走 IPVS 轉(zhuǎn)發(fā)典型場(chǎng)景是節(jié)點(diǎn)本地服務(wù)或者需要直連的網(wǎng)段。這個(gè)參數(shù)不設(shè)的話某些本地回環(huán)訪問(wèn)可能被錯(cuò)誤地卷進(jìn) IPVS 轉(zhuǎn)發(fā)出現(xiàn)訪問(wèn)不通的怪現(xiàn)象。參數(shù)默認(rèn)值推薦場(chǎng)景注意事項(xiàng)ipvs.schedulerrr通用場(chǎng)景長(zhǎng)連接考慮 sh性能不均考慮 wrripvs.strictARPfalse同網(wǎng)段部署建議 true避免 ARP 搶答導(dǎo)致流量錯(cuò)亂ipvs.excludeCIDRs空有本地直連需求時(shí)配置排除后該網(wǎng)段不走 IPVSipvs.tcpTimeout0沿用內(nèi)核默認(rèn)長(zhǎng)連接業(yè)務(wù)可調(diào)大過(guò)大會(huì)占用 conntrack 表ipvs.udpTimeout0沿用內(nèi)核默認(rèn)UDP 業(yè)務(wù)按需調(diào)整UDP 無(wú)連接狀態(tài)超時(shí)影響回收2.3 回滾預(yù)案的設(shè)計(jì)任何改造都要想好怎么退。IPVS 切回 iptables 的操作本身不復(fù)雜——把模式改回去、重啟 kube-proxy 即可但回滾的時(shí)機(jī)和觸發(fā)條件要提前定好。我給自己定的紅線是灰度節(jié)點(diǎn)上 Service 訪問(wèn)錯(cuò)誤率超過(guò) 0.5% 持續(xù) 5 分鐘或者出現(xiàn)大面積連接超時(shí)立即回滾該節(jié)點(diǎn)?;貪L前要保留現(xiàn)場(chǎng)ipvsadm -Ln的輸出、iptables-save的快照、kube-proxy 的日志這些是事后定位問(wèn)題的關(guān)鍵。我習(xí)慣在切換前先跑一遍ipvsadm -Ln /tmp/ipvs-before.txt和iptables-save /tmp/iptables-before.txt回滾后對(duì)比差異能快速判斷是配置問(wèn)題還是內(nèi)核問(wèn)題。3. 實(shí)操過(guò)程從配置修改到驗(yàn)證落地3.1 環(huán)境預(yù)檢與內(nèi)核模塊加載正式動(dòng)手前先在目標(biāo)節(jié)點(diǎn)上做一輪預(yù)檢。這一步不能省我見過(guò)太多人直接改配置重啟結(jié)果 kube-proxy 起不來(lái)日志里報(bào)cant find ip_vs module回頭再補(bǔ)模塊中間服務(wù)已經(jīng)抖了一輪。預(yù)檢命令按順序執(zhí)行# 檢查內(nèi)核版本建議 4.19 以上 uname -r # 檢查 IPVS 相關(guān)模塊是否已加載 lsmod | grep -E ip_vs|nf_conntrack # 若未加載手動(dòng)加載并驗(yàn)證 modprobe ip_vs modprobe ip_vs_rr modprobe ip_vs_wrr modprobe ip_vs_sh modprobe nf_conntrack # 再次確認(rèn)模塊狀態(tài) lsmod | grep ip_vs # 檢查 ipvsadm 工具是否可用用于后續(xù)驗(yàn)證 which ipvsadm || echo ipvsadm not installed如果modprobe報(bào)模塊找不到說(shuō)明內(nèi)核沒(méi)編進(jìn)這些模塊需要裝kernel-modules-extra之類的包或者換內(nèi)核。這一步過(guò)了再確認(rèn)ipvsadm工具在后面驗(yàn)證全靠它。注意模塊加載是臨時(shí)的重啟后會(huì)丟失。要持久化得把模塊名寫進(jìn)/etc/modules-load.d/ipvs.conf每行一個(gè)模塊名。別只modprobe完就以為萬(wàn)事大吉節(jié)點(diǎn)一重啟又回到解放前。3.2 修改 kube-proxy 配置的兩種路徑配置修改分兩種部署形態(tài)路徑完全不同先確認(rèn)你的集群是哪種。DaemonSet 形態(tài)kube-proxy 以 Pod 形式跑在kube-system命名空間。配置在 ConfigMapkube-proxy里改mode字段為ipvs并按需補(bǔ)上ipvs段。改完 ConfigMap 后需要重啟 kube-proxy 的 Pod 讓配置生效——注意是重啟 Pod不是滾動(dòng)更新 Deployment因?yàn)?kube-proxy 是 DaemonSet直接kubectl rollout restart daemonset/kube-proxy -n kube-system即可。apiVersion: v1 kind: ConfigMap metadata: name: kube-proxy namespace: kube-system data: config.conf: |- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs ipvs: scheduler: rr strictARP: true excludeCIDRs: [] # 其余字段保持原樣系統(tǒng)服務(wù)形態(tài)kube-proxy 以 systemd 服務(wù)跑在宿主機(jī)配置文件通常在/var/lib/kube-proxy/config.conf或/etc/kubernetes/kube-proxy-config.yaml具體路徑看你的部署方式。改完mode: ipvs后systemctl restart kube-proxy重啟服務(wù)。這種形態(tài)下沒(méi)有 ConfigMap 兜底改之前務(wù)必先備份原文件cp config.conf config.conf.bak回滾時(shí)直接覆蓋回去。兩種形態(tài)改完后都要確認(rèn) kube-proxy 日志里出現(xiàn)類似Using ipvs Proxier的字樣這是模式生效的直接證據(jù)。如果日志還是Using iptables Proxier說(shuō)明配置沒(méi)被讀到檢查路徑和字段名是否寫對(duì)。3.3 驗(yàn)證 IPVS 轉(zhuǎn)發(fā)表與 Service 連通性配置生效后驗(yàn)證分三層轉(zhuǎn)發(fā)表、連通性、性能。第一層看轉(zhuǎn)發(fā)表。ipvsadm -Ln會(huì)列出所有 virtual service 和對(duì)應(yīng)的 real server。正常情況下集群里有多少個(gè) Service含 ClusterIP 類型這里就該有多少條 virtual service 記錄每條下面掛著對(duì)應(yīng)的后端 Pod 地址和端口。如果條目數(shù)明顯偏少可能是 kube-proxy 還沒(méi)同步完等幾十秒再看如果一直偏少查 kube-proxy 日志有沒(méi)有同步報(bào)錯(cuò)。# 查看 IPVS 轉(zhuǎn)發(fā)表 ipvsadm -Ln # 統(tǒng)計(jì) virtual service 數(shù)量與集群 Service 數(shù)對(duì)比 ipvsadm -Ln | grep -c TCP\|UDP # 查看某個(gè)具體 Service 的后端分布 ipvsadm -Ln | grep -A 5 10.96.0.10第二層看連通性。在節(jié)點(diǎn)上直接訪問(wèn)一個(gè) ClusterIP 的 Service確認(rèn)能通再?gòu)?Pod 內(nèi)部訪問(wèn)確認(rèn)跨節(jié)點(diǎn)也通。我習(xí)慣用curl打一個(gè)已知的 HTTP 服務(wù)配合-w輸出耗時(shí)順便看延遲是否正常。如果訪問(wèn)不通先ipvsadm -Ln看該 Service 有沒(méi)有對(duì)應(yīng)條目再看后端 Pod 是否 Ready逐層排除。第三層看性能。對(duì)比切換前后的新建連接延遲和 kube-proxy 資源占用。我用的方法是ab或wrk對(duì)某個(gè) Service 壓一輪記錄 P99 延遲同時(shí)top看 kube-proxy 進(jìn)程的 CPU。正常情況下 IPVS 模式下 kube-proxy 的 CPU 應(yīng)該明顯低于 iptables 模式延遲抖動(dòng)也更小。3.4 灰度推進(jìn)與全量切換的節(jié)奏控制單節(jié)點(diǎn)驗(yàn)證通過(guò)后進(jìn)入灰度推進(jìn)。我的做法是按節(jié)點(diǎn)分批每批之間留觀察窗口。具體操作上如果是 DaemonSet 形態(tài)可以通過(guò)給節(jié)點(diǎn)打標(biāo)簽配合 nodeSelector 控制哪些節(jié)點(diǎn)先跑新配置或者直接分批重啟不同節(jié)點(diǎn)上的 kube-proxy Pod用kubectl delete pod指定節(jié)點(diǎn)上的那個(gè) Pod讓它重建時(shí)讀到新 ConfigMap。每批推進(jìn)后重點(diǎn)盯三個(gè)指標(biāo)Service 訪問(wèn)錯(cuò)誤率、節(jié)點(diǎn)上ipvsadm -Ln的條目數(shù)是否與預(yù)期一致、kube-proxy 日志有無(wú)異常。這三個(gè)指標(biāo)穩(wěn)定 24 小時(shí)再推下一批。全量切換完成后別急著收工再觀察一周因?yàn)橛行﹩?wèn)題比如長(zhǎng)連接超時(shí)、conntrack 表滿需要時(shí)間才暴露。提示灰度期間如果發(fā)現(xiàn)某節(jié)點(diǎn)異?;貪L只需把該節(jié)點(diǎn)的 kube-proxy 配置改回iptables并重啟不影響其他已切換節(jié)點(diǎn)。這種按節(jié)點(diǎn)獨(dú)立回滾的能力是灰度策略最大的價(jià)值別為了省事跳過(guò)灰度。4. 常見問(wèn)題與排查技巧實(shí)錄4.1 切換后 Service 訪問(wèn)不通的排查路徑這是最高頻的問(wèn)題排查要按層走別一上來(lái)就瞎改。我的排查順序是先看 IPVS 表有沒(méi)有該 Service 的條目再看后端 real server 是否健康最后看網(wǎng)絡(luò)路徑和 conntrack。如果ipvsadm -Ln里壓根沒(méi)有目標(biāo) Service 的條目問(wèn)題在 kube-proxy 同步層——查 kube-proxy 日志有沒(méi)有Failed to sync之類的報(bào)錯(cuò)常見原因是 Service 的某些字段 IPVS 不支持比如某些特殊協(xié)議的端口名或者 kube-proxy 權(quán)限不足調(diào)不了 netlink。如果條目在但后端為空說(shuō)明 Endpoints 沒(méi)同步過(guò)來(lái)查 Service 的 selector 是否匹配到 Pod、Pod 是否 Ready。如果條目和后端都在但訪問(wèn)不通就要看 conntrack 和網(wǎng)絡(luò)路徑了conntrack -L | grep ClusterIP看有沒(méi)有異常狀態(tài)的連接tcpdump抓包看包到底走到了哪。現(xiàn)象可能原因排查命令處理方式IPVS 表無(wú)該 Servicekube-proxy 同步失敗查 kube-proxy 日志修復(fù)配置或權(quán)限后重啟有條目但后端為空Endpoints 未同步kubectl get endpoints檢查 selector 和 Pod 狀態(tài)條目后端正常但訪問(wèn)超時(shí)conntrack 或網(wǎng)絡(luò)路徑問(wèn)題conntrack -L、tcpdump清理 conntrack 或查路由偶發(fā)訪問(wèn)失敗ARP 沖突或 strictARP 未開arping、查 strictARP 配置開啟 strictARP長(zhǎng)連接頻繁斷開tcpTimeout 過(guò)短查 ipvs.tcpTimeout調(diào)大超時(shí)值4.2 conntrack 表滿導(dǎo)致的連接丟棄IPVS 依賴 conntrack 做連接跟蹤表滿時(shí)會(huì)直接丟新連接表現(xiàn)為偶發(fā)訪問(wèn)失敗或新建連接超時(shí)。這個(gè)問(wèn)題在 iptables 模式下也存在但 IPVS 模式下因?yàn)檗D(zhuǎn)發(fā)路徑變化暴露得更明顯。排查方法是conntrack -C看當(dāng)前條目數(shù)sysctl net.netfilter.nf_conntrack_max看上限。如果當(dāng)前數(shù)逼近上限就是表滿了。處理分兩步短期調(diào)大nf_conntrack_max長(zhǎng)期優(yōu)化nf_conntrack_tcp_timeout_established等超時(shí)參數(shù)讓空閑連接更快回收。# 查看當(dāng)前 conntrack 條目數(shù)和上限 conntrack -C sysctl net.netfilter.nf_conntrack_max # 臨時(shí)調(diào)大上限重啟失效 sysctl -w net.netfilter.nf_conntrack_max1048576 # 持久化寫入 echo net.netfilter.nf_conntrack_max1048576 /etc/sysctl.conf注意調(diào)大 conntrack 上限會(huì)占用更多內(nèi)存按節(jié)點(diǎn)內(nèi)存量力而行。一般每 1 萬(wàn)條目約占用幾 MB百萬(wàn)級(jí)條目要預(yù)留幾百 MB 內(nèi)存別在小內(nèi)存節(jié)點(diǎn)上盲目調(diào)大。4.3 內(nèi)核模塊缺失與版本不匹配模塊缺失的表現(xiàn)是 kube-proxy 起不來(lái)日志報(bào)cant find ip_vs或failed to load ip_vs。前面預(yù)檢章節(jié)已經(jīng)講了怎么加載這里補(bǔ)充一個(gè)容易忽略的點(diǎn)模塊版本要和內(nèi)核版本匹配。有些節(jié)點(diǎn)升級(jí)過(guò)內(nèi)核但沒(méi)重啟uname -r顯示的是新內(nèi)核實(shí)際跑的還是舊內(nèi)核modprobe加載的模塊和運(yùn)行內(nèi)核不匹配會(huì)出現(xiàn)加載成功但功能異常的情況。確認(rèn)方法是對(duì)比uname -r和uname -a的輸出以及/lib/modules/$(uname -r)目錄是否存在。如果目錄不存在說(shuō)明當(dāng)前運(yùn)行內(nèi)核沒(méi)有對(duì)應(yīng)模塊需要重啟到新內(nèi)核或者裝匹配的模塊包。這個(gè)坑比較隱蔽因?yàn)閙odprobe可能不報(bào)錯(cuò)但 IPVS 功能就是不對(duì)。4.4 從 IPVS 回退 iptables 的注意事項(xiàng)回退操作本身簡(jiǎn)單但有幾個(gè)細(xì)節(jié)要注意。第一回退前先ipvsadm -C清空 IPVS 表避免殘留條目干擾第二回退后 iptables 規(guī)則會(huì)重新全量生成大規(guī)模集群下這個(gè)同步過(guò)程可能耗時(shí)幾秒到幾十秒期間 Service 訪問(wèn)會(huì)有短暫抖動(dòng)要選低峰期操作第三回退后確認(rèn)iptables-save的規(guī)則數(shù)恢復(fù)到切換前的量級(jí)如果明顯偏少說(shuō)明 kube-proxy 還沒(méi)同步完再等等?;赝撕髣e急著刪 IPVS 模塊留著不影響 iptables 模式運(yùn)行萬(wàn)一還要再切回去省得重新加載。真正要清理的是那些為 IPVS 臨時(shí)加的 sysctl 參數(shù)如果確認(rèn)不再用 IPVS再考慮回滾這些參數(shù)。5. 我踩過(guò)的坑與實(shí)操心得5.1 strictARP 沒(méi)開導(dǎo)致的偶發(fā)超時(shí)這個(gè)坑值得單獨(dú)說(shuō)。早期一次切換后Service 訪問(wèn)出現(xiàn)偶發(fā)超時(shí)概率大概千分之幾抓包發(fā)現(xiàn)是節(jié)點(diǎn)對(duì)不屬于自己的 ClusterIP 做了 ARP 應(yīng)答導(dǎo)致流量被引到錯(cuò)誤節(jié)點(diǎn)。根因是strictARP沒(méi)開節(jié)點(diǎn)在 ARP 層面搶答了。開啟strictARP: true后問(wèn)題消失。這個(gè)參數(shù)的作用是讓節(jié)點(diǎn)只對(duì)屬于自己的 IP 做 ARP 應(yīng)答避免同網(wǎng)段下的 ARP 沖突。如果你的集群節(jié)點(diǎn)和 Pod 在同一網(wǎng)段或者用了某些會(huì)廣播 ARP 的網(wǎng)絡(luò)方案強(qiáng)烈建議開啟。代價(jià)是幾乎沒(méi)有收益是消除一類隱蔽的偶發(fā)故障。5.2 調(diào)度算法選錯(cuò)導(dǎo)致的長(zhǎng)連接抖動(dòng)默認(rèn)的rr輪詢?cè)诙踢B接場(chǎng)景很均衡但長(zhǎng)連接場(chǎng)景下如果后端 Pod 數(shù)量變化擴(kuò)縮容輪詢會(huì)把新連接分到新 Pod而老連接還掛在老 Pod 上導(dǎo)致負(fù)載不均。我遇到過(guò)一次擴(kuò)容后新 Pod 幾乎沒(méi)流量、老 Pod 被打滿的情況就是rr在長(zhǎng)連接下的典型表現(xiàn)。后來(lái)對(duì)長(zhǎng)連接業(yè)務(wù)單獨(dú)用sh源地址哈希同一源 IP 固定落到同一后端擴(kuò)縮容時(shí)影響面可控。但sh也有代價(jià)——后端 Pod 數(shù)量變化時(shí)哈希映射會(huì)重排部分連接會(huì)重新分布所以擴(kuò)縮容要配合優(yōu)雅下線讓老連接自然結(jié)束。5.3 監(jiān)控指標(biāo)要跟著換切 IPVS 后原來(lái)基于 iptables 規(guī)則數(shù)做的監(jiān)控會(huì)失效因?yàn)橐?guī)則數(shù)驟降不代表異常。要補(bǔ)上 IPVS 維度的監(jiān)控ipvsadm -Ln --stats能拿到每個(gè) virtual service 的連接數(shù)、包量、字節(jié)量這些才是判斷轉(zhuǎn)發(fā)是否正常的依據(jù)。我習(xí)慣把ipvsadm -Ln --stats的輸出定期采集對(duì)比 Service 數(shù)量和連接分布異常時(shí)能快速定位是哪個(gè) Service 出了問(wèn)題。另外 kube-proxy 自身的 metrics 里IPVS 模式下的kubeproxy_sync_proxy_rules_duration_seconds和kubeproxy_sync_proxy_rules_last_timestamp_seconds依然有效可以用來(lái)判斷同步是否及時(shí)。這兩個(gè)指標(biāo)在切換后要重點(diǎn)盯同步延遲變大往往意味著 kube-proxy 壓力上來(lái)了。5.4 節(jié)點(diǎn)重啟后的模塊持久化前面提過(guò)模塊加載是臨時(shí)的這里再?gòu)?qiáng)調(diào)一次。節(jié)點(diǎn)重啟后如果模塊沒(méi)持久化kube-proxy 起不來(lái)整個(gè)節(jié)點(diǎn)的 Service 訪問(wèn)全掛。持久化方法是在/etc/modules-load.d/下建配置文件把ip_vs、ip_vs_rr、ip_vs_wrr、ip_vs_sh、nf_conntrack都寫進(jìn)去每行一個(gè)。這個(gè)文件在系統(tǒng)啟動(dòng)早期被讀取確保模塊在 kube-proxy 啟動(dòng)前就加載好。我現(xiàn)在的習(xí)慣是任何涉及內(nèi)核模塊的改造第一件事就是把持久化配置寫好再談其他。因?yàn)楣?jié)點(diǎn)重啟是遲早的事臨時(shí)加載的模塊撐不過(guò)一次重啟這個(gè)坑踩一次就夠記一輩子。6. 改造后的效果與后續(xù)可擴(kuò)展方向全量切換并穩(wěn)定運(yùn)行一個(gè)月后我復(fù)盤了幾個(gè)關(guān)鍵數(shù)據(jù)kube-proxy 的 CPU 占用從平均單核 40% 降到 8% 左右規(guī)則同步耗時(shí)從秒級(jí)降到百毫秒級(jí)Service 訪問(wèn)的 P99 延遲抖動(dòng)明顯收斂。這些收益在 Service 規(guī)模繼續(xù)增長(zhǎng)時(shí)會(huì)進(jìn)一步放大因?yàn)?IPVS 的轉(zhuǎn)發(fā)開銷不隨 Service 數(shù)量線性增長(zhǎng)。后續(xù)可擴(kuò)展的方向一個(gè)是把 IPVS 的調(diào)度算法按業(yè)務(wù)類型做精細(xì)化配置比如在線業(yè)務(wù)用rr、長(zhǎng)連接用sh、批處理用wrr通過(guò) kube-proxy 的配置分段或者按命名空間做差異化另一個(gè)是結(jié)合 IPVS 的統(tǒng)計(jì)接口做更細(xì)粒度的流量觀測(cè)把每個(gè) Service 的連接數(shù)、包量納入監(jiān)控大盤替代原來(lái)基于 iptables 規(guī)則的觀測(cè)方式。這兩件事都不難難的是先把模式切換這步走穩(wěn)后面的優(yōu)化才有基礎(chǔ)。我個(gè)人在實(shí)際操作中的體會(huì)是kube-proxy 換 IPVS 這件事技術(shù)難度不高難在節(jié)奏控制和細(xì)節(jié)確認(rèn)。預(yù)檢做扎實(shí)、灰度不跳過(guò)、回滾預(yù)案想清楚這三件事做到位切換過(guò)程基本不會(huì)出大問(wèn)題。真正容易翻車的地方往往是那些看起來(lái)不起眼的參數(shù)比如 strictARP和持久化配置比如模塊加載它們平時(shí)不顯山露水出問(wèn)題時(shí)卻能讓整個(gè)節(jié)點(diǎn)網(wǎng)絡(luò)癱瘓。把這些細(xì)節(jié)提前想到、做到比事后救火省心得多。