一站式動態(tài)多環(huán)境建設案例
01 問題背景
致景科技成立于 2013 年 12 月,是領先的紡織產(chǎn)業(yè)互聯(lián)網(wǎng)企業(yè),國家高新技術企業(yè)。旗下?lián)碛小鞍俨肌?、“全布”、“天工”、“致景金條”、“致景紡織智造園”、“致景智慧倉物流園”等業(yè)務板塊,致力于通過大數(shù)據(jù)、云計算、物聯(lián)網(wǎng)等新一代信息技術,全面打通紡織服裝行業(yè)的信息流、物流和資金流,幫助行業(yè)實現(xiàn)協(xié)同化、柔性化、智能化的升級,構建紡織服裝縱向一體化的數(shù)智化綜合服務平臺。
我們作為集團公司已經(jīng)成立 2 年多的一個業(yè)務團隊,項目并行開發(fā)上線的情況越來越多。值得一提的是,我們目前處于微服務化拆分剛開始的階段,目前 35 個微服務,拆完之后大概會去到 60 個左右。在這樣的背景下,原先大家都使用一套開發(fā)/測試/生產(chǎn)環(huán)境串行跑研發(fā)流程,隨著項目數(shù)量、開發(fā)測試需求的變多,微服務拆分的進行,原先的方式已經(jīng)不太適合我們。下面簡單羅列一下,我們在這過程中所遇到的三個問題。
1.項目測試環(huán)境被搶占
最典型的問題就是一個項目測試環(huán)境經(jīng)常性被缺陷修復的測試流程搶占,導致項目測試時斷時續(xù),對測試而言缺乏沉浸式體驗,同時測試環(huán)節(jié)成為項目并行度的主要瓶頸,驗證影響項目迭代的進度。
2.開發(fā)聯(lián)調環(huán)境不穩(wěn)定
為了保證開發(fā)的體驗,開發(fā)環(huán)境是允許開發(fā)同學自由發(fā)布。由于使用一套環(huán)境,不同的同學進行開發(fā)環(huán)境發(fā)布,經(jīng)常性地導致聯(lián)調中斷。不少開發(fā)同學轉而尋求端到端的線下聯(lián)調,在個人機器上部署上下游應用,這種模式在微服務化推廣之后,特別是面對眾多的微服務應用基本上寸步難行。如何解決開發(fā)階段代碼調試的便攜性,成為了我們遇到的第二個問題。
3.線上灰度環(huán)境的缺乏
第三個問題也是最重要的,之前我們缺少專門提供給產(chǎn)品經(jīng)理進行功能驗證的預發(fā)環(huán)境。新功能完成測試之后直接上線到線上環(huán)境,研發(fā)團隊為了避免避免對客戶產(chǎn)生不良影響,經(jīng)常性地將發(fā)布計劃安排在晚上。拋開研發(fā)團隊的發(fā)布幸福度不談,線上環(huán)境缺乏灰度發(fā)布能力意味著新功能上線以后就會對全量用戶放開,一旦發(fā)生了產(chǎn)品設計缺陷或者代碼漏洞的情況,那么影響面將會是全網(wǎng)的,風險巨大且不可控。
綜上所述,我們需要解決線下缺乏隔離的多套環(huán)境來支持多項目的開發(fā)和測試,同時在線上需要具備靈活的流量路由策略支持灰度發(fā)布需求。
02 方案調研與探索
結合我們公司實際情況,我們的目標是開發(fā)團隊不依賴運維團隊,即 DEV = OPS 。我們可以一鍵拉起邏輯隔離的開發(fā)/項目環(huán)境,同時可以支持預發(fā)環(huán)境隔離,對于生產(chǎn)環(huán)境可以通過配置灰度規(guī)則流量、自然流量來進行全鏈路灰度的驗證。
根據(jù)我們對當前問題的分析,參考目前互聯(lián)網(wǎng)上的解決方案,都指向了項目環(huán)境治理和服務流量治理的方案,我們稍微羅列下常用的幾種方案,我們最終選擇的是阿里云微服務引擎 MSE 全鏈路灰度 + 云效應用交付平臺 APPSTACK 的集成方案。
1.自研 Ribbon 實現(xiàn)
我們使用的是 Spring Cloud 框架,在平時的業(yè)務開發(fā)過程中,后端服務與服務之間的調用往往通過 Fegin 或者 RestTemplate 兩種調用方式。這其中是通過 Ribbon 這個組件幫我們做了負載均衡的功能。灰度的核心就是路由,我們可以通過重寫 Ribbon 默認的負載均衡算法,在負載均衡調用之前,增加流量路由的邏輯,那么就意味著我們能夠控制服務流量的轉發(fā)。
這個方案要實施下去,對于大廠來說確實可以從 0 到 1 再到 100 進化出來,對我們來說,如果僅僅是實現(xiàn)一個路由的功能,做一個只支持核心場景的簡陋版確實不是非常困難的事情。但如果要達到成熟可應用的階段,需要投入專門的技術資源對其進行管理與維護,同時由于 Spring Cloud 微服務框架本身的復雜性,隨著微服務數(shù)量逐步增多,鏈路越來越長,相關的微服務治理問題的定位與解決,也會耗費不菲的時間成本。
2.物理隔離(藍綠發(fā)布)
這種方案需要為要灰度的服務搭建一套網(wǎng)絡隔離、資源獨立的環(huán)境,在其中部署服務的灰度版本。由于與基礎環(huán)境隔離,基礎環(huán)境中的其他服務無法訪問到需要灰度的服務,所以需要在灰度環(huán)境中冗余部署這些服務,以便整個調用鏈路正常進行流量轉發(fā)。此外,注冊中心等一些其他依賴的中間件組件也需要冗余部署在灰度環(huán)境中,保證微服務之間的可?性問題,確保獲取的節(jié)點 IP 地址只屬于當前的網(wǎng)絡環(huán)境。這個方案需要為這些業(yè)務場景采用堆機器的方式來維護多套灰度環(huán)境,會造成運維、機器成本過大,成本和代價遠超收益;當然如果應用數(shù)目很小,就兩三個應用,這個方式還是很方便的,可以接受的。
3.MSE 標簽路由+APPSTACK 應用編排(我們的選擇)
這兩款產(chǎn)品的說明文檔見鏈接
云效應用交付平臺 AppStack :
https://help.aliyun.com/document_detail/321856.html
阿里云微服務引擎 MSE 全鏈路灰度 :
https://help.aliyun.com/document_detail/170454.html
我們假定通過上面的兩篇文章,讀者已經(jīng)對這兩個產(chǎn)品已經(jīng)有了簡單的了解,一句話介紹就是:APPSTACK 負責應用的環(huán)境管理和流水線發(fā)布,MSE 負責流量的全鏈路灰度。
- MSE 全鏈路灰度的重要概念
對照下面的 MSE 標簽路由的圖,我們重點介紹下 MSE 標簽路由的幾個重要概念如應用的打標、流量染色/自動染色、標識鏈路傳遞等,同時下圖也是我們采用方案的核心原理,使用域名來標識不同的邏輯隔離環(huán)境。
核心示意圖
(1)應用(服務)打標
對照核心示意圖,我們發(fā)現(xiàn)每個應用都有個(base/gray)的 tag,有了這個 tag,我們才可以根據(jù) tag 定義流量規(guī)則。
我們創(chuàng)建 MSE 應用的時候是通過特定 annotation 和環(huán)境變量給 MSE 應用打標的。
特定 annotation:
alicloud.service.tag=dev1
環(huán)境變量:
spring.cloud.nacos.discovery.metadata.version
增加特定的 annotation 跟環(huán)境變量
比如通過 annotation 打標之后我們就可以在 MSE 的標簽路由中進行流量規(guī)則定義,同時我們也可以看到 Nacos 里面的服務有了一個標簽相關的元數(shù)據(jù)(_micro.service.env_);
MSE 流量規(guī)則配置
Nacos 里面元數(shù)據(jù)信息
也有額外增加一個容器環(huán)境變量的做法:
spring.cloud.nacos.discovery.metadata.version
這種做法會在 Nacos 的服務元數(shù)據(jù)中增加一個 version 屬性,像 MSE 云原生網(wǎng)關就會借助這個 version 屬性進行流量管理,而 MSE 全鏈路灰度是借助 alicloud.service.tag 定義的標簽進行流量管理。
容器中增加 gray 相關的環(huán)境變量
MSE 流量規(guī)則配置出現(xiàn) gray 節(jié)點
Nacos 里面有gray環(huán)境的元數(shù)據(jù)信息
MSE 云原生網(wǎng)關可以選擇 gray 版本
(2) 流量染色/自動染色
簡單講,流量染色就是流量帶上了特別的標識,對于 HTTP 請求來說,是請求頭里帶了一些標識信息,對于 Message 來說,是消息頭里帶了標識信息;我們這里主要講下 HTTP 流量染色問題;一種是人工的往 HTTP 請求里面增加標識信息,比如前端請求后端 API 的時候,增加一個 xx:111 的標識信息,那我們就說這個流量被染色了。而自動染色,則說的是一個沒有標識的 HTTP 請求,經(jīng)由某個打了標的 nacos 服務之后,往后調用下一個服務的時候,自動會帶上這個 nacos 服務的標簽信息在請求頭里;最簡單的舉例就是 a 應用調用 b(gray)應用,那b應用調用后面的 c 應用的時候,會自動帶上 x-mse-tag:gray 的請求頭,這就是自動染色。
這里特別提到 x-mse-tag:xxx 這個標識,他是 MSE 系統(tǒng)保留的標識,不僅僅代表了染色,同時也代表了鏈路傳遞(這個請求鏈路上的各個節(jié)點都會依次傳遞這個標簽下去)和默認的路由規(guī)則(優(yōu)先選擇 xxx 標識的服務,沒有找到的情況下,再選擇 base 服務-沒有打標的),這個默認路由規(guī)則是不需要顯式定義的。
我們的解決方案也是特別地利用了這一點,對照核心示意圖,我們在域名名字中添加了流量標識,然后在 Ingress-Nginx 中將流量標識解析出來然后通過 x-mse-tag:xxx 的方式一路傳遞下去,這樣就實現(xiàn)了在整個鏈路上優(yōu)先選擇 xxx 標識的服務,使用沒有標識的 base 服務進行兜底。
(3)標識鏈路傳遞
流量被染色,也就是請求頭中有特定標識之后,這個標識在調用鏈路中如何可以傳遞下去,比如一個 HTTP 請求,帶了 user-id:100 的頭,陸續(xù)需要經(jīng)過 A->B->C。即調用 A 的時候帶了 user-id:100,A 調用 B 的時候也希望可以帶上 user-id:100 的請求頭,同樣 B 調用 C 的時候也要帶上 user-id:100。這個就是標識的鏈路傳遞,有了這個標識鏈路傳遞,我們才可以在為 A/B/C 應用定義按 user-id 的值進行路由的策略。MSE 的標識鏈路傳遞的方法是定義環(huán)境變量 alicloud.service.header=x-user-id,在入口應用 A(所有版本,gray+base)增加該環(huán)境變量以后,往后的調用 B 和 C 的過程中,都會自動添加請求頭 x-user-id 進行傳遞,這樣就方便我們在 A,B,C 節(jié)點按照特有規(guī)則進行路由定義。當然 x-mse-tag 這個特殊的請求頭默認就是鏈路傳遞的,MSE 會把這個標識層層傳遞下去并進行默認的路由規(guī)則(tag 優(yōu)先,base 兜底);MSE 標識鏈路傳遞的原理如下,借助分布式鏈路追蹤的框架的實現(xiàn)方式,每個應用的探針攔截請求并解析標識,然后暫存到線程空間,在往后調用的時候再通過探針把標識塞到下個請求。通過分布式鏈路追蹤的框架完成標識傳遞。
- 阿里云效應用交付 APPSTACK 簡述
我們把云效 APPSTACK 引入進來,主要目的是方便開發(fā)同學通過白屏的管理方式自助完成 MSE 所需要的配置工作,同時在微服務架構下,我們希望應用進行拆分之后,每個應用都有自己的 owner。
通過 APPSTACK 我們可以屏蔽 K8s 的 deployment,service,ingress 等細節(jié),研發(fā)同學面向的就是應用+環(huán)境+流水線。這樣最終開發(fā)人員在 APPSTACK 通過流水線完成應用的環(huán)境部署,每個環(huán)境都會按照 MSE 標簽路由的要求打上不同的標識。
應用的多套環(huán)境部署
圖片每個環(huán)境都會按照 MSE 標簽路由的要求打上不同的標識
在這里我們不展開講述 APPSTACK 的核心功能,我們這里主要的就是借助應用編排,讓每個應用的每個環(huán)境部署的時候,可以設置好 MSE 標簽路由所需要的各種環(huán)境變量和 annotation。
03 我們的解決方案
我們在調研了以上能力之后,根據(jù)自己公司的實際場景與業(yè)務需求,根據(jù)不同環(huán)境的特性,定義了多種環(huán)境的抽象,基于此構建了一站式動態(tài)多環(huán)境的能力,并針對主要場景設計了不同的實施方案。
1 環(huán)境定義
通過對阿里云微服務引擎 MSE 標簽路由和云效應用編排 APPSTACK 的調研,結合我們前面提到所面臨的問題,我們最終定義了我們整個研發(fā)體系所需要的環(huán)境體系即:多套的開發(fā)環(huán)境(含基礎環(huán)境)+多套項目環(huán)境(含基礎環(huán)境)+(集成)測試環(huán)境+預發(fā)環(huán)境+(支持灰度)生產(chǎn)環(huán)境,如下圖
多套開發(fā)環(huán)境:目標是支持多個項目的在開發(fā)階段的開發(fā)聯(lián)調,核心要求是各項目動態(tài)隔離并且支持端云互聯(lián),項目動態(tài)隔離是每個項目都有自己的開發(fā)聯(lián)調環(huán)境且只需部署有變動應用,端云互聯(lián)是開發(fā)可以將自己本地跑的應用注冊到這個 MSE 這個體系里面來,實現(xiàn)可以本地調試的目的,兩個研發(fā)可以點對點地進行本地 debug 來跟蹤問題。開發(fā)基礎環(huán)境是負責兜底服務調用的,每個應用生產(chǎn)部署之后都需要同步更新開發(fā)基礎環(huán)境,保障基礎環(huán)境是最新的生產(chǎn)版本。
多套項目環(huán)境:目標是支持耗時較長的大型項目,比如重大技改,重大業(yè)務項目,需要長時間占用測試環(huán)境跟內外部關聯(lián)方進行穩(wěn)定測試的。核心要求是各個項目動態(tài)隔離。關于項目動態(tài)隔離的定義同上。
測試環(huán)境:目標是支持短平快的項目測試和集成測試,比如日常的缺陷修復,或者多個小項目需要集成到一起發(fā)布,同時也是我們日常自動化測試的環(huán)境。項目環(huán)境中的特性分支也需要經(jīng)過測試環(huán)境的自動化測試才可以上線。
預發(fā)環(huán)境:目標是支持產(chǎn)品經(jīng)理在真實環(huán)境中驗證產(chǎn)品功能,進行驗收,預發(fā)環(huán)境使用的基礎建設如數(shù)據(jù)庫等同生產(chǎn)環(huán)境是一致的,當然這里對系統(tǒng)設計也會提出更高的要求,比如需要保持向前兼容,就像數(shù)據(jù)庫,只能增列不能減列,不能在 sql 中使用 select * 等等,這些我們通過 DMS 進行數(shù)據(jù)庫結構變更的約束和通過代碼檢查保障,此處不贅述。
生產(chǎn)環(huán)境:目標是支持規(guī)則流量+自然流量的全鏈路灰度,這里的規(guī)則流量指的是帶有明顯特征的流量,通過 MSE 的流量規(guī)則能夠清晰定義的請求,比如請求頭,參數(shù),cookie,body 中數(shù)據(jù)符合規(guī)則的。自然流量則相反,我們不指定任何特征,比如全部流量的 1%導入到灰度環(huán)境,這個我們就理解成自然流量。
綜合來看,目前的環(huán)境體系里,開發(fā)環(huán)境和項目環(huán)境涉及到動態(tài)隔離,所以需要部署基礎環(huán)境來完成服務兜底的能力,這個基礎環(huán)境也就是 MSE 標簽路由中無標簽(base)應用的提供者。
這一套環(huán)境體系的流轉流程主要有:
1. 拉取特性分支進入開發(fā)環(huán)境進行本地開發(fā)和前后端聯(lián)調,然后提測到項目環(huán)境
2. 項目環(huán)境由測試團隊完成功能測試之后,將應用部署到(集成)測試環(huán)境
3. 在(集成)測試環(huán)境同其他特性分支一起完成集成,并通過自動化測試和簡單驗證,就可以部署至預發(fā)環(huán)境
4. 產(chǎn)品經(jīng)理在預發(fā)環(huán)境進行功能驗收測試,通過之后可以發(fā)布到生產(chǎn)環(huán)境進行灰度驗證
5. 在生產(chǎn)環(huán)境可以按照規(guī)則流量+自然流量進行灰度驗證,通過之后就可以導入全部流量
6. 最后將特性分支合并至主干后用最新的生產(chǎn)版本更新開發(fā)/項目基礎環(huán)境。
2.主要場景實施
- 場景一:項目隔離的動態(tài)多環(huán)境
按照我們的解決方案,項目環(huán)境要實現(xiàn)的是邏輯隔離的動態(tài)多環(huán)境,相當于每個應用我們要通過 APPSTACK 部署基礎環(huán)境(負責兜底的無標簽 base 應用)和動態(tài)項目環(huán)境(有變更的)同時我們需要保障前端調用后端的域名可以轉換成 x-mse-tag 的請求頭。
1. 通過 APPSTACK 部署好有標簽的應用(項目環(huán)境)和沒標簽的應用(基礎環(huán)境),下列截圖僅做示范
2. 在 ingress-nginx 中解析域名中的 tag 屬性轉換成 x-mse-tag 請求頭鏈路傳遞,通過 ingress 配置攜帶 header 方式到 api 網(wǎng)關。
通過注解
nginx.ingress.kubernetes.io/configuration-snippet 實現(xiàn),具體如下:
metadata:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: proxy_set_header x-mse-tag dev1
通過這樣簡單配置之后,前端調用后端服務的時候,只要通過特定的域名請求,那 ingres-nginx 就可以把這個域名對應的請求自動添加一個 x-mse-tag 的請求傳遞到 api 網(wǎng)關應用,然后借助這個 x-mse-tag 的特殊請求頭,在調用下游服務的時候,就會一路優(yōu)先選擇 dev1 標簽的服務,沒有 dev1 標簽的服務就會去找兜底的 base 服務。
- 場景二:規(guī)則流量全鏈路灰度的生產(chǎn)環(huán)境
1. 通過 APPSTACK 在生產(chǎn)環(huán)境部署好有灰度標識的應用。
2. 定義流量路由規(guī)則,在 MSE 控制臺為本次灰度鏈路中的入口應用設置流量路由規(guī)則,比如本次發(fā)布更新了 A-B-C 三個應用,A 就是入口應用。
通過這種方式定義之后,我們可以設置符合某些特征的流量進入到 A 應用的 gray 版本,并且向后層層傳遞過去,不用每個應用重復設置路由規(guī)則。這就會滿足了規(guī)則流量的全鏈路灰度的要求。
- 場景三:自然流量全鏈路灰度的生產(chǎn)環(huán)境
1. 通過APPSTACK在生產(chǎn)環(huán)境部署好有灰度標識的應用,略圖。這里至少需要為本次項目的入口應用 A(也可以是全部應用)增加一個自動染色的變量 profiler.micro.service.tag.trace.enable=true,這個變量會把經(jīng)過這個入口應用 A 的流量自動染色,往后傳遞的時候自動增加 x-mse-tag 的請求頭,從而實現(xiàn)全鏈路灰度
2. 定義流量規(guī)則,即自然流量的多少比例進入 gray 環(huán)境
這樣通過入口應用的自動染色,以及入口應用的自然流量分批,我們就可以讓進入到入口應用 gray 節(jié)點的自然流量進入到全鏈路的灰度環(huán)境(灰度優(yōu)先,基礎應用兜底)。
目前為止,我們實現(xiàn)了默認按照域名進行項目/開發(fā)多環(huán)境邏輯動態(tài)隔離的效果;同時提供給研發(fā)團隊便捷的白屏管理的工具,可以由項目組獨立拉起整個環(huán)境,通過三個場景化的實施方案,完美解決了開篇提到的三個問題。
3.相關技術原理概要介紹
MSE + APPSTACK 的解決方案,核心的地方在于流量規(guī)則的定義以及流量標識的傳遞,其核心的解決方案在于流量中標識的解析和傳遞,我們再看一次這個標識傳遞的圖:
怎么樣才能實現(xiàn)這里面最核心的解析 Extract 和注入 Inject 的功能呢?
答案是探針,為每個 MSE 管理的應用運行時候增加一個 java agent 的探針,完成一個類似 JVM AOP 的能力,在 ACK(K8s)的容器中,MSE 通過如下的方式自動為應用安裝 java agent;
1. 配置 Webhook,然后根據(jù) Pod 或者 Namespac中的 Labels,來判斷是否要掛載 Java Agent。如果需要掛載,則就對 Pod 的聲明?件做出后續(xù)修改
2. 獲取并添加環(huán)境變量 JAVA_TOOL_OPTIONS,?于加載 Java Agent。
3. 給業(yè)務容器添加 Volume,?于存儲 Java Agent 的?件內容。
4. 給 Pod 添加 Init container,?于在業(yè)務容器啟動前下載 Java Agent
最終我們每個被 MSE 管理的應用在 POD 層面可以看到如下信息:
有了這個探針,我們可以攔截所有 HTTP REQUEST 的處理類,將我們關心的標識信息暫存起來,然后在應用內部消費 Nacos 服務的時候增加 MSE 支持的路由邏輯,選擇合適的服務 provider(比如是打了 gray 標的),同時在調用這個 provider 的服務的時候,可以將暫存的流量標識帶在請求頭里繼續(xù)傳遞下去,大體上跟我們最開始提到的自研 Ribbon 的方式相似。不過 MSE 在這塊做得比較成熟,同時不單單支持了 HTTP REST 這種服務調用方式,還包括了 Dubbo、RocketMQ 的消息灰度、數(shù)據(jù)庫灰度等。當然全鏈路灰度僅僅是微服務引擎 MSE 一個很小的功能,微服務引擎 MSE(Microservices Engine)是一個面向業(yè)界主流開源微服務生態(tài)的一站式微服務平臺,提供注冊配置中心(原生支持 Nacos/ZooKeeper/Eureka)、云原生網(wǎng)關(原生支持 Ingress/Envoy)、微服務治理(原生 支持Spring Cloud/Dubbo/Sentinel,遵循 OpenSergo 服務治理規(guī)范)的能力,有興趣的同學可以看看官方文檔。
04 借事修人
作為創(chuàng)業(yè)團隊來講,能夠快速具備一站式解決服務治理問題,是一件非常酷的事。這整個方案的討論實施過程中,研發(fā)團隊對于 K8s、Nginx Ingress、MSE 都有較深入的理解。“像我們部門研發(fā)團隊,沒有專門的運維團隊,每個開發(fā)人員都可以深入了解每個產(chǎn)品的來龍去脈,想想就很有意義?!?/p>