Categories
程式開發

WebAssembly 與 Kubernetes雙劍合璧:機遇與挑戰


無處不在的WebAssembly

如果評選2019年編程技術的“網紅”,無論是前端圈還是後端圈 WebAssembly (WASM) 都絕對能夠高票入選。然而,如果評選最被“低估”的技術,我覺得 WebAssembly 也可以輕鬆入圍。借用伏爾泰曾評價神聖羅馬帝國的句式 “既不神聖,也不羅馬,更非帝國”,我們也可以說WebAssembly “既不限於Web,更不是Assembly(彙編語言)”。

在2019年12月,萬維網聯盟 (World Wide Web Consortium – W3C) 宣布WebAssembly核心規范正式成為Web標準, 這使得 WebAssembly 成為互聯網上與 HTML, CSS, and JavaScript並列的第四種官方語言,可以原生的運行在瀏覽器上。而更加重要的是,WebAssembly 作為一個安全的、可移植、高效率的虛擬機沙箱,可以在 Internet 的任何地方、任何平台(不同操作系統,不同CPU體系架構下)安全運行應用。 WebAssembly已得到了所有主流瀏覽器廠商的廣泛支持(Google Chrome, Microsoft Edge, Apple Safari, Mozilla Firefox等),然而它的影響已經遠超Web。

WebAssembly的設計初衷之一是為了解決JavaScript的性能問題,使得Web網頁應用有接近本機原生應用的性能。作為一個通用、開放、高效的底層虛擬機抽象,眾多編程語言(如C/C++, Rust, 等)可以將現有應用編譯成為WASM的目標代碼,運行在瀏覽器中 。這讓應用開發技術與運行時技術解耦,極大促進了代碼復用。

Mozilla在2019年3月推出了 WebAssembly System Interface(WASI),來標準化WebAssembly應用與系統資源的交互抽象,比如文件系統訪問,內存管理,網絡連接等,類似POSIX這樣的標準API。 WASI規範大大拓展了WASM應用的場景,可以讓其可以超越瀏覽器環境,作為一個獨立的虛擬機運行各種類型的應用。同時,平台開發商可以針對具體的操作系統和運行環境提供WASI接口不同的實現,可以在不同設備和操作系統上運行跨平台的 WebAssembly 應用。這可以讓應用執行與具體平台環境實現解耦。這一切使得“Build Once, Run Anywhere”的理想逐漸形成現實。 WASI 的示意圖如下所示。 2019年月,為了進一步推動模塊化 WebAssembly 生態系統,Mozilla、Fastly、英特爾和紅帽公司攜手成立了字節碼聯盟(Bytecode Alliance),共同領導 WASI 標準、 WebAssembly 運行時、語言工具等工作。

WebAssembly 與 Kubernetes雙劍合璧:機遇與挑戰 1

原圖:https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/

WASM與容器相愛相殺

WebAssembly是否會取代容器?

正因為 WebAssembly 所具備的的安全、可移植、高效率,輕量化的特點,非常適於應用安全沙箱場景。 WASM得到了容器、函數計算、IoT/邊緣計算等社區的廣泛關注。 Docker創始人Solomon Hykes在WASI發布之際的一句Twitter,更是成為了去年容器和WebAssembly社區引用頻率最高的一句話之一。

WebAssembly 與 Kubernetes雙劍合璧:機遇與挑戰 2

Fastly, Cloudflare等CDN廠商基於WebAssembly技術實現了更加輕量化的應用安全沙箱,可以在一個進程內部運行多個獨立的用戶應用。阿里雲CDN團隊EdgeRoutine也實現了類似技術。與容器技術相比,WASM可以實現毫秒級冷啟動時間和極低的資源消耗。

WebAssembly 與 Kubernetes雙劍合璧:機遇與挑戰 3

原圖:https://blog.cloudflare.com/cloud-computing-without-containers/

當然,世界上沒有完美的技術。任何沙箱技術不可能同時滿足執行效率、安全隔離性和通用性這三個維度的要求。 WASM在安全隔離和通用性等方面與Docker Container等存在差距。雖然如此,我們還是看到了WebAssembly技術巨大的潛力。

WebAssembly容器

