唱吧DevOps的落地,微服務CI/CD的範本技術解讀

推薦閱讀:

https://blog.csdn.net/m0_46995061/article/details/106124211

https://blog.csdn.net/m0_46995061/article/details/106470873

https://blog.csdn.net/m0_46995061/article/details/106243292

1、業務架構:從單體式到微服務

K歌亭是唱吧的一條新業務線,旨在提供線下便捷的快餐式K歌方式,用戶可以在一個電話亭大小的空間裏完成K歌體驗。K歌亭在客戶端有VOD、微信和Web共三個交互入口,業務複雜度較高,如長連接池服務、用戶系統服務、商戶系統、增量更新服務、ERP等。對於服務端的穩定性要求也很高,因爲K歌亭擺放地點不固定,很多場所的運營活動會造成突發流量。

爲了快速開發上線,K歌亭項目最初採用的是傳統的單體式架構,但是隨着時間的推移,需求的迭代速度變得很快,代碼冗餘變多,經常會出現牽一髮動全身的改動。重構不但會花費大量的時間,而且對運維和穩定性也會造成很大的壓力;此外,代碼的耦合度高,新人上手較困難,往往需要通讀大量代碼纔不會踩進坑裏。

鑑於上述弊端,我們決定接下來的版本里採用微服務的架構模型。從單體式結構轉向微服務架構中會持續碰到服務邊界劃分的問題:比如,我們有user服務來提供用戶的基礎信息,那麼用戶的頭像和圖片等是應該單獨劃分爲一個新的service更好還是應該合併到user服務裏呢?如果服務的粒度劃分的過粗,那就回到了單體式的老路;如果過細,那服務間調用的開銷就變得不可忽視了,管理難度也會指數級增加。目前爲止還沒有一個可以稱之爲服務邊界劃分的標準,只能根據不同的業務系統加以調節,目前K歌亭拆分的大原則是當一塊業務不依賴或極少依賴其它服務,有獨立的業務語義,爲超過2個的其他服務或客戶端提供數據,那麼它就應該被拆分成一個獨立的服務模塊。

在採用了微服務架構之後,我們就可以動態調節服務的資源分配從而應對壓力、服務自治、可獨立部署、服務間解耦。開發人員可以自由選擇自己開發服務的語言和存儲結構等,目前整體上使用PHP做基礎的Web服務和接口層,使用Go語言來做長連接池等其他核心服務,服務間採用thrift來做RPC交互。

2、系統架構的構思與解讀

2.1 容器編排

唱吧K歌亭的微服務架構採用了Mesos和Marathon作爲容器編排的工具。在我們選型初期的時候還有三個其他選擇,Kubernetes、 Swarm、 DC/OS:

  • DC/OS:作爲Mesosphere公司的拳頭產品,基本上是希望一統天下的節奏。所以組件很多,功能也很全面。但是對於我們在進行微服務架構初期,功能過於龐大,學習成本比較高,後期的生產環境維護壓力也比較大。
  • Swarm:Docker公司自己做的容器編排工具,當時瞭解到100個以上物理節點會有無響應的情況,對於穩定性有一些擔憂。
  • Kubernetes:Google開源的的容器編排工具,在選型初期還沒有很多公司使用的案例,同時也聽到了很多關於穩定性的聲音,所以沒有考慮。但是在整個2016年,越來越多的公司開始在線上使用Kubernetes,其穩定性逐步提高,如果再選型應該也是個好選擇。
  • Mesos:因爲了解到Twitter已經把Mesos用於生產環境,並且感覺架構和功能也相對簡單,所以最後選擇了Mesos+Marathon作爲容器編排的工具。

唱吧DevOps的落地,微服務CI/CD的範本技術解讀

 

2.2 服務發現

我們採用了etcd作爲服務發現的組件,etcd是一個高可用的分佈式環境下的 key/value 存儲服務。在etcd中,存儲是以樹形結構來實現的,非葉結點定義爲文件夾,葉結點則是文件。我們約定每個服務的根路徑爲/v2/keys/service/$service_name/,每個服務實例的實際地址則存儲於以服務實例的uuid爲文件名的文件中,比如賬戶服務account service當前啓動了3個可以實例,那麼它在etcd中的表現形式則如下圖:

