天天看點

網易容器雲平台的微服務化實踐(一)

本文由  網易雲 釋出。

作者:馮常健

摘要:網易雲容器平台期望能給實施了微服務架構的團隊提供完整的解決方案和閉環的使用者體驗,為此從 2016 年開始,我們容器服務團隊内部率先開始進行 dogfooding 實踐,看看容器雲平台能不能支撐得起容器服務本身的微服務架構,這是一次很有趣的嘗試。

一旦決定做微服務架構,有很多現實問題擺在面前,比如技術選型、業務拆分問題、高可用、服務通信、服務發現和治理、叢集容錯、配置管理、資料一緻性問題、康威定律、分布式調用跟蹤、CI/CD、微服務測試,以及排程和部署等等,這并非一些簡單招數能夠化解。實踐微服務架構的方式有千萬種,我們探索并實踐了其中的一種可能性,希望可以給大家一個參考。本文是《網易容器雲平台的微服務化實踐》系列文章的第一篇。

Docker 容器技術已經過了最早的喧嚣期,逐漸在各大公司和技術團隊中應用。盡管以今天來看,大家從觀念上已經逐漸認可 “将鏡像定義為應用傳遞标準,将容器作為應用運作的标準環境” 的觀點,但還是有相當一部分人在迷惑容器技術作為一個标準,應該怎麼落地,怎樣才能大規模線上應用,怎麼玩才能真正解放生産力,促進軟體傳遞效率和品質?答案其實在應用的架構當中。

微服務架構不是因 Docker 容器技術而生,但确實是因容器技術而火。容器技術提供了一緻性的分發手段和運作環境,使得隻有微服務化後的應用架構,才能配合容器發揮其最大價值。而微服務化架構引入了很大的複雜性,隻有應用容器化以及規模化的容器編排與排程才能避免運維效率下降。容器技術和微服務化架構之間本是一種相輔相成的互補關系。

網易容器雲平台的前身是網易應用自動部署平台 (OMAD),它能夠利用 IaaS 雲提供的基礎設施,實作包括建構和部署一體化在内的整個應用生命周期管理。2014 年,以 Docker 為代表的容器技術進入大衆視野,我們驚喜地發現,容器技術是自動部署平台從工具型應用進化為平台型應用過程中最重要的一塊拼圖。原本使用者需要初始化主機,然後借助自動部署平台完成應用的建構和部署。引入容器技術之後,使用者從功能開發到測試到一鍵部署上線,整個應用傳遞過程中不用關心主機初始化、主機間通信、執行個體排程等一系列應用之外的問題。這簡直是信仰 DevOps 的人的福音。

我們從 2015 年開始探索容器技術的最佳實踐方式,從當初 “胖容器” 與容器叢集的産品形态,到後來關于有狀态和無狀态服務的定義,以及如今的新計算與高性能計算,我們一直在思考并豐富着容器技術的應用場景。無論産品形态如何調整,容器雲平台的核心概念一直是 “微服務”,通過微服務這一抽象提供高性能的容器叢集管理方案,支援彈性伸縮、垂直擴容、灰階更新、服務發現、服務編排、錯誤恢複、性能監測等功能,滿足使用者提升應用傳遞效率和快速響應業務變化的需求。網易雲容器平台期望能給實施了微服務架構的團隊提供完整的解決方案和閉環的使用者體驗,為此從 2016 年開始,我們容器服務團隊内部率先開始進行 dogfooding 實踐,一方面檢驗容器雲平台能不能支撐得起容器服務本身的微服務架構,另一方面通過微服務化實踐經驗反哺容器雲平台産品設計,這是一次很有趣的嘗試,也是我們分享容器雲平台微服務化架構實踐的初衷。

在談容器服務的微服務架構實踐之前,有必要先把網易雲容器服務大緻做個介紹。目前網易雲容器服務團隊以 DevOps 的方式管理着30+微服務,每周建構部署次數 400+。網易雲容器服務架構從邏輯上看由 4 個層次組成,從下到上分别是基礎設施層、Docker 容器引擎層、Kubernetes (以下簡稱 K8S)容器編排層、DevOps 和自動化工具層:

網易容器雲平台的微服務化實踐(一)

容器雲平台整體業務架構如下:

網易容器雲平台的微服務化實踐(一)

抛開容器服務具體業務不談,僅從業務特征來說,可以分成以下多種類型(括号内為舉例的微服務):

面向終端使用者 (OpenAPI 服務網關)、面向服務(裸機服務)

同步通信(使用者中心)、異步通信(建構服務)

資料強一緻需求(etcd 同步服務)、最終一緻需求(資源回收服務)

吞吐量敏感型(日志服務)、延時敏感型(實時服務)