我的理解是WebAssmebly可以成為一種容器類型,類似Linux Container或者Windows Container一樣。成為一個跨平台的標準應用分發方式和運行時環境。

應用分發

Docker容器的一個重要貢獻是其標準化了容器化應用打包規範 Docker Image,而且它已經成為開放容器計劃(Open Container Initiative – OCI)的鏡像格式標準。 Docker鏡像提供了自包含、自描述的鏡像格式。它可以將應用以及其依賴的環境信息打包在一起,從而實現應用與運行環境解耦,讓容器應用可以輕鬆運行在從本地開發環境到雲端生產環境的不同場景中。並且社區圍繞Docker鏡像構建了繁榮的工具鏈生態,如Docker Hub可以進行應用分發和CI/CD協同,Nortary/TUF項目可以保障應用可信地分發、交付。

對與WebAssembly,目前社區提供了類似NPM的包管理實現 WAPM,可以較好地支持應用的分發。為WebAssembly應用構建Docker鏡像,可以實現雙贏的局面。

  • WebAssembly開發者可以完全復用Docker/OCI鏡像規範和工具鏈,進一步簡化應用分發和交付。比如,我們可以將Nginx的WASM鏡像作為基礎鏡像,基於這個鏡像可以構建包含不同Web內容的應用鏡像;我們可以利用tag對應用版本進行追踪;利用Docker Registry進行應用分發;在這個過程我們還可以進一步利用數字簽名來保障安全的軟件供應鏈。
  • Docker鏡像規範支持Multi-Arch鏡像,可以簡化不同CPU體系架構(如x86, ARM, RISC-V等)的應用鏡像的構建與分發。而WebAssembly天生具備可移植性,大大簡化了跨平台Docker應用鏡像的構建和分發。

我提供了一個技術原型示例項目,https://github.com/denverdino/wasm-container-samples,大家可以參考其中的例子來構建WASM容器鏡像。由於WebAssembly應用採用緊湊的二進制格式,而且沒有任何操作系統依賴,WASM應用可以構建出非常小的容器鏡像。大家可以自行感受一下:

$ sudo ctr image ls
REF                                                           TYPE                                                 DIGEST                                                                  SIZE      PLATFORMS   LABELS
docker.io/denverdino/c-http-server-wasm:latest                application/vnd.docker.distribution.manifest.v2+json sha256:2efa759f46f901cda2e6a9b4228c423b17a960c06e957964e72c21dc5b42408f 29.2 KiB  linux/amd64 -
docker.io/denverdino/hellowasm:latest                         application/vnd.docker.distribution.manifest.v2+json sha256:cadcc8b07eb82b18db2c8f500fa2b11e5ebf2e9054cfa687e4ffe44861860132 8.2 KiB   linux/amd64 -
docker.io/denverdino/nginxwasm:latest                         application/vnd.docker.distribution.manifest.v2+json sha256:8735c82524a463b842b7c79f2c1be8094ee1c57cfd34154f68752fbe79c25998 582.7 KiB linux/amd64 -

安全隔離

WebAssembly的最初設計目標是讓應用可以安全運行在瀏覽器中。 WASM虛擬機提供的的沙箱和內存隔離機制,可以有效減少安全攻擊面。而當WebAssembly走出瀏覽器,面向更加通用的場景。 WASM也面對更加複雜的安全挑戰。

WASI 提供了基於能力的安全模型。 WASI應用遵循最小權限原則,應用只能訪問其執行所需的確切資源。傳統上,如果應用需要打開文件,它會帶路徑名字符串調用系統操作o​​pen。然後系統調用會檢查應用是否具有訪問該文件的相​​關權限,比如Linux實現了基於用戶/組的權限模型。這樣隱式的安全模型,依賴於正確的安全管理配置,比如一旦特權用戶執行了一個惡意應用,它就可以訪問系統中任意的資源。而對於WASI應用而言,如果它需要需要訪問指定文件等系統資源,需要從外部顯式傳入加有權限的文件描述符引用,而不能訪問任何其他未授權資源。這中依賴注入的方式可以避免傳統安全模型的潛在風險。一個示意圖如下

WebAssembly 與 Kubernetes雙劍合璧:機遇與挑戰 4

