天天看點

WebAssembly + Dapr = 下一代雲原生運作時?下一代雲原生應用運作時走向詩和遠方道阻且長,行則将至

WebAssembly + Dapr = 下一代雲原生運作時?下一代雲原生應用運作時走向詩和遠方道阻且長,行則将至

作者 | 易立

來源 |

阿裡巴巴雲原生公衆号

雲計算已經成為了支撐數字經濟發展的關鍵基礎設施。雲計算基礎設施也在持續進化,從 IaaS,到容器即服務(CaaS),再到 Serverless 容器和函數 PaaS (fPaaS 或者 FaaS),新的計算形态相繼出現。以容器和 Serverless 為代表的雲原生技術正在重塑整個應用生命周期。

WebAssembly + Dapr = 下一代雲原生運作時?下一代雲原生應用運作時走向詩和遠方道阻且長,行則将至

在 Gartner 分析報告中,雲計算基礎設施的發展路徑,也是雲原生特質逐漸增強的過程。其具體表現在:

  • 子產品化越來越高- 更加細粒度的計算單元,如容器和 Serverless 函數,更加适于微服務架構的應用傳遞,可以更加充分利用雲的能力,提升架構靈活性。
  • 可程式設計性越來越高- 可以通過聲明式 API 和政策進行實作自動化管理與運維,可以通過 Immutable Infrastructure (不可變基礎設施)進一步提升分布式應用運維的确定性。
  • 彈性效率越來越高- VM 可以實作分鐘級擴容;容器與 Serverless 容器可以實作秒級擴容;借助排程優化,函數可以做到毫秒級擴容。
  • 韌性越來越高- Kubernetes 提供了強大自動化編排能力,提升應用系統自愈性。而 Serverless 進一步将穩定性、可伸縮性和安全等系統級别複雜性下沉到基礎設施,開發者隻需關注自身業務應用邏輯,進一步釋放了生産力,提升系統的可恢複能力。

分布式雲則是雲計算發展的另外一個重要趨勢,公有雲的服務可以拓展到不同的實體位置,讓計算進一步貼近客戶。分布式雲讓客戶享受雲計算的便利的同時,也可以滿足對計算實時性和安全合規的訴求。這也推動了企業應用架構的變化 - 應用要能夠在不同的環境進行部署、遷移,以最優化的方式提供服務。

進一步随着移動網際網路,AI 與 IoT 等新技術的湧現,無處不在的計算已經成為現實。與此同時,這也在催生算力的多樣性,X86 架構一統天下的時代已經過去,ARM/RISC-V 等晶片新勢力不但稱雄移動通信和嵌入式裝置領域,也在向邊緣計算和資料中心市場發起進攻。開發者甚至需要讓應用支援不同的 CPU 體系架構,比如我們可以将一個圖像識别應用部署在邊緣或者 IoT 等不同環境、不同體系架構的裝置之上運作。

在分布式雲、邊緣計算、雲端一體等新的雲計算場景下,下一代雲原生應用運作時将具備什麼樣的特點?

下一代雲原生應用運作時

1. 無處不在的計算催生下一代可移植、高性能、輕量化的安全沙箱

容器應用采用自包含的打包方式 -- 容器鏡像,它包含了應用代碼和依賴的系統元件,可以實作應用與基礎設施解耦,讓應用可以在公共雲、專有雲等不同的運作環境以一緻的方式進行部署、運維,簡化了彈性和遷移。此外 Docker 鏡像規範支援多架構(Multi-Arch)鏡像,可以簡化不同 CPU 體系架構(如 x86, ARM 等)的應用鏡像的建構與分發。

函數應用隻包含用于事件響應的代碼包,這将應用傳遞格式從原生二進制檔案提升到了進階語言層面。這也給應用的可移植性帶來了更大的想象空間,理論上甚至可以屏蔽執行環境 CPU 體系架構的差異。比如對于不依賴本地代碼的 Python/NodeJS 等腳本或者 Java 應用,無需修改就可以在 x86 或者 ARM 等不同 CPU 架構上運作。

