如何使用Pact和Docker保障微服務在生產環境中的通信

alexire 6年前發布 | 30K 次閱讀 微服務 Docker

本文要點

  • 如何在生產上線之前未測試微服務間的通信,那接下來恐怕就會有段不好過的日子了。
  • 消費者驅動契約預先定義了交互,可以使用它實現微服務間的交互測試
  • 消費者驅動契約可以用于驗證你正在構建的服務消費者驅動契約可以讓你構建出消費者需要的產品,而不是你認為他們需要的產品。
  • Pact框架是消費者驅動契約的一種實現。簡單的JSON文件定義了兩個微服務之間預期請求和響應的結構。
  • Docker 可以使微服務擺脫平臺依賴。你可以利用它在本地和持續集成環境中測試你的微服務。

一開始,有很多的巨型單體應用。這些巨型單體應用是大型應用,包含了針對于一個項目的所有業務邏輯。隨著時間的推移,這些巨型單體應用往往會越來越大,越來越笨重,也就越來越難部署了。于是,誕生了新的模式:微服務。

大多數人都知道微服務是什么,以及關于可伸縮性和業務領域崩潰的問題。微服務的另一個主要部分是通信。如果使用巨型單體應用,你會有些訪問數據源的方法調用,而現在就是通過網絡向其他服務發HTTP請求了。

這種通信需要大量的管理。比如,在Java中,如果一個方法不存在,那么代碼就無法編譯。然而,如果一個HTTP端點不存在,那么你可能在應用部署時才會剛剛發現這一點。

對于改過并立即部署的微服務時,你如何使這種混亂變得有序?你如何確保服務繼續按恰當的消費格式提供正確的數據給下流的服務?測試變成非常非常重要。

就像大多數測試形式一樣,規模較小的待測對象更容易測試。微服務也不例外,但這么做也帶來了一些有意思的挑戰。例如,如果一個流程需要15個微服務,那么你怎么來測試呢?

你得讓所有的應用都在你本地運行起來。當然,你可能有更多的微服務,這種方法是無法伸縮的。例如,Uber 有超過一千個微服務。你的蘋果筆記本根本不可能處理這么多服務。

你可能有一個質保或測試環境,在上面運行所有的應用。這很不錯,你可以看到使用著真實數據的應用,擁有所有活動的部件。這種方式最大的缺點是你必須得一口氣發布整個應用。團隊無法獨立地部署這些服務,必須把所有東西當作一個整體來測。微服務的任何一點兒變更,都意味著你需要重新驗證整個質保環境,以確保方方面面都仍然可以正常運轉。

于是,就需要消費者驅動契約(Consumer Driven Contracts)了。

消費者驅動契約

消費者驅動契約不是什么新發明。它們已經存在很多年了。Ian Robinson在Martin Fowlers的博客上發表了一篇非常精彩的文章, 完善概括了消費者驅動契約。

那么,什么是消費者驅動契約(CDC)呢?

它是消費服務和提供服務間的契約,聲明消費者想從提供服務那里以已定義的格式獲得什么內容。

它們是消費和提供服務彼此如何交互的一種描述方式,但在這里,消費服務是通信的驅動方。

這種方式有幾個關鍵的好處。第一,它們可以使服務獨立地部署。你不再需要擁有一個具備所有服務的質保環境了,不再需要用它將所有服務作為一個整體來運行以去測試應用的運轉了。你不需要做長長的、脆弱的、昂貴的流程測試以確保服務可以啟動和通信。

另一個關鍵好處是,這種契約可以由一個服務獨立地生成。假想一下,你需要一個用于提供服務新端點。你知道它大概應該是什么樣的,以及新的消費者將需要的確切數據。

那么就可以為提供者團隊生成消費者驅動契約讓他們去實現了。你可以放心,只要提供團隊遵循這個消費者驅動契約,那么所有東西都可以正常工作。這種方式非常適用于分布式團隊。這份消費者驅動契約形成之后,消費團隊和提供團隊就可以同時去忙自己的代碼了,這時,整個世界是分離開來的。

最后,消費者驅動契約更微妙的地方在于,它們能讓你驗證正在做的東西。從提供者的視角你不能100%地確保所提供的服務實際會滿足消費者的需要。通過從消費者的視角出發,你可以定義應提供什么,以及應采用什么樣的格式。

