微服務與持續交付

發布時間:2016/8/12 11:56:35  關鍵字:微服務、持續交付、微服務與持續交付

  十年以前,軟件在一年之內的交付次數屈指可數。

  過去的十年間,交付的過程一直被不斷地優化和改進。從早期的RUP模型、敏捷、XP、Scrum,再到近幾年的精益創業、DevOps,都力求能更有效地降低交付過程所耗費的成本并提高效率,從而盡早實現軟件的價值。

  持續交付是一種軟件開發策略,用于優化軟件交付的流程,以盡快得到高質量、有價值的軟件。這種方法能幫助組織更快地驗證業務想法,并通過快速迭代的方式持續為用戶提供價值。

  對于任何一個可交付的軟件來說,必然要經歷分析、設計、開發、測試、構建、部署、運維的過程。而從持續交付的角度來分析,對于任何一個可部署的獨立單元,它都應該有一套獨立的交付機制,來有效支撐其開發、測試、構建、部署與運維的整個過程。

  如第2章所述,微服務將一個應用拆分成多個獨立的服務,每個服務都具有業務屬性,并且能獨立地被開發、測試、構建、部署。換句話說,每個服務都是一個可交付的“系統”。那么在這種細粒度的情況下,如何有效保障每個服務的交付效率,快速實現其業務價值呢?

  本文,我們就來探討微服務與持續交付。本文的內容主要包括:

  • 什么是持續交付
  • 持續交付的核心
  • 微服務與持續交付

  從技術上講,持續交付是軟件系統的構建、部署、測試、審核、發布過程的一種自動化實現,而其中的核心則是部署流水線。因為部署流水線能夠將這幾個環節有效地連接起來。當然,像探索性測試、易用性測試,以及管理人員的審批流程等還是需要一定的手工操作,如圖1所示。

