在近日的 ArchSummit 全球架構師峯會 2021 上海站上,繼網易副總裁、杭研院執行院長、互聯網技術委員會主席、網易數帆總經理汪源發表主題演講《打造開放的雲原生操做系統和系統軟件架構》以後,網易技術委員會委員、網易數帆基礎架構總監張曉龍向與會者進一步講述了網易數帆在雲原生中間件上的思考、實現與經驗。本文爲演講內容實錄。前端
今天給你們分享咱們面向生產環境的中間件容器化實踐,主要包括四個部分的內容:數據庫
第一部分從基礎中間件面臨的運維挑戰出發,介紹網易解決這些挑戰的技術演進路徑,以及爲何要去作中間件容器化。編程
第二部分介紹中間件容器化的需求以及網易數帆總體平臺架構。後端
第三部分針對中間件容器化過程當中的一些共性問題,給出咱們的思考,以及最佳實踐。緩存
最後是中間件容器化工做的總結和將來的計劃。性能優化
基礎中間件的挑戰
在容器技術出來以前,基礎中間件技術如 MySQL、Redis、Kafka 等早已開源,併成爲服務端架構設計的標準組件,一個典型的互聯網應用,數據庫、緩存、消息隊列三大中間件是必不可少的。網絡
架構師應用這些中間件去架構一個個應用平臺很是簡單,但運維人員遇到了較大的問題,包括以下 5 個方面:架構
- 中間件自己是比較複雜的分佈式系統,運維須要理解這些分佈式系統的工做原理,編寫出適合它們的運維腳本,複雜性很是高;
- 運維效率比較低下,50 個如下 MySQL 實例用手工運維可能沒有問題,但 500、1000 個數據庫實例,或者如網易雲音樂的數千個 Redis 實例,若是還用手工腳原本運維,效率必然很低;
- 穩定性不足,這是因爲運維人員老是用手工腳原本運維,在線上抄命令,不當心抄錯命令可能中間件就宕了;
- 傳統的中間件是部署在物理機上面的,而物理機制沒辦法提供很強的資源彈性;
- 全部比較資深的中間件運維都基本上在互聯網上大廠,由於這些運維很是複雜,通常企業很難招到一個很是專業的運維,咱們認爲解決這個挑戰的最佳實踐,是將中間件運維能力雲服務化。
將這些中間件作成雲服務有幾個優點。第一是運維簡單易上手,第二可以高效地實現大批量實例的自動化運維,第三有很強的 SLA 保障,由於不須要敲太多手工的一個命令。第四是能借助 IaaS 彈性資源能力快速擴容。最後由於整個運維變得簡單,再也不須要大量的專業人員就能夠幫業務運維好中間件。框架
其實公有云廠商也看到了這個趨勢,國內三大主流公有云都把開源的基礎中間件作成了雲服務。我想這主要有兩個緣由:首先,IaaS 資源層面競爭趨於同質化,把 PaaS 中間件作成雲服務能夠消耗更多的資源,把用戶綁定得更深;其次,中間件做爲雲上的增值服務,毛利率遠高於雲主機、雲硬盤,因此不少公有云用戶不喜歡 RDS,本身買雲主機搭 MySQL。運維
爲了解決中間件運維複雜性的挑戰,網易在六七年前就研發了一個雲基礎中間件平臺。這個平臺有一些技術特色,首先是基於 IaaS 提供資源彈性,也就是說中間件運行的計算資源是雲主機,存儲資源是雲盤,網絡資源可能就是在租戶的 VPC 裏面。
第二它採用了 IaaS 的租戶隔離策略,若是一個租戶想要中間件實例,平臺就用他的雲主機、雲硬盤自動化地幫他搭起來,能夠作到不一樣租戶之間很好的隔離。
咱們當時研發了 6 款基礎中間件雲服務,業務團隊研發產品須要中間件,它只須要接入這些雲服務就能夠了,不須要從新作一遍。咱們主要作的是左邊的控制管理部分,好比實例高可用、部署安裝、實例管理等。當時咱們也取得了一些成效,大大提高了運維團隊對中間件的運維能力。
隨着時間的推移,第一代基礎中間件暴露出了三大缺陷,難以解決。第一大缺陷是極限性能不足。由於它使用 KVM 虛擬機做爲計算資源,比在物理上運行有很是大的性能折損,沒辦法知足業務高負載/高壓力下對中間件性能和穩定性的苛刻要求。
第二是實現資源成本過高,由於它是基於 OpenStack 來提供資源編排能力,另外 KVM 虛擬化技術強隔離的特性使得內存資源沒辦法在多箇中間件實例之間共享,這兩個因素使得跑在虛擬機上的中間件實例部署密度很是低,哪怕有租戶的中間件負載不高,他也不可能把內存釋放,由於 KVM 是強隔離的。
第三點它的交付很是不靈活,它就跟網易的 IaaS 綁定,沒辦法支持咱們將來把它商業化,輸出到網易之外的企業,這個企業的基礎設施多是在公有云上,也多是在本身的 IDC 機房。
中間件容器化的思考
近幾年,Docker、Kubernetes 等容器技術誕生並飛快發展,無狀態應用的容器化已經成熟,咱們認爲容器做爲一個新的已經普遍落地的基礎設施的技術,完美地對應了第一代基礎中間件的缺陷能力—弱隔離有有助於資源共享;輕量化的虛擬化可以消除性能損耗,知足業務在高負載場景;基於鏡像進行標準化的封裝,有利於高效交付;還有強大靈活的調度能力;最關鍵的一點,它是整個雲原生技術棧的一個基石。
Kubernetes 編排技術,最關鍵的是它跟基礎設施是鬆耦合的,使得咱們可以將應用搬到任何一個地方,由於它就是面向混合雲設計的。另外它是面向大規模生產環境的設計,繼承了 Google 的大規模生產環境的經驗,因此用容器技術解決中間件服務化的問題是有但願的。
網易內部基於 Kubernetes 構建了一套雲原生操做系統,它向下可以適配各種的基礎設施資源,向上可以做爲各類應用負載的統一提供商--這也是 Kubernetes 的目標之一。中間件正是整個雲原生操做系統所要支撐的一類業務。從這個角度來看,中間件容器化也是瓜熟蒂落的。
中間件容器化要解決它的運維問題,尤爲下面幾個需求必需要考慮的。
第一,生命週期的管理,咱們須要容器化中間件平臺可以幫助運維完成對於中間件實例級別的各類運維操做,網易數帆會基於 Kubernetes Operator 這一套框架來實現。
第二點是高可用的部署,中間件,特別是在追求更高的可用性的狀況下,每每要作多機房的部署,一箇中間件集羣裏面的全部實例,要按照什麼樣的比例分佈在不一樣的機房,標準的 Kubernetes 調度器沒辦法作到,咱們須要擴展 Kubernetes 的調度器來實現這樣的編排。
同時,還要完善監控告警的指標,這個指標就對應雲原生的 Prometheus 的可觀測性體系。
性能是第一代中間件的一個痛點,咱們要確保容器化中間件基本達到物理機部署的性能才能支撐核心應用,這須要有針對性地優化各種中間件實例的性能。
還有一點是產品化,由於咱們但願中間件容器化不只可以在網易使用,還可以商業化輸出,因此咱們參考公有云上 RDS、Redis 的產品形態,須要有同等的產品能力,可以在任意的基礎設施上低成本、靈活交付,咱們必須採用鬆耦合和高複用的架構設計。
網易數帆選擇了 Kubernetes Operator 的機制。從深層次理解,Kubernetes 構建了一個分佈式系統部署運維所需的「原語」,它內置的對象如 Pod、Node、Deployment、StatefulSet 等,都是爲了實現一個典型的無狀態分佈式系統提出來的。這些內置的對象相互配合,使得無狀態應用的部署和運維很是高效。
可是 Kubernetes 內置的這些對象沒辦法直接解決中間部署運營的問題。第一點,中間件是有狀態的,它的狀態是存儲,可能網絡 IP。第二,中間件實例與無狀態應用的實例不一樣,後者的副本相互之間沒有關係,而中間件實例和實例之間、副本和副本之間是有關係的,是要相互訪問的,中間件之間造成一個複雜的拓撲關係,好比在作故障恢復時,Redis 兩個副本之間是有主從關係的。
社區在兩年多以前也開始實現中間件或者說有狀態的應用,提出了一套 Operator 開發框架。若是咱們把 Kubernetes 理解成爲一個操做系統,那麼 Operator 就是在這個操做系統上開發原生應用的一套開發框架,支持更高效、更自動化、更可擴展的開發方式。
Operator 有 4 個特色,第一它是須要開發出來,是遵循的聲明式的編程理念,有對象的定義,還有控制器部署。Operator 實際上是一個控制器,遵循着觀察、分析、行動的決策鏈閉環。若是用戶定義了 4 個資源,Operator 就分析這 4 個資源當前的狀態和目標狀態有哪些不一致。
圖中能夠看到當前的狀態有 1 個 Pod,他如今是 0.0.1 的版本,咱們定義的狀態要求 0.02,還少了一個 Pod,若是發現了不一致,它會有一些 Action,再擴一個 Pod,把它升級到 0.0.2。咱們實現 Operator,其實就是去寫這些 Action 應該怎麼作。這其實是封裝了特定領域的運維知識跟經驗,可以被設計用來管理複雜的狀態應用。
Operator 開發框架的主體包括三部分,第一部分 operator-sdk,研發的一個腳手架;第二部分是 operator-lifecycle-manager,一個生命週期管理的組件;第三部分是 operatorhub.io,既然任何人均可覺得開發一個應用,一個它能夠部署安裝運維的應用,他就應該能夠把這個應用放到一個應用市場,operatorhub.io 就是這樣的一個應用市場。
不一樣的機構去開發 Operator,在運維看來是有必定的成熟級別的,應用部署都可以自動化運維,這是對應運維最但願的一個級別。最基本的第一個級別就是基本安裝 Operator,該怎麼去作到把原來安裝部署腳本,用 Operator 這種工程模式實現。
這是網易數帆實現的一個基於 Kubernetes Operator 的中間件平臺架構,包括控制面和數據面。左邊控制面面向運維管理的能力,包括一些跟中間件業務無關的可是你們都須要的通用組件,如審計、認證權限、控制檯等。
中間就是中間件 Operator,在這裏咱們用 Operator 的機制研發了 Redis、Kafka、MySQL 等中間件。
咱們實現了中間件的生命週期管理,這些 Operator 自己也是運行在 Kubernetes 的上面,並且它是一種無狀態應用,以 Deployment 方式能夠運行在上面,由於它的狀態都是存在 etcd 裏面的。
再下面是 Kubernetes 的管控面,Master 節點須要的一些組件。
最下面是日誌、監控、報警的組件,咱們自研的一個日誌管理平臺實現從採集信息去動態更新它的配置,以及把日誌收集上來。
右邊是中間件的數據面,我畫了三個 Node,咱們把一箇中間件的集羣用 StatefulSet 來實現,每個實例跑在一個 Pod 上,每一個 Pod 可能會聲明它的對持久卷的用途,Pod 跟 Node 之間是有拓撲關係的,它須要相互進行數據和拓撲同步,用於狀態變動以及故障恢復。每一個節點上都會運行 Kubernetes 的兩個組件,Kublet,kube-proxy,還有一個採集器,用於日誌監控。
咱們還實現了 Pod 的掛盤功能,無論是本地盤仍是遠程盤,經過 StorageClass 的方式去實現,這也是 Kubernetes 的標準。
中間件容器化的共性問題與解決之道
接下來探討中間件容器化過程當中的一些共性問題的解決辦法。中間件最大的特色在於它是有狀態的,Kubernetes 只負責計算的編排,中間件的狀態存儲有兩種可能,一種是遠程存儲,一種是本地存儲。
咱們認爲遠程存儲是最佳實踐。若是你在私有云環境上有一套相似於開源 Ceph 的遠程分佈式存儲,應該絕不猶豫地使用它來存。若是說 Ceph 性能不足,你能夠找其餘更好的分佈式存儲來去直接用。若是你在公有云上,那你應該絕不猶豫地用雲盤來做爲中間件的存儲。
不少狀況下,本地存儲是不得已而爲之的一個選擇,由於沒有太靠譜的分佈式存儲,有可能這個分佈式存儲性能不行,和用本地盤跑起來相差很遠,也有可能分佈式系統後端可靠性不行,會丟數據。
爲此,咱們實現了本地存儲的接入。咱們作本地存儲需求有兩個,一是要求當 Pod 去申請 PVC 的時候作好動態管理配置,本地盤在建立、刪除時,要去作對應的操做。同時在 Pod 調度時,要實現它與本地盤強綁定,既然 Pod 開始建立的時候,有本地盤在某一個 Node 上,你必須保證 Pod 通過故障恢復或者重調度以後仍是跑在那個 Node 上,以確保中間件數據不丟失。
在技術實現上,咱們對於節點上的本地磁盤引入了一個 LVM 去動態的管理,也採用了 Kubernetes Local PV,後者的不足在於須要運維提早在節點上建立 PV,這個是不可取的。因此咱們作了兩件事,一是調度器擴展,實現本地存儲的資源準備,在建立 Pod 時聲明所需本地盤的大小,它就可以動態給建立掛載到這個 Pod 裏面去,不須要運維提早手動準備。
如圖中一個 Pod 的調度過程,用戶建立了一個 Pod,它聲明瞭一個 PVC,咱們加了一個本地存儲調度器擴展,先作一個預調度,算一下每一個節點上的本地盤的存儲容量夠不夠,若是夠就把 Node 的信息也放到 PVC 裏面,接下來通知這個 Node 上一個本地存儲資源準備器,讓資源準備器收到請求的時候去調用 LVM 把存儲資源給建立出來,並把對應的 PV 建立出來。在資源準備器上把 PV 和 PVC 綁定,而後通知調度器能夠把 Pod 調度到這個節點上,由於聲明的本地存儲已經準備好。接下來用 Kubernetes 把那個節點所在的本地盤掛載到 Pod 裏面去,完成一個總體的調度。
關於中間件容器化的網絡,有兩個場景的實現。第一個場景,咱們設計的中間件運行在不一樣的基礎設施上,對應不一樣的網絡配置,若是是物理網絡,能夠用 Calico、Flannel 這樣的網絡方案,直接用它的 CNI;若是是公有云,就對接公有云上的 VPC 網絡,好處是每一家公有云都爲 Kubernetes 提供了一個標準 CNI,使得運行在雲主機上的 Kubernetes 能夠去接入他們的網絡。
第二個場景,咱們須要優化網絡性能。咱們引入了一個容器的 SR-IOV 方案,好處是可以作到優於物理機的低時延。它採用的是網卡直通技術實現,可以下降 50%的時延,能夠知足一些對時延要求很高的超高性能任務需求,但 PPS 提高不了。直通少了網絡傳輸的虛擬化開銷,可是缺點也比較明顯,這個方案只能用在物理網絡,由於它徹底依賴於硬件網卡,沒法用在公有云上實現網絡加速。
在物理網絡環境上要去處理網卡異構問題,包括說是咱們可能用英特爾網卡,可能有 Mellanox 的網卡,須要對 VF(SR-IOV 的一個概念)進行精細管理。咱們把 VF 當成一個擴展的調度資源,經過標準的 Kubernetes Device Plugin 來發現和註冊節點的 VF 資源, 結合 label 和 taint 標記,原生的調度器就能夠進行資源管理和分配。
輕舟中間件的集羣是用 StatefulSet 抽象的,每一個實例都是 StatefulSet 的一個 Pod,StatefulSet 只能作到 Pod 的名字不變,它發生不一樣更新的時候,或者掛了再恢復的時候,都保持 Pod 的名字不變,可是它沒辦法保持 Pod 的 IP 不變。然而,在傳統的中間件運維眼裏,基於物理機部署的 IP 是不變的,機器重啓以後也仍是原來的 IP,因此他們的一些運維習慣,都是喜歡用 IP 而不是域名。
爲了讓容器化中間件可以更快地推廣落地,以及兼顧已有的應用,咱們作了保持 StatefulSet 的 IP 不變的功能,經過引入一個全局的容器地址池組件接管對 Pod IP 的分配來實現。建立 StatefulSet 的時候,把分配給它的 IP 記錄好,哪怕 Pod 更新的時候被刪掉,IP 還給保持住不釋放,等它從新建起來以後,若是名字跟原來那個是同樣的,就把這個 IP 從新分配給他。
工程化,咱們研發容器化中間件,相對於第一代基於虛擬化的中間件,由於重用了 Kubernetes 內置的一些概念以及它在運維、控制上的一些機制,使得咱們去研發相同的基礎中間件,研發代價可以大幅度減小,這個體如今代碼比第一代基礎中間件要減小不少,固然這個代碼減小也是有代價的——開發人員必須很是瞭解 Kubernetes Operator 這套開發框架,必須得深入地理解 Kubernetes 聲明式編程的概念,他才能寫出來。
在質量保障方面,咱們作了兩個事情,第一個就是混沌測試,就是故障測試,基於開源的 ChaosBlade 去模擬 Kubernetes 資源故障對中間件服務的影響,另外咱們也藉助 Kubernetes e2e 測試框架來確保運維人員可以模擬各類中間件實例的生命週期操做是否正常。
還有一點,要作中間件實例生命週期管理,須要作監控、告警,不少狀況下它的 UI 都是有共同之處,UI 的使用模式都是同樣的,這是咱們設計的一個前端頁面渲染,渲染引擎使得用動態表單機制可以很快地開發控制檯,後端經過配置一下就能夠實現控制檯業務的開發能力,這樣使得研發代價更小。
性能優化,咱們採起了一些策略,使得容器化中間件的性能基本接近於它運行在物理機上的水平。咱們在 CPU 開了性能模式,下降喚醒延遲。在內存方面,咱們關閉 SWAP 及透明大頁,調優同步內存髒頁回寫閾值,這些都是參數級的調優。
I/O 方面使能內核 blk-mq,增大預讀緩存。還有一個比較重要的就是網卡中斷,咱們將物理方法中斷跟容器的 veth 虛擬網卡中斷處理跟 CPU 給隔離了,確保系統性能不發生抖動。
NUMA 也是咱們優化的一點,這在高負載上面體現得比較明顯。咱們使得容器部署感知 NUMA 拓撲,將 Pod 儘可能的分配在本地的 NUMA,儘可能不要讓一個 Pod 跨 NUMA,以避免帶來比較大的 CPU 緩存的開銷。
第一代中間件的一個缺陷是不可以去往外交付。去年咱們作了容器化中間件這個產品,名字叫輕舟中間件,具有基礎中間件的標準能力。在接入層咱們也增長了一些能力,由於咱們基於 Kubernetes 來作的,運維人員甚至能夠經過 Kubectl、YAML 文件就能夠運維中間件。中間件服務層,咱們實現了 7 個基礎中間件服務,這些中間件基本上具有了前面提到的核心運維能力。
總體上中間件基於 Operator,可以跑在任意 Kubernetes 集羣之上,底層的資源無所謂,公有云的虛擬機能夠做爲 Kubernetes 的 Node,雲盤能夠做爲 Kubernetes 的存儲。另外,咱們也容許社區基於 Operator 開發的一些中間件在咱們的平臺上跑。
將來展望
技術是爲業務服務的,中間件最大的痛點是運維,要把它作到託管的雲服務去解決,而容器技術的優點使得中間件容器化成爲實現中間件雲服務的最佳實踐。在實現上須要 Operator,須要有更加雲原生的模式來把容器化中間件給研發出來,固然對開發人員的要求也很高的。
將來的計劃有兩點,第一,咱們如今的容器化中間件平臺能夠跑在任意 Kubernetes 上面,可是咱們仍是要作到跑在 Kubernetes 發行版上,如 OpenShift、Rancher 等,但願容器化中間件這些 Operator 也能跑在上面,可是須要作一些兼容。第二,咱們總體是想建設雲原生操做系統,中間件是其中的一個負載,我爲何不把中間件的負載和無狀態應用負載實現混部?這樣能夠給公司帶來更高的一個資源利用率,能夠下降成本。
謝謝你們!