服务网格 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
请求头之后访问成功,插件已生效。