歡迎光臨
每天分享高質量文章

知乎部署系統演進

 

應用部署是軟體開發中重要的一環,保持快速迭代、持續部署,減少變更和試錯成本,對於互聯網公司尤為重要。本文將從部署系統的角度,介紹知乎應用平臺從無到有的演進過程,希望可以對大家有所參考和幫助。
知乎部署系統由知乎工程效率團隊打造,服務於公司幾乎所有業務,每日部署次數在 2000 次左右,在啟用藍綠部署的情況下,大部分業務的生產環境上線時間可以在 10 秒以下(不包含金絲雀灰度驗證過程)。
目前知乎部署系統主要實現了以下功能:
  • 支持容器、物理機部署,支持在線、離線服務、定時任務以及靜態檔案的部署

  • 支持辦公網絡預上線

  • 支持金絲雀灰度驗證,期間支持故障檢測以及自動回滾

  • 支持藍綠部署,在藍綠部署情況下,上線和回滾時間均在秒級

  • 支持部署 Merge Request 階段的代碼,用於除錯

 

下文將按時間順序,對部署系統的功能演進進行介紹。

 

技術背景

 

在介紹部署系統之前,首先需要對知乎的相關基礎設施和網絡情況進行簡單的介紹。
知乎網絡情況
知乎的網絡如圖所示:
知乎網絡環境簡圖
主要劃分為三個部分:
  • 生產環境網絡:即知乎對外的在線服務器網絡,基於安全性考慮,與其他網絡環境完全隔離。

  • 測試環境網絡:應用在部署到生產環境之前,首先會部署在測試環境,測試環境網絡上與生產環境完全隔離。

  • 辦公室網絡:即知乎員工內部網絡,可以直接訪問測試環境,也可以通過跳板機訪問生產環境服務器。

 

流量管理
知乎採用 Nginx + HAProxy 的方式管理應用的流量走向:

知乎在線業務流量架構
應用開發者在 Nginx 平臺上配置好 Location 和 HAProxy 的對應關係,再由 HAProxy 將流量分發到 Real Server 上去,同時 HAProxy 承擔了負載均衡、限速、熔斷等功能。
持續集成
知乎採用 Jenkins + Docker 進行持續集成,詳見《知乎容器化構建系統設計和實踐》,持續集成完成後,會生成 Artifact,供部署系統以及其他系統使用。

 

物理機部署

 

像大多數公司一樣,知乎最開始是以物理機部署為主,業務自行編寫腳本進行部署,部署時間長、風險大、難以回滾。在這種情況下,大約在 2015 年,初版的部署系統 nami (取名自《海賊王》娜美)誕生了。
最初的部署系統採用 Fabric 作為基礎,將 CI 產生的 Artifact 上傳到物理機上解壓,並使用 Supervisor 進行行程管理,將服務啟動起來:
物理機部署
初版的部署系統雖然簡單,但是為了之後的改進奠定了基礎,很多基礎的概念,一直到現在還在使用。
應用(App)與服務(Unit)
與 CI 相同,每個應用對應一個 GitLab Repo,這個很好理解。
但是在實際使用過程中,我們發現,同一套代碼,往往對應著多個運行時的服務,比如以部署系統 nami 本身為例,雖然是同一套代碼,但是在啟動的時候,又要分為:
  • API 服務

  • 定時任務

  • Celery 離線佇列

 

這些運行單元的啟動命令、引數等各不相同,我們稱之為服務(Unit)。用戶需要在部署系統的前端界面上,為每個 Unit 進行啟動引數、環境變數等設置,整個應用才能正常啟動起來。
候選版本(Candidate)
所有的部署都是以 CI 產生 Artifact 作為基礎,由於 Artifact 的不可變性,每次部署該 Artifact 的結果都是可預期的。也就是說,每個 Artifact 都是代碼的一次快照,我們稱之為部署的候選版本( Candidate)。
由於每次 CI 都是由 GitLab 的 Merge Request 產生,候選版本,其實就是一次 MR 的結果,也就是一次代碼變更。通常情況下,一個候選版本對應一個 Merge Request:
每個候選版本對應一個 Merge Request
如圖所示是某個應用的候選版本串列,每個候選版本,用戶都可以將其部署到多個部署階段(Stage)。
部署階段(Stage)
上文提到,知乎服務器網絡分為測試環境和生產環境,網絡之間完全隔離。應用總是先部署測試環境,成功後再部署生產環境。
在部署系統上,我們的做法是,對每個候選版本的部署,拆分成多個階段(Stage):