唱吧DevOps的落地,微服務CI/CD的範本技術解讀

 

當一個服務實例向etcd寫入地址成功時我們就可以認爲當前服務實例已經註冊成功,那麼當這個服務實例由於種種原因down掉了之後,服務地址自然也需要失效,那麼在etcd中要如何實現呢?

注意,圖中的每個文件有一個ttl值,單位是秒,當ttl的值爲0時對應的文件將會被etcd自動刪除。當每個服務實例啓動之後第一次註冊時會把存活時間即ttl值初始化爲10s,然後每隔一段時間去刷新ttl,用來像向etcd彙報自己的存活,比如7s,在這種情況下基本上可以保證服務有效性的更新的及時性。如果在一個ttl內服務down掉了,則會有10s鐘的時間是服務地址有效;而服務本身不可用,這就需要服務的調用方做相應的處理,比如重試或這選擇其它服務實例地址。

我們服務發現的機制是每個服務自注冊,即每個服務啓動的時候先得到宿主機器上面的空閒端口;然後隨機一個或多個給自己並監聽,當服務啓動完畢時開始向etcd集羣註冊自己的服務地址,而服務的使用者則從etcd中獲取所需服務的所有可用地址,從而實現服務發現。

同時,我們這樣的機制也爲容器以HOST的網絡模式啓動提供了保證。因爲BRIDGE模式確實對於網絡的損耗太大,在最開始就被我們否決了,採用了HOST模式之後網絡方面的影響確實不是很大。

2.3 監控,日誌與報警

唱吧DevOps的落地,微服務CI/CD的範本技術解讀

 

我們選擇Prometheus彙總監控數據,用ElasticSearch彙總日誌,主要的原因有:

  1. 生態相對成熟,相關文檔很全面,從通用的到專用的各種exporter也很豐富。
  2. 查詢語句和配置簡單易上手。
  3. 原生具有分佈式屬性。
  4. 所有組件都可以部署在Docker容器內。

Mesos Exporter,是Prometheus開源的項目,可以用來收集容器的各項運行指標。我們主要使用了對於Docker容器的監控這部分功能,針對每個服務啓動的容器數量,每個宿主機上啓動的容器數量,每個容器的CPU、內存、網絡IO、磁盤IO等。並且本身他消耗的資源也很少,每個容器分配0.2CPU,128MB內存也毫無壓力。

在選擇Mesos Exporter之前,我們也考慮過使用cAdvisor。cAdvisor是一個Google開源的項目,跟Mesos Exporter收集的信息八成以上都是類似的;而且也可以通過image字段也可以變相實現關聯服務與容器,只是Mesos exporter裏面的source字段可以直接關聯到marathon的application id,更加直觀一些。同時cAdvisor還可以統計一些自定義事件,而我們更多的用日誌去收集類似數據,再加上Mesos Exporter也可以統計一些Mesos本身的指標,比如已分配和未分配的資源,所以我們最終選擇了Mesos Exporter。

如下圖,就是我們監控的部分容器相關指標在Grafana上面的展示:

唱吧DevOps的落地,微服務CI/CD的範本技術解讀

 

Node exporter,是Prometheus開源的項目,用來收集物理機器上面的各項指標。之前一直使用Zabbix來監控物理機器的各項指標,這次使用NodeExporter+Prometheus主要是出於效率和對於容器生態的支持兩方面考慮。時序數據庫在監控數據的存儲和查詢的效率方面較關係數據庫的優勢確實非常明顯,具體展示在Grafana上面如下圖:

唱吧DevOps的落地,微服務CI/CD的範本技術解讀

 