同時,消費服務使用任何數據都不應需要任何額外的邏輯或轉換,因為契約已經從消費者的視角定義了格式。

消費者驅動契約不是銀彈 .有許多東西消費者驅動契約都未涉及。首先,它們不是業務邏輯的測試。這些應由服務的單元測試來覆蓋。

如果你打算改動一個端點潛在的業務邏輯,但不改變其返回的數據格式,那么就應該沒問題,因為消費服務不必擔心數據是如何創建或交付的。如果消費服務介意數據是如何形成的,那么就說明業務邊界未得到適當地定義。然而,就所有事情一樣,變更時團隊間的溝通還是必要的。

消費者驅動契約也不是服務間的服務層協議(SLA)。它們不聲明一項服務應有多久的有效期,每分鐘要能處理多少請求。

還要注意的是,消費者驅動契約不能驗證外部API服務響應。例如,消費者驅動契約不能驗證谷歌地圖。如果外部來源決定改變他們的格式,那這是人家的權力。和第三方合作伙伴保持溝通總是件有益無害的事,需要時就做好遷移的準備。

Rightmove是如何使用消費者驅動契約的

Rightmove 是英國最大的房地產門戶網站,每天的請求數超過5千萬。我們幫人們找到他們的夢想家園,無論它是古老的農舍,還是流行的倫敦公寓。在Rightmove ,我們有許多已經集成和部署的微服務。這就產生了大量的挑戰和協調,從而促使我們使用了消費者驅動契約。

在Rightmove內部使用了消費者驅動契約之后,我們提出了一個愿望清單,這樣我們就可以更容易地分享我們出于任何原因所做的任何事情了。

消費者驅動契約愿望清單是:

  1. 描述提供者和消費者之間請求和響應的統一格式
  2. 能夠簡單制定契約的方式
  3. 保存已制定契約的方式
  4. 針對契約測試服務的方式,要在我們的持續集成/持續交付流程中以自動化的方式進行
  5. 本地執行這些測試的方式

1.對于消費者驅動契約的格式

消費者驅動契約是一個概念,所以要用的話就需要一個已定義的、共同約定的格式。其本質,無非就是這樣的一種陳述:“如果我發送這種請求,預期得到這種結果”。于是,輪到 Pact Foundation 出面了。他們創建了一種用于描述請求和響應的一致的JSON格式。在Pact,契約被稱為條約(pact),它們是簡單的JSON文件,包含請求和預期的響應。如果你的服務發送一個HTTP GET請求到 /user/125sb45sd,預期得到200 Ok返回碼和一個JSON報文,以及其他預期的報頭。

條約最值得稱道的地方就是它是由JSON來寫的,這是種通用數據格式。幾乎每種程序語言都有JSON解析器,所以圍繞條約的生成有許多的類庫。實際上,服務的消費和提供甚至不需要用同一種語言編寫。

2.消費者驅動契約的創建

Pact基金會也有很多用許多不同語言實現的用于創建消費者驅動契約的類庫。如果用Java,你可以找到 Pact JVM library 。創建一個條約看起來非常像寫一個JUnit測試。

如何使用Pact和Docker保障微服務在生產環境中的通信

(來自 – Pact JVM Consumer JUnit Library )

當這個“測試”運行時,它將生成一個JSON文件,也就是條約。這意味著,對于開發人員來說很容易就能理解將發生什么,然后去構建和擴展它們。你的條約是以代碼的方式定義的,是可復寫的。只要你像JUnit測試那么一運行,就將創建出條約文件,無論何時何地,比如在構建的時候。

3.消費者驅動契約的保存

在Rightmove,我們有我們自己內部構建的條約代理服務(盡管也有一個是由 Pact基金會 提供的),它把我們所有的條件都保存在一個集中的地方。然后就可以使用RESTful API進行查詢了。我們在服務構建過程中根據服務名和版本號來儲存所有的條約。

我們還有一個自主開發的已部署的版本(Deployed Versions)的服務。該已部署的版本服務讓我們可以了解哪些服務的版本已經部署了,在什么時候部署的。搭配條約代理,讓我們可以更清楚地了解變更,從而進行更好地測試。