構建/部署階段
圖中該應用有 6 個階段:
  • (B)構建階段:即 CI 生成 Artifact 的過程。

  • (T)測試環境:網絡、資料都與生產環境相隔離。

  • (O)辦公室階段:一個獨立的容器,只有辦公室網絡可以訪問,其他與線上環境相同,資料與生產環境共享。

  • (C)金絲雀1:生產環境 1% 的容器,外網可訪問。

  • (C)金絲雀2:生產環境 20% 的容器,外網可訪問。

  • (P)生產環境:生產環境 100% 容器,外網可訪問。

部署階段從前到後依次進行,每個 Stage 的部署邏輯大致相同。
對於每個部署階段,用戶可以單獨設置,是否進行自動部署。如果所有部署階段都選擇自動部署,那麼應用就處於一個持續部署(Continuous Deployment)的過程。
基於 Consul 和 HAProxy 的服務註冊與發現
每次部署物理機時,都會先將機器從 Consul 上摘除,當部署完成後,重新註冊到 Consul 上。
上文提到,我們通過 HAProxy 連接到 Real Server,原理就是基於 Consul Template 對 HAProxy 的配置進行更新,使其總是指向所有 RS 串列。
另外,在遷移到微服務架構之後,我們編寫了一個稱為 diplomat 的基礎庫,從 Consul 上拉取 RS 串列,用於 RPC 以及其他場景的服務發現。

 

容器部署

 

舊版容器系統 Bay
2015 年末,隨著容器大潮的襲來,知乎也進入容器時代,我們基於 Mesos 做了初版的容器編排系統(稱為 Bay),部署系統也很快支持了容器的部署。
Bay 的部署很簡單,每個 Unit 對應一個容器組,用戶可以手動設置容器組的數量和其他引數。每次部署的時候,滾動地上線新版本容器,下線舊版本容器,部署完成後所有舊版本容器就都已回收。對於一些擁有數百容器的大容器組,每次部署時間最長最長可以達到 18 分鐘。
各項功能完善
在遷移到容器部署的過程中,我們對部署系統也進行了其他方面的完善。
首先是健康檢查,所有 HTTP、RPC 服務,都需要實現一個 /check_health 接口,在部署完成後會對其進行檢查,當其 HTTP Code 為 200 時,部署才算成功,否則就會報錯。
其次是在線/離線服務的拆分,對於 HTTP、RPC 等在線業務,採用滾動部署;對於其他業務,則是先啟動全量新版本容器,再下線舊版本容器。
預上線與灰度發佈

 

基於容器,我們可以更靈活地增刪 Real Server,這使得我們可以更簡單地將流量拆分到不同候選版本的容器組中去,利用這一點,我們實現了辦公室網絡預上線和金絲雀灰度發佈。
辦公室網絡預上線
為了驗證知乎主站的變更,我們決定在辦公室網絡,提前訪問已經合併到主幹分支、但還沒有上線的代碼。我們在 Nginx 層面做了流量拆分,當訪問源是辦公室網絡的時候,流量流向辦公室專屬的 HAProxy:

辦公室流量拆分
對於部署系統來說,所需要做的就是在「生產環境」這個 Stage 之前,加入一個「辦公室」Stage,對於這個 Stage,只部署一個容器,並將這個容器註冊到辦公室專屬的 HAProxy,從外網無法訪問此容器。
金絲雀灰度發佈
在 2016 年以前,知乎部署都是全量上線,新的變更直接全量上線到外網,一旦出現問題,很可能導致整個網站宕機。
為瞭解決這個問題,我們在「辦公室」和「生產環境」Stage 之間,加入了「金絲雀1」和「金絲雀2」兩個 Stage,用於灰度驗證。
原理是,部署一定量額外的新版本容器,通過 HAProxy,隨機分發流量到這些新版本容器上,這樣如果新版本代碼存在問題,可以在指標系統上明顯看出問題:

