知乎部署系统演进
作者 | Iven Hsu
來源 | 知乎專欄
本文將從部署系統的角度,介紹了知乎應用平臺從無到有的演進過程。
應用部署是軟件開發中重要的一環,保持快速迭代、持續部署,減少變更和試錯成本,對于互聯網公司尤為重要。本文將從部署系統的角度,介紹知乎應用平臺從無到有的演進過程,希望可以對大家有所參考和幫助。
知乎部署系統由知乎工程效率團隊打造,服務于公司幾乎所有業務,每日部署次數在?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?年開始開發,到目前為止,功能上已經比較成熟。其實,部署系統所承擔的責任不僅僅是上線這么簡單,而是關系到應用從開發、上線到維護的方方面面。良好的部署系統,可以加快業務迭代速度、規避故障發生,進而影響到一家公司的產品發布節奏。
知乎部署系統以及?ZAE?開發者平臺由知乎工程效率(EP)團隊開發和維護,主要貢獻者為@Iven?Hsu?@Cosven?@Amyyyyy?@lfyzjck。工程效率團隊致力于提高業務開發效率,提高工程代碼質量和業務交付質量,統一知乎開發者的開發規范、流程和框架。對于這方面感興趣的小伙伴們,可以與?iven@zhihu.com?聯系。
總結
- 上一篇: 关于Mybatis,我总结了 10 种通
- 下一篇: Go会接替Java,成为下一个企业级编程