圖1 持續交付

  1 持續交付的核心

  在持續交付過程中,需求以小批量形式在團隊的各個角色間順暢流動,并以較短的周期完成小粒度的頻繁發布。實際上,頻繁的交付不僅能持續為用戶提供價值,而且能產生快速的反饋,幫助業務人員制定更好的發布策略。

  因此,持續交付的核心在于三個字:小、頻、快。

  • 小批量價值流動

  通過建立自動化的構建及部署機制,將業務功能以小批量的方式,從需求產生端移動到用戶端。

  • 頻繁可發布

  通過建立自動化的構建及部署機制,將小批量的業務功能頻繁地從需求產生端移動到用戶端,持續地交付價值。

  • 快速反饋

  通過建立高效的反饋機制,快速驗證需求是否有效。同時根據反饋,及時指導業務團隊并調整策略,優先為用戶交付高價值的功能。

  持續交付讓業務功能在整個軟件交付過程中以小批量方式在各角色間順暢流動,通過更頻繁的、低風險的發布快速獲得用戶反饋,以此來持續達成業務目標。

  2 微服務架構與持續交付

  從交付的角度來分析,對于任何一個可部署的獨立單元,它都應該有一套獨立的部署流水線,來有效支撐其開發、測試、構建、部署與運維的整個過程。

  在微服務架構中,由于每個服務都是一個獨立的、可部署的業務單元,因此,每個服務也應該對應著一套獨立的持續交付流水線,可謂是“麻雀雖小,五臟俱全”。

  接下來,讓我們看看在微服務的架構中,如果構建這樣一套持續交付的流水線,各個環節需要做什么樣的準備。

  2. 1 開發

  對于微服務架構而言,如果希望構建獨立的持續交付流水線,我們在開發階段應該盡量做到如下幾點。

  • 獨立代碼庫

  對于每一個服務而言,其代碼庫和其他服務的代碼庫在物理上應該是隔離的。所謂物理隔離,是指代碼庫本身互不干擾,不同的服務有不同的代碼庫訪問地址。譬如,對于我們平時使用的SVN、GIT等工具,每個服務都對應且只對應一個獨立的代碼庫URL。如下所示,分別表示產品信息服務和客戶信息服務的代碼庫。

  http://github.com/xxxxx/products-service

  http://github.com/xxxxx/customers-service

  除此之外,對不同服務隔離代碼庫的另一個好處在于,對某服務的代碼進行修改,完全不用擔心影響其他服務代碼庫中的代碼,在很大程度上避免了修改一處,導致多處發生缺陷的情況。

  • 服務說明文件

  對于每一個服務而言,都應有一個清晰的服務說明,描述當前服務的信息,同時幫助團隊更快地理解并快速上手。譬如,在筆者的微服務實踐過程中,對于每一個代碼庫,其服務說明都包括如下幾個部分。

  1. 服務介紹

  • 服務提供什么功能,譬如產品服務主要提供產品數據的獲取或者存儲。
  • 誰是服務的消費者。譬如產品服務的消費者為電商的前端網站系統或者CRM系統。

  2. 服務維護者

  • 挑選1~2個團隊的成員,作為服務的負責人,登記其姓名、電子郵件、電話等聯系方式,以便其他團隊遇到問題能及時找到服務的負責人。

  3. 服務可用期

  • 服務可用周期,如7×24小時,或周一~周五(7:00~19:00)等。
  • 可用率,可用率是指服務可以正常訪問的時間占總時間的百分比,如99.9%或者99%。如果服務一天內都可以訪問,則服務當天的可用率為100%。如果服務有3分鐘訪問中斷,而一天共有1440分鐘,那么服務的可用率為: ((1440 - 3) / 1440) * 100%,也就是99.79%。
  • 響應時間,指服務返回數據的可接受響應時間。譬如為0.5~1秒。

  4. 定義環境,描述服務運行的具體環境,通常包括:

  • 生產環境
  • 類生產環境
  • 測試環境

  5. 開發,描述開發相關的信息,通常包括:

  • 如何搭建開發環境
  • 如何運行服務
  • 如何定位問題

  6. 測試,描述測試相關的信息,通常包括:

  • 測試策略
  • 如何運行測試
  • 如何查看測試的統計結果,譬如測試覆蓋率、運行時間、性能等。

  7. 構建,描述持續集成以及構建相關的信息,通常包括:

  • 持續集成訪問的URL
  • 持續集成的流程描述
  • 構建后的部署包

  8. 部署,描述部署相關的信息,通常包括:

  • 如何部署到不同環境
  • 部署后的功能驗證

  9. 運維,描述運維相關的信息,通常包括:

  • 日志聚合的訪問
  • 告警信息的訪問
  • 監控信息的訪問
  • 代碼所有權歸團隊

  團隊的任何成員都能向代碼庫提交代碼,做到任何服務代碼的所有權歸團隊。

  代碼所有權歸團隊,它表現的更多的是團隊協同工作的觀念,即集體工作的價值大于每個個體生產價值的總和。當所有權屬于集體的時候,那么每個開發者就不應當出于個人原因來降低代碼質量。代碼質量上出現的問題應該在整個團隊的努力下共同處理。

  相反,如果某段代碼背后的業務知識沒有適當地分享給其他人,那么代碼的演變逐漸變為依賴于具體的某個人,瓶頸也就由此而產生。

  • 有效的代碼版本管理工具

  代碼版本管理工具的使用早已經成為開發者必備的核心技能之一,譬如Git、Mercurial,以及CVCS(Centralized Version Control System)等。不過,團隊最好能使用分布式版本控制工具(DVCS,Distributed Version Control System),它可以避免由于客戶端不能連接服務器所帶來的無法提交代碼的問題。

  • 代碼靜態檢查工具

  另外,團隊也需要有代碼靜態檢查工具,幫助完成代碼的靜態檢查。譬如Java語言的CheckStyle、Ruby語言的Rubocop等。

  另外,代碼度量(Code Metrics)工具,譬如常用的SonarQube、Ruby的Cane等,能夠保障團隊內部代碼的一致性和可維護性。

  • 易于本地運行

  作為團隊的開發人員,當我們從代碼庫檢出(Check out)某服務的代碼后,應該花很短的時間、很低的成本就能在本地環境中將服務運行起來。如果依賴于外部資源,并且構建、使用成本較高,就應該考慮采取其他打樁的機制來模擬這些外部資源。這類外部資源通常指數據庫、云存儲、緩存或者第三方系統等。

  譬如,筆者最近參與的一個企業內部系統改造項目,使用了OKTA集成單點登錄的功能。開發環境下當然也可以使用OKTA,但由于網絡、安全、審批等多種原因,極大影響了開發人員在本地環境訪問OKTA的效率。最后,團隊采用打樁的機制,構建了一套符合OKTA協議的模擬OKTA,在本地使用。在開發環境下,通過加載這個模擬的OKTA,有效地解決了本地訪問OKTA時間較長的問題。

  另外一個例子是,筆者在系統中使用了AWS的S3服務。由于權限、網絡等多種因素的存在,本地開發時使用S3的成本非常高,因此就構建了一套模擬的S3環境。當服務運行在開發環境時,加載開發模式的環境變量,訪問本地的Mock S3環境;而在生產環境,則使用生產模式的S3地址。在不改變任何代碼的前提下,幫助團隊快速在本地搭建運行環境并演示,極大地提高了開發效率,如圖2所示。