Filebeat是用來替換Logstash-forwarder的日誌收集組件,可以收集宿主機上面的各種日誌。我們所有的服務都會掛載宿主機的本地路徑,每個服務容器的會把自己的GUID寫入日誌來區分來源。日誌經由ElasticSearch彙總之後,聚合的Dashboard我們統一都會放在Grafana上面,具體排查線上問題的時候,會用Kibana去查看日誌。

Prometheus配置好了報警之後可以通過AlertManager發送,但是對於報警的聚合的支持還是很弱的。在下一階段我們會引入一些Message Queue來自己的報警系統,加強對於報警的聚合和處理。

ElastAlert是Yelp的一個Python開源項目,主要的功能是定時輪詢ElasticSearch的API來發現是否達到報警的臨界值,它的一個特色是預定義了各種報警的類型,比如frequency、change、flatline、cardinality等,非常靈活,也節省了我們很多二次開發的成本。

2.4 事務追蹤系統——KTrace

對於一套微服務的系統結構來說,最大的難點並不是實際業務代碼的編寫,而是服務的監控和調試以及容器的編排。微服務相對於其他分佈式架構的設計來說會把服務的粒度拆到更小,一次請求的路徑層級會比其他結構更深,同一個服務的實例部署很分散,當出現了性能瓶頸或者bug時如何第一時間定位問題所在的節點極爲重要,所以對於微服務來說,完善的trace機制是系統的核心之一。

目前很多廠商使用的trace都是參考2010年Google發表的一篇論文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》來實現的,其中最著名的當屬twitter的zipkin,國內的如淘寶的eagle eye。由於用戶規模量級的逐年提升,分佈式設計的系統理念越來越爲各廠商所接受,於是誕生了trace的一個實現標準opentracing ,opentracing標準目前支持Go、JavaScript、Java、 Python、Objective-C、C++六種語言。 由sourcegraph開源的appdash是一款輕量級的,支持opentracing標準的開源trace組件,使用Go語言開發K歌亭目前對appdash進行了二次開發,並將其作爲其後端trace服務(下文直接將其稱之爲Ktrace),主要原因是appdash足夠輕量,修改起來比較容易。唱吧K歌亭業務的膠水層使用PHP來實現,appdash提供了對protobuf的支持,這樣只需要我們自己在PHP層實現middleware即可。

在trace系統中有如下幾個概念

(1)Annotation

一個annotation是用來即時的記錄一個事件的發生,以下是一系列預定義的用來記錄一次請求開始和結束的核心annotation

  1. cs - Client Start。 客戶端發起一次請求時記錄
  2. sr - Server Receive。 服務器收到請求並開始處理,sr和cs的差值就是網絡延時和時鐘誤差
  3. ss - Server Send: 服務器完成處理並返回給客戶端,ss和sr的差值就是實際的處理時長
  4. cr - Client Receive: 客戶端收到回覆時建立。 標誌着一個span的結束。我們通常認爲一但cr被記錄了,一個RPC調用也就完成了。

其他的annotation則在整個請求的生命週期裏建立以記錄更多的信息 。

(2)Span

由特定RPC的一系列annotation構成Span序列,span記錄了很多特定信息如 traceId, spandId, parentId和RPC name。

Span通常都很小,例如序列化後的span通常都是kb級別或者更小。 如果span超過了kb量級那就會有很多其他的問題,比如超過了kafka的單條消息大小限制(1M)。 就算你提高kafka的消息大小限制,過大的span也會增大開銷,降低trace系統的可用性。 因此,只存儲那些能表示系統行爲的信息即可。

(3)Trace

一個trace中所有的span都共享一個根span,trace就是一個擁有共同traceid的span的集合,所有的span按照spanid和父spanid來整合成樹形,從而展現一次請求的調用鏈。

目前每次請求由PHP端生成traceid,並將span寫入Ktrace,沿調用鏈傳遞traceid,每個service自己在有需要的地方埋點並寫入Ktrace。舉例如下圖:

唱吧DevOps的落地,微服務CI/CD的範本技術解讀

 