4.在持續集成/持續交付管道中運行測試

在我們的持續交付管道中,有一個測試關卡,我們會在此檢查該服務是否已經符合消費者驅動契約。

我們的持續交付管道是一個非常標準的事務,我們從那里提交任務,其不僅會生成條約,還將生成可交付的產品工件。該產品工件將上傳到我們的資源庫,而同時條約會上傳到條約代理。

在“提交”這一步時,我們還會生成一個應用程序的樁(如果它是提供者),它只包含控制器和一個模擬的服務層。這個“樁”代替消費者驅動契約測試中的產品工件,因為該工件不需要數據源或上游依賴,它們已經被模擬了。

如何使用Pact和Docker保障微服務在生產環境中的通信

如果消費者驅動契約測試這一步失敗,那么整個管道也將失敗。這意味著,潛在的產品工件不能繼續往下走,必須得看看什么出錯了。

所以,這些測試實際看起來是什么樣的呢?

首先,我們必須把這個過程分解為兩個不同的流程,提供者流程和消費者流程。測試運行依賴于待測服務是消費服務還是提供服務。應該注意的是,如果一個服務即是消費者又是提供者,我們就會運行兩次。

最初,我們只測試提供者及其流程。然而,我們發現在他們各自的管道分別測試消費者流程和提供者流程好處更大。這確保了使用者不能改變想從API中得到的東西,而不需要提供者意識到這些變化,并為它們做好準備。雖然這需要團隊間進行一些額外的協調,但是它能讓我們在生產環境中回滾應用時更有信心,知道消費服務仍能正常工作,它仍可以被消費。

提供者測試流程

如何使用Pact和Docker保障微服務在生產環境中的通信

我們圍繞一組具體的微服務展開思考,這樣就能更容易理解這個流程了。在本例中,我們想測試一個名為Location的服務。它有兩個消費者服務:Location-Frontend 和 Management。

如何使用Pact和Docker保障微服務在生產環境中的通信

下面是測試過程的樣子,拆分為:

  1. 下載用于Location 服務的最新的Location-Frontend 和Management服務條約。
  2. 下載模擬的Location 服務
  3. 啟動這個模擬的Location 服務
  4. Gradle 運行器讀取這些條約,發送請求并檢查響應
  5. 停掉那個模擬的Location 服務

首先,我們下載了用于Location 服務的JSON條約文件。條約代理很容易做到了這一點,它可以查找Locations 消費者,以及用于這些消費者的最新條約。具體在本例中,即為Management 服務和 Location-Frontend。

接下來,我們下載Location 服務。然而,我們下載的并不是“真正的”Location 服務,也就是那個實際將被部署的服務。而是它的模擬版,里面已經清除了邏輯服務和數據層。它只有控制器和可啟動的web服務。這樣就消除了對上游服務和數據源的依賴。這個樁只是展示了API層和偽造的數據。記住,消費者驅動契約不是業務邏輯的測試。我們用Spring實現了這一點,替換了注入的依賴。其他依賴注入框架同樣也能實現它。

為針對這個樁運行條約的內容,我們使用了一個為Gradle定制的運行器。這是我們內部用 Pact JVM Gradle 插件 實現的。它其實起到了一個裝具的作用,啟動樁、閱讀條約、發送請求,然后記錄結果。

消費者條約流程

在下載樁工件和運行條約這一部分,消費者條約流程與提供者條約流程非常類似。然而,還有些關鍵的不同。

如何使用Pact和Docker保障微服務在生產環境中的通信

再強調一次,當你圍繞一組具體的微服務展開思考時,就很容易理解它了。

如何使用Pact和Docker保障微服務在生產環境中的通信

例如,我們一起來考慮一下 Location-Frontend。Location-Frontend是我們的 針對前端的后端 (或BFF)服務,用于顯示搜索結果。為做到它,它先通過Location 服務查找一個location (位置),然后把它傳遞給我們的和Search(檢索)服務。這表示Location-Frontend會從兩個提供者處消費。

當消費者管理到達消費者驅動契約測試階段時,它會從條約代理那里下載自己最新的條約。如果一個服務從多個提供者那里消費,將會從每個提供者那里下載條約。

