萬字解讀雲原生時代,如何從 0 到 1 構建 K8s 容器平臺的 LB(Nginx)負載均衡體系

碼農談IT發表於2022-12-20

雲原生時代,如何從 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 建設的開發人員,我們需要能夠站在業務方的角度去考慮,如下圖所示,有這些業務需求:

萬字解讀雲原生時代,如何從 0 到 1 構建 K8s 容器平臺的 LB(Nginx)負載均衡體系

詳細說明如下:

  • • 體驗需求

    • • 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 需要提供哪些功能呢?如下圖所示,有這些運維需求:

萬字解讀雲原生時代,如何從 0 到 1 構建 K8s 容器平臺的 LB(Nginx)負載均衡體系

詳細說明如下:

  • • 負載均衡器的相關管理

    • • 負載均衡器的自動化指令碼部署,因為運維需要部署負載均衡器,那麼怎麼樣能夠實現更為智慧的自動化指令碼部署,而不是零散的各個命令去操作呢?這塊依賴於我們提供的一些操作步驟和子命令,然後結合 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 配置。比如:

      upstream test-api {
          {{ k8sBuildUpstream  "default.test-back" "port=8080" "max_fails=3" "fail_timeout=3s"  }}

會渲染成相應服務的節點列表和埠:

      upstream test-api {
          server 10.1.1.7:8080  max_fails=3  fail_timeout=3s;
          server 10.1.1.9:8080  max_fails=3  fail_timeout=3s;
       }

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 來管理。示例配置如下:

[Unit]
Description=nginx-controller daemon
Documentation=/www/nginx-controller/bin/nginx-controller -h
After=nginx.service
Wants=nginx.service

[Service]
Type=simple
ExecStart=/www/nginx-controller/bin/nginx-controller --slow-start=true  --is_dynamic=true ${OPTIONS}
ExecStop=/bin/kill -SIGTERM $MAINPID
ExecReload=/bin/kill -HUP $MAINPID
KillSignal=SIGQUIT
Restart=on-failure
RestartSec=3s

[Install]
WantedBy=multi-user.target

只要將這個配置放到 /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 ,如下:

萬字解讀雲原生時代,如何從 0 到 1 構建 K8s 容器平臺的 LB(Nginx)負載均衡體系

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 級別的。利用這個特性來判斷節點是否為新增節點,總結來看需要滿足的條件如下:

# 1. 節點在deployment釋出週期內
LatestPod.ReadyStatus.LastTransitionTime + progressDeadlineSeconds > CurTime

# 2. 節點ready後未超過minReadySeconds視窗
CurPod.ReadyStatus.LastTransitionTime + minReadySeconds > CurTime

# 3. 當前節點初始化時間與最新節點初始化時間差值未超過minReadySeconds視窗,防止擴步長限流
CurPod.InitializedStatus.LastTransitionTime + minReadySeconds > LatestPod.InitializedStatus.LastTransitionTime

如果是新增節點的話,則設定其 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 開發設計的核心考量點有如下:

萬字解讀雲原生時代,如何從 0 到 1 構建 K8s 容器平臺的 LB(Nginx)負載均衡體系

詳細說明如下:

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 功能,使用如下:

upstream backend {
    server backend1.example.com slow_start=30s;
    server backend2.example.com;
}

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 為維度來統計


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024924/viewspace-2928716/,如需轉載,請註明出處,否則將追究法律責任。

相關文章