然而理想很豐滿,現實很骨感,可移植性和廠商鎖定是函數 PaaS 發展的攔路虎。

  • 很多腳本代碼依然需要通過調用原生代碼來實作資料處理和調用中間件(如資料庫驅動),但是編譯原生代碼需要建構環境與目标執行環境一緻才能保障相容性。比如 AWS Lambda / 阿裡雲函數計算都要求二進制原生代碼依賴指定的核心和 libc 版本。是以,越來越多的函數 PaaS 服務支援容器鏡像作為載體,來簡化函數應用打包和依賴管理。
  • 函數應用通常依賴後端服務(BaaS, Backend as a Service)實作資料通路與計算處理等能力,由于 BaaS 不存在任何标準,這樣很難将在 AWS Lambda 上開發的函數應用移植到阿裡雲的函數計算服務。

在 Serverless 計算中,現有的主流技術是利用沙箱容器技術,如 AWS Firecraker 或者阿裡雲沙箱容器,來實作強隔離的安全執行環境,但是也帶來更大的資源消耗。雖然現在阿裡雲沙箱容器經過優化可以實作 300ms 的冷啟動速度,接近 Docker 這樣的 OS 容器啟動速度,但是還無法滿足函數 PaaS 毫秒級的啟動要求,目前需要通過的排程政策,預留一定的 standby 執行個體才可以滿足,但是這樣也引入了更多的資源消耗。

WebAssembly(WASM) 是一個新的 W3C 規範,是一個通用、開放、高效、安全的底層虛拟機抽象。它的設計初衷是為了解決JavaScript的性能問題,使得 Web 應用有接近本機原生應用的性能。可以将現有程式設計語言應用,如 C/C++, Rust 等,編譯成為 WASM 的位元組碼,運作在浏覽器中的一個沙箱環境中。

WASM 讓應用開發技術與運作時環境解耦,極大促進了代碼複用。Mozilla 更在 2019 年推出了 WebAssembly System Interface(WASI),它提供類似 POSIX 這樣的标準 API 來标準化 WebAssembly 與系統資源的互動抽象,比如檔案系統通路,記憶體管理等。WASI 的出現拓展了 WASM 的應用場景,可以讓其作為一個虛拟機運作各種類型的服務端應用。WASM/WASI 為應用的可移植性帶來全新的希望,為了進一步推動 WebAssembly 生态發展,Mozilla、Fastly、英特爾和紅帽公司攜手成立了位元組碼聯盟(Bytecode Alliance),共同上司 WASI 标準、 WebAssembly 運作時、工具等工作。

WebAssembly 所具備的的安全、可移植、高效率,輕量化的特點,為應用沙箱的發展帶來了全新的思路。WASM 可以輕松實作毫秒級冷啟動時間和極低的資源消耗。同時 WASM 位元組碼比原生機器碼有更高的安全級别。此外,WASI 實作了細粒度基于能力的安全模型,遵循最小權限原則。在執行過程中,WASI 應用隻能通路由依賴注入指明的确切資源集,這種方式與傳統粗粒度的作業系統級隔離相比,進一步收斂了安全攻擊面。

正因如此,WASM/WASI 得到了 Serverless、IoT/邊緣計算等社群的廣泛關注。Fastly、Cloudflare 等廠商相繼釋出了基于 WebAssembly 技術實作了更加輕量化的 Serverless 服務。

然而 WebAssembly 在伺服器端的應用之路依然布滿荊棘。首先 WASI 的能力還在非常早期的狀态,一些關鍵能力依然缺失,首當其沖的就是缺乏标準化的網絡通路能力:

https://github.com/WebAssembly/WASI/issues/315

目前 WASI 應用僅能做一些計算類任務,基本無法實作分布式應用,也無法調用多樣性的後端服務和 Redis、MySQL、Kafka 等應用中間件。這大大限制了 WASI 的應用場景。

當理想撞上現實,頭破血流還是絕處逢生?

2. 下一代可移植應用運作時加速程式設計界面上移,應用基礎設施能力下沉

Dapr 是微軟開源的面向雲原生應用的分布式應用運作時,目标使所有開發人員能夠使用任何語言和任何架構輕松地建構彈性的、事件驅動的、可移植的微服務應用。

WebAssembly + Dapr = 下一代雲原生運作時?下一代雲原生應用運作時走向詩和遠方道阻且長,行則将至

Dapr 實作了一系列建構高性能、可伸縮、高可用的分布式應用的設計模式,比如提供了服務發現和服務調用能力,也實作了一個簡單、一緻的程式設計模型來支援事件驅動應用架構。