圖2 模擬S3存儲

  2. 2 測試

  對于微服務架構,如果希望構建獨立的持續交付流水線,我們在測試方面應該注意以下幾點。

  • 集成測試的二義性

  對于任何一個服務而言,單元測試必不可少。但是否需要集成測試,團隊可以根據喜好自行決定。筆者個人建議明確定義集成測試的范圍,因為“集成”這個詞,很難有一個準確的度量機制。到底什么樣的組合才叫集成?其可以是對外部不同系統之間的測試組合,也可以是系統內部實現邏輯、類與類之間調用的組合,因此“集成測試”這個術語,在團隊和組織內部,容易在溝通過程中產生誤解。

  • Mock與Stub

  對于單元測試而言,我們可以使用Mock框架幫助我們完成對依賴的模擬(Mock)或者打樁(Stub),譬如Java的Mockito、Ruby的RSpec等。當然,如果對象之間的依賴構建成本不高,也可以使用真實的調用關系而非Mock或者Stub機制。關于Mock和Stub的區別,有興趣的讀者可以參考ThoughtWorks首席科學家馬丁·福勒的Mocks Aren’t Stubs這篇文章。

  • 接口測試

  除了單元測試覆蓋代碼邏輯外,至少還應該有接口測試來覆蓋服務的接口部分。注意,對于服務的接口測試而言,更關注的是接口部分。譬如,作為數據的生產者,接口測試需要確保其提供的數據能夠符合消費者的要求。作為數據的消費者,接口測試需要確保,從生產者獲取數據后,能夠有效地被處理。另外,對于服務與服務之間的交互過程,最好能設計成無狀態的。

  • 測試的有效性

  如果單元測試的覆蓋率夠高,接口測試能有效覆蓋服務的接口,那么基本上測試機制就保障了服務所負責的業務邏輯以及和外部交互的正確性。

  有些朋友可能會存在疑問,是否需要使用行為測試的框架,譬如像Cucumber、JBehave等的工具,基于不同的場景,做一些類似用戶行為的測試呢?

  實際上,這里并沒有確定的答案。在筆者參與的項目中,通常都是在最外層做一部分行為測試,原因有以下幾點。

  1. 通常我們所說的服務,大多是不涉及用戶體驗部分的。也就是說,作為服務,更關注的是數據的改變,而不是同用戶的交互過程。譬如,當我們從電商網站挑選某件商品、下單,一個新的訂單就生成了。這時候,訂單的狀態可能是“新建”。隨后,當完成付款時,那么訂單的狀態可能會被更新成“已支付”。如果忽略狀態更新的實現流程,譬如同步更新、異步更新等,那么從服務的角度而言,其并不在意用戶到底是從PC端的瀏覽器,還是手機上的APP來完成訂單的支付,服務自身只完成了一件事:就是完成訂單狀態從“新建”到“已支付”的更新。

  2. 服務作為整個應用的一部分,能夠獨立存在,那必然有其對應的邊界條件。前面提到,單元測試保障內部邏輯,接口測試保障接口,在這樣的前提下,服務的正確性和有效性在大部分情況下已經得到了驗證。

  3. 從經典的測試金字塔來看,越是偏向于用戶場景、行為的測試,其成本越高,反饋的周期也越長;相反,越是接近代碼級別的測試,成本越低,反饋周期也相對較短,如圖3所示。