讓我們把這個過程分解為幾步:

  1. 為Location-Frontend下載最新的條約,包括Location 和Search(提供者)
  2. 針對每個提供者:
    • a. 下載和啟動樁
    • b. Gradle運行器閱讀Location-Frontend和提供者之間的條約,并發送請求/檢查響應
    • c. 關閉模擬的提供者
  3. 發布結果

就像你看到的,我們仍然下載了提供者樁,但是是針對于我們的消費者生成的單個條約運行它的,而不是用于這些提供者的每個消費者的多個條約。

它與提供管道的流程實際上是相同的,只是我們為單個指定消費者提供了各自的提供者。

5.運行本地的測試

就像上面所說的,我們為實際的微服務創建了一個模擬的副本,這些樁處于數據層和任何額外的東西之上。它差不多就是一個有著控制器和偽造的靜態數據的外殼。

使用微服務的一個問題就是得在本地將它們作為一個整體去運行全部所有。條約只在一定程度上解決了這一點,但它可以很好地測試你本地的應用條約,而不需要通過整個交付管道。

于是,Docker 來了。

Docker 讓我們可以在任何平臺上以同樣的方式運行我們的樁應用,包括在管道中。有許多能讓我們與Docker 交互的類庫,所以Rightmove創建了一個名為條約運行器(Pact Runner)的新項目,它是一個單獨的jar。

條約運行器做出來替代了我們的Gradle運行器。Gradle運行器是我們做的,用于運行消費者和提供者流程。所以,它分為兩個項目,每個針對其中一個流程。我們發現它很難維護,我們不可以用它來運行本地的條約測試。而條約運行器就是旨在解決這些問題的。它不是基于 Pact JVM Gradle 類庫了,而是依賴于 Pact JVM Provider 來寫的。這讓我們有了更好的控制力,能讓我們寫一些輕便的東西,即能作為消費者運行器,也能作為提供者運行器。使用Docker 能使其不依賴于平臺,也改進了我們條約測試的速度,因為我們可以在本地緩存服務的圖片,并在運行不同的測試時復用它們。

應該注意的是,條約運行器遵循上面所述的消費者和提供者流程,它只是此邏輯的實現。例如,依賴于測試流程,條約運行器將下載樁提供者,它們是Docker 鏡像的形式,將此鏡像作為容器啟動,針對這些容器運行條約,然后關掉它并發布結果。

條約運行器使團隊可以運行將在交付管道內運行的同樣的測試。

總結

雖然這個方式不夠完美,但它使我們的團隊可以獨立地工作,使團隊在部署自己的服務時有信心它們的變更不會破壞其他的服務。它還改進了團隊間的交流,有助于在早期了解開發人員關于API設計的想法。未來的改進包括將條約用作自動化生成端點文檔的來源,它將使API的版本管理更加輕松。

自在Rightmove實施消費者驅動契約以來,我們得到了很多收獲。我們的團隊能夠獨立部署服務,而不依賴于其他服務,同時有信心他們的服務能保持通信。我們回顧了幾個迭代,從只測試提供者,到測試消費者和提供者,以及測試實時應用和回滾的應用。我們意識到需要一種簡化本地測試的方式,從而引領著我們走向了Docker 提供的平臺無關的特性。

就如何針對你的項目完成此類測試, Pact 基金會 有許多偉大的工具和文檔,如果沒有它,以上任何測試我們都做不到。非常感謝為此付出艱苦工作的每個人,是他們完成了所有的成果,它們都是免費、開源的。

關于作者

如何使用Pact和Docker保障微服務在生產環境中的通信 Harry Winser  是Rightmove搜索團隊的技術經理。之前他在平臺團隊,在那里他從事許多有意思的內部項目,這些項目讓開發人員可以持續地交付代碼。他不工作的時候,通常會去改改小型項目、寫寫博客、彈彈吉他,或者駕他的小船在英國的運河上巡游。

查看英文原文:  How to be Confident that Your Microservices Can Still Communicate in Production with Pact and Docker

 

來自:http://www.infoq.com/cn/articles/microservices-consumer-driven-contracts-pact-docker

 

 本文由用戶 alexire 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!