UCloud API&SDK 工程化實踐(一):如何設計與實現 SDK 全生命週期自動化

摘要

對於公有云服務,API 是客戶接入使用的主路徑之一,API 的質量和持續穩定性至關重要,在使用者角度,代表着雲服務的質量和穩定性。如何妥善管理API 以及基於API的 SDK, 如何在產品快速發展的同時,持續的保障 API 的穩定性、可靠性,是一個非常大的挑戰。

UCloud 接入產品開發團隊在 2019 年下半年,通過工程化的手段,打通了 API 管理、測試管理、網關日誌、Gitlab CI 等內部系統,以 API 團隊的契約爲中心改造了整條工具鏈產品的發佈鏈路,對 API&SDK 進行閉環校驗,持續保證質量。並對基於 Github 的開源項目構建了通用的發佈管道和版本策略。

當前工程化共支持了 16 個產品 300+ API SDK 的日常發佈,以及 SDK側長達半年的每日迴歸測試。從需要 1 人天/產品的人工開發成本,到 5 分鐘完成一次新產品 SDK 代碼生成,工程效率產生了質的飛躍。

本文介紹了 UCloud 在 API/SDK 工程化改造上的一些實踐,以供大家交流與參考。

概覽

1、爲什麼需要進行圍繞 API/SDK 的工程體系建設

UCloud 接入產品團隊負責研發工具鏈產品與網關,探索控制檯以外的基礎設施接入方式。陸續發佈了包括 Terraform、Packer、CLI、移動網絡探測等接入工具並全部開源。工具鏈產品由於便於使用,更符合現代軟件開發的理念等特點,成爲了客戶 DevOps 團隊接入的最主要手段。

怎麼定義工具鏈產品?首先對於雲廠商來說,與 Restful 等面向資源的 API(Resource-Oriented)風格不同,UCloud 開放的 OpenAPI 大多是面向行爲(Action-Oriented)的。OpenAPI 除了實現 CRUD 之外,還負責發送各種各樣控制平面(Control Plane)的指令,如資源的伸縮啓停。

那麼對於 UCloud 來說,工具鏈產品的價值在於,API 實現最小顆粒度的行爲,工具鏈產品組合這些行爲,進行更高層次的抽象。比如 Terraform 對於資源依賴圖的抽象,CLI 對行爲的級聯和批量操作,Packer 對鏡像的生命週期管理等等。工具鏈產品構建在 API/SDK 之上,是 API/SDK 的高層次封裝。

圖 1 :工具鏈產品示例

工具鏈產品區別於控制檯的特點在於,工具鏈產品實踐了基礎設施即代碼(IaC,Infrastructure as Code)這一理念,設施與行爲都可以使用代碼來嚴格地描述與版本化,易於集成。同時工具鏈產品在運行時的生命週期是全自動的,在實際的應用場景中,當我們定義好一個資源拓撲,或者基於工具鏈產品編寫了一個腳本之後,我們期望它的運行過程是全自動的,不需要人工干預。

由於上述兩個特點,工具鏈產品對外的接口必須保證是嚴格的,不能隨意變動,這就要求 API 也必須是嚴格的。同時對於公有云 API/SDK 可用性的重度依賴,使得我們不得不尋求一些工程上的方法,來確保工具鏈產品的整個生命週期都是可用的。

2、面臨的問題

• 開發容易變更難,工具鏈產品包括幾乎所有 IaaS 產品的流量接入,日常變更十分繁瑣,需要自動化變更。

• 工具鏈產品全線開源,作爲知名的雲廠商,必須保證開源社區的核心指標,即 90% 覆蓋率,A 以上的代碼風格評級,需要有成熟的機制來自動化驗證。

• 相比友商,我們的團隊規模小很多,需要儘可能複用現有設施,對系統變更最頻繁的部分做自動化生成,充分發揮技術帶來的效率紅利。

全景圖

爲了解決上述問題,團隊在 2919 年下半年開始進行 API/SDK 工程體系的建設,第一期的目標是使工具鏈產品所依賴的 SDK 實現全自動化的代碼生成。

圖 2 :API/SDK 工程體系建設全景圖

從圖中可以看出,工具鏈產品團隊構建了一個通用的發佈代理服務,用來執行 CI Job、處理相關的發佈任務,並通過機器人與 Github 做了打通。

相關技術解析

1、API 建模

爲了使 API 能夠得到統一管理,同時防止產品間豎井式的信息隔離,UCloud 測試平臺部在 2017 年底打造了公共的 API 管理平臺「優效」,將所有現網 API 的定義收斂至統一的 API 註冊中心上,使用自定義的格式來形式化地描述 API。

在接下來的一年時間裏,優效添加了測試管理功能,支持了一種基於 DSL 表達式的行爲驅動測試開發模式,測試團隊在優效上編輯測試步驟,使用 DSL 完成數據的抽取轉換和對比,構建測試場景並執行。

2018 年下半年,我們團隊開始進行工具鏈產品的研發,包括資源編排、CLI 及其依賴的全線 IaaS 產品控制平面 OpenAPI SDK,發佈了第一版基於優效自動生成的 Go SDK。