每個色塊是一個span,表明了實際的執行時間,通常的調用層級不會超過10,點擊span則會看到每個span裏的annotation記錄的很多附加信息,比如服務實例所在的物理機的IP和端口等,trace系統的消耗一般不會對系統的表現影響太大,通常情況下可以忽略,但是當QPS很高時trace的開銷就要加以考量,通常會調整採樣率或者使用消息隊列等來異步處理。不過,異步處理會影響trace記錄的實時性,需要針對不同業務加以取捨。

目前K歌亭在生產環境裏的QPS不超過1k,所以大部分的記錄是直接寫到ktrace裏的,只有歌曲搜索服務嘗試性的寫在kafka裏,由mqcollector收集並記錄,ktrace的存儲目前只支持MySQL。一個好的trace設計可以極快的幫你定位問題,判斷系統的瓶頸所在。

2.5 自動擴容

唱吧DevOps的落地,微服務CI/CD的範本技術解讀

 

在服務訪問峯值的出現時,往往需要臨時擴容來應對更多的請求。除了手動通過Marathon增加容器數量之外,我們也設計實現了一套自動擴縮容的系統來應對。我們擴縮容的觸發機制很直接,根據各個服務的QPS、CPU佔用、內存佔用這三個指標來衡量,如果三個指標有兩個指標達到,即啓動自動擴容。我們的自動擴容系統包括3個模塊:

  1. Scout:用於從各個數據源取得自動擴容所需要的數據。由於我們的日誌全部都彙總在ElasticSearch裏面,容器的運行指標都彙總到Prometheus裏面,所以我們的自動擴容系統會定時的請求二者的API,得到每個服務的實時QPS、CPU和內存信息,然後送給Headquarter。
  2. Headquarter:用於數據的處理和是否觸發擴縮容的判斷。把從Scout收到的各項數據與本地預先定義好的規則進行比對,如果有兩個指標超過定義好的規則,則通知到Signalman模塊。
  3. Signalman:用於調用各個下游組件執行具體擴縮容的動作。目前我們只會調用Marathon的/v2/apps/{app_id}接口,去完成對應服務的擴容。因爲我們的服務在容器啓動之後會自己向etcd註冊,所以查詢完容器狀態之後,擴縮容的任務就完成了。

3、基於Mesos+Marathon的CI/CD

3.1 持續集成與容器調度

在唱吧,我們使用Jenkins作爲持續集成的工具。主要原因是我們想在自己的機房維護持續集成的後端,所以放棄了Travis之類的系統。

在實施持續集成的工作過程中,我們碰到了下列問題:

  1. Jenkins Master的管理問題。多個團隊共享一個Master,會導致權限管理困難,配置改動、升級門檻很高,Job創建和修改有很多規則;每個團隊用自己的Master,會導致各個Master之間的插件、更新、環境維護有很多的重複工作。
  2. Jenkins Slave 資源分配不平均:忙時Jenkins slave數量不足,Job運行需要排隊;閒時Jenkins Slave又出現空閒,非常浪費資源。
  3. Jenkins job運行需要的環境多種多樣,比如我們就有PHP,java,maven,Go,python等多種編譯運行環境,搭建和維護slave非常費時。
  4. 多個開發人員的同時提交,各自的代碼放到各自獨立的測試環境進行測試。

基於以上問題,我們選擇使用Mesos和Marathon來管理Jenkins集羣,把Jenkins Master和Jenkins Slave都放到Docker容器裏面,可以非常有效的解決以上問題。基礎架構如下圖:

唱吧DevOps的落地,微服務CI/CD的範本技術解讀

 

  1. 不同開發團隊之間使用不同的Jenkins Master。把公用的權限、升級、配置和插件更新到私有Jenkins Master鏡像裏面,推到私有鏡像倉庫,然後通過Marathon部署新的Master鏡像,新團隊拿到的Jenkins Master就預安裝好了各種插件,各個現有團隊可以無縫接收到整體Jenkins的升級。
  2. 各種不同環境的Jenkins Slave,做成Slave鏡像。按照需要,可以通過Swarm Plugin自動註冊到Jenkins master,從而組織成slave pool的形式;也可以每個job自己去啓動自己的容器,然後在容器裏面去執行任務。
  3. Jenkins job從容器調度的角度分成兩類,如下圖:
  • 環境敏感型:比如編譯任務,需要每次編譯的環境完全乾淨,我們會從鏡像倉庫拉取一個全新的鏡像啓動容器,去執行Job,然後再Job執行完成之後關閉容器。
  • 時間敏感型:比如執行測試的Job,需要儘快得到測試結果,但是測試機器的環境對於測試結果沒什麼影響,我們就會從已經啓動好的Slave Pool裏面去拉取一個空閒的Slave去執行Job。然後再根據Slave被使用的頻率去動態的擴縮容Slave pool的大小就好了。

唱吧DevOps的落地,微服務CI/CD的範本技術解讀

 

3.2 CI/CD流程

基於上述的基礎架構,我們定義了我們自己的持續集成與持續交付的流程。其中除了大規模使用Jenkins與一些自定製的Jenkins插件之外,我們也自己研發了自己的部署系統——HAWAII。

在HAWAII中可以很直觀的查看各個服務與模塊的持續集成結果,包括最新的版本,SCM revision,測試結果等信息,然後選擇相應的版本去部署生產環境。

唱吧DevOps的落地,微服務CI/CD的範本技術解讀

 

在部署之前,可以查看詳細的測試結果和與線上版本的區別,以及上線過程中的各個步驟運行的狀態。

唱吧DevOps的落地,微服務CI/CD的範本技術解讀

 

基於上述基礎架構,我們的CI/CD流程如下:

唱吧DevOps的落地,微服務CI/CD的範本技術解讀

 

  1. SVN或者GIT收到新的代碼提交之後,會通過hook啓動相應的Jenkins job,觸發整個CI流程。
  2. Jenkins從私有鏡像倉庫拉取相對應的編譯環境,完成代碼的編譯。
  3. Jenkins從私有鏡像倉庫拉取相對應的運行時環境,把上一步編譯好的產品包打到鏡像裏面,並生成一個新版本的產品鏡像。產品鏡像是最終可以部署到線上環境的鏡像,該鏡像的metadata也會被提交到部署系統HAWAII,包括GUID,SCM revision,Committer,時間戳等信息
  4. 將步驟3中生成的產品鏡像部署到Alpha環境(該環境主要用於自動化迴歸測試,實際啓動的容器其實是一個完整環境,包括數據容器,依賴的服務等)。
  5. Jenkins從私有鏡像倉庫拉取相對應的UT和FT的測試機鏡像,進行測試。測試完成之後,會銷燬Alpha環境和所有的測試機容器,測試結果會保存到部署系統HAWAII,並會郵件通知到相關人員。
  6. 如果測試通過,會將第3步生成的產品鏡像部署到QA環境,進行一系列更大範圍的迴歸測試和集成測試。測試結果也會記錄到HAWAII,有測試不通過的地方會從第1步從頭開始迭代。
  7. 全部測試通過後,就開始使用HAWAII把步驟3中生成的產品鏡像部署到線上環境。

小結

隨着互聯網的高速發展,各個公司都面臨着巨大的產品迭代壓力,如何更快的發佈高質量的產品,也是每個互聯網公司都面臨的問題。在這個大趨勢下,微服務與DevOps的概念應運而生,在低耦合的同時實現高聚合,也對新時代的DevOps提出了更高的技術與理念要求。

這也是我們公司在這個新的業務線上面進行,進行嘗試的主要原因之一。對於微服務、容器編排、虛擬化、DevOps這些領域,我們一步一步經歷了從無到有的過程,所以很多方面都是本着從滿足業務的目標來儘量嚴謹的開展,包括所有的服務與基礎架構都進行了高併發且長時間的壓力測試。

在下一步的工作中,我們也有幾個核心研究的方向與目標,也希望能跟大家一起學習與探討:

  1. 完善服務降級機制
  2. 完善報警機制
  3. 完善負載均衡機制
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章