此外 Dapr 通過基礎設施屏蔽了應用通路後端服務的技術細節,如資源綁定、安全管理,可觀測性等等。這個對 Serverless 應用非常重要,一方面将開發和部署進行了解耦,讓開發者和運維團隊可以通過關注點分離簡化系統複雜性;一方面,可以将短生命周期、無狀态的 Serverless 應用邏輯,與資料庫連接配接池管理這樣的長期運作,有狀态的中間件通路能力進行解耦,提升了 Serverless 應用的可伸縮性和運作效率。

“Any language, any framework, anywhere” 是 Dapr 的重要設計目标。Dapr 通過在應用和後端服務之間,通過 Sidecar 方式提供一個抽象層,并通過标準化的 HTTP/gRPC API 實作了應用的可移植性,和後端服務的可替換性。

走向詩和遠方

WebAssembly + Dapr = 下一代雲原生運作時?下一代雲原生應用運作時走向詩和遠方道阻且長,行則将至

我們可以将 WebAssembly 和 Dapr 相結合,來實作可移植、強隔離、輕量化的微服務應用架構。Dapr sidecar 與 WASM 虛拟機部署在一起。WASI 應用通過 HTTP/gRPC 通路本地的 Dapr 服務端點,由 Dapr 代理連接配接各種後端服務或者實作服務間通信。

這樣的架構設計讓 WASI 應用的安全邊界非常清晰,符合 WASI 安全模型,WASI 應用隻能通過 Dapr sidecar 實作外部服務通路。同時在這個架構中,隻有 WASM 虛拟機和 Dapr 作為可信的環境依賴以原生機器碼運作。而應用是可移植的 WASM 位元組碼,大大提升了架構的可移植性和安全性。

來自微軟 Deis Labs 的 Radu Matei,最近提供了一個實驗性項目可以為 WASI 添加 HTTP 支援。詳見:

https://deislabs.io/posts/wasi-experimental-http/ 

在此基礎上,我們來建構一個最小原型,驗證 WebAssembly 與 Dapr 相結合的技術可行性。

1. Dapr 環境準備

我們首先按照 

https://docs.dapr.io/getting-started/

 的流程:

$ dapr init
⌛  Making the jump to hyperspace...
✅  Downloading binaries and setting up components...
✅  Downloaded binaries and completed components set up.
ℹ️  daprd binary has been installed to /Users/yili/.dapr/bin.
ℹ️  dapr_placement container is running.
ℹ️  dapr_redis container is running.
ℹ️  dapr_zipkin container is running.
ℹ️  Use `docker ps` to check running containers.
✅  Success! Dapr is up and running. To get started, go here: https://aka.ms/dapr-getting-started


$ dapr run --app-id myapp --dapr-http-port 3500
WARNING: no application command found.
ℹ️  Starting Dapr with id myapp. HTTP Port: 3500. gRPC Port: 63734
ℹ️  Checking if Dapr sidecar is listening on HTTP port 3500
...
ℹ️  Checking if Dapr sidecar is listening on GRPC port 63734
ℹ️  Dapr sidecar is up and running.
✅  You're up and running! Dapr logs will appear here.           

2. 利用 Redis 作為 WASI 應用的狀态存儲

我們下面利用 Dapr 的 Get Started 的例子,利用 Redis 作為 WASI 應用的狀态存儲。具體邏輯如下圖。

WebAssembly + Dapr = 下一代雲原生運作時?下一代雲原生應用運作時走向詩和遠方道阻且長,行則将至

注:下面的應用需要 Rust 和 AssemblyScript 環境配置,請大家自行完成。

我們在 Radu 項目的基礎上 fork 了一個版本,首先來下載下傳代碼,并進行建構。

$ git clone https://github.com/denverdino/wasi-experimental-http
$ cd wasi-experimental-http
$ cargo build
...
    Finished dev [unoptimized + debuginfo] target(s) in 3m 02s           

我們利用 AssemblyScript 來實作了這個測試應用,測試代碼如下:

$ cat tests/dapr/index.ts
// @ts-ignore
import { Console } from "as-wasi";
import { DaprClient, StateItem } from "./dapr";
import { JSON } from "assemblyscript-json";


Console.log("Testing Dapr API ....")