原圖:https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/

我們可以看到WASI的安全模型與傳統操作系統安全模型非常不同,而且還在持續演進中。比如字節碼聯盟提出了 nanoprocess 來解決應用模塊間的安全協同和信任傳遞。

WebAssembly/WASI的安全模型依然存在不足,比如

  • 資源隔離:
    • 對於內存資源,WebAssembly實現了線性內存模型。 WebAssembly應用只能利用索引訪問傳入的一段邏輯線性內存。而WASM虛擬機負責確定內存的實際物理地址,WASM應用無法獲知內存的真實地址,也無法通過越界訪問等方式發動攻擊。所以理論上,可以對WASM應用進行資源容量限制。但是目前部分WASM虛擬機還無法對內存進行精確的隔離限制
    • 對於CPU資源,部分的WASM虛擬機實現可以對應用使用的CPU資源進行計量,但是大多無法實現精確的配額限制、優先級和搶占式調度。
    • I/O資源,比如IOPS等,WASM目前完全沒有相關的隔離能力。
  • 網絡安全:
    • WASI的Capability模型對於文件系統訪問相對比較容易保護。但是這個靜態的安全模型無法適用於動態的網絡應用場景。在微服務架構中,應用經常通過Service Registry進行服務發現,為服務的調用者和提供者實現動態的調用綁定。這個語義是無法用靜態的capability模型描述和注入的。這也導致了WASI的網絡部分API還處於討論之中。現有的WASI網絡安全模型,以及相關討論

Linux操作系統和容器技術已經提供了非常完備的資源隔離和安全隔離實現。與WebAssembly結合在一起可以應對不同場景對不同隔離級別的需求。

  • 共享進程資源 – 多個WASM應用模塊運行在一個WASM虛擬機進程內部,依賴WASM運行時進行隔離。隔離級別低,控製粒度比較粗,資源開銷極小。可以以較小代價保障系統安全。適合受限問題域的應用安全隔離。
  • 獨立進程資源 – 不同WASM應用模塊運行在不同的WASM虛擬機進程中,可以復用操作系統的進程級隔離能力,比如CGroup。此外,還可以利用類似Kubernetes中的Network Policy (網絡策略),或者服務網格(如Istio)等技術,對進程的網絡訪問進行細粒度的控制,甚至實現零信任網絡。隔離級別比較高,控製粒度比較細,資源開銷適中。可以應用於更加通用的場景。

注:當然利用安全沙箱如虛擬化等技術,結合WebAssembly,可以進一步最小化安全攻擊面,但是ROI不高。

調度與編排

在雲時代,Kubernetes已經成為分佈式環境下資源調度和應用編排的事實標準。 Kubernetes可以屏蔽底層設施的差異性。可以在同一個K8s集群中包含x86、ARM等不同體系架構的節點,可以支持Linux,Windows等不同的操作系統。 Kubernetes和WebAssembly相結合可以進一步提升應用的可移植性。

微軟的Deis Labs年初發布了一個實驗項目, https://github.com/deislabs/krustlet 來利用 Virtual Kubelet類似的架構調度 WebAssembly 應用。但是這個方式有很多局限,無法借助容器方式進行應用分發,也無法利用 K8s 的語義進行資源編排。

難得有一個春節假期可以宅在家里間,我基於Derek McGowan去年的一個實驗性項目https://github.com/dmcgowan/containerd-wasm,完善了containerd的WASM shim實現。可以讓containerd支持WASM container,並且可以利用Kubernetes集群管理和調度 WASM container。

項目的代碼實現: https://github.com/denverdino/containerd-wasm

注:這個項目更多是概念驗證,進程管理、資源限制,性能優化等的細節並沒未完整實現。

整個系統的架構設計如下,“container-shim-wasm-v1”做為Containerd的擴展,利用 wasmer 作為WASM應用運行時環境,可以實現與runc容器一致的用戶體驗。

WebAssembly 與 Kubernetes雙劍合璧:機遇與挑戰 5

我們還會將其註冊為 K8s 的一個RuntimeClass ,允許用戶利用K8s來交付和運維WASM應用。

