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