let dapr = new DaprClient()
dapr.saveState("statestore", "weapon", JSON.Value.String("Death Star"))

let o = JSON.Value.Object()
o.set("name", "Tatooine")
o.set("test", 123)
let item = new StateItem("planets", o)
let items: StateItem[] = [item]
dapr.saveBulkState("statestore", items)

let testObj = dapr.getState("statestore", "planets")
let testStr = dapr.getState("statestore", "weapon")

if (testStr.toString() == "Death Star" && testObj.isObj && (<JSON.Integer>(<JSON.Obj>testObj).getInteger("test")).valueOf() == 123) {
    Console.log("Test successfully!")
} else {
    Console.log("Test failed!")
}           

代碼邏輯非常簡單,就是建立一個 Dapr 用戶端,然後通過 REST API,進行 Dapr 的狀态管理。我們可以快速驗證一下。

$  cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.19s
     Running `target/debug/wasi-experimental-http-wasmtime-sample`
Testing Dapr API ....
POST http://127.0.0.1:3500/v1.0/state/statestore with [{"key":"weapon","value":"Death Star"}]
POST http://127.0.0.1:3500/v1.0/state/statestore with [{"key":"planets","value":{"name":"Tatooine","test":123}}]
GET http://127.0.0.1:3500/v1.0/state/statestore/planets
GET http://127.0.0.1:3500/v1.0/state/statestore/weapon
Test successfully!
module instantiation time: 333.16637ms           

3. 關鍵要點分析

wasi-experimental-http 項目在 Wasmtime (來自 Bytecode Alliance 的一個 WASM 實作)虛拟機上實作了擴充,支援在 WASI 應用中,通路 HTTP 服務。它還提供了一個 AssemblyScript 的 HTTP Client 實作。

wasi-experimental-http 項目:

https://github.com/deislabs/wasi-experimental-http/

在此之上,我們為 AssemblyScript 提供一個 Dapr 的封裝,可以參見:

https://github.com/denverdino/wasi-experimental-http/blob/main/tests/dapr/dapr.ts
// @ts-ignore
import { Console } from "as-wasi";
import { Method, RequestBuilder, Response } from "../../crates/as";

import { JSONEncoder, JSON } from "assemblyscript-json";

export class StateItem {
  key: string
  value: JSON.Value
  etag: string | null
  metadata: Map<string, string> | null

  constructor(key: string, value: JSON.Value) {
    this.key = key
    this.value = value
    this.etag = null
    this.metadata = null
  }
}

...

export class DaprClient {
  port: i32
  address: string

  constructor() {
    this.address = "127.0.0.1"
    this.port = 3500
  }

  stateURL(storeName: string): string {
    return "http://" + this.address + ":" + this.port.toString() + "/v1.0/state/" + storeName
  }

  saveState(storeName: string, key: string, value: JSON.Value): boolean {
    let item = new StateItem(key, value)
    let items: StateItem[] = [item]
    return this.saveBulkState(storeName, items)
  }

  saveBulkState(storeName: string, items: StateItem[]): boolean {
    // Handle field
    let encoder = new JSONEncoder();

    // Construct necessary object
    encoder.pushArray(null);
    for (let i = 0, len = items.length; i < len; i++) {
      let item = items[i]
      encoder.pushObject(null);
      encoder.setString("key", item.key)
      encodeValue(encoder, "value", item.value)
      if (item.etag != null) {
        encoder.setString("etag", <string>item.etag)
      }
      encoder.popObject()
    };
    encoder.popArray();
    // Or get serialized data as string
    let jsonString = encoder.toString();
    let url = this.stateURL(storeName);
    Console.log("POST " + url + " with " + jsonString);
    let res = new RequestBuilder(url)
      .method(Method.POST)
      .header("Content-Type", "application/json")
      .body(String.UTF8.encode(jsonString))
      .send();
    let ok = res.status.toString() == "200"
    res.close();
    return ok
  }

  getState(storeName: string, key: string): JSON.Value {
    let url = this.stateURL(storeName) + "/" + key;
    Console.log("GET " + url);
    let res = new RequestBuilder(url)
      .method(Method.GET)
      .send();
    let ok = res.status.toString() == "200"
    let result = <JSON.Value> new JSON.Null()
    if (ok) {
      let body = res.bodyReadAll();
      result = <JSON.Value>JSON.parse(body)
    }
    res.close();
    return result
  }
};
           