CPU 計算密集型(簽名認證中心)、網絡 IO 密集型(鏡像倉庫)

線上業務(Web 服務)、離線業務(鏡像檢查)

批處理任務(計費日志推送)、定時任務(分布式定時任務)

長連接配接(WebSocket 服務網關)、短連接配接(Hook 服務)

……

一旦決定做微服務架構,有很多現實問題擺在面前,比如技術選型、業務拆分問題、高可用、服務通信、服務發現和治理、叢集容錯、配置管理、資料一緻性問題、康威定律、分布式調用跟蹤、CI/CD、微服務測試,以及排程和部署等等......這并非一些簡單招數能夠化解。

作為主要程式設計語言是 Java 的容器服務來說,選擇 Spring Cloud 去搭配 K8S 是一個很自然的事情。Spring Cloud 和 K8S 都是很好的微服務開發和運作架構。從應用的生命周期角度來看,K8S 覆寫了更廣的範圍,特别像資源管理,應用編排、部署與排程等,Spring Cloud 則對此無能為力。從功能上看,兩者存在一定程度的重疊,比如服務發現、負載均衡、配置管理、叢集容錯等方面,但兩者解決問題的思路完全不同,Spring Cloud 面向的純粹是開發者,開發者需要從代碼級别考慮微服務架構的方方面面,而 K8S 面向的是 DevOps 人員,提供的是通用解決方案,它試圖将微服務相關的問題都在平台層解決,對開發者屏蔽複雜性。舉個簡單的例子,關于服務發現,Spring Cloud 給出的是傳統的帶注冊中心 Eureka 的解決方案,需要開發者維護 Eureka 伺服器的同時,改造服務調用方與服務提供方代碼以接入服務注冊中心,開發者需關心基于 Eureka 實作服務發現的所有細節。而 K8S 提供的是一種去中心化方案,抽象了服務 (Service),通過 DNS+ClusterIP+iptables 解決服務暴露和發現問題,對服務提供方和服務調用方而言完全沒有侵入。

對于技術選型,我們有自己的考量,優先選擇更穩定的方案,畢竟穩定性是雲計算的生命線。我們并不是 “K8S 原教旨主義者”,對于前面提到的微服務架構的各要點,我們有選擇基于 K8S 實作,比如服務發現、負載均衡、高可用、叢集容錯、排程與部署等。有選擇使用 Spring Cloud 提供的方案,比如同步的服務間通信;也有結合兩者的優勢共同實作,比如服務的故障隔離和熔斷;當然,也有基于一些成熟的第三方方案和自研系統實作,比如配置管理、日志采集、分布式調用跟蹤、流控系統等。

我們利用 K8S 管理微服務帶來的最大改善展現在排程和部署效率上。以我們目前的情況來看,不同的服務要求部署在不同的機房和叢集(聯調環境、測試環境、預釋出環境、生産環境等),有着不同需求的軟硬體配置(記憶體、SSD、安全、海外通路加速等),這些需求已經較難通過傳統的自動化工具實作。K8S 通過對 Node 主機進行 Label 化管理,我們隻要指定服務屬性 (Pod label),K8S 排程器根據 Pod 和 Node Label 的比對關系,自動将服務部署到滿足需求的 Node 主機上,簡單而高效。内置滾動更新政策,配合健康檢查 (liveness 和 readiness 探針)和 lifecycle hook 可以完成服務的不停服更新和復原。此外,通過配置相關參數還可以實作服務的藍綠部署和金絲雀部署。叢集容錯方面,K8S 通過副本控制器維持服務副本數 (replica),無論是服務執行個體故障(程序異常退出、oom-killed 等)還是 Node 主機故障(系統故障、硬體故障、網絡故障等),服務副本數能夠始終保持在固定數量。

網易容器雲平台的微服務化實踐(一)

Docker 通過分層鏡像創造性地解決了應用和運作環境的一緻性問題,但是通常來講,不同環境下的服務的配置是不一樣的。配置的不同使得開發環境建構的鏡像無法直接在測試環境使用,QA 在測試環境驗證過的鏡像無法直接部署到線上……導緻每個環境的 Docker 鏡像都要重新建構。解決這個問題的思路無非是将配置資訊提取出來,以環境變量的方式在 Docker 容器啟動時注入,K8S 也給出了 ConfigMap 這樣的解決方案,但這種方式有一個問題,配置資訊變更後無法實時生效。我們采用的是使用 Disconf 統一配置中心解決。配置統一托管後,從開發環境建構的容器鏡像,可以直接送出到測試環境測試,QA 驗證通過後,上到演練環境、預釋出環境和生産環境。一方面避免了重複的應用打包和 Docker 鏡像建構,另一方面真正實作了線上線下應用的一緻性。