Nginx 指標大盤
其中,「金絲雀1」階段只啟動相當於「生產環境」階段 1% 的容器,「金絲雀2」階段則啟動 20% 數量的容器。
為了避免每次部署到金絲雀後,都依賴人工去觀察指標系統,我們在部署系統上,又開發了「金絲雀自動回滾」功能。主要原理是:
  • 將金絲雀階段的指標與生產環境的指標分離

  • 金絲雀部署完成後,對指標進行檢測,與生產環境進行對比,如果發現異常,則銷毀金絲雀容器,並通知用戶

  • 如果在 6 分鐘內沒有發現指標異常,則認為代碼沒有明顯問題,才允許用戶部署「生產環境」Stage

 

金絲雀出現異常,回滾時會自動通知開發者
金絲雀階段自動監測的指標包括該應用的錯誤數、響應時間、資料庫慢查詢數量、Sentry 報錯數量、移動端 App 崩潰數量等等。

 

新版容器部署

 

針對舊版容器系統 Bay 部署速度慢、穩定性差等問題,我們將容器編排從 Mesos 切換到 Kubernetes,在此基礎上開發出新一代的容器系統 NewBay。
相應地,部署系統也針對 NewBay 進行了一番改造,使得其在功能、速度上均有明顯提升。
藍綠部署
在舊版 Bay 中,每個 Unit 對應唯一的容器組,新版本容器會改寫舊版本容器,這會導致:
  • 一旦部署失敗,服務將處於中間狀態,新舊版本會同時在線

  • 回滾舊版本代碼速度較慢,而且有可能會失敗

我們設計了一套新的部署邏輯,實現了藍綠部署,即新舊版本容器組同時存在,使用 HAProxy 做流量切換:

藍綠部署可以有效減少回滾時間
這使得:
  • 流量的切換原子化,即使部署失敗也不會存在新舊版本同時在線的情況

  • 由於舊版本容器組會保留一段時間,這期間回滾代碼僅需要將流量切回舊版本,回滾時間可以達到秒級

預部署
使用 NewBay 之後,大型專案的部署時間由原來的 18 分鐘降至 3 分鐘左右,但這其中仍有優化的空間。
為了加快部署速度,我們會在金絲雀階段,提前將「生產環境」Stage 所需要的全量容器異步地啟動起來,這樣在部署「生產環境」Stage 時,僅需要將流量切換為全量即可:

預部署可以有效減少上線時間
通過這方面的優化,在全量上線到生產環境時,上線時間同樣可以達到秒級。

 

分支部署

 

以上部署均是針對代碼合併到主幹分支後進行的部署操作,或者可以稱之為「上線流程」。
但是實際上很多情況下,我們的代碼在 Merge Request 階段就需要進行部署,以方便開發者進行自測,或者交由 QA 團隊測試。
我們在 CI/CD 層面對這種情況進行了支持,主要原理是在 MR 提交或者變更的時候就觸發 CI/CD,將其部署到單獨的容器上,方便開發者進行訪問。

多個 Merge Request 同時部署和除錯
分支部署實現細節較多,篇幅所限,在此不進行展開。

 

部署系統平臺化

 

為了方便用戶使用 CI/CD,管理應用資源,處理排查故障等,我們將整套知乎的開發流程進行了平臺化,最終實現了 ZAE(Zhihu App Engine):

ZAE 是一套完整的開發者平臺
用戶可以方便地查看部署進度和日誌,併進行一些常規操作:

在 ZAE 上查看部署進度

尾聲

 

知乎部署系統從 2015 年開始開發,到目前為止,功能上已經比較成熟。其實,部署系統所承擔的責任不僅僅是上線這麼簡單,而是關係到應用從開發、上線到維護的方方面面。良好的部署系統,可以加快業務迭代速度、規避故障發生,進而影響到一家公司的產品發佈節奏。
原文鏈接:https://zhuanlan.zhihu.com/p/60627311

赞(0)

分享創造快樂