測試應用的 main 函數,會建立一個 Wasmtime 運作時環境,并為其添加為 HTTP 擴充,并加載執行測試應用的 WASM 位元組碼:

https://github.com/denverdino/wasi-experimental-http/blob/main/src/main.rs
fn main() {
    let allowed_domains = Some(vec![
        "http://127.0.0.1:3500".to_string(),
    ]);
    let module = "tests/dapr/build/optimized.wasm";
    create_instance(module.to_string(), allowed_domains.clone()).unwrap();
}

/// Create a Wasmtime::Instance from a compiled module and
/// link the WASI imports.
fn create_instance(
    filename: String,
    allowed_domains: Option<Vec<String>>,
) -> Result<Instance, Error> {
    let start = Instant::now();
    let store = Store::default();
    let mut linker = Linker::new(&store);

    let ctx = WasiCtxBuilder::new()
        .inherit_stdin()
        .inherit_stdout()
        .inherit_stderr()
        .build()?;

    let wasi = Wasi::new(&store, ctx);
    wasi.add_to_linker(&mut linker)?;
    // Link `wasi_experimental_http`
    let http = HttpCtx::new(allowed_domains, None)?;
    http.add_to_linker(&mut linker)?;

    let module = wasmtime::Module::from_file(store.engine(), filename)?;

    let instance = linker.instantiate(&module)?;
    let duration = start.elapsed();
    println!("module instantiation time: {:#?}", duration);
    Ok(instance)
}           

道阻且長,行則将至

WASM/WASI 為輕量化、可移植、預設安全的應用運作時提供了良好的基礎,在區塊鍊等領域 WebAssembly 已經得到了廣泛的應用。然而,對于通用性的伺服器端應用,WASM/WASI 的差距還非常明顯。由于 berkeley socket 這樣标準化的網絡程式設計接口的缺失,隻能通過擴充 WASM 虛拟機的方式來進行補齊。此外 WASM 的多線程能力還沒有被标準化,目前的 HTTP 調用采用阻塞式同步調用,還無法實作高效和穩定的網絡通信。

此外,另外 WASM/WASI 的一個短闆就是開發效率和生态建設。目前而言,雖然衆多的程式設計語言已經逐漸開始提供 WebAssembly 的支援,但是對于普通開發者而言,AssemblyScript 這樣的腳本語言是更加合适的選擇。AssemblyScript 複用了 TypeScript 的文法,與 Rust/C++ 相比,大大降低了學習曲線,也提供了非常好的 IDE 工具體驗,如 VS Code 等。但是與 TypeScripty 通過翻譯成為 JavaScript 執行不同,AssemblyScript 應用會被編譯成 WASM 位元組碼執行。AssemblyScript 本質上是一個靜态類型的編譯型語言,本質上與 JS/TS 這樣的動态類型的解釋型語言非常不同。二者在文法上也有一些不同,比如目前 AssemblyScript 缺少對閉包 (closure) 和正規表達式 (Regex) 等常用功能支援,這讓開發 WASM 應用還是有一定的技術門檻。

另外與 NPM 強大的生态相比,AssemblyScript 社群也很年輕。很多功能都需要從頭建構,比如對 JSON 的序列化與反序列化,我們選擇了 _

https://github.com/nearprotocol/assemblyscript-json_

 ,但是其易用性和性能與成熟的 JSON 類庫還有一定差距。當然我們也看到 AssemblyScript 的快速成長,以及越來越多的開發者開始貢獻 AssemblyScript 代碼庫,比如 regex 支援等等。

Dapr 的出現為 WASM/WASI 開發通用的分布式應用,尤其是為可移植的、Serverless 化的應用帶來另外一縷曙光。然而 Dapr 也并非完美:API 标準化在提升對後端服務可移植性的同時也阻礙了對差異化能力的支援。Sidecar 架構在提升靈活性的同時增加了部署和管理複雜性。

作為一個理性樂觀派,任何技術都有其青澀的時代,期待社群的共同努力讓計算無處不在、創新觸手可及的理想成為現實。

WebAssembly + Dapr = 下一代雲原生運作時?下一代雲原生應用運作時走向詩和遠方道阻且長,行則将至

繼續閱讀