一、背景
最近中途參與的一個項目是和Serverless、Faas相關的,項目的整體架構和實現都參考了開源項目openwhisk,因此,同事們在編碼時都會參考openwhisk的源碼。因為以前從沒有接觸過這方面的知識,因此想學習下。關於Serverless和Faas的概念場景等,可以參考下,這兩篇博客,Serverless 架構:用服務代替服務器和Serverless應用場景。因為我也是剛剛接觸,很多地方不是很了解。
二、Openwhisk簡介
OpenWhisk 是屬於 Apache 基金會的開源 FaaS 計算平台官網鏈接, 由 IBM 在2016年公布並貢獻給開源社區(github頁面),IBM Cloud 本身也提供完全托管的 OpenWhisk FaaS服務 IBM Cloud Function。從業務邏輯上看,OpenWhisk 同 AWS Lambda 一樣,為用戶提供基於事件驅動的無狀態的計算模型,並直接支持多種編程語言(理論上可以將任何語言的 runtime 打包上傳,間接調用)。
Openwhisk的特點:
(1)高性能、高擴展性的分布式Faas計算平台
(2)函數的代碼及運行是全部在Docker容器中進行,利用Docker引擎實現Faas函數運行的管理、負載均衡和擴展
(3)同時,Openwhisk架構中的所有其他組件(API網關、控制器、觸發器)也全部運行在Docker容器中,這使得其全棧可以比較容易的部署在IAAS/PAAS平台上 。
(4)更重要的是,相比其他Faas實現,Openwhisk更像是一套完整Serverless解決方案,除了容易調用和函數管理,Openwhisk還包括了身份驗證/鑒權、函數異步觸發等功能。
三、系統概覽
Openwhisk是一個事件驅動的計算平台,它運行代碼以響應事件或直接調用,下圖顯示了其 體系結構:
在上述架構中,代碼時基於事件(Event)觸發的。事件產生於事件源(feed),而可以用於觸發函數的事件源可以是多種多樣的,例如數據庫的更改,超過特定溫度的IoT傳感器讀數,新提交代碼到Github存儲庫等。事件與對應的函數代碼,通過規則(Rule)綁定。通過匹配事件對應的規則,Openswhisk會觸發對應的行為(Action)。值得注意的是,多個Action可以串聯,完成復雜的操作。
四、Openwhisk的工作原理
作為一個開源項目,openwhisk集成了很多其他組件Nginx,Kafka,Docker,CouchDB等,為了更加詳細的解釋所有組件,我們可以使用下面這個例子,來追蹤整個系統的調用過程。
假設我們有包含以下代碼塊的文件action.js:
function main() {
console.log('Hello World');
return { hello: 'world' };
}
使用下面命令創建動作
wsk action create myAction action.js
現在,我們可以使用以下命令調用該操作:
wsk action create myAction action.js
那么我現在來看看在Openwhisk中具體發生了什么,整個過程如下圖所示:
(1)Nginx
Openwhisk面向用戶的API完全基於HTTP,遵循Restful設計,因此,通過wsk-cli發送的命令本質上是向其發送HTTP請求,上面的命令大致可以翻譯為:
POST /api/v1/namespaces/$userNamespace/actions/myAction
Host: $openwhiskEndpoint
此處的Nginx主要用於接受Http請求,並將處理后的Http請求直接轉發給controller
(2)控制器(Controller)
控制器是真正開始處理請求的地方。控制器使用Scala語言實現,並提供了對應的Rest API,接受Nginx轉發的請求。Controller分析請求內容,進行下一步處理。下面的很多個步驟都會和其有關系。
(3)身份驗證和鑒權:CouchDB
繼續用上一步用戶發出的Post請求為例,控制器首先需要驗證用戶的身份和權限。用戶的身份信息(credentials)保存在CouchDB的用戶身份數據庫中,驗證無誤后,控制器進行下一步處理。
(4)再次CouchDB,得到對應的Action的代碼及配置
身份驗證通過后,Controller需要從CouchDB中加載此操作(在本例中為myAction)。操作記錄主要要執行的代碼和要傳遞給操作的默認參數,並與實際調用請求中包含的參數合並。它還包含執行時對其施加的資源限制,例如允許使用的內存。
(5)Consul和負載均衡
到了這一步,控制器已經有了觸發函數所需要的全部信息,在將數據發送給觸發器(Invoker)之前,控制器需要和 Consul 確認,從 Consul 獲取處於空閑狀態的觸發器的地址。Consul 是一個開源的服務注冊/發現系統,在 OpenWhisk 中 Consul 負責記錄跟蹤所有觸發器的狀態信息。當控制器向 Consul 發出請求,Consul 從后台隨機選取一個空閑的觸發器信息,並返回。值得注意的是:無論是同步還是異步觸發模式,控制器都不會直接調用觸發器API,所有觸發請求都會通過 Kafka 傳遞。
(6)發送請求進Kafka
考慮使用kafka主要是擔心發生以下兩種狀況:
1、系統崩潰,丟失調用請求
2、系統可能處於繁重的負載之下,調用需要等待其它調用首先完成。
Openwhisk考慮到異步情況的發生,考慮異步觸發的情況,當控制器得到 Kafka 收到請求消息的的確認后,會直接向發出請求的用戶返回一個 ActivationId,當用戶收到確認的 ActivationId,即可認為請求已經成功存入到 Kafka 隊列中。用戶可以稍后通過 ActivationId 索取函數運行的結果。
(7)Invoker運行用戶的代碼
Invoker從對應的Kafka topic中接受控制器傳來的請求,會生成一個Docker容器,注入動作代碼,試用傳遞給他的參數執行它,獲取結果,消滅容器。這也是進行了大量性能優化以減少開銷並縮短響應時間的地方。
(8)CouchDB存儲請求結果
Invoker的執行結果最終會被保存在CouchDB的whisk數據庫中,格式如下所示:
{
"activationId": "31809ddca6f64cfc9de2937ebd44fbb9",
"response": {
"statusCode": 0,
"result": {
"hello": "world"
}
},
"end": 1474459415621,
"logs": [
"2016-09-21T12:03:35.619234386Z stdout: Hello World"
],
"start": 1474459415595,
}
保存的結果中包括用戶函數的返回值,及日志記錄。對異步觸發用戶,可以通過步驟6中返回的 activationID 取回函數運行結果。同步觸發的的結果和異步觸發一樣保存在 CouchDB 里,控制器在得到觸發結束的確認后,從 CouchDB 中取得運行結果,直接返回給用戶。
本文主要參考了:
(1)https://github.com/apache/incubator-openwhisk/blob/master/docs/about.md
(2)https://blog.xinkuo.me/post/apache-openwhisk.html#apache-openwhisk%E7%AE%80%E4%BB%8B