為什么80%的碼農都做不了架構師?>>> ??
作者:林帆(花名金戟),阿里巴巴研發效能部技術專家
相關閱讀:在阿里,我們如何管理代碼分支
前言
阿里的許多實踐看似簡單,背后卻蘊涵著許多思考,譬如測試環境的管理。
互聯網產品的服務通常是由Web應用、中間件、數據庫和許多后臺業務程序組成的,一套運行環境就是一個自成一體的小生態。最基本的運行環境是線上環境,部署產品的正式發布版本,為用戶提供持續可靠的服務。
除此以外,還有許多不對外部用戶開放的運行環境,用于產品團隊日常的開發和驗證,統稱為測試環境。正式環境的穩定性,除去軟件自身的質量因素,主要與運行的主機、網絡等基礎設施相關,而測試環境的穩定性則更多受到人為因素影響。由于頻繁的版本變更,以及部署未經充分驗證的代碼,測試環境出故障的情況屢見不鮮。
良好的代碼提交習慣、適當的變更前檢查有助于減少故障的發生,但無法徹底杜絕后患。增加多套測試環境副本能夠有效控制故障的影響范圍,然而企業的資源終歸有限,降低測試環境成本和提高測試環境穩定性成為了矛盾的兩面。
在這個領域里,獨具匠心的阿里研發效能團隊設計了一種服務級復用的虛擬化技術,稱為“特性環境”,其巧妙的思路令人贊嘆。本文將圍繞測試環境管理的話題,聊聊這種具有阿里特色的工作方式。
測試環境管理的困局
測試環境的用途很廣泛,常見的測試環境譬如系統集成測試環境、用戶驗收測試環境、預發布測試環境、灰度測試環境等,它們體現了產品的交付生命周期,也間接反映出整個團隊的組織結構。
小作坊型產品團隊的測試環境管理起來十分簡單,每個工程師本地就能啟動全套軟件組件進行調試,倘若不放心,再加上一個公共的集成測試環境也就足夠。
隨著產品規模擴大,本地啟動所有服務組件逐漸變得既費時又費事,工程師們只能在本地運行一部分待調試的組件,然后利用公共測試環境上的其余組件組成完整系統。
與此同時,團隊規模的擴張,使得每個團隊成員的職責進一步細分,新的子團隊被劃分出來,這意味著項目的溝通成本增加,公共測試環境的穩定性開始變得難以控制。在這個過程中,測試環境管理復雜性帶來的影響,不僅體現在服務聯調變得繁瑣,更直接反映在交付流程和資源成本的變化上。
在交付流程方面,一個顯著的變化是測試環境種類增多。出于不同的用途和目的,工程師們設計出了各式各樣的專用測試環境。這些測試環境的組合形成了各個企業獨具特色的交付流程。下圖展示了一種用于大型項目的復雜交付流程。
從單獨服務的角度來看,環境與環境之間是由流水線相連的,再加上自動化測試或手工審批操作組成關卡,實現環境之間的傳遞。通常越高級別環境的部署頻率越低,因此相對穩定性也越高。與之相反,在級別較低的環境上,就隨時可能存在新的部署,會打擾正在使用該環境的其他人。有時為了復現某些特殊的問題場景,一些開發者不得不直接登錄到服務器上面去“搞事情”,進一步影響環境的穩定性和可用性。
面對隨時可能崩潰的測試環境,小企業會試著去“堵”:約束服務變更時間、設立嚴格的變更規范,大企業則善于用“疏”:增加測試環境副本,隔離故障影響范圍。顯然,不堪重負的測試環境一定越“堵”越“漏”,千年以前大禹治水的故事早就揭示了的道理,刻意的管控拯救不了脆弱的測試環境。
近年來,DevOps文化的興起,端到端解放了開發者的雙手,這對于測試環境的管理而言卻是一把雙刃劍。一方面,DevOps鼓勵開發人員參與運維,了解產品的完整生命周期,有助于減少不必要的低級運維事故;另一方面,DevOps讓更多的手伸向測試環境,更多的變更、更多的Hotfix出現了。這些實踐從全局來看利大于弊,然而并不能緩解測試環境的動蕩。單純的流程疏通同樣拯救不了脆弱的測試環境。
那么該投入的還得投入。將不同團隊所用的低級別測試環境各自獨立,此時每個團隊看到的都是線性流水線,從整體上觀察,則會程現出河流匯聚的形狀。
由此推廣,理想情況下,每位開發者都應該得到獨占且穩定的測試環境,各自不受干擾的完成工作。然而由于成本因素,現實中在團隊內往往只能共享有限的測試資源,不同成員在測試環境相互干擾成為影響軟件開發質量的隱患。增加測試環境副本數本質上是一種提高成本換取效率的方法,然而許多試圖在成本和效率之間尋找最優平衡的探索者們,似乎都在同一條不歸路上越行越遠。
由于客觀的規模和體量,上述這些測試環境管理的麻煩事兒,阿里的產品團隊都無法幸免。
首先是測試環境種類的管理。
在阿里內部,同樣有十分豐富的測試環境區分。各種測試環境的命名與其作用息息相關,雖然業界有些常用的名稱,但都未形成權威的標準。實際上,環境的名稱只是一種形式,關鍵還在于各種測試環境應當分別適配于特定應用場景,且場景之間應當或多或少存在一些差異。
這種差異有些在于運行的服務種類,譬如性能測試環境很可能只需要運行與壓力測試相關的那部分訪問量最大的關鍵業務服務,其他服務運行了也是浪費資源。有些差異在于接入數據的來源,譬如開發自測的環境的數據源與正式環境肯定不一樣,這樣測試使用的假數據就不會污染線上用戶的請求;預發布環境(或用戶驗收測試環境)會用與正式環境一致的數據源(或正式數據源的拷貝),以便反映新功能在真實數據上運行的情況;自動化測試相關的環境會有單獨的一套測試數據庫,以避測試運行過程中受到其他人為操作的干擾。
還有些差異在于使用者的不同,譬如灰度和預發布環境都使用正式的數據源,但灰度環境的使用者是一小撮真實的外部用戶,而預發布環境的使用者都是內部人員。總之,沒必要為一個不存在業務特殊性的測試場景專門發明一種測試環境。
在集團層面,阿里對流水線形式的約束相對寬松。客觀的講,只有在一線的開發團隊知道最適合團隊的交付流程應該是什么樣子。阿里的開發平臺只是規范了一些推薦的流水線模板,開發者可在此基礎上進行發揮。列舉幾個典型的模板例子:
這里出現了幾種外界不太常見的環境類型名稱,稍后會詳細介紹。
其次是測試環境成本的管理。
成本管理的問題十分棘手且十分值得深究。與測試環境相關的成本主要包括管理環境所需的“人工成本”和購買基礎設施所需的“資產成本”。通過自動化以及自服務化的工具可以有效降低人工相關的成本,自動化又是個很大的話題,宜另起一篇文章討論,此處暫且收住。
資產購買成本的降低依賴技術的改良和進步(排除規模化采購帶來的價格變化因素),而基礎設施技術的發展史包括兩大領域:硬件和軟件。硬件發展帶來的成本大幅下降,通常來自于新的材料、新的生產工藝、以及新的硬件設計思路;軟件發展帶來的基礎設施成本大幅下降,目前看來,大多來自于虛擬化(即資源隔離復用)技術的突破。
最早的虛擬化技術是虛擬機,早在20世紀50年代,IBM就開始利用這種硬件級的虛擬化方法獲得成倍的資源利用率提升。虛擬機上的不同隔離環境之間各自運行完整操作系統,具有很好的隔離性,通用性強,但對于運行業務服務的場景,顯得略為笨重。2000年后,KVM、XEN等開源項目使得硬件級虛擬化廣泛普及。
與此同時,另一種更輕量的虛擬化技術出現了,以OpenVZ、LXC為代表的早期容器技術,實現了建立于操作系統內核之上的運行環境虛擬化,減少了獨立操作系統的資源消耗,以犧牲一定隔離性為代價,獲得更高的資源利用率。
之后誕生的Docker以其鏡像封裝和單進程容器的理念,將這種內核級虛擬化技術推上百萬人追捧的高度。阿里緊隨技術前進的步伐,早早的就用上了虛擬機和容器,在2017年雙十一時,在線業務服務的容器化比例已經達到100%。然而,接下來的挑戰是,基礎設施資源利用率還能做得更高嗎?
甩掉了虛擬機的硬件指令轉換和操作系統開銷,運行在容器中的程序與普通程序之間只有一層薄薄的內核Namespace隔離,完全沒有運行時性能損耗,虛擬化在這個方向上似乎已經發展到了極限。唯一的可能是,拋開通用場景,專注到測試環境管理的特定場景上,繼續尋找突破。終于,阿里在這個領域里發現了新的寶藏:服務級虛擬化。
所謂服務級虛擬化,本質上是基于消息路由的控制,實現集群中部分服務的復用。在服務級虛擬化方式下,許多外表龐大的獨立測試環境實際只需要消耗極小的額外基礎設施資源,即使給每個開發者配備一套專用的測試環境集群都不再是吹牛。
具體來說,在阿里的交付流程上,包含兩種特殊類型的測試環境:“公共基礎環境”和“特性環境”,它們形成了具有阿里特色的測試環境使用方法。公共基礎環境是一個全套的服務運行環境,它通常運行一個相對穩定的服務版本,也有些團隊將始終部署各服務的最新版本的低級別環境(稱為“日常環境”)作為公共基礎環境。
特性環境是這套方法中最有意思的地方,它是虛擬的環境。從表面上看,每個特性環境都是一套獨立完整的測試環境,由一系列服務組成集群,而實際上,除了個別當前使用者想要測試的服務,其余服務都是通過路由系統和消息中間件虛擬出來的,指向公共基礎環境的相應服務。由于在阿里通常的開發流程中,開發任務需要經過特性分支、發布分支和諸多相關環節最后發布上線,大多數環境都從發布分支部署,唯獨這種開發者自用的虛擬環境部署來自代碼特性分支的版本,故可稱為“特性環境”(阿里內部叫“項目環境”)。
舉個具體例子,某交易系統的完整部署需要由鑒權服務、交易服務、訂單服務、結算服務等十幾種小系統以及相應的數據庫、緩存池、消息中間件等組成,那么它的公共基礎環境就是這樣一套具備所有服務和周邊組件的完整環境。假設此時有兩套特性環境在運行,一套只啟動了交易服務,另一套啟動了交易服務、訂單服務和結算服務。對于第一套特性環境的使用者而言,雖然除交易服務外的所有服務實際上都由公共基礎環境代理,但在使用時就像是自己獨占一整套完整環境:可以隨意部署和更新環境中交易服務的版本,并對它進行調試,不用擔心會影響其他用戶。對于第二套特性環境的使用者,則可以對部署在該環境中的三個服務進行聯調和驗證,倘若在場景中使用到了鑒權服務,則由公共基礎環境的鑒權服務來響應。
咋看起來,這不就是動態修改域名對應的路由地址、或者消息主題對應的投遞地址么?實事并沒那么簡單,因為不能為了某個特性環境而修改公共基礎環境的路由,所以單靠正統路由機制只能實現單向目標控制,即特性環境里的服務主動發起調用能夠正確路由,若請求的發起方在公共基礎環境上,就無法知道該將請求發給哪個特性環境了。對于HTTP類型的請求甚至很難處理回調的情況,當處于公共基礎環境的服務進行回調時,域名解析會將目標指向公共基礎環境上的同名服務。
如何才能實現數據雙向的正確路由和投遞呢?不妨先回到這個問題的本質上來:請求應該進入哪個特性環境,是與請求的發起人相關的。因此實現雙向綁定的關鍵在于,識別請求發起人所處的特性環境和進行端到端的路由控制。這個過程與“灰度發布”很有幾分相似,可采用類似的思路解決。
得益于阿里在中間件領域的技術積累,和鷹眼等路由追蹤工具的廣泛使用,識別請求發起人和追溯回調鏈路都不算難事。如此一來,路由控制也就水到渠成了。當使用特性環境時,用戶需要“加入”到該環境,這個操作會將用戶標識(如IP地址或用戶ID)與指定的特性環境關聯起來,每個用戶只能同時屬于一個特性環境。當數據請求經過路由中間件(消息隊列、消息網關、HTTP網關等),一旦識別到請求的發起人當前處在特性環境中,就會嘗試把請求路由給該環境中的服務,若該環境沒有與目標一致的服務,才路由或投遞到公共基礎環境上。
特性環境并不是孤立存在的,它可以建立在容器技術之上,從而獲得更大的靈活性。正如將容器建立在虛擬機之上得到基礎設施獲取的便利性一樣,在特性環境中,通過容器快速而動態的部署服務,意味著用戶可以隨時向特性環境中增加一個需要修改或調試的服務,也可以將環境中的某個服務隨時銷毀,讓公共基礎環境的自動接替它。
還有一個問題是服務集群調試。
配合AoneFlow的特性分支工作方式,倘若將幾個服務的不同特性分支部署到同一個特性環境,就可以進行多特性的即時聯調,從而將特性環境用于集成測試。不過,即使特性環境的創建成本很低,畢竟服務是部署在測試集群上的。這意味著每次修改代碼都需要等待流水線的構建和部署,節約了空間開銷,卻沒有縮短時間開銷。
為了進一步的降低成本、提高效率,阿里團隊又搗鼓出了一種開腦洞的玩法:將本地開發機加入特性環境。在集團內部,由于開發機和測試環境都使用內網IP地址,稍加變通其實不難將特定的測試環境請求直接路由到開發機。這意味著,在特性環境的用戶即使訪問一個實際來自公共基礎環境的服務,在后續處理鏈路上的一部分服務也可以來自特性環境,甚至來自本地環境。現在,調試集群中的服務變得非常簡單,再也不用等待漫長的流水線構建,就像整個測試環境都運行在本地一樣。
DIY體驗特性環境
覺得服務級虛擬化太小眾,離普通開發者很遠?實事并非如此,我們現在就可以動手DIY個體驗版的特性環境來玩。
阿里的特性環境實現了包括HTTP調用、RPC調用、消息隊列、消息通知等各類常用服務通信方式的雙向路由服務級虛擬化。要完成這樣的功能齊全的測試環境有點費勁,從通用性角度考慮,咱不妨從最符合大眾口味的HTTP協議開始,做個支持單向路由的簡易款。
為了便于管理環境,最好得有一個能跑容器的集群,在開源社區里,功能齊全的Kubernetes是個不錯的選擇。在Kubernetes中有些與路由控制有關的概念,它們都以資源對象的形式展現給用戶。
簡單介紹一下,Namespace對象能隔離服務的路由域(與容器隔離使用的內核Namespace不是一個東西,勿混淆),Service對象用來指定服務的路由目標和名稱,Deployment對象對應真實部署的服務。類型是ClusterIP(以及NodePort和LoadBalancer類型,暫且忽略它們)的Service對象可路由相同Namespace內的一個真實服務,類型是ExternalName的Service對象則可作為外部服務在當前Namespace的路由代理。這些資源對象的管理都可以使用YAML格式的文件來描述,大致了解完這些,就可以開始動工了。
基礎設施和Kubernetes集群搭建的過程略過,下面直接進正題。先得準備路由兜底的公共基礎環境,這是一個全量測試環境,包括被測系統里的所有服務和其他基礎設施。暫不考慮對外訪問,公共基礎環境中的所有服務相應的Service對象都可以使用ClusterIP類型,假設它們對應的Namespace名稱為pub-base-env。這樣一來,Kubernetes會為此環境中的每個服務自動賦予Namespace內可用的域名“服務名.svc.cluster”和集群全局域名“服務名.pub-base-env.svc.cluster”。有了兜底的保障后,就可以開始創建特性環境了,最簡單的特性環境可以只包含一個真實服務(例如trade-service),其余服務全部用ExternalName類型的Service對象代理到公共基礎環境上。假設它使用名稱為feature-env-1的Namespace,其描述的YAML如下(省略了非關鍵字段的信息):
kind: Namespace
metadata:
name: feature-env-1
kind: Service
metadata:
name: trade-service
namespace: feature-env-1
spec:
type: ClusterIP
...
kind: Deployment
metadata:
name: trade-service
namespace: feature-env-1
spec:
...
kind: Service
metadata:
name: order-service
namespace: feature-env-1
spec:
type: ExternalName
externalName: order-service.pub-base-env.svc.cluster
...
kind: Service
...
注意其中的order-service服務,它在當前特性環境Namespace中可以使用局部域名order-service.svc.cluster訪問,請求會路由到它配置的全局域名order-service.pub-base-env.svc.cluster,即公共基礎環境的同名服務上處理。處于該Namespace中的其它服務感知不到這個差異,而是會覺得這個Namespace中部署了所有相關的服務。
若在特性的開發過程中,開發者對order-service服務也進行了修改,此時應該將修改過的服務版本添加到環境里來。只需修改order-service的Service對象屬性(使用Kubernetes的patch操作),將其改為ClusterIP類型,同時在當前Namespace中創建一個Deployment對象與之關聯即可。
由于修改Service對象只對相應Namespace(即相應的特性環境)內的服務有效,無法影響從公共基礎環境回調的請求,因此路由是單向的。在這種情況下,特性環境中必須包含待測調用鏈路的入口服務和包含回調操作的服務。例如待測的特性是由界面操作發起的,提供用戶界面的服務就是入口服務。即使該服務沒有修改,也應該在特性環境中部署它的主線版本。
通過這種機制也不難實現把集群服務局部替換成本地服務進行調試開發的功能,倘若集群和本地主機都在內網,將ExternalName類型的Service對象指向本地的IP地址和服務端口就可以了。否則需要為本地服務增加公網路由,通過動態域名解析來實現。
與此同時,云效也正在逐步完善基于Kubernetes的特性環境解決方案,屆時將會提供更加全面的路由隔離支持。值得一提的是,由于公有云的特殊性,在聯調時將本地主機加入云上集群是個必須克服的難題。為此云效實現了通過隧道網絡+kube-proxy自身路由能力,將本地局域網主機(無需公網IP地址)加入到不在同一內網Kubernetes集群進行聯調的方式。其中的技術細節也將在近期的云效公眾號向大家揭曉,敬請留意。
小結
當許多人還在等待,在虛擬機和容器之后,下一輪虛擬化技術的風口何時到來的時候,阿里已經給出了一種答案。創業者的心態讓阿里人懂得,能省必須省。其實,限制創新的往往不是技術而是想象力,服務級虛擬化的理念突破了人們對環境副本的傳統認知,以獨特的角度化解了測試環境成本與穩定性的矛盾。
作為一種頗具特色的技術載體,特性環境的價值不僅僅在于輕量的測試環境管理體驗,更在于為每位開發人員帶來流暢的工作方式,實則是“簡約而不簡單”。
實踐出真知,阿里巴巴云效平臺致力于解決大型項目協作、敏捷高速迭代、海量代碼托管、高效測試工具、分布式秒級構建、大規模集群部署發布等世界級業務和技術難題,為阿里巴巴集團內部、生態伙伴以及云上開發者服務。誠摯歡迎業界同行與我們探討交流。
相關閱讀:
在阿里,我們如何管理代碼分支
當kubernetes應用遇到阿里分批發布模式
點此了解云效測試平臺
作者: 云效鼓勵師
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。