注:RuntimeClass是 Kubernetes v1.12 引入的新概念,可以讓Kubernetes支持多種不同的容器運行時,比如 runc容器、或者Kata Containers,gVisor等安全沙箱容器。更多細節可以參考,containerd與安全沙箱的Kubernetes初體驗

Talk is Cheap, 放碼過來

首先,我們將利用Minikube創建一個K8s測試環境,並將 Containerd 作為Kubernetes集群的容器運行時。

創建虛擬機測試環境

創建Minikube K8s集群,並將 Containerd 作為Kubernetes集群容器運行時

minikube start --image-mirror-country cn 
    --iso-url=https://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/iso/minikube-v1.6.0.iso 
    --registry-mirror=https://tgtsuwdg.mirror.aliyuncs.com 
    --container-runtime=containerd

進入 Minikube 虛擬機

$ minikube ssh
                         _             _
            _         _ ( )           ( )
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __
/' _ ` _ `| |/' _ `| || , <  ( ) ( )| '_`  /'__`
| ( ) ( ) || || ( ) || || |` | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`___/'(_,__/'`____)

配置環境所需依賴

  • wasmer 0.13
  • minikube缺省安裝了container 1.2.x,需要升級 containerd 1.3.x
  • 我提供了一個預編譯的 containerd-wasm-shim-v1,也可自己編譯一個版本。
cd ~

# Install Wasmer 0.13.1
curl -L -O https://github.com/wasmerio/wasmer/releases/download/0.13.1/wasmer-linux-amd64.tar.gz
gunzip wasmer-linux-amd64.tar.gz
tar xvf wasmer-linux-amd64.tar
sudo cp bin/* /usr/bin/

# Upgrade containerd to v1.3.2
curl -L -O https://github.com/containerd/containerd/releases/download/v1.3.2/containerd-1.3.2.linux-amd64.tar.gz
gunzip containerd-1.3.2.linux-amd64.tar.gz
tar xvf containerd-1.3.2.linux-amd64.tar
sudo systemctl stop containerd
sudo cp bin/* /usr/bin/
sudo systemctl restart containerd

# Install containerd-wasm-shim
wget http://kubernetes.oss-cn-hangzhou.aliyuncs.com/containerd-wasm/containerd-shim-wasm-v1
chmod +x containerd-shim-wasm-v1
sudo mv containerd-shim-wasm-v1 /usr/bin/

配置 containerd 支持 WASM shim

在containerd配置文件中添加 wasm shim相關配置,並重啟containerd。

$ cat <

測試 Hello World WASM容器應用

$ sudo ctr image pull docker.io/denverdino/hellowasm:latest
docker.io/denverdino/hellowasm:latest:                                            resolved       |++++++++++++++++++++++++++++++++++++++|
manifest-sha256:cadcc8b07eb82b18db2c8f500fa2b11e5ebf2e9054cfa687e4ffe44861860132: done           |++++++++++++++++++++++++++++++++++++++|
layer-sha256:ecda28441283ecf01d35bca0361f2c1ef26a203454a06789ee5ce71ba1e32ca3:    done           |++++++++++++++++++++++++++++++++++++++|
config-sha256:57974480d640c8d60d254a8b0fa4606b2c7107fe169bc3ddd455091277c3a5e4:   done           |++++++++++++++++++++++++++++++++++++++|
elapsed: 3.0 s                                                                    total:   0.0 B (0.0 B/s)
unpacking linux/amd64 sha256:cadcc8b07eb82b18db2c8f500fa2b11e5ebf2e9054cfa687e4ffe44861860132...
done
$ sudo ctr run --rm --runtime io.containerd.wasm.v1 docker.io/denverdino/hellowasm:latest test1
Hello world

測試 Nginx的WASM容器應用

$ sudo ctr image pull docker.io/denverdino/nginxwasm:latest
docker.io/denverdino/nginxwasm:latest:                                            resolved       |++++++++++++++++++++++++++++++++++++++|
manifest-sha256:8735c82524a463b842b7c79f2c1be8094ee1c57cfd34154f68752fbe79c25998: exists         |++++++++++++++++++++++++++++++++++++++|
layer-sha256:27f4d8ad067fbb709d18ea5acd7a5ddfb85851e5d9f030636e9da3d16cc4bd07:    done           |++++++++++++++++++++++++++++++++++++++|
config-sha256:a55bd3bdb9d00fdac5ee2f64bfc1856e58e8bb90587943969ad3d8115f4ced70:   done           |++++++++++++++++++++++++++++++++++++++|
elapsed: 3.0 s                                                                    total:   0.0 B (0.0 B/s)
unpacking linux/amd64 sha256:8735c82524a463b842b7c79f2c1be8094ee1c57cfd34154f68752fbe79c25998...
done
$ sudo ctr run --rm --runtime io.containerd.wasm.v1 docker.io/denverdino/nginxwasm:latest test2
2020/02/01 07:01:21 [notice] 30672#0: using the "select" event method
2020/02/01 07:01:21 [notice] 30672#0: nginx/1.15.3
2020/02/01 07:01:21 [notice] 30672#0: built by clang 6.0.1  (emscripten 1.38.11 : 1.38.11)
2020/02/01 07:01:21 [notice] 30672#0: OS: Linux 4.19.81
2020/02/01 07:01:21 [notice] 30672#0: getrlimit(RLIMIT_NOFILE): 1024:1024

在 Minikube 外部,可以用如下方式獲得 nginx 應用的訪問地址

$ echo http://$(minikube ip):8080
http://192.168.64.13:8080

利用瀏覽器打開上述地址,顯示如下

WebAssembly 與 Kubernetes雙劍合璧:機遇與挑戰 6

創建WASM容器的RuntimeClass CRD

為了將WASM容器可以被Kubernetes所調度,我們需要創建一個RuntimeClass CRD

下載示例文件

$ git clone https://github.com/denverdino/wasm-container-samples
$ cd wasm-container-samples

註冊 RuntimeClass “wasm”,這個值

$ cat wasm-runtimeclass.yaml
apiVersion: node.k8s.io/v1beta1
kind: RuntimeClass
metadata:
  name: wasm
handler: wasm

$ kubectl apply -f wasm-runtimeclass.yaml
runtimeclass.node.k8s.io/wasm created

$ kubectl get runtimeclass
kubectl get runtimeclass
NAME   CREATED AT
wasm   2020-02-01T06:24:12Z

在K8s中運行WASM容器應用

在K8s應用的yaml manifest中,我們可以在Pod Spec上指明所需 runtimeClassName。下面我們就用K8s來部署一個nginx的WASM容器。

$ cat nginx-wasm.yaml
apiVersion: v1
kind: Pod
metadata:
 name: nginx-wasm
spec:
 runtimeClassName: wasm
 containers:
 - name: nginx
   image: denverdino/nginxwasm
   ports:
     - containerPort: 8080

$ kubectl apply -f nginx-wasm.yaml
pod/nginx-wasm created

$ kubectl get pod
NAME         READY   STATUS    RESTARTS   AGE
nginx-wasm   1/1     Running   0          9s

新機遇、新希望

目前為止,WebAssembly 技術仍處於初期階段,WASI也有很多局限性。但是社區的進展非常快,SIMD 指令支持,多線程處理等規範也正在快速演進中。 WebAssembly已經打破次元壁,將高性能的計算能力帶領到Web瀏覽器端,越來越多的計算密集型的遊戲、AI模型預測、和數據處理應用被移植到瀏覽器端,可以為應用提供更加優化的用戶體驗。

WebAssembly更廣闊的空間在雲計算領域、區塊鍊等分佈式計算領域。 WebAssembly 輕量、敏捷、安全的特性,可以有效降低Serverless應用啟動速度和資源消耗。同時WebAssembly的可移植,可以讓應用一致運行在從雲端服務器到邊緣IoT設備等不同平台環境中,讓計算無處不在。

利用containerd的擴展機制,可以為WebAssembly應用提供與其他容器應用一致的、抽象的、應用分發、交付和運維模型,可以在Kubernetes集群中進行統一調度和管理。希望通過類似的探索可以簡化基於WebAssembly的分佈式應用管理和運維。

後記

本文寫在2020年的春節期間,這個春節注定將會被所有人銘記。眾志成城,抗擊疫情!天佑中華,武漢加油!

原文鏈接

https://developer.aliyun.com/article/744310