服務網格 ASM(Service Mesh)支持在網格代理中部署Wasm插件來實現一些自定義處理邏輯。Proxy-Wasm社區提供了Wasm的Rust SDK。您可以使用Rust來開發Wasm。本文介紹如何使用Rust開發一個簡單的Wasm插件。
背景信息
Wasm提供了近乎原生二進制的運行效率,并且具有運行時沙箱,更加安全。由于目前Wasm存在一些缺陷,例如在自帶垃圾回收的語言中存在性能問題,因此更加推薦使用用戶自行管理內存的語言,比如C++、Rust等。相比于C++,Rust目前在編譯、構建環節要更加方便,但是編寫門檻稍高一些。您可以根據實際情況自行選擇。
前提條件
已添加集群到ASM實例,且ASM實例版本為1.18及以上。
已啟用Sidecar注入。具體操作,請參見配置Sidecar注入策略。
已創建入口網關。
已部署httpbin應用,且可以正常訪問。具體操作,請參見部署httpbin應用。
已創建阿里云容器鏡像服務企業版(企業版實例支持OCI鏡像)。具體操作,請參見創建企業版實例。
示例說明
本文將編寫一個基于Rust語言的Wasm插件。完成后,我們將生成Wasm二進制文件,并將其打包到鏡像中。在構建完鏡像后,上傳至OCI鏡像倉庫的鏡像服務。上傳完成后,我們將在ASM中配置WasmPlugin資源,以將該插件應用于指定的網格代理。
該插件用于判斷請求中是否存在allow: true
的請求頭。如果不存在,則返回403狀態碼及指定的響應體。如果存在,則正常訪問httpbin應用。
步驟一:開發環境準備
安裝Rustup。具體操作,請參見Install Rust。
執行以下命令,安裝編譯Wasm二進制要用到的工具鏈。
rustup target add wasm32-wasi
如果已經安裝過,請執行以下命令更新Rust。
rustup update
步驟二:編寫插件
新建一個插件目錄
rust-example
,切換到此目錄并執行以下命令。cargo init --lib
編輯生成的
Cargo.toml
文件,添加以下內容。[lib] # 聲明這是一個可以被C/C++調用的動態庫 crate-type = ["cdylib"] [dependencies] log = "0.4.8" proxy-wasm = "0.2.2"
將以下內容添加到
src/lib.rs
中。use log::info; use proxy_wasm::traits::*; use proxy_wasm::types::*; proxy_wasm::main! {{ proxy_wasm::set_log_level(LogLevel::Trace); proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> { Box::new(HttpHeadersRoot) }); }} struct HttpHeadersRoot; // 一些基礎工具函數,加入這一句后,可以直接在self中調用各種工具函數 // 比如:self.set_property(path, value) impl Context for HttpHeadersRoot {} impl RootContext for HttpHeadersRoot { fn get_type(&self) -> Option<ContextType> { Some(ContextType::HttpContext) } fn create_http_context(&self, context_id: u32) -> Option<Box<dyn HttpContext>> { Some(Box::new(HttpHeaders { context_id })) } } struct HttpHeaders { context_id: u32, } impl Context for HttpHeaders {} impl HttpContext for HttpHeaders { fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action { info!("#{} wasm-rust: on_http_request_headers", self.context_id); match self.get_http_request_header("allow") { Some(allow) if allow == "true" => { Action::Continue } _ => { info!("#{} wasm-rust: allow header not found or is not true, deny by default", self.context_id); self.send_http_response( 403, vec![("Content-Type", "text/plain")], Some(b"Forbidden by ASM Wasm Plugin, rust version\n"), ); Action::Pause } } } }
執行以下命令編譯插件。
cargo build --target wasm32-wasi --release
編譯成功后,會生成一個target文件,最終的WASM二進制文件為
target/wasm32-wasi/release/rust_example.wasm
。
步驟三:制作OCI鏡像并推送至阿里云容器鏡像服務
使用以下內容,創建Dockerfile。
FROM scratch
# 主要邏輯就是將生成的wasm二進制文件拷貝到鏡像中,重命名為plugin.wasm
ADD target/wasm32-wasi/release/rust_example.wasm ./plugin.wasm
構建和推送鏡像的步驟,請參見制作OCI鏡像并推送至阿里云容器鏡像服務。鏡像名稱和Tag請根據實際情況自行指定。
步驟四:將Wasm插件應用在網關上
具體操作步驟,請參見將Wasm插件應用在網關上。請確保配置的WasmPlugin中的URL填寫正確的鏡像地址。
步驟五:驗證插件是否生效
使用數據面集群的kubeconfig,執行以下命令,開啟網關的Wasm組件debug日志。
kubectl -n istio-system exec ${網關pod名稱} -c istio-proxy -- curl -XPOST "localhost:15000/logging?wasm=debug"
執行以下命令,訪問網格的httpbin應用。
curl ${ASM網關IP}/status/418
預期輸出:
Forbidden by ASM Wasm Plugin, rust version
查看網關Pod日志。日志示例如下。
2024-09-05T08:33:31.079869Z info envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1195 wasm log istio-system.header-authorization: #2 wasm-rust: on_http_request_headers thread=35 2024-09-05T08:33:31.079943Z info envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1195 wasm log istio-system.header-authorization: #2 wasm-rust: allow header not found or is not true, deny by default thread=35 {"authority_for":"xx.xx.xx.xx","bytes_received":"0","bytes_sent":"43","downstream_local_address":"xx.xx.xx.xx:80","downstream_remote_address":"xx.xx.xx.xx:xxxxx","duration":"0","istio_policy_status":"-","method":"GET","path":"/status/418","protocol":"HTTP/1.1","request_id":"d5250d1a-54b3-406d-8bea-5a51b617b579","requested_server_name":"-","response_code":"403","response_flags":"-","route_name":"httpbin","start_time":"2024-09-05T08:33:31.079Z","trace_id":"-","upstream_cluster":"outbound|8000||httpbin.default.svc.cluster.local","upstream_host":"-","upstream_local_address":"-","upstream_response_time":"-","upstream_service_time":"-","upstream_transport_failure_reason":"-","user_agent":"curl/8.9.0-DEV","x_forwarded_for":"xx.xx.xx.xx"}
加上
allow: true
請求頭,再次訪問網關的httpbin應用。curl ${ASM網關IP}/status/418 -H "allow: true"
預期輸出:
-=[ teapot ]=- _...._ .' _ _ `. | ."` ^ `". _, \_;`"---"`|// | ;/ \_ _/ `"""`
可以看到,帶有
allow: true
請求頭之后訪問成功,插件已生效。