圖3 測試金字塔

  2. 3 持續集成

  持續集成經過多年的發展,已成為系統構建過程中眾所周知的最佳實踐之一。對于每個獨立的、可部署的服務而言,應為其建立一套持續集成的環境(Continuous Integration Project)。

  當團隊成員向服務的代碼庫提交代碼后,配置好的持續集成工程會通過定期刷新或者WebHook的方式檢測到代碼變化,觸發并執行之前開發階段定義的靜態檢查、代碼度量、測試以及完成構建的步驟,如圖4所示。

圖4 持續集成

  常用的企業級持續集成服務器有Jenkins、Bamboo以及GO等,在線的持續集成平臺有Travis-CI、Snap-CI等。

  更多關于持續集成的細節,請參考ThoughtWorks首席科學家馬丁·福勒的這篇文章http://www.martinfowler.com/articles/continuousIntegration.html。

  2. 4 構建

  每個服務都是一個可獨立部署的業務單元,經過靜態檢查、代碼度量、單元測試、接口測試等階段后,構建符合需求的部署包。

  部署包存在的形式是多種多樣的,可以是deb包、rpm包,能在不同UNIX操作系統平臺直接安裝;也可以是zip包、war包等,只需將其復制到指定的目錄下,執行某些命令,就可以工作。當然,也有可能是基于某特定的IAAS平臺,譬如亞馬遜的AMI,我們稱之為映像包(Image)。

  另外,作為容器化虛擬技術的代表,Docker(一個開源的Linux容器)的出現,允許開發者將應用以及依賴包打包到一個可移植的Docker容器中,然后發布到任何裝有Docker的Linux機器上。

  通過使用Docker,我們可以方便地構建基于Docker的部署鏡像包。

  2. 5 部署

  對于每個獨立的服務而言,如果希望構建獨立的持續交付流水線,需要選擇部署環境并制定合適的部署方式來完成部署。通常,我們可以從如下兩個維度考慮如何進行部署。

  1. 部署環境

  • 基于云平臺

  我們知道,云平臺是一個很廣的概念,其中主要包括IAAS、PAAS和SAAS三層。當基于云平臺部署的時候,要先分清楚部署的環境,即部署將發生在哪一層。由于SAAS層是相對于應用的使用者而言,軟件即服務。即對使用者而言,不需要再去考慮本地安裝、數據維護等因素,直接通過在線的方式享受服務,這和我們討論的部署環境沒有關系。因此,這里我們主要討論IAAS層和PAAS層的部署。另外,筆者這里沒有區分是公有云還是私有云。公有云指運行在Internet上的云服務,私有云則通常指運行在企業內部Intranet的云服務。

  • 基于IAAS層

  云平臺的IAAS層,通常包括運行服務的基礎資源,譬如計算節點、網絡、負載均衡器、防火墻等。因此,對于該層的部署包而言,實際上應該是一個操作系統映像,映像里包含運行服務所需要的基本環境,譬如JVM環境、Tomcat服務器、Ruby環境或者Passenger配置等。當在IAAS層部署服務時,不僅可以使用映像創建新的節點,也可以創建其他系統相關的資源,譬如負載均衡器、自動伸縮監控器、防火墻、分布式緩存等。

  • 基于PAAS層

  PAAS層并不關心基礎資源的管理,它更關注的是服務或者應用本身。因此,對于該層的部署包而言,通常是能直接在UNIX操作系統安裝的二進制包(譬如deb包或者rpm包等),或者是壓縮包(譬如zip包、tar包、jar包或者war包等),將其復制到指定目錄下解壓縮,啟動相關容器,就可以工作。

  除此之外,也可以使用PAAS平臺提供的工具或者SDK,直接對當前的代碼進行部署。譬如Heroku提供的命令行,就能很方便地將Java、Ruby、NodeJS等代碼部署到指定的環境中。

  • 基于數據中心

  云平臺已經成為大家公認的未來趨勢之一,但是對于很多傳統的企業,由于組織或者企業內部多年業務、數據的積累,以及組織架構、團隊、流程固化等原因,無法從現有數據中心一步遷移到云端。而且,針對傳統的數據中心,其對應的環境通常比較復雜,既沒有IAAS那種按需創建資源的靈活性,也沒有PAAS這種資源能夠被自動化調配的可伸縮性。這時候,對于數據中心而言,部署就相對較麻煩,需要投入更多的成本構建環境以及調配資源。

  很多企業也開始嘗試在數據中心的節點上創建虛擬機(譬如VMware、Xen等),以幫助簡化資源的創建以及調配。

  • 基于容器技術

  容器技術,是一種利用容器(Container)實現虛擬化的方式。同傳統的虛擬化方式不同的是,容器技術并不是一套完全的硬件虛擬化方法,它無法歸屬到全虛擬化、部分虛擬化和半虛擬化中的任意一個,它是一個操作系統級的虛擬化方法,能為用戶提供更多的資源。

  過去兩年里,Docker的快速發展使其成為容器技術的典型代表。Docker可以運行在任意平臺上,包括物理機、虛擬機、公有云、私有云、服務器等,這種兼容性使我們不用擔心生產環境的操作系統或者平臺的差異性,能夠很方便地將Docker的映像部署到任何運行Docker的環境中。

  2. 部署方式

  部署方式,是指通過什么樣的方法將服務有效地部署到相應的環境。對于服務而言,由于部署環境的不同,采用的部署方式自然也不同,如圖5所示。

