知乎部署系統演進

應用部署是軟件開發中重要的一環,保持快速迭代、持續部署,減少變更和試錯成本,對於互聯網公司尤爲重要。本文將從部署系統的角度,介紹知乎應用平臺從無到有的演進過程,希望可以對大家有所參考和幫助。

知乎部署系統由知乎工程效率團隊打造,服務於公司幾乎所有業務,每日部署次數在 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)辦公室階段:一個獨立的容器,只有辦公室網絡可以訪問,其他與線上環境相同,數據與生產環境共享。
  • ©金絲雀1:生產環境 1%
    的容器,外網可訪問。
  • ©金絲雀2:生產環境 20% 的容器,外網可訪問。
  • §生產環境:生產環境 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 年開始開發,到目前爲止,功能上已經比較成熟。其實,部署系統所承擔的責任不僅僅是上線這麼簡單,而是關係到應用從開發、上線到維護的方方面面。良好的部署系統,可以加快業務迭代速度、規避故障發生,進而影響到一家公司的產品發佈節奏。

知乎部署系統以及 ZAE 開發者平臺由知乎工程效率(EP)團隊開發和維護,主要貢獻者爲
@Iven Hsu @Cosven @Amyyyyy @lfyzjck。工程效率團隊致力於提高業務開發效率,提高工程代碼質量和業務交付質量,統一知乎開發者的開發規範、流程和框架。對於這方面感興趣的小夥伴們,可以與 [email protected] 聯繫。

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