萬字解讀云原生時代,如何從 0 到 1 構建 K8s 容器平臺的 LB(Nginx)負載均衡體系
云原生時代,基于 Kubernetes 的容器編排方案是當下最優選擇,各個中型、大型互聯網公司全都擁抱 Kubernetes,沒有其他方案可以與 Kubernetes 匹敵。
所有業務(尤其是高并發業務)的訪問必然要通過負載均衡 LB 代理層,服務端高并發系統離不開負載均衡,大中型公司下,負載均衡代理層都是有專人進行獨立開發和建設的,云原生 Kubernetes 容器平臺下的 LB 代理層,同樣需要有專人來負責建設和維護。那么 Kubernetes 容器平臺基礎下的的 LB(Nginx) 負載均衡代理層要怎么建設?和非容器平臺下的 LB 建設有什么異同?建設的核心要點和當下最優的方案是什么?相信看完本文,都會對 Kubernetes 容器平臺的 LB(Nginx)負載均衡了然于心,并且可以快速深入建設 Kubernetes LB(Nginx)負載均衡體系。還可以了解到,一個中大型公司,是如何從 0 到 1 來構建大規模 Kubernetes 容器平臺的 LB(Nginx)負載均衡體系的一些非常寶貴的實戰經驗。
適應人群 :Kubernetes 開發者、LB 開發者、Kubernetes 基礎運維人員、LB(Nginx)從業者、容器平臺開發 or 架構設計人員。
一、容器 LB 建設的背景
PS:如果對 Kubernetes 基本概念還不熟,那么需要先理解一下 Kubernetes,本文是針對對 Kubernetes 基本概念有一定理解的基礎上來進行分析和設計。
1.初識負載均衡(LB)
負載均衡(Load Balancer,簡稱 LB)是指把客戶端訪問的流量通過負載均衡器,然后根據指定的一些負載均衡策略進行轉發,最終可以均勻的分攤到后端上游服務器上,然后上游服務器進行響應后再返回數據給客戶端。負載均衡的最常見應用是充當反向代理,通過負載均衡,可以大大的提高服務的響應速度、提高并發請求、提高穩定性(防止單點故障)。
- 負載均衡的基本實現方案,從業界來看,一般分為軟件和硬件兩大類,軟件負載均衡又可以分層如4層、7層負載均衡,如下:
- 硬件負載均衡
- 如 F5,性能好,但是貴。一般的互聯網公司都沒有采集硬件負載均衡
- 軟件負載均衡
- 目前這兩個都可以實現 4 層,但是更多的還是使用 Nginx 的 7 層功能。
- 4 層:典型的如 LVS
- 7 層:典型的如 Nginx、HAProxy
2.容器化下 LB 的異同點
在物理機時代,還沒有容器化之前,典型的負載均衡的建設方案就是搭建一套 Nginx 集群,提供 7 層的代理;搭建一套 LVS 集群,提供 4 層代理方案。并且同時,一般 7 層之上,都有一個 4 層代理,流量的基本流向就是 client -> LVS(4 層) -> Nginx(7層) -> server 。
在物理機這個時代,運維人員對 Nginx 的 upstream 的配置,基本都是手動添加修改各個 server,然后推送配置上線應用。傳統的物理機時代的維護方式,是基于后端 server 的 IP 基本是固定的,比如,你上線一個 WebServer 的服務,要部署到哪些機器上,這個是事先確定好的了,IP 會固定不變,不管你怎么升級,服務都還是固定在這些機器上,因此這個時代這樣的維護方式,并沒有太多問題,大家以往也都維護的挺和諧。
在容器化時代,基于 Kubernetes 的容器化平臺下,LB 的建設有哪些差異呢?主要分為兩大塊:
? 后端服務的 IP,會由于集群的調度,IP 是可變的,每當你部署、升級等操作的時候,IP 都會改變,那么這個時候,我們顯然不能夠再繼續采用原有寫死 IP 的方式來進行 7 層代理的維護了。由于服務 IP 的不確定性,我們必須要改變姿勢,不能由人為填充 Nginx 的 upstream 的 server ip 的方式,只能通過動態的獲取和變更,這個就需要 LB 能夠主動發現后端服務并且動態更新
? Kubernetes 的容器化平臺下,集群內部的網絡是虛擬的,虛擬網絡的 IP 在集群外部是無法訪問的,因此還需要解決好容器集群內外的網絡互通問題。
二、容器 LB 負載均衡怎么建設
1.Kubernetes 的負載均衡
Kubernetes 本身有內置一個集群內部的負載均衡方案,叫 kube-proxy,但是這個只能內部訪問,并且功能稍顯不足;而實際上,我們的容器平臺,必須要提供集群外部訪問的功能,因為你的用戶(客戶端)都是在集群外部。
Kubernetes 負載均衡相關的方案,包括:
- 集群內部負載均衡【內置】
Pod IP 在集群內部都是互通的,因此集群內部無需考慮網絡互通問題
每個 Node 節點上的 kube-proxy,就是集群內置的內部負載均衡的解決方案;但是只限于集群內部,并且功能有限
- 集群外部負載均衡【額外添加】
社區提供的 nginx-ingress-controller 方案可以滿足需求
云廠商的 Cloud provider 也可以滿足需求
參考 nginx-ingress-controller 的模式,自建 LB 方案
由此可見,如果是在自己 IDC 內部建設容器 LB 方案,那么只能采用自建方案 或者基于 nginx-ingress-controller 方案來建設;如果是上云的話,那么可以自建,也可以直接采用云廠商的方案。
下面所有的介紹,都是基于自建方案來設計,在 IDC 內部,我們要怎么從 0 到 1 來建設 K8s 容器的 LB 體系。
2.業務需求
業務功能需求就在于,業務(開發)使用容器 LB 體系的時候,他們會需要哪些需求,包括怎么使用、需要哪些功能、需要哪些策略,作為容器 LB 建設的開發人員,我們需要能夠站在業務方的角度去考慮,如下圖所示,有這些業務需求:
詳細說明如下:
- 體驗需求
LB 分組:這個業務非常核心,需要獨立的 LB 集群,也就是 LB 代理層需要分組
域名解析線路:如果是多集群、多 IDC,那么服務暴露的域名,要怎么解析,是全 IDC 都解析,還是只解析到某一個集群
7 層代理的一些高級配置,如 uri 的 rewrite 規則、自定義一些特殊配置
大部分用戶:業務要暴露自己的服務只需要足夠簡單的配置和理解,他們不需要也不想關注服務暴露的細節,要的就是一個結果,我的服務部署了,我要暴露出去給 client 端調用
小眾用戶:業務非常核心,有各種不確定因素存在,業務開發人員需要關注細節
- 負載均衡代理層的常規功能需求
要能夠統計 SLA ,包括 QPS、慢請求、錯誤數 等
要能夠針對異常進行告警
要能夠支持常見的負載均衡算法,如輪詢、最小連接、hash 等
負載均衡代理層要能夠支持超時、重試等基本功能
負載均衡代理層還必須要能夠支持對后端服務的健康檢查
基本的服務暴露:支持 4 層、7 層的代理方案,支持 7 層的 HTTP、HTTPS,也支持基本的 PATH 路由
域名:服務暴露的時候,每個服務肯定需要有自己的域名,那么這個域名需要能夠支持默認按照一定規則生成,還需要能夠支持自定義域名;具體怎么選擇就看業務自己的需求
內外網的需求:有些業務是直接給 APP 調用的,那么必然需要暴露到外網;而有些業務只是需要集群內部訪問,那么就暴露到內網即可;
upstream 上游(后端)服務的基本策略
監控和統計
- 負載均衡代理層的高級策略需求
限流策略:高可用服務必須要有的功能,通過 LB 代理層進行限流,防止流量太大從而導致后端過載引發整體故障
熔斷保護機制:當服務發現異常,并且通過限流還不能解決的時候,需要能夠直接熔斷,也就是直接斷開請求,防止影響到其他業務
灰度放量:當業務新上線一個功能(版本迭代)的時候,首先需要進行灰度放量,然后觀察,看是否滿足預期,如果滿足預期則繼續灰度放量;如果有異常則需要馬上回滾
3.運維需求
我們建設的容器 LB 方案,最終是要交付給運維同學去使用的,運維必須要把控好整個公司的流量入口,LB 就是整個公司的流量入口;而且一般業務同學也沒有權限去操作 LB 相關的配置。那么,站在運維的角度來看,容器 LB 需要提供哪些功能呢?如下圖所示,有這些運維需求:
詳細說明如下:
- 負載均衡器的相關管理
負載均衡器的自動化腳本部署,因為運維需要部署負載均衡器,那么怎么樣能夠實現更為智能的自動化腳本部署,而不是零散的各個命令去操作呢?這塊依賴于我們提供的一些操作步驟和子命令,然后結合 ansible 來封裝實現
負載均衡器的擴縮容,部署完了之后,后續還可能有擴縮容需求,比如國慶期間、春節期間、大促期間,這是需要提前擴容的,那么怎么能夠快速擴縮容?怎么更自動化?這塊同樣也是需要結合 ansible 來封裝實現
負載均衡器的分組,對運維而言,穩定性是首要的,那么線上的業務,有重要的服務,也有非重要的服務,一般而言,對重要核心的服務、流量非常大的服務,都需要單獨的分組,用來進行物理上的隔離和管控
- 權限管控和審計
權限,一般而言,公司建設 Kubernetes 容器平臺,都會有一套管理平臺系統,所有人都是通過管理平臺來操作,包括運維和開發。如部署業務服務、上下線、LB 的操作和管理等等。那么既然是這樣,那么必須要控制好權限,不同角色有不同的操作權限,避免所有人都能夠操作負載均衡的相關配置,只有管理員 或者 運維人員才能夠操作
審計,線上的所有變更,都需要有審計,方便回溯問題
- 業務服務的配置操作
Nginx 負載均衡的基本配置檢測,要能夠通過管理平臺來實現,包括基本檢測和異常檢測,檢測通過才能執行變更
Nginx 負載均衡配置的灰度和回滾機制,灰度是說變更之前,需要先灰度 1 個 Nginx 節點,確保這次變更沒有問題之后,才能全量變更;回滾是說如果灰度出現問題,那么需要能夠快速回滾到上一個版本
Nginx 負載均衡配置的基本查看、搜索;可以全局管理所有配置;可以搜索關鍵字來快速定位配置
- 穩定性的相關操作(流控)
業務限流,當業務流量過大之后,根據實際情況進行限流,避免打滿后端服務
灰度放量,業務更新之前需要一個灰度逐步放量的過程
- LB 系統和域名管理系統打通
中大型公司而言,都會有內部的域名管理系統,每個服務都會有一個對外暴露的域名來訪問,那么域名管理系統必須要和 LB 系統打通并且聯動起來,形成一個完整的操作鏈。這就需要用戶暴露一個服務的時候,并不用事先申請域名,直接在 LB 系統這里進行申請即可。
4.基本方案和基本原則
Kubernetes 下,后端服務都是 Pod 的形態,Pod 要能夠實現對外的負載均衡,就必須要成為 nginx 的 upstream。而 Pod 的 IP 是隨時都可能變化的,為此,就需要一個 Nginx-Controller 來動態發現 Pod,然后渲染為 nginx 的 upstream;Nginx-Controller 就是一個 Nginx 再加上一個 Controller(發現 Pod 并渲染為 upstream)。
所以,就需要我們能夠自研一個 Nginx-Controller 組件來實現了,那么這個 Nginx-Controller 有些什么要求 ?
A,集群內外的網絡要能互通
基本要求就是:
- 集群內,Nginx-Controller 要能夠將流量分發給 Pod
需要將 Nginx-Controller 納入到 Kubernetes 的節點中,也就是部署 Nginx-Controller 的機器必須是 Kubernetes 的 Node 節點
- 集群外,外網的請求要能夠轉發到 Nginx-Controller 中
這就需要部署 Nginx-Controller 的機器能夠和外部互通,一個最簡單的方式就是,Nginx-Controller 采用二進制部署,使用 Node 主機的網絡,這樣就可以了
因為 Node IP 是互通的,只有 Pod IP 不互通
B,動態發現 Pod 并且渲染為 nginx 配置
首先,我們需要能夠 watch 到 Pod、Service、 Endpoints 等資源的變化,這個就需要和 K8s API Server 交互,一般我們現在都是使用 Golang 語言來實現,因此可以基于官方的 client-go 來實現
在這,我們需要提供一套統一的模板配置,方便業務配置,然后自動渲染。因為 Nginx-Controller 要 watch 的業務服務資源是未知的,隨時可以增加或者刪除,那么最好能夠有一套模板機制來實現,對于 Golang,可以通過 Golang 的 template包來封裝模板的實現,結合模版和當前 Service、Endpoints 的情況,渲染成對應的 nginx 配置。比如:
會渲染成相應服務的節點列表和端口:
C,實現灰度、全量、回滾的機制
Nginx-Controller 雖然可以動態渲染 nginx 配置了,但是作為線上服務,必須需要有灰度、全量、回滾的機制。
因為我們的容器 LB 是需要分組的,每一組 LB 也都會有多個 nginx 節點,灰度就是指,我們的配置要發布,首先灰度一個節點,確保這個節點 OK 之后,再灰度到下一個 nginx 節點,或者可以全量到所有 nginx 節點。回滾則是指當我們灰度一個節點之后發現有問題,則回滾這個節點的配置。
怎么實現呢?可以通過兩個 configmap 來解決灰度和全量更新的問題,configmap-canary 這個作為灰度的 configmap,并且通過 annotation 來標記哪些是要灰度的 nginx 節點的 IP,這樣 nginx controller 如果識別到configmap-canary 里面的變化,則通過 annotation 的 IP 來判斷是否是本節點的,如果是本節點的則渲染配置并且 reload nginx,從而生效,如果不是本節點的,那么則丟棄。當要全量的時候,則:
- 首先,將所有的全量節點追加到 configmap-canary 的annotation["ip"]字段中,nginx-controller 讀取該字段,匹配ip字段,匹配節點更新配置
- 然后,如果確保已經全量成功,那么則先將 configmap-canary 的內容覆蓋到 configmap-release 中,然后再清空 configmap-canary 中的 IP 列表;這樣就可以完成整個灰度和全量的過程。
如果灰度的時候,發現異常了,需要回滾,那么直接清空 configmap-canary 中的 IP 列表;然后再回滾到上一個版本后,重新再走一遍發布流程來完成回滾操作
D,容器 LB 組件本身的管理和部署
上面說到容器 LB 組件本身(Nginx-Controller)需要二進制部署到 Node 主機上,那么要合理的管理這種二進制部署的需要一直運行的程序,一個較常見并且優雅的姿勢就是通過 systemd 來管理。示例配置如下:
只要將這個配置放到 /usr/lib/systemd/system/ 中,systemd 就可以管理起來了。
E,各種統計和監控
Nginx-Controller 代理層所需的監控包括如下:
- 進程的監控
進程是否存活、是否出現 panic 等
- 日志監控
日志首先要采集,然后要對錯誤日志進行監控,可以使用 ELK
- 基本指標監控
Nginx-Controller 的一些基本指標監控,可以使用 Prometheus
比如 reload 次數、更新次數、更新是否失敗 等。。。。
- LB 所在主機的機器性能監控
CPU:idle、system、user 等指標
網卡軟中斷
網絡帶寬:流入和流出帶寬指標、網卡丟包指標
內存使用、swap 使用
磁盤 IO:讀、寫兩方面
剩余句柄數
- LB 代理層的基本業務指標監控
SLA
錯誤統計
延遲統計
域名維度、path 維度等
三、容器 LB 體驗優化(LB 架構產品設計)
1.初期的架構圖
我們既然是從 0 到 1 來構建 K8s 的 負載均衡體系,那么初期必然是需要從物理機轉向容器,一般的選擇是為了能夠保證項目可以正常實施,容器 LB 這塊的抉擇,會結合著運維同學的一些習慣、可接受性以及更少的改動、更高的穩定性來做一些架構上的取舍。
沒有容器化之前,7 層代理的架構一般是 client -> CDN -> LVS -> 物理機 Nginx -> server ;
為了滿足上述訴求,在容器化之初,容器 LB 可能還不穩定,需要逐步導量過來,因此整體架構會是client -> CDN -> LVS -> 物理LB -> 容器LB(Nginx-Controller) -> POD ,如下:
LVS 和 Nginx 都需要做高可用,因此:
- LVS 就是通過 keepalive 本身來做高可用,并且 LVS 需要配置萬兆網卡,因為所有流量都要經過 LVS。
- Nginx 的高可用和高并發就是建立一組 Nginx(多個 Nginx 實例),然后掛到 LVS 下面做心跳檢測和流量分發
LVS 4 層代理可以對 Nginx 做檢測來保證高可用
LVS 4 層代理可以基于 4 層做流量分發到 Nginx 上
- 容器 LB(Nginx-Controller) 和 Pod 的網絡需要能夠互通,因此 容器 LB 也需要建立在 Kubernetes 集群之內,在同一個網絡架構下
Kubernetes 容器平臺的網絡可以選擇 Calico
2.最優的架構圖
在項目中后期,容器 LB 傾向穩定之后,那么我們要考慮的就是性能問題、成本問題、體驗問題了,為此,架構需要逐步演進。
- 首先,物理機 Nginx 的存在,會導致多了一層鏈路
增加響應耗時
增加配置管理的復雜度
增加問題排查的鏈路分析
增加機器成本
- 其次,Nginx-Controller 這個方案,有更優的替代方案,那就是nginx-ingress-controller
整體的最優的架構流向就是: client -> CDN -> LVS -> Nginx-Ingress-Controller -> Pod
Nginx-Ingress-Controller 的具體介紹在后面章節進行分析。
3.體驗優化
優化 1:實現動態 upstream,減少 Nginx Reload 帶來的 502
為何需要支持動態 upstream 呢?這是因為,在 K8s 下,服務的 Pod IP 會經常改變,比如每次發布更新的時候 Pod IP 都會變化,這也就意味著,nginx 的 upstream 的 server 列表會經常改變,那么每次 IP 有變化的時候,nginx 都需要 reload 的話,那么在線上高并發、大流量的場景下,長連接的服務會經常在 nginx reload 的時候出現 502,這個是不能接受的,非常影響業務的 SLA
那么為何長連接的服務會經常在 nginx reload 的時候出現 502 呢?這個要重點分析下 nginx 在進行 reload 的時候,對于老連接是怎么處理的,一個確定的流程是:
- 如果當前連接是空閑狀態,那么直接關閉
- 如果當前連接還在等待 upstream response,那么會等待請求處理結束或者超時 (proxy_read_timeout),再關閉
這一過程對于短連接的請求,是挺合理的,表現也挺正常的。但是對于長連接場景,nginx 有些處理不好的地方。對于長連接請求,nginx 在處理完最后一個請求,返回 response 的時候,他依然是返回 Connection: keepalive 的 response header。這樣就會導致會有一個時間窗口差,在 nginx 對于這個連接進行 close 以及到 Linux 內核完整 close 這個連接,并且發出 FIN 到 client 這個時間段內,client 端如果是高并發的場景,那么由于是長連接,因此很也可能會繼續復用這個連接來發起新的請求給 Nginx,這樣 Nginx 機器所在的 Linux 內核看到對于一個已關閉的連接還有新的請求,那么就會直接返回 RST 包,從而導致了 client 的一些 502 的錯誤。
優化 2:實現 SlowStart 功能,減少 Pod 啟動初期的 SLA 性能下降
SlowStart 策略,指的是,在 Pod 初次啟動并且能夠對外提供服務之后,剛開始給一個緩沖時間,在這個緩沖時間內,先提供小流量的請求,進行有 weight 權重的 RR 算法,只允許非常小比例的流量;這個緩沖時間之后,再開始無權重的 RR 算法。
一般而言,Pod 的 Readiness 探針是可 worker 之后,就認為這個 Pod 可以開始對外提供服務了。但是針對某些 Java 服務,Readiness 探針 OK 后,還不能馬上提供大量服務,因為 Java 需要啟動 Java 虛擬機,初始化相關系統、組件;還有一些各種內存池、線程池 等初始化工作要做;而這些初始化工作在某些情況下可能需要一點耗時;或者某些情況下是有請求過來后才進行初始化,但是由于初始化需要時間,因此 Readiness 探針 OK 之后,還不能馬上提供大量服務,否則在啟動的時候就可能造成服務的些許不穩定,從而降低 SLA,給業務帶來影響。這個是我們實際 Java 項目所得出的結論,因為 jit 的影響,如果在低流量下完成 jit 編譯,這樣給一個緩沖時間,最終效果就是可以提高 SLA。目前這個功能其實是一個規避措施,按理來說需要業務方自己解決的,因為不同的業務方可能情況也有些區別。
具體怎么實現呢?這就要結合 Kubernetes 本身機制來綜合實現了。一般 Kubernetes 中服務的部署是通過 Deployment + Service 來部署一個服務;那么這樣的話,服務就可以支持 Deployment 的滾動更新的特性,通過配置MaxSurge(如 25%),MaxUnavailable(如 25%),minReadySeconds(如 30s),progressDeadlineSeconds(如 600s) 幾個參數來控制滾動策略,可以實現每次滾動升級過程中新舊一起加起來的總的 Pod 數會小于等于(1+MaxSurge)* desiredPods,而 available 可以的 Pod 節點數可以保證大于等于 MaxUnavailable * desiredPods,新增 Pod 節點 ready 后等待最少 minReadySeconds 后成 available,整個滾動流程超過 progressDeadlineSeconds 600s 停滯則認為失敗,回滾舊版本。
為此,SlowStart 的機制實現就可以利用這個特性了,如果開啟了 SlowStart 功能,那么就判斷 Pod 節點是否是本次更新新啟動的節點,如果是新啟動的的 Pod 節點則調整其 Pod 的 weight 成預設比例(一般是較小權重),當節點 ready 時間超過 MinReadySeconds 后 ,恢復 weight 成正常權重(默認:100) ,從而實現 SlowStart 慢啟動。這個機制的 SlowStart 功能實現的慢啟動針對的是整個業務的 Service 級別的。利用這個特性來判斷節點是否為新增節點,總結來看需要滿足的條件如下:
如果是新增節點的話,則設置其 weight 為 100 * slow-start-weight,并且設置 service 級別的觸發器,在LatestPod.ReadyStatus.LastTransitionTime + minReadySeconds + 10s - CurTime 時間后恢復為默認權重( weight=100)。
優化 3:LB 配置發布和運維域名管理系統打通,減少服務暴露的流程步驟
一般的互聯網公司,運維這邊都會有自己的域名管理系統,開發人員可以通過提單的方式,讓運維給自己的服務分配一個域名(內網、外網);然后開發人員拿到這個域名之后呢,再和自己的服務綁定,這個綁定的過程就是服務暴露的過程。服務暴露就是指在 LB 這邊建立對應的規則,然后讓就可以通過這個域名來訪問對應的服務了。
這個服務暴露的過程,首先需要人工提單,拿到域名后再進行手動配置,為此,如果公司有合適的機制和契機,那么應該需要將容器 LB 進行服務暴露的過程和域名管理系統打通,當業務需要進行服務暴露的時候,不再需要通過多個平臺的操作來完成,只需要在容器 LB 這邊的管理平臺中進行服務暴露,然后內部可以自動生成域名或者自定義域名,然后自動和域名管理系統打通,然后正式生效對外提供服務。
這樣的優化主要的目的就是為了提升用戶體驗,減少中間的人工操作環境,從而也可以進一步減少人力成本。
優化 4:移除物理機 Nginx,優化鏈路,降低成本
我們前面說到,在初期的時候,為了保證穩定和過渡,還是需要有物理機 Nginx 的存在,物理機 Nginx 的主要作用有兩方面:
- 其一,可以通過物理機 Nginx 這一層來對容器 LB 的流量進行灰度放量,同時可以能及時回滾
- 其二,整個公司的業務服務,會有很多依然部署在物理機上,初期只會有小部分服務會開始逐步往容器進行遷移,因此物理機 Nginx 還必須要保留
但是在項目中后期,容器 LB 會逐步趨于穩定,此時,就需要逐步移除物理機 Nginx,直接是 LVS 到容器 LB,但是移除物理機 Nginx 需要有大量的工作要去梳理,因為物理機 Nginx 的配置是手動配置的,可能有很多差異化、特性化的配置。
優化 5:采用 nginx-ingress-controller 方案,減少 nginx 配置的干預,一步到位
前面說到 nginx-ingress-controller 可以作為最優方案來替代 Nginx-Controller, nginx-ingress-controller 產生的主要目的就在于能夠將 Kubernetes 中的 Service 所代理的 Pod 服務暴露在 Kubernetes 集群之外,這樣就能夠打通集群內外的訪問問題,通過 ingress 可以直接進行七層的負載均衡,并且可以對外訪問,同時減少了一些復雜的配置。
因此,請求流程 client -> LVS VIP -> ingress-controller -> 業務 POD
具體的 nginx-ingress-controller 方案參看下面最后的說明。
四、容器 LB 開發設計的核心考量點
容器 LB 開發設計的核心考量點有如下:
詳細說明如下:
1.支持動態 upstream 的實現【非常重要】
K8s 容器平臺下,業務服務的 Pod 的是動態變化的,比如再每次重新部署、滾動升級、被驅逐重建等情況之后, Pod 的 IP 都是會發生改變。每次 Pod IP 改變,那么就意味著 Nginx 的 upstream 發生了變化,如果沒有實現動態 upstream,那么將會導致每次 Pod IP 變化,Nginx 都需要進行異常 Reload 操作。在線上大規模集群下,如果業務的 QPS 請求很高,Nginx 頻繁 Reload 會導致 client 端的長連接請求在 Nginx Reload 的時候出現 502,這樣將降低業務的 SLA,故而無法提供高可靠的服務保障。
故而,只要我們實現了動態 upstream,比如基于 lua 模塊的實現,那么不管后端 Pod IP 如何變化,Nginx 后端 upstream 的 IP 將會通過 lua 共享內存傳遞并進行負載均衡,因此 Nginx 將不會進行 Reload,從而會大大提高 SLA 服務質量。
2.支持后端 pod 的健康檢查
Pod 本身,K8s 的 kubelet 會做健康檢查,那么容器 LB 層面為何還需要對 Pod(業務服務)做健康檢查呢?
- kubelet 本身可能會出現故障導致不能及時摘除異常的 Pod,因此我們不能完全信任 kubelet
- 如果 Node 節點出現異常,那么 kubelet 把 pod 標記不可用,基本需要幾十秒,也就是影響幾十秒之后才能檢測到
3.SlowStart 策略
Nginx 的商業版本有支持 slow_start 功能,使用如下:
SlowStart 策略是指配置了 SlowStart 策略的 server,在 SlowStart 時間范圍內,先給一定量的流量(比如 0% - 1%),在過了 SlowStart 時間之后,再恢復 100% 的流量。
這樣,在 SlowStart 時間范圍內,這個 server 就可以在低流量下處理一些服務內部初期的一些事情,比如 Java 服務,可以在低流量下完成 jit 編譯、完成 Java 虛擬機初始化等,這樣,當過了 SlowStart 時間之后,等一切就緒在恢復 100% 的流量,可以保證服務可以對外提供更好的質量。
當前,這個是商業版本的實現,開源版本無法使用,因此就需要我們自己實現,在 K8s 下,容器 LB 的 SlowStart 功能的具體實現可以參考文章前面的說明。
4.巡檢模塊
巡檢模塊不僅僅是針對容器 LB,可以是針對所有容器基礎模塊,這個的目的就在于,人為模擬一些實際情況,通過巡檢,把容器 LB 的各個環節都定期檢測一遍。這個在上線初期尤為重要。
巡檢模塊的實現至少包括如下:
- 解耦待巡檢服務(利于增加不同的巡檢模塊)
- 多久檢測一次(間隔、重試)
- 檢測的異常定義(比如 latency、error 等)
- 出現異常的處理機制(比如告警、輸出日志等)
通過巡檢模塊,可以有如下優勢:
- 首先可以保證容器 LB 出現問題能夠及時發現,因為是自定義任務來檢測容器 LB 的各個環節,因此大概率可以先于業務本身發現。巡檢模塊出現問題之后,需要及時告警給相關人員進行處理
- 然后因為巡檢了容器 LB 的各個環節,因此如果巡檢模塊沒有出現問題,那么容器 LB 的整體就基本是正常的,這個對于維護人員的信心度可以大大增強。
5.Nginx SLA 統計模塊
業界用的多是 tengine 的 ngx_http_reqstat_module,如果想要更優化,可以在此基礎上進行擴展,增加如下這些功能:
- 慢請求統計
- 支持 http 自定義錯誤碼(如 6xx 7xx) 等的統計
- 自定義 http status 的統計
- 支持以 upstream 為維度來統計
6.性能壓測和優化
容器 LB 必須要進行大量壓測和優化,以求達到最優的性能,提供最穩定的服務。
本文轉載自微信公眾號「 后端系統和架構」,作者「AllenWu」,可以通過以下二維碼關注。
轉載本文請聯系「 后端系統和架構」公眾號。