圖5 部署方式的演變

  • 手動部署

  對于傳統的數據中心環境,考慮到資源有限以及安全性等因素,通常的部署方式都是使用SSH工具,登錄到目標機上,下載需要的部署包,然后復制到指定的位置,最后重啟服務。

  • 腳本部署

  由于部署團隊每次都要手動下載、復制,不僅效率低,而且人為出錯的概率也大。因此,很多企業和組織使用Shell腳本將這些下載、復制、重啟等過程逐漸實現自動化,大幅提升了效率。Shell腳本的優勢是兼容性好,但其弊端在于實現功能所需要的代碼量大,可讀性也較差,時間長了不易維護。

  • 基礎設施部署自動化

  隨著業務的發展,很多組織以及團隊逐漸發現,環境的安裝和配置、應用或者服務的部署所耗費的成本越來越高。“基礎設施自動化”這個概念的提出,正好有效地解決了這一類問題。于是,越來越多的組織開始嘗試使用Chef、Puppet、Ansible等工具,完成軟件的安裝和配置,以及應用或者服務的部署。

  • 應用部署自動化

  無須過多的人工干預、一鍵觸發即可完成部署的自動化方式可以說是任何組織,從業務、開發到運維都希望達成的目標。但說起來容易,實現起來卻很難,而且這也不是一蹴而就的過程。需要隨著組織或者企業在業務的演進過程中、技術的積累過程中,逐漸實現自動化。通常,應用部署的自動化主要包括以下兩部分。

  • 映像部署

  私有云、公有云的出現,使得部署方式發生了顯著的變化。面對的環境不一樣,因此部署包也不一樣。以前的war包、rpm包,在基于IAAS的云平臺上,都可以變成映像。譬如,對于亞馬遜的AWS云環境,可以方便地使用其提供的系統映像(AMI)來完成部署。

  基于映像的最顯著優勢在于,能夠在應用需要擴容的時候,更有效、迅速地擴容。原因在于,映像本身已經包括了操作系統和應用運行所需要的所有依賴,啟動即可提供服務。而基于war、rpm包等的部署,擴容時通常需要先啟動同構的節點,安裝依賴,之后才能部署具體的war包或者rpm包。

  • 容器部署

  利用容器技術,譬如Docker,構建出的部署包也可以是一個映像。該映像能運行在任何裝有Docker的環境中,有效地解決了開發與部署環境不一致的問題。同時,由于Docker是基于Linux容器的虛擬化技術,能夠在一臺機器上構建多個容器,因此也大大提高了節點的利用率。

  所以,就微服務架構本身而言,如何有效地基于部署環境選擇合適的部署方式,并最終完成自動化部署,是一個值得團隊或者組織不斷探討和實踐的過程。

  2. 6 運維

  由于每個服務都是一個可以獨立運行的業務單元,同時每個服務都運行在不同的獨立節點上。因此,需要為服務建立獨立的監控、告警、快速分析和定位問題的機制,我們將它們統一歸納為服務的運維。

  • 監控

  監控是整個運維環節中非常重要的一環。監控通常分為兩類:系統監控與應用監控。系統監控關注服務運行所在節點的健康狀況,譬如CPU、內存、磁盤、網絡等。應用監控則關注應用本身及其相關依賴的健康狀況,譬如服務本身是否可用、其依賴的服務是否能正常訪問等。

  關于監控,目前業界已經有很多成熟的產品,譬如Zabbix、NewRelic、Nagios以及國內的OneAPM等。對于筆者參與的項目,服務節點的運行環境大都基于AWS(使用EC2、ELB以及ASG等),因此使用AWS的CloudWatch作為系統監控工具的情況比較多。關于應用監控,通常使用NewRelic和Nagios作為監控工具。

  • 告警

  告警是運維環節另外一個非常重要的部分。我們知道,當系統出現異常時,通過監控能發現異常。這時候,通過合適的告警機制,則能及時、有效地通知相關責任人,做到早發現問題,早分析問題,早修復問題。由于每個服務都是獨立的個體,因此針對不同的服務,都應該能提供有效的告警機制,確保當該服務出現異常時,能夠準確有效地通知到相關責任人,并及時解決問題。

  對于告警工具,業界較有名的是PagerDuty,它支持多種提醒方式,譬如屏幕顯示、電話呼叫、短信通知、電郵通知等,而且在無人應答時還會自動將提醒級別提高。除此之外,之前提到的常用的監控產品也能提供告警機制。

  • 日志聚合

  除此之外,日志聚合也是運維部分必不可少的一環。由于微服務架構本質上是基于分布式系統之上的軟件應用架構方式,隨著服務的增多、節點的增多,登錄節點查看日志、分析日志的工作將會耗費更高的成本。通過日志聚合的方式,能有效將不同節點的日志聚合到集中的地方,便于分析和可視化。

  目前,業界最著名的日志聚合工具是Splunk和LogStash,不僅提供了有效的日志轉發機制,還提供了很方便的報表和定制化視圖。更多關于Splunk與LogStash的信息,請參考其官方網站。

  3 小結

  本文首先講述了持續交付的概念及其核心,接著討論了在微服務架構的實施過程中,如果建立基于服務的細粒度的持續交付流水線,應該考慮的因素。雖然微服務中的服務只是整個應用程序的一個業務單元,但作為一個可以獨立發布、獨立部署的個體,它必然也要遵循持續交付的機制和流程,包括開發、測試、集成、部署以及運維等,可謂是“麻雀雖小,五臟俱全”。通過搭建穩定的持續交付流水線,能夠幫助團隊頻繁、穩定地交付服務。

众人帮太赚钱了