2019 年下半年,我們通過跨部門協作對測試表達式進行了中間表示生成,實現了將 DSL 表達式完整地轉譯成各語言 SDK 調用代碼,完成了與測試團隊的工作流整合。

圖 3 :API、SDK、測試相互依存

這一系列工作的意義在於,API 作爲企業與雲廠商之間交互的契約,需要具有實際的效力,而這個效力是需要一些工程上的手段來確立的。API、測試與 SDK 相互作用,構成一個有機的整體,才能使這個契約具有實際的效力。

2、SDK 代碼生成

代碼生成是編譯器領域的一個典型問題,目前各界對於相關技術已經有了很充分的研究,而 SDK 生成使用的技術並不複雜。這裏只是簡單分享一下,UCloud 在代碼生成過程中使用了哪些技術,以及如何將一個不規則的遺留問題,通過形式化的方法轉化爲一個已知領域的問題的思路,希望對大家有所啓發。

2.1 調用方代碼生成

由於雲服務 SDK 對於編程語言類型系統中,類型安全的需要,雲服務的 SDK 除了提供一種萬能的,泛化調用方式(就像各語言的 HTTP Client 一樣),還會將每一個 API 的參數和返回通過類型系統顯式的定義出來。比如聲明成 Python/Java 中的類,Go 中的結構體。

得益於測試平臺部在優效上的早期工作,調用方代碼只需使用模版引擎將目標代碼渲染出來即可。對於一些版本割裂比較嚴重的語言,如 Python 2/3,我們選擇利用語言原生的抽象語法樹來對代碼進行二次剪枝處理。

由於衆所周知的原因,Python 2 原計劃於 2020 年 1 月停止維護,雖然受一些原因影響,停止維護的時間向後延期了一段時間,但 Python 2 的壽終正寢已成定局。但作爲雲廠商而言,依然存在不少客戶的存量系統依然在使用 Python 2,其中不乏大體量的客戶,存量系統改造對他們來說,是一個風險收益很難權衡的問題,所以雲廠商依然要做好長期支持 Python 2 的準備。

圖 4 :Python 3 to 2 語法樹剪枝

UCloud 對於 Python 2/3 SDK 的生成方式是,首先生成 Python 3 的代碼,之後將 Python 3 的代碼轉換成抽象語法樹(AST,Abstract Syntax Tree),之後遍歷這棵 AST,移除其中 Python 3 Only 的節點,如 type hints,添加一些兼容性的節點,例如新式類 object 基類,utf8 header 等等,最終從這棵樹中重建出 Python 2 SDK 的代碼。

2.2 測試代碼生成

優效測試模塊由測試團隊於2018 年開發上線,使用一種可視化配置來編寫測試,遵循行爲驅動開發的理念(BDD,Behavior-driven development),使測試定義與實現解藕。

由於接入產品團隊對於 API 的重度依賴,如果想保證工具鏈產品的可靠性,必須對依賴的 SDK 進行充分測試。而且由於 SDK 原則上要覆蓋所有公有云 IaaS 產品,出於成本和責權劃分等因素的考慮,複用現有存量的測試用例是唯一可行的驗證手段。

圖 5 :測試結構抽象

類似於 ThoughtWorks 的 3S (Specification、Scenario、Step)抽象,UCloud 將測試抽象爲測試解決方案(Solution)、測試集(Set)、測試步驟(Step) 三個層次。多個測試步驟(Step)構成一個測試集(Set),順序執行;多個測試集構成一個解決方案(Solution),並行執行。

每一個測試步驟由請求、綁定和驗證三個部分構成,幷包含一些控制類屬性如重試、延時、快速終止等。以處理片狀測試(Flaky Testing )的場景。

測試步驟中請求、綁定和驗證的值都可以使用一種 DSL 表達式來編寫,可以處理一些複雜邏輯,如獲取時間戳、四則運算、數據抽取等。

測試代碼生成的難點就在於,原有的測試執行引擎是使用 Python 解釋執行的,類型系統比較薄弱,同時由於沒有通過形式化的文法定義表達式,隨着時間的推移,表達式變得越來越不規範,如何將 DSL 表達式,轉譯成 SDK 的測試代碼,成了一個巨大的挑戰。

一個測試表達式的示例如下,可以看出這個表達式的語法還是比較簡單的。

${u_eval(${u_get_timestamp(10)} - 3600)}

對於形如上式的簡單表達式解析,有兩種思路,一種是直接手寫一個遞歸下降的解析器,將詞法分析和語法分析一併完成,我們早期也是這麼處理的。

但是隨着時間的推移,我們發現不僅僅只有上述那一種表達式形式,更多不規範的表達式寫法被挖掘出來,我們意識到,一味改 Parser 並不是一個長久的辦法,應該將文法清晰地定義出來,形成一個共識,只複用那些較爲規範的測試集,這樣才能應用在多種不同的編程語言上。所以我們迴歸了傳統的寫法,手寫 Lexer,通過  Yacc 生成 Parser。

圖 6 :語法分析構造語法樹

