應用部署是軟體開發中重要的一環,保持快速迭代、持續部署,減少變更和試錯成本,對於網際網路公司尤為重要。本文將從部署系統的角度,介紹知乎應用平臺從無到有的演進過程,希望可以對大家有所參考和幫助。
知乎部署系統由知乎工程效率團隊打造,服務於公司幾乎所有業務,每日部署次數在 2000 次左右,在啟用藍綠部署的情況下,大部分業務的生產環境上線時間可以在 10 秒以下(不包含金絲雀灰度驗證過程)。
-
支援容器、物理機部署,支援線上、離線服務、定時任務以及靜態檔案的部署
-
支援辦公網路預上線
-
支援金絲雀灰度驗證,期間支援故障檢測以及自動回滾
-
支援藍綠部署,在藍綠部署情況下,上線和回滾時間均在秒級
-
支援部署 Merge Request 階段的程式碼,用於除錯
在介紹部署系統之前,首先需要對知乎的相關基礎設施和網路情況進行簡單的介紹。
-
生產環境網路:即知乎對外的線上伺服器網路,基於安全性考慮,與其他網路環境完全隔離。
-
測試環境網路:應用在部署到生產環境之前,首先會部署在測試環境,測試環境網路上與生產環境完全隔離。
-
辦公室網路:即知乎員工內部網路,可以直接訪問測試環境,也可以透過跳板機訪問生產環境伺服器。
知乎採用 Nginx + HAProxy 的方式管理應用的流量走向:
應用開發者在 Nginx 平臺上配置好 Location 和 HAProxy 的對應關係,再由 HAProxy 將流量分發到 Real Server 上去,同時 HAProxy 承擔了負載均衡、限速、熔斷等功能。
知乎採用 Jenkins + Docker 進行持續整合,詳見《知乎容器化構建系統設計和實踐》,持續整合完成後,會生成 Artifact,供部署系統以及其他系統使用。
像大多數公司一樣,知乎最開始是以物理機部署為主,業務自行編寫指令碼進行部署,部署時間長、風險大、難以回滾。在這種情況下,大約在 2015 年,初版的部署系統 nami (取名自《海賊王》娜美)誕生了。
最初的部署系統採用 Fabric 作為基礎,將 CI 產生的 Artifact 上傳到物理機上解壓,並使用 Supervisor 進行行程管理,將服務啟動起來:
初版的部署系統雖然簡單,但是為了之後的改進奠定了基礎,很多基礎的概念,一直到現在還在使用。
與 CI 相同,每個應用對應一個 GitLab Repo,這個很好理解。
但是在實際使用過程中,我們發現,同一套程式碼,往往對應著多個執行時的服務,比如以部署系統 nami 本身為例,雖然是同一套程式碼,但是在啟動的時候,又要分為:
這些執行單元的啟動命令、引數等各不相同,我們稱之為服務(Unit)。使用者需要在部署系統的前端介面上,為每個 Unit 進行啟動引數、環境變數等設定,整個應用才能正常啟動起來。
所有的部署都是以 CI 產生 Artifact 作為基礎,由於 Artifact 的不可變性,每次部署該 Artifact 的結果都是可預期的。也就是說,每個 Artifact 都是程式碼的一次快照,我們稱之為部署的候選版本( Candidate)。
由於每次 CI 都是由 GitLab 的 Merge Request 產生,候選版本,其實就是一次 MR 的結果,也就是一次程式碼變更。通常情況下,一個候選版本對應一個 Merge Request:
如圖所示是某個應用的候選版本串列,每個候選版本,使用者都可以將其部署到多個部署階段(Stage)。
上文提到,知乎伺服器網路分為測試環境和生產環境,網路之間完全隔離。應用總是先部署測試環境,成功後再部署生產環境。
在部署系統上,我們的做法是,對每個候選版本的部署,拆分成多個階段(Stage):
-
(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 以及其他場景的服務發現。
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,隨機分發流量到這些新版本容器上,這樣如果新版本程式碼存在問題,可以在指標系統上明顯看出問題:
其中,「金絲雀1」階段只啟動相當於「生產環境」階段 1% 的容器,「金絲雀2」階段則啟動 20% 數量的容器。
為了避免每次部署到金絲雀後,都依賴人工去觀察指標系統,我們在部署系統上,又開發了「金絲雀自動回滾」功能。主要原理是:
金絲雀階段自動監測的指標包括該應用的錯誤數、響應時間、資料庫慢查詢數量、Sentry 報錯數量、移動端 App 崩潰數量等等。
針對舊版容器系統 Bay 部署速度慢、穩定性差等問題,我們將容器編排從 Mesos 切換到 Kubernetes,在此基礎上開發出新一代的容器系統 NewBay。
相應地,部署系統也針對 NewBay 進行了一番改造,使得其在功能、速度上均有明顯提升。
在舊版 Bay 中,每個 Unit 對應唯一的容器組,新版本容器會改寫舊版本容器,這會導致:
我們設計了一套新的部署邏輯,實現了藍綠部署,即新舊版本容器組同時存在,使用 HAProxy 做流量切換:
使用 NewBay 之後,大型專案的部署時間由原來的 18 分鐘降至 3 分鐘左右,但這其中仍有最佳化的空間。
為了加快部署速度,我們會在金絲雀階段,提前將「生產環境」Stage 所需要的全量容器非同步地啟動起來,這樣在部署「生產環境」Stage 時,僅需要將流量切換為全量即可:
透過這方面的最佳化,在全量上線到生產環境時,上線時間同樣可以達到秒級。
以上部署均是針對程式碼合併到主幹分支後進行的部署操作,或者可以稱之為「上線流程」。
但是實際上很多情況下,我們的程式碼在 Merge Request 階段就需要進行部署,以方便開發者進行自測,或者交由 QA 團隊測試。
我們在 CI/CD 層面對這種情況進行了支援,主要原理是在 MR 提交或者變更的時候就觸發 CI/CD,將其部署到單獨的容器上,方便開發者進行訪問。
為了方便使用者使用 CI/CD,管理應用資源,處理排查故障等,我們將整套知乎的開發流程進行了平臺化,最終實現了 ZAE(Zhihu App Engine):
使用者可以方便地檢視部署進度和日誌,併進行一些常規操作:
知乎部署系統從 2015 年開始開發,到目前為止,功能上已經比較成熟。其實,部署系統所承擔的責任不僅僅是上線這麼簡單,而是關係到應用從開發、上線到維護的方方面面。良好的部署系統,可以加快業務迭代速度、規避故障發生,進而影響到一家公司的產品釋出節奏。
原文連結:https://zhuanlan.zhihu.com/p/60627311