嗨~ 歡迎閱讀 ExplainThis 全端開發雙週報。在這一期的主題文,我們將會談討系統設計的常考題之一「如何設計一個程式碼部署系統 Designing a Code Deployment System」。
此篇內容源自於部署平台 Zeabur 團隊在 E+ 的直播工作坊。在該場工作坊當中,Zeabur 團隊除了完整解析架構設計外,還談到如何設計全球多區域部署、如何從部署平台角度抵擋 DDoS 攻擊等滿滿的洞見。
如果你想看直播工作坊的回放,以及想看更深入的前後端內容,歡迎訂閱 E+ (連結)。另外,假如你有新的專案,想找更簡單好用的部署平台,不論是前端或後端,都蠻推薦 Zeabur (連結)。
如何設計一個部署系統 Designing a Code Deployment System
比起許多系統設計的題目需要專門的知識 (例如即時共編需要懂 CRDTs 這類算法,或者鄰近服務需要懂 Quad Tree),部署系統更需要對系統廣度有所理解。要能有效設計一個能處理高併發,同時速度快的部署系統,需要對部署過程中發生哪些事情,有全盤的理解,才能針對各個面向做優化。
因此在工作坊最開始,Zeabur 團隊先帶大家回顧部署過程中會發生什麼事。大家也可以先停下來想一想,今天當寫完程式碼,到讓程式碼被部署,會經過哪些流程?
直觀初步想,如果要有一個可以自動部署程式的系統,我們約莫需要經過以下的流程。程式碼推到遠端的程式碼存放處 (例如 GitHub),透過 Webhook 來通知部署系統的後端,後端根據最新的程式碼進行建構。
這邊要先停一下,當我們在提程式碼建構時,要先回答一個問題「建構出什麼東西可以被部署?」。大家不妨先停下來想一下,被面試官問這問題時,你會怎麼回答?
以目前業界常見會部署的東西,大致可以分為以下幾類:
靜態檔案 (static files)
容器映像檔 (container image)
無服務函式 (serverless function)
以及一些針對不同語言的檔案,例如Java 有 JAR,或者用 Go、Rust 等語言可以部署二進制的檔案
這時下一個問題來了,上面這些建構出來的東西,分別會被放到哪裡? 因為總是需要放到某個地方,然後在我們綁定完域名 (bind domain) 後,客戶端才可以訪問到。大家不妨也思考一下,如果被面試官問到,你會怎麼回答?
上面的前三種類別,分別會放在以下地方:
靜態檔案會放在 OSS (例如 AWS 的 S3)
容器映像檔則會放在 Docker 或是用 Kubernetes 管理的多個節點
無服務函式則會放在 AWS Lamda 這類放無服務函式的地方
從部署平台的角度概覽後,接著我們從使用者的角度來看。這邊一樣先來個常考的面試題目,當使用者在瀏覽器上輸入 https://explainthis.io (或任何網址),接著會發生什麼事?
假如只討論大方向的話,一開始會有 DNS 解析,然後拿到 CNAME record,這個 CNAME record 會隨著路由不斷指向下一個 CNAME record,然後最終會指向某個 A record,也就是最終的 IP 位置。
而這個 IP 位置就會指向上面提到的,存放部署好的東西的位置。以靜態檔案來說,就會是指向某個 OSS,然後拿到部署好的內容後,讓客戶端可以造訪。
假如把上面兩段連在一起,就會是這樣
上面有提到,自動化部署平台,需要有偵測到程式碼被推到遠端程式碼庫的機制。一般來說會是透過 Webhook 來做到。Webhook 在做的事情就是當今天發生某件事,例如遠端程式碼更新,會觸發某個通知,例如通知部署系統的後端,這時部署系統就可以進一步去做後面的操作。
具體來說,部署系統要能去分析要怎麼用了什麼程式語言、用了什麼框架、版本是什麼、有哪外部套件。因為如果能分析出來,就能夠自動去建構。
這時問題來了,要如何知道用什麼語言? 這其實很簡單,不同的語言會有相對應不同的檔案類型。舉例來說,如果是 Node.js 可以去找 package.json
檔案;或者如果是 Java 可以去找 pom.xml
或者 gradle.build
。透過各個語言獨有的檔案,就能判斷是用什麼語言。而用什麼框架,則是可以進一步去該語言的相關檔案找,例如假如是用 Next.js,就可以在 package.json
裡面找到使用 Next.js。
因為每個語言都有其對應的建構方式,所以當能夠完成上面的分析,就有辦法去做到自動化建構。舉例來說,如果知道該專案使用 Node.js,那就可以用對應的 npm 來處理。這塊 Zeabur 有一個開源專案叫 zbpack,處理了各種程式語言的解析到建構,這邊我們先不展開細講,推薦有興趣的人可以去讀原始碼 (連結在此)開源專案可以做到。
如上面有提到的,當今天建構完,我們要進一步去部署。而靜態檔案與無服務函式會相對簡單處理,因為靜態檔案可以直接呼叫 S3 的 API (或其他 OSS 的 API),而無服務函式則可以直接呼叫 Lamda 的 API,來完成部署。
但假如要處理容器的映像檔,就有比較多要考量的。雖然說可以直接透過 Docker 來跑容器,但是如果今天要同時跑跑一千、一萬、十萬個容器,會需要好幾台伺服器一起跑,就需要額外的解決方案。Kubernetes 是目前業界常用的方案,它可以幫忙把好幾台伺服器合起,協助調度找到最適合的伺服器來跑。
總結目前為止提到的,從宏觀的流程上來說,會像下面這樣
從上方的流程圖可以看到,使用者綁定 GitHub 後,當把程式碼推到 GitHub 後,GitHub 的 Webhook 會通知部署系統的後端。這時系統的後端透過 Kubernetes 的 Create Job (一個一次性的容器) 去執行分析與建構。建構完把映像檔推到 Registry (可以理解成 Docker Hub)。
完成後,通知部署系統的後端,後端就可以去 Kubernetes 創建一個會一直跑的容器 (Create Deployment),這個容器會去 Registry 把映像檔拉回來,然後讓服務跑起來。
這時就有一個可以追問的面試問題。如果同時有成千上萬的人要部署時,上面的那個部分,容易成為系統的瓶頸? 推薦大家可以先想一想
一般來說,會是建構 (build) 到推送映像檔 (Push Image) 這段。一般來說,建構一個前端可能是五分鐘,建構一個 Go 的伺服器可能要兩分鐘,當流量一大,同時多個併發的任務,這段比較可能被卡住。
這也是為什麼上面提到要用 Kubernetes,因為高併發時,後端可以同時開好幾個 Job,Kubernetes 會去做容器調度,這時有些 Job 會跑起來,有些會是沒辦法被排程 (因為集群滿了),要等到在正在跑的的跑完後,才會排下去跑。可以把這個設計理解成一個佇列 (queue),先進來的任務會先被處理,而處理端如果滿了,還沒被排程的任務就會在佇列中等,直到有多的機器有餘力處理,才會在依序被排入。
不過在一個部署系統中,會加入更多的要素在。舉例來說,會加入優先權 (priority),例如有些任務比較重要,會優先排到被處理 (像是付費使用者的任務可以被優先處理,直到有閒置運算資源才處理免費使用者)。又或者當想要更大量處理時,可以透過 Kubernetes 自動擴展 (auto scaling),同時開多個 Job 來加速。
以上我們基本走過了部署系統的整體架構,也談了在這個架構下的瓶頸為何、要如何處理。在 Zeabur 團隊的分享中,有進一步去談可能會被深入追問的問題,例如當部署完成後,假如用 k8s 這類工具,因為同時會有多台機器,那該如何處理 IP 位置該指向哪裡? 同時談了設計全球多區域部署、如何從部署平台角度抵擋 DDoS 攻擊。
這些更深入內容的文字整理,我們都放在 E+,該場直播的完整回放,也可以在 E+ 中看到。想要深入理解如何設計部署系統,歡迎加入 E+ (連結)。
[本期推薦]
當談到即時共編文件時,CRDTs 經常會被提到,《A Gentle Introduction to CRDTs》是很好的入門 (連結)
有在寫 React 的人,你知道要如何偵測頁面中任何地方的點擊嗎? 如果不熟的話,可以參考《How to Detect Clicks Anywhere on a Page in React》一文 (連結 )
寫程式時,經常會用到各種程式語言的樣式工具,但你知道該如何實作一個嗎? 《How to write a code formatter》詳盡地解析了如何自己寫一個 (連結)
重構程式碼雖然能讓產品或系統改善,但同時意味著有風險要面對。該如何確保重構做的好、沒把東西改壞呢? 《The High-Risk Refactoring》提供了一些策略 (連結)
想要提升網頁應用程式的效能,Partial Prerendering 是可以用上的技巧。《Power of Partial Prerendering with Bun》一文做了不錯的簡介 (連結)
不論是馬斯克或黃仁勳,經常在對談或演講時提到第一性原理思考 (first principles thinking)。所謂第一性原理思考,是要從本質拆解,而不是直接類比或模仿。前幾天看到一個有趣同時值得一題的例子,很好地說明為什麼第一性原理思考很重要 (連結)
最近社群討論度很大的一個 AI 產品,是 Humane 的 Ai Pin。Humane 這家公司是由兩位前 Apple 員工創辦,去年拿到 2.3 億美元融資,投資人包含微軟、OpenAI 甚至 Sam Altman 本人。然而最近 Humane 推出的 Ai Pin 被全世界最有名的科技評論 YouTuber MKBHD 評價為他至今評論過最糟的產品 (the worst product)。技術上很困難,不代表對使用者就有價值,這是許多技術導向的開發者,需要有時時提醒自己的 (連結)
做自己感興趣的事,例如貢獻於開源社群,前提是要有永續模式 (連結),對開源有興趣的人,這篇《On Getting Paid for Open Source》談了如何打造永續模式 (連結)
當軟體工程師,除了寫程式,各種團隊協作與溝通也少不了。《How to share your point of view (even if you’re afraid of being wrong)》一文談到,在與人溝通時,如何分享自己的觀點,非常有洞見 (連結)