原文釋出與我的部落格 lambdas.dev,歡迎交流。
這篇文章的內容不侷限於前端釋出,其他語言框架一樣適用。
我們都知道在適合的場景中將業務部署至 Serverless
相比於傳統服務有極大優勢,諸如降低成本、彈性伸縮、高擴充套件能力、高穩定性等等這裡不再細說,我們這次聊聊
在業務中如何把服務合理的部署到 Serverless
。本文中的 Serverless
指代適用於部署服務的 FaaS
元素,大多數雲平臺都提供 FaaS
支援,
如 AWS Lambda / Azure Functions 等。
由於 Serverless
的特性約束,我們需要在固定的程式設計模型上構建模組,這無論是對移植還是構建新專案都有巨大阻礙,同時還會與原有服務的結合性、部署與維護的難度,標準化等等
問題值得考慮,如今市場上推出了很多基於雲平臺的 Serverless
提供的快速部署服務,它們在抹平差異與提高體驗上做出了很多成果,遺憾的是至今沒有以此為基礎純粹的開源系統,
這裡我借鑑了很多 Serverless
部署平臺設計的經驗,提出一種基於無服務化的可在生成使用的工程化的部署平臺設計思路,大家可以在此基礎上設計構建現代化的部署平臺。
在此之前,你可以閱讀有關 Serverless 架構的基礎知識,瞭解其與傳統運維架構的區別。另,我在下文提出的模型已 在生產上得到實踐,設計細節也經過一些優化,但介紹時會盡量減少技術細節,多談論具有普適性的方法。
優勢
在開始之前我們先討論構建專用的 Serverless
部署平臺的優缺點,其中一部分是 Serverless
本身的特性所致,這證明了合適的場景下遷移至 Serverless
是有
巨大成效的,一部分是平臺設計上的優勢:
-
高速的部署。
傳統部署方式常常一次花費數分鐘,且難以控制更別說維護與擴充套件。新平臺讓部署始終保持在秒的量級,部分專案從觸發到完成部署能夠保持在 10 秒以下。
-
專案具備自動伸縮與高可用性。極低的資源消耗。
這是
Serverless
自有的優勢。 -
高可控。
下文的設計中我們保持了多個函式端點,回滾與備用方案簡單可靠。
-
輕量。
也是由於部署成為輕、快、小的工作,大量的工程都得以拆分,按模組推進迭代使所有的工作都向輕的方向發展。
-
高開發者體驗。
除去專案上的優勢,部署平臺保證了在任意階段回溯、中斷的可能性,且可拆卸非常易於維護,對於應用的開發者來說也具備感知低、體驗高的優勢。
流程總覽
我們可以把所有的部署環節分為 4 個步驟,包括從發起部署請求開始到部署完成的階段:
- 在使用者機器 (或 CI/CD 服務端機器) 的本地同步環節
- 通用服務端的鑑權、處理、同步構建資訊
- 構建服務的打包與編譯
- 部署服務,部署至 Serverless 並蒐集必要資訊
[serverless bridge] --> [builder] --> [init shell]
[auth] ↓
[ci/cd hook] | ↓ |--> [build service]
[user cli] | <--> [general service] --|
[website] | ↓ |--> [deploy service] --> [server less]
[build database] |--> [router]
|--> [log][health][...]
-----------------------------------------
[code storage]
複製程式碼
關於取捨上的疑問:
-
我們為什麼把 編譯 與 部署 的工作不放在一個環境中執行?
部署 Serverless 時,未來很有可能擴充套件多個語言,並不僅侷限於前端,甚至可能業務需求超出當前的平臺的構建功能,這時可以允許使用者為構建過程自定義指令碼,此時編譯部分 相當於只提供上下文的一個容器環境,它是清潔的,使用者即便在其中如何操作也只會影響相對於此使用者的業務程式碼,並不會對部署的環境變數、指令碼、私密資訊造成影響。另一方面, 部署服務實際上消耗的 CPU 極低,相比於構建服務更健壯,將部署服務單獨執行不僅便於實現
redeploy
類的需求,也更能保證在回滾的可靠性。 -
區分多個服務的通訊成本?
存在通訊成本,但很低。實際上面對更復雜的業務時我們可能以上步驟拆分的更細緻,但服務之間還是隻有 觸發/被觸發 的事件關係,並不會長時間掛起通訊。即便我們把所有服務設計 在一個程式中,也只是共享記憶體,省去了對使用者部署專案的原始碼讀寫時間,事實上這與構建時間相比不值一提。另外由於我們在
code diff
上有著良好的設計,每次對使用者原始碼的 讀寫成本會更低。(事實上即便你把所有的功能雜糅在一期,在回滾、檢視構建結果等需求的面前,還是需要儲存使用者程式碼) -
接入第三方鑑權的難度?
很多公司已經有了自己的鑑權系統,服務越多意味著每個服務都要單獨接入鑑權系統且針對不同的許可權設計不同的控制範圍 (而且面對上游的變動你得做非常多的垃圾工作),這是 很多讓人頭疼的工作。這裡我們只需將通用服務端接入鑑權,其他的服務既鑑權也不鑑權,具體我們下文細說。
按上所示我們梳理一下全部的部署流程:
- 使用者端通過
cli
hook
或其他方式將程式碼上傳到通用服務 (我們也可以稱為web service
,因為主要為前端提供服務),在通用服務中我們至少連結一個鑑權服務與部署資料庫, 即可以辨明 使用者 - 專案 - 部署 等關係。 - 在通用服務中還需要對待部署的原始碼 diff,將變更後的程式碼儲存到程式碼庫 (
data-storage
) 並更新一個版本,隨後向build-service
發起一個構建通知。 build-service
根據指定標識拉取指定程式碼,預處理程式碼並在眾多的 構建服務堆 中找到一個合適的構建指令碼去處理,最後開始構建。build-service
完成一次構建之後只發出通知並開始清理環境 (如上傳完成的程式碼、清理構建日誌等)。準備開始下一次構建。- 在通用服務中通知
deploy service
開始部署。 deploy service
也是通過標識開始拉取構建完成的程式碼,檢查後根據通知部署至serverless
並設定指定的路由、閘道器、日誌等操作。- 最後由
general service
通知使用者部署完畢,返回各地址與詳細資訊。
收集部署程式碼
無論是通過 Git Hook 還是使用者本地執行命令,收集原始碼都是一次部署的第一步,與常見的打包上傳、構建後上傳、推映象等方式不同,我們需要更注重使用者程式碼塊的重量與收集速度,
因為按本文的架構設計我們需要把所有的原始碼檔案全部上傳到 general service
,這當然不可能每次傳輸數十兆的檔案,常見的解決方案是將使用者的每個基礎檔案或資料夾與記錄對比,
只上傳修改後檔案,這和 Git 有點像。
假設我們的服務中包含了使用者所有的檔案描述與雜湊,那麼就可以輕易對比一個檔案是否改動過,在邏輯的實現上可以考慮以目錄的層級為基礎,廣度優先逐層對比, 直到收集完所有的檔案與它們的描述資訊,再逐一傳送給服務端,最後我們向服務端請求一個部署指令。在此也包含幾個常見問題:
- 使用者的鑑權
- 專案的歸屬
- 部署的許可權
- 部署版本等單次部署資訊
面對 使用者-專案-部署 這樣非常簡單的邏輯關係我們可以輕易在服務端實現,不必細說,只談談何時為它們建立關係。
我們可以把 專案的維護、部署的維護 看作是不同的資源,但它們與專案的原始碼都沒有任何關係。在記錄時,假設這次部署沒有任何原始碼,
我們只為使用者記錄 建立專案 - 繫結專案與使用者 - 建立部署版本 - 繫結部署版本與專案
簡單的關係,最後再開始接受這次的原始碼上傳,將這次的原始碼看做一個庫存放在 OSS 或
其他低頻寫入的資料庫中,在寫入原始碼時可以將部署版本寫作字首或是描述資訊中 (非 SQL)。這樣就可以通過任一原始碼檔案找到部署版本,也能通過任意版本找到所有原始碼。舉個例子,
本地存在多個檔案存放於不同資料夾中,開始這次的原始碼收集:
-
同步使用者、專案資訊,建立一個部署版本,準備使用部署版本來上傳原始碼。
-
收集原始碼:
2.a 從目錄層級淺到深逐一收集,立即與服務端對比,如果未改動則放棄資料夾下的所有檔案
2.b 在原有檔案物件 (如:
Stats
) 的基礎上為每一個已經改動的檔案建立新的描述物件,包含該檔案的相對位置2.c 統計改動大小等資訊,依次上傳所有的改動
2.d 所有的改動上傳完畢,請求服務端鎖住所有檔案並且不再接受改動
-
服務端驗證所有檔案的合法性後將 檔案描述資訊 與檔案一同寫入 OSS (這裡以 OSS 為例) 的某個屬於此專案的版本集合內。
-
服務端收到部署
v-d.d.d
版本的請求與部署相關資訊,向build service
發起通知同時更新版本的記錄資訊。
先說說這樣設計的目的。在收集程式碼部分我們著重於兩點:一是把所有的原始碼拆分開按檔案上傳,只記錄它們的位置資訊,而非普通的打包;二是將原始碼與 部署、專案 等業務邏輯關係 分開處理。記住這兩點,這是此文部署服務設計的關鍵所在。
很多的部署平臺、服務的需求提現上之所以不能夠按需部署就是因為他們粗暴的把單次原始碼打包上傳,對於來自 Git Hook 觸發的原始碼更是如此,即便有 diff
也是在經過網路傳輸之後,
這其中已經浪費了非常多的網路傳輸時間,想象一下當你改動了一個數十甚至上百兆的專案後,被迫要等數十秒甚至數分鐘才能完成構建的觸發,特別是在外網、跳板連線時,效率可想而知。
甚至它們在構建後只是粗暴的回收構建容器,更無需談與上次構建的原始碼對比。這裡我們可能只需要通過數十次 簡單地、快速地、併發地 HEAD
請求就可以弄清楚究竟要
上傳多少程式碼,服務端也只需要併發地將 hash
與 OSS 內專案的集合進行查詢是否有此 hash
相關檔案即可,實際落地時,大型專案的 query & diff
時間也能控制在數秒左右,
這樣的設計可以極大的節約構建前的準備時間。
上文多次提及將編號版本的原始碼儲存上 OSS 或其他資料庫中,這樣設計有多個意義:原始碼在多數情況下是較大較多的檔案塊,甚至有時還會包含媒體型別檔案,特點又是單次寫入永久不會 改寫,未來在其他服務中最多也只會讀取,單獨儲存這樣的大量資料就不會影響原有的資料庫,也非常利於我們在未來環境變更、業務變更、效能優化。比如當你需要遷移到某個雲服務商時, 你可以考慮他們內網通訊的一些大檔案儲存服務。
準備構建的設計
在 general service
的準備
按上文設計,直到檔案同步全部完成我們都沒有開始構建,這是因為我們把 構建 這個動作的許可權仍舊放在 general service
上,這樣設計的優勢在於使用者可以在任何時間
以自己的鑑權資訊構建指定版本號的所有檔案,rebuilt
這類的需求在當前部署系統的設計中可以順理成章的實現。比如你正在使用 Git Hook 反覆的觸發構建而程式碼並沒有變化時
這顯得很有用。
在構建的請求中,別忘了收集一些構建所需的資訊,如使用者青睞的指令碼、指定的 before-build
、指定的 builder
(下文詳細說明) 等等。現在可以通過 general service
向
build service
傳送一個通知:『構建開始了』。
在 build service
的準備工作中,幾乎沒有任何關於鑑權的要求,只要是來自服務推送的構建就完成,很簡單。我們在程式碼層面幾乎只接受一個簡單的資訊,
就是所謂的版本編號,因為所有的原始碼已經儲存在了額外的空間,我們可以只通過此唯一版本號輕易的找到與本次構建相關的所有檔案並將檔案全部同步下來。(這次同步程式碼是隻讀的,
但也必然是內網的同步,損耗時間的極短,不必擔心)
[general service] <---> [build service] <--- [code storage]
複製程式碼
在 general service
這端,我們還不能扔掉使用者或是 Git,還需要 hold 住這個連結並向他們傳輸一下構建日誌資訊,這裡你可以選用第三方的日誌服務或是直接透傳構建伺服器的日誌資料庫。
無論如何,日誌檢視的許可權仍舊還在中央服務的這裡,這非常易於你設計或接入許可權系統。在未來,你還可以在中央服務上加入一些其他指令,如中止構建、立刻重新開始等等,中央服務
也只需要簡單的更新一次自己的記錄資料庫並與 build service
發出通知即可。
在 build service
的準備
好了,我們直到收到構建通知時可以簡單的開始在 code storage
上下載原始碼,但這還不夠,我們還得將原始碼檔案復原成使用者上傳之前的層級關係。
也許你已經想到了,很簡單。由於我們在每個檔案的描述資訊上都記錄了它所在的相對位置,build service
只需要一次遍歷即可恢復整個程式碼庫。
當然,這些使用者程式碼在未來需要隔離構建,我們至少要準備一些容器,這裡有兩種方案:
-
使用單獨準備的指令碼來控制容器。
單獨指令碼的好處是單獨的指令碼更容易分離和維護,甚至你可以用自己熟悉的語言來寫管理指令碼,比如有很多成熟的
python
shell
管理指令碼,只需簡單的修改即可使用。 難點在於當你要從程式碼中注入一段邏輯或引數時只能通過環境變數這個辦法,如果有互動、控制類的需求還需要在程式碼中保留這個執行shell
的子程式資訊並管理。 -
使用程式設計通過介面控制。
與上述相反,你可以在程式碼中根據業務邏輯更加精細的調整容器,如預先建立一個池分配資源。當然你需要自己完成所有的控制細節,如建立、管理、掛載、同步日誌等。
在真正的啟動容器之前最好別忘了設定好環境變數。假設我們有一個基礎的構建指令碼 build-init.sh
,build-init.sh
內不應該涉及任何具體的語言、框架、編譯方式等資訊,
我們藉助於這個初始指令碼來規劃在不同的語言、框架時如何構建 (具體在下文中介紹) 。而在收到 構建通知 時攜帶的元資訊最好在容器的環境變數中設定好,這可能是
當前專案的構建偏好、指定初始化位置等資訊,但絕不應該是任何金鑰。在構建容器初始化設定的所有環境變數都應該只與這次構建相關,與部署無關,不要有任何包含
專案所處的業務資訊。這是為了構建的安全考慮。
構建
我見過很多在 Jenkins
上寫幾句指令碼就自稱自動化、打包個映象就算髮布的平臺 (可能你也正在公司裡遭受這種『平臺』的折磨),這些『平臺』服務大多有一些通病:
- 釋出慢 (因為構建很吃力)
- 支援語言少,擴充套件極難
- 構建指令碼除錯困難,構建指令碼除錯許可權混亂
- 構建用時不透明、不隔離、不可控
在你下定決心要優化構建指令碼時,花了五牛二虎之力找到那個寫在一堆命令中間的命令後發現,居然是隨手安裝的某個服務,面對依賴眾多年代久遠長篇累贅的指令碼時你才發現
優化它們幾乎是不可能的事。並無誇張,造成混亂的原因是構建平臺設計之初沒有將 構建 這一部分解耦,更沒有在根據不同語言設計可擴充套件的構建插槽,面對不斷迭代的
新語言、新框架需求,逐漸變得無法維護,體驗低下。當然這些人中也不乏佼佼者,它們找到了一個偷懶的好辦法,就是在每次構建時都讓使用者自行寫一個 dockerfile
,既不關心
過程也不關心內容,也就是我們常說的『無為運維』之道。
構建原理
由於要在 Serverless
上執行我們的構建結果,所以僅僅構建是不能滿足需求的,我們還需要一些功能來抹平 Serverless
與微控制器之間的溝壑。我們的目標是構建過程像在微控制器
上一樣簡單,但也要能夠無縫遷移到 Serverless
。除此之外,構建服務可能面臨多語言、多框架的問題,在能夠完成任務的基礎上要做到進行合理的抽象拆解,讓構建過程簡單到誰都可以寫,
每種框架的指令碼也能自由切換。本文以 NodeJS
為例,介紹設計的方案:
[builder A] [builder B] [builder C] ... // repos
---------------------------------
[npm / cdn / oss]
---------------------------------
↓
--------- <container> ----------
init.sh
↓
starter.js (nodejs shim)
↓
require([builder])(configs)
↓
output files
↓
[end.sh]
複製程式碼
在一個構建過程中,會有以下幾步:
-
在容器啟動時掛載啟動
init.sh
(或者任何你在容器指定命令時第一次啟動的指令碼)。這個指令碼用於維護基礎環境。 -
用於構建的
starter.js
。當然你也可以跳過步驟 1 只用NodeJS
解決所有問題。2.a
starter.js
收集使用者指定的構建目標,同時也可以自行做預構建,如分析目標框架。2.b 根據指定框架在
npm
/cdn
/oss
或者任何你想要的地方下載準備的指令碼,我們可以稱為builder
。2.c 引用
builder
並向其中注入當前構建資訊。如 入口、輸出目標、可用工作目錄等。2.d 由於
init.sh
的初始化,builder
只在可用工作目錄內構建並輸出結果到出口。 -
NodeJS
退出,end.sh
收集退出碼等資訊並將輸入檔案上傳或儲存到code storage 2
,也就是專用的輸出程式碼儲存空間。
為了能夠保證構建完成,我們也需要準備相應的 builder
,粒度可根據場景決定。這樣做的好處是我們所維護的 builder
不再是一段在子程式中執行的指令碼,而是有上下文的,經過
預處理的外掛,比如這裡外掛 python
外掛,編寫者根本不需要考慮程式和預處理問題,builder
編寫者只需要寫一個有具體引數的函式 (當然你可以用一個通用型別來檢查約束這個外掛函式),
函式只做一件事,根據傳入的引數做指定的構建。在公司內部,任何新框架新語言的改革都可以輕易的維護 builder
專案 (新增一種外掛即可),如果是企業級或開源服務,也可以讓各個
語言、框架的專家分別負責自己的 builder
(now
就是這樣做的),他們對於自己浸淫已久的框架有有著自己的優化手段。 當然,如果你喜歡,也可以一直在外掛裡寫 shell
。
既然存在上下文同時又是框架專屬外掛,測試與維護起來更加輕而易舉,在更改、新增一個外掛時可以通過測試用例來保證構建的穩定性。
連結 Serverless
上一節中提到在構建過程中插入以程式語言為基礎的外掛,也許你會擔心這樣做增加了系統的複雜性,其實不然,在面對 Serverless
不同的程式語言介面時,我們還是需要考慮
把每種程式語言、框架進行手動接入,因為一個普通的專案是無法直接部署在 Serverless
平臺上。不同平臺在不同語言上對上下文、handler 等有著不同的約束,
你需要根據自己選用的平臺 (如 AWS / Azure / Google / Aliyun) 和框架來考慮如何連結。
同樣以 NodeJS
為例,在大多數的 Serverless
平臺上他們對 req
/ res
/ context
等物件進行了自己的封裝,我們可以設計一個 bridge.js
來抹平之間的差異:
// bridge.js
module.exports.handler = (req, res, context) => {
// req.xx = ...
// res.json = function() {}...
try {
// PLACEHOLDER
} catch (e) {
console.error(e)
process.exit(1)
}
return code(req, res, context)
}
複製程式碼
在每個 builder
中,應考察平臺與語言框架之間的差異,來設計 bridge
,最後將 PLACEHOLDER
替換成構建後的入口。如在 NodeJS
中我們需要擴充套件 req
/ res
至標準
HTTP
物件。在不同的 Serverless
平臺上真正部署時,都可以指定從 bridge
開始執行,經過中介軟體抹平差異後開始引用業務程式碼。
在部署中我們儘可能的不 hack
使用者程式碼,這裡還可以統一整合一些監控、健康檢查、日誌的服務。
回收程式碼
在構建結束時構建服務需要將 outputs
回收到指定儲存空間而非直接傳遞釋出,一方面是便於多次覆蓋釋出,一方面是需要展示給使用者檢視具體的構建成果,有必要時我們也可以讓使用者
下載這些檔案手動部署。構建完成得到正確的退出碼時從容器中拷貝出程式碼上傳,與收集程式碼的工作類似,這時你可以為每個檔案記錄一次位置資訊打散再儲存,也可以打包壓縮後儲存在 OSS,
因為大多數 Serverless
都支援從 OSS 直接部署一個壓縮的程式碼包,這樣我們就省去了很多後續工作。
注意,到此為止我們都還沒有處理業務相關的環境變數、金鑰等業務資訊,所以從容器 outputs
取出的程式碼包雖是已構建的但也不能正常執行,我們還需要在部署服務中將它們結合分發
到各個環境中。是的,本文的設計中構建環節並不區分環境,構建程式碼本身就不包含環境資訊,如果你的業務出現構建與環境強關聯,建議你把構建服務部署在多個節點上。(當然,並不推薦
這樣做)
部署
部署目標
Serverless
的特點是啟動快,單次執行時並不處理複雜業務,能夠將業務解耦成細粒度的邏輯使開發者專注於邏輯本身,所以我們通常不會在每個 Serverless
節點的業務上
部署業務路由。並非無法實現,你可以通過傳統路由的方式識別不同的路徑引數來解決不同事務,但在很多人的設計下這可能會使單個 Serverless
服務成為一個巨大而全面
的應用,保持著長時間執行而非拆分元件,這就喪失了原本的優勢:橫向擴充套件能力、低成本、高速部署與啟動、自動收縮等等。
試想一下,如果你業務中的核心業務被呼叫與計算的頻率較高,而周邊服務較少,甚至可以依賴快取,那完全可以將它們解耦成多個服務,通過你剛剛構建的平臺一鍵高速部署,
核心業務有著 Serverless
自動收縮、高負載、極速啟動的的特點,而周邊服務則多數時間的處於『休眠』狀態節省資源,如必要,還可以繼續橫向擴充套件服務無痛接入。
這在某些場景下是非常難得且高效的解決方案。
那是不是所有的業務路由和轉發都要交付閘道器或是服務商提供的負載均衡器來解決?不完全如此。當你考慮到所有業務適合作為單個模組時則可以講它們部署在單個 Serverless
節點上,涉及模組之間的聯絡則可以交於外部,絕不是單個計算函式只處理單個問題。簡而言之你可以做到任何一點,其中的考量視業務場景而定。
還有一點值得在部署之前考慮,由於 Serverless
是無狀態的,我們不會在容器記憶體儲任何資料 (這樣做沒有意義),常見的解決方案是連線第三方的資料庫與服務,如在不同
的雲平臺會有內網可操作的資料庫與其他服務。部署時所有的第三方服務都需要有多環境。我們需要謹記無狀態的特點,它是好的程式設計風格但不適合所有人,
這意味著你不能再依賴記憶體儲存所有狀態,也不能在所有的服務中共享記憶體資訊 (它們本來就處於不同的容器中),每當你要儲存一些狀態時都要考慮通訊。
部署方式
由於我們在構建時已經把程式碼包完整的保持在 OSS,所以程式碼塊的部署工作非常簡單,只需每次接受 general service
通知時找到相應的儲存位置即可 (不下載)。按上文所述,
general service
在呼叫同時還會收集使用者配置的 Serverless
外部路由資訊、金鑰、環境變數等,我們需要加以整理並逐一建立:
- 為使用者在
Serverless
平臺建立專案、服務等,具體視不同的平臺介面而定。 - 建立一個
Lambda
(視平臺),入口指定為我們固定的bridge
。 - 填入使用者指定的記憶體大小、執行上限、金鑰等資訊。
- 建立閘道器等第三方服務 (如果有)。
- 根據使用者、專案資訊建立子域名,並指向此
Lmabda
。 - 根據指定的快取資訊建立 CDN (如果有)。
完成這些步驟後一個可以被業務直接使用的 Serverless
服務基本部署完成,我們可以在 deploy service
直接落庫並通知 general service
部署完成,將聚合資訊
傳遞給使用者。
在各個 Serverless
服務平臺中,只要我們沒有開始呼叫服務總是不計費 (或計費極低),所以在部署時的策略與傳統的替換、滾動等是不同的,我們總是新增而非修改,每當
收到一個新的通知,無論是新專案、新版本還是重新部署我們都會建立一個全新的函式服務並配置全新的子域 (或其他標記方式)。當某個節點需要轉到生產時我們以閘道器、負載均衡、
自定義路由、域名解析等方式將生產流量指向函式即可,這樣做的好處顯而易見:
- 總是可以回溯到任意版本且設計簡單、回滾高速
- 按端點統計資源,大型業務也能一目瞭然
- 可以做
pre-production
預熱,無縫切換 - 多環境只需指定多服務與多域名連線至指定的端點即可
對於部署前端資源,可以讓資源靜態資源執行在一個函式中,在外層做 CDN 即可,對於前端現在流行的 SSR,函式計算的容器就是完美的服務端。
優化與實踐
關於部署速度
與傳統的打包部署相比,本文介紹的基於 FaaS
平臺的部署架構可以極大的提高部署速度,同時由於專案的解構與拆解,一個函式模組從收集直到部署完成只需要幾秒的時間,
即便你的專案足夠大,也在秒的量級。一方面得益於程式碼包更新的優化,一方面得益於 Serverless
端點幾乎可以忽視的啟動時間。對於重視部署速度、高速迭代的團隊而言,
這幾乎最優的解決方案。
有少數專案在業務拆分後還是有非常多的外部依賴導致 build
時間過長,我們可以額外建立一個 cache storage
用於快取的外部依賴,在構建時按專案區分即可。這部分
依賴可以提供在 builder
的上下文中,由每個 builder
自行對比決定什麼場景需要使用外部快取,不同語言框架的設計各不相同,也可以做到各個領域內的優化。
關於構建容器
雖然文中推薦手動掛載容器構建程式碼,但你仍舊可以選擇使用 Serverless
構建程式碼,也就是每一個構建單獨建立一個函式端點,優勢是減少管理和開發成本,但目前國內的
雲平臺普遍沒有較高函式執行的記憶體、程式碼空間也比較低,實用性較差,如果正在接入 AWS / Azure 等平臺 (或是自建,
如基於 Kubernetes
的 Fission),可以試試這種更簡單的方案。
安全性
關於平臺的安全性與可用性你可以在相關 Serverless
平臺檢視,部署平臺承擔部署但並非執行容器。對於部署平臺,安全性的考慮集中在插入的部署指令碼,但始終只在容器中
進行構建並不會對其他專案產生影響,如必要也可以對 builder
採取白名單與過濾的做法。
管理
我們收集了關於專案、部署、日誌等資訊,如果你需要一個類似 CMDB 的視覺化控制檯,完全可以依據資料這些聚合給 WEB 服務,沒有什麼難度。
在 bridge
中我們已統一插入了針對業務的監控,針對執行時效能監控可以連結到雲平臺的查詢介面。