Spring Cloud Hystrix 在我們的微服務治理中扮演了重要角色,我們對它做了二次開發,提供更靈活的故障隔離、降級和熔斷政策,滿足 API 網關等服務的特殊業務需求。程序内的故障隔離僅是服務治理的一方面,另一方面,在一個應用混部的主機上,應用間應該互相隔離,避免程序間互搶資源,影響業務 SLA。比如絕對要避免一個離線應用失控占用了大量 CPU,使得同主機的線上應用受影響。我們通過 K8S 限制了容器運作時的資源配額(以 CPU 和記憶體限制為主),實作了程序間的故障和異常隔離。K8S 提供的叢集容錯、高可用、程序隔離,配合 Spring Cloud Hystrix 提供的故障隔離和熔斷,能夠很好地實踐 “Design for Failure” 設計哲學。

服務拆分的好壞直接影響了實施微服務架構的收益大小。服務拆分的難點往往在于業務邊界不清晰、曆史遺留系統改造難、資料一緻性問題、康威定律等。從我們經驗來看,對于前兩個問題解決思路是一樣的:1)隻拆有确定邊界能獨立的業務。2)服務拆分本質上是資料模型的拆分,上層應用經得起倒騰,底層資料模型經不起倒騰。對于邊界模糊的業務,即使要拆,隻拆應用不拆資料庫。

以下是我們從主工程裡平滑拆出使用者服務的示例步驟:

網易容器雲平台的微服務化實踐(一)

将使用者相關的 UserService、UserDAO 分離出主工程,加上 UserController、UserDTO 等,形成使用者服務,對外暴露 HTTP RESTful API。

将主工程使用者相關的 UserService 類替換成 UserFaçade 類,采用 Spring Cloud Feign 的注解,調用使用者服務 API。

主工程所有依賴 UserServce 接口的地方,改為依賴 UserFaçade 接口,平滑過渡。

經過以上三個步驟, 使用者服務獨立成一個微服務,而整個系統代碼的複雜性幾乎沒有增加。

資料一緻性問題在分布式系統中普遍存在,微服務架構下會将問題放大,這也從另一個角度說明合理拆分業務的重要性。我們碰到的大部分資料一緻性場景都是可以接受最終一緻的。“定時任務重試+幂等” 是解決這類問題的一把瑞士軍刀,為此我們開發了一套獨立于具體業務的 “分布式定時任務+可靠事件” 處理架構,将任何需保證資料最終一緻的操作定義為一種事件,比如使用者初始化、執行個體重建、資源回收、日志索引等業務場景。以使用者初始化為例,注冊一個使用者後,必須對其進行初始化,初始化過程是一個耗時的異步操作,包含租戶初始化、網絡初始化、配額初始化等等,這需要協調不同的系統來完成。我們将初始化定義為一種 initTenant 事件,将 initTenant 事件及上下文存入可靠事件表,由分布式定時任務觸發事件執行,執行成功後,清除該事件記錄;如果執行失敗,則定時任務系統會再次觸發執行。對于某些實時性要求較高的場景,則可以先觸發一次事件處理,再将事件存入可靠事件表。對于每個事件處理器來說,要在實作上確定支援幂等執行,實作幂等執行有多種方式,我們有用到布爾型狀态位,有用到 UUID 做去重處理,也有用到基于版本号做 CAS。這裡不展開說了。

網易容器雲平台的微服務化實踐(一)

從我們的實踐經驗來看,當業務邊界與組織架構沖突時,甯願選擇更加符合組織架構的服務拆分邊界。這也是一種符合康威定律的做法。康威定律說,系統架構等同于組織的溝通結構。組織架構會在潛移默化中限制軟體系統架構的形态。違背康威定律,非常容易出現系統設計盲區,出現 “兩不管” 互相推脫的局面,我們在團隊間、團隊内都碰到過這種情況。

本文是《網易容器雲平台的微服務化實踐》系列文章的第一篇,介紹了容器技術和微服務架構的關系,我們做容器雲平台的目的,以及簡單介紹了網易雲容器服務基于 Kubernetes 和 Spring Cloud 的微服務化實踐經驗。限于篇幅,有些微服務架構要點并未展開,比如服務通信、服務發現和治理、配置管理等話題;有些未提及,比如分布式調用跟蹤、CI/CD、微服務測試等話題,這些方面的實踐經驗會在後續的系列文章中再做分享。實踐微服務架構的方式有千萬種,我們探索并實踐了其中的一種可能性,希望可以給大家一個參考。

了解網易雲:

網易雲官網:https://www.163yun.com/

新使用者大禮包:https://www.163yun.com/gift

網易雲社群:https://sq.163yun.com/

繼續閱讀