Golang 寫 Lexer,參考了 Rob Pike 2011 年的 Slide,《 Lexical Scanning in Go》,這裏不再贅述,其核心思想就是將狀態轉移的動作抽象成函數,描述出在 One Pass 過程中,遇到每個字符時的狀態轉移動作。下圖是 Lexer 過程中的狀態轉移:

圖 7 :Lexer 狀態機

Parser 的部分我們使用了 goyacc,通過定義好的文法,生成解析器代碼,解析器的輸入是詞法分析階段產生的 Token,輸出是一棵 JSON 格式的表達式語法樹,下圖是文法和語法樹的示例:

圖 8 :語法樹示例

有了 JSON 格式的中間表示,我們在任何語言中,讀出這棵語法樹,之後用簡單的模版引擎,就可以渲染出想要的目標代碼,生成 SDK 的測試代碼。

3、運行時抽象

在 SDK 的代碼中,有一部分代碼是公共的,並且極少變更,實現了請求序列化、重試、日誌、錯誤處理等等,對於這部分代碼需要一個統一的運行時抽象來減少開發成本。SDK 的本質是請求的生命週期管理,該場景有一個典型的抽象:

圖 9 :洋蔥圈模型

圖片來源:Egg.js 文檔《異步編程模型》小節

洋蔥圈模型是面向切面編程(AOP)的經典應用,業務邏輯作爲洋蔥的一層表皮,當一個請求發起的時候,經過一層一層的前側的洋蔥表皮,例如參數注入,簽名算法等,到達洋蔥中心,請求響應時,從洋蔥中心再經過後側的洋蔥表皮例如日誌、重試、錯誤處理等,返回結果。

這樣做的好處在於,業務邏輯與請求生命週期是完全正交的,可以更加靈活地添加或刪除業務邏輯。例如客戶可以自定義 API 返回錯誤時它的公共處理行爲,注入自定義模塊等等,保持業務邏輯是易理解,可測試的。

4、持續發佈

OpenAPI SDK 項目包含幾乎所有 IaaS 產品的 API 變更,作爲一個典型的高頻變更業務,項目早期面臨以下問題:

• 手工發佈有出錯風險,雖然可以補救,但會給客戶造成困擾

• 研發負擔大,開源項目發佈非常繁瑣,需要變更版本號、標籤、README、Release Note 等,浪費研發資源

• 僅有自動化測試,沒有自動化變更,造成二者會產生不一致現象

因爲相比友商,UCloud 的人員數量少很多,所以更需要充分發揮技術帶來的效率紅利。接入產品部最終選擇了使用 Gitlab CI + Github Bot 來對 SDK 等開源工具鏈產品進行全自動化發佈。

5、流水線設計

首先,我們將 SDK 發佈拆分爲日常發佈與窗口發佈,將大量具有中斷性質的審查任務移動到每週的固定時間進行處理,保證團隊在高工作負載下依然能夠保證足夠的研發能力。並對兩種發佈任務分別設計了全自動化的發佈流水線,如下圖所示:

圖 10 :日常發佈流程

圖 11 :窗口發佈流程

日常發佈任務會執行日常的代碼生成和測試工作,合入代碼倉庫。在窗口發佈任務中會使用 Github 機器人進行版本凍結和發佈工作,並推送 SDK 到各語言官方製品倉庫中。

6、版本策略

可以看到,在每週的發佈窗口內,如果有發佈任務,SDK 將對版本進行自動化變更,版本號遵循以下策略自動計算:

• 合併PR 前,引導代碼貢獻者提供格式化的描述信息

• 每次發佈時,歸併上次發佈之間所有PR 的描述信息

• 通過Pull Request Comment 計算版本

• 新特性,新產品:主版本+1

• 特性增強,新API:次版本+1

• 問題修復,更新API:補丁版本+1

• 如果是預覽版,則主版本號鎖定爲零,從次版本號開始遞增

例如:

圖 12 :版本自動計算規則

7、服務化

在做 SDK 相關能力的時候,常常有團隊希望使用 SDK 與兄弟團隊聯調,或在產品內測時使用 SDK 自我驗證,這就需要 SDK 能夠提供主動發佈能力,由外部團隊觸發構建,並生成預覽版。

對於這種場景,接入產品團隊對發佈流程進行了服務化改造,提供了一個簡單的代理服務,用來觸發 CI Job:

圖 13 :代理服務拓撲

CI/CD 服務化帶來的收益包括,發佈流程的標準化、模塊化,可複用性顯著提升,發佈流水線本身作爲業務領域統一建模,使得發佈本身是可維、可測的。並且給將來更進一步的改造打下堅實的基礎。

總結與展望

在過去的半年中,UCloud 重新梳理了 API 模型,添加了 SDK 通用抽象,使用編譯器前端和模版技術對 SDK 代碼生成問題進行了統一建模,將發佈流水線自動化,完成了 API/SDK 能力的第一期工程體系建設。

下一階段的目標是,將 API/SDK 工程化的能力集成到 Git 工作流中,使得任何人都可以自助地進行發佈,而無需特定的團隊參與。我們在每一個階段的工作都會通過文章記錄並分享出來,給業界有類似場景的小夥伴分享實際落地的經驗以供參考,希望可以對大家有所幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章