0%

Rust开发istio-wasm扩展

前言

Istio 1.5 开始支持在数据面支持Wasm扩展,相关规范以及SDK在proxy-wasm,目前提供有三种 SDK 实现,分别是C++RustAssemblyScript,其中AssemblyScriptsolo-io/proxy-runtime。除了 SDK Solo 还发布了 WebAssembly Hub,并配有wasme CLI 工具,为 Wasm 扩展的开发提供了不错体验。本文主要介绍如何使用 Rust SDK 开发 Istio Wasm 扩展。

更新历史

2020 年 11 月 28 日 - 初稿

扩展阅读


Wasme 工具

WebAssembly Hub Getting Started

1
2
3
4
5
$ curl -sL https://run.solo.io/wasme/install | sh
$ export PATH=$HOME/.wasme/bin:$PATH
$ wasme --version

wasme version 0.0.14

Rust 项目

Rust安装

1
2
3
$ rustc --version

rustc 1.43.1 (8d69840ab 2020-05-04)

创建Rust项目

使用lib模板创建项目

1
$ cargo new hello-wold --lib 

编辑Cargo.toml

添加依赖

1
2
3
[dependencies]
log = "0.4.8"
proxy-wasm = "0.1.0" # The Rust SDK for proxy-wasm

配置动态库编译

1
2
3
[lib]
path = "src/lib.rs"
crate-type = ["cdylib"]

Wasm 扩展

源码wasm-hello-world-rust

先通过 Rust SDK 了解扩展功能,编辑src/lib.rs

1
2
3
4
5
6
7
8
9
10
use proxy_wasm as wasm;

struct HelloWorld {
context_id: u32,
}

impl wasm::traits::Context for HelloWorld {}
impl wasm::traits::RootContext for HelloWorld{}
impl wasm::traits::StreamContext for HelloWorld{}
impl wasm::traits::HttpContext for HelloWorld{}

安装依赖

1
$ cargo build

要了解扩展看下几个 Context 的结构,其中on_*开头的便是扩展点,其他get_*set_*add_*等提供了不能接口能力。

为 Request 添加自定义 Header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use log::info;
use proxy_wasm as wasm;

#[no_mangle]
pub fn _start() {
proxy_wasm::set_log_level(wasm::types::LogLevel::Trace);
proxy_wasm::set_http_context(
|context_id, _root_context_id| -> Box<dyn wasm::traits::HttpContext> {
Box::new(HelloWorld { context_id })
},
)
}

struct HelloWorld {
context_id: u32,
}

impl wasm::traits::Context for HelloWorld {}

impl wasm::traits::HttpContext for HelloWorld {
fn on_http_request_headers(&mut self, num_headers: usize) -> wasm::types::Action {
info!("Got {} HTTP headers in #{}.", num_headers, self.context_id);
let headers = self.get_http_request_headers();
let mut authority = "";

for (name, value) in &headers {
if name == ":authority" {
authority = value;
}
}

self.set_http_request_header("x-hello", Some(&format!("Hello world from {}", authority)));

wasm::types::Action::Continue
}
}

编译

1
$ cargo build --target wasm32-unknown-unknown --release

runtime-config.jsonwasme build时需要的,可以通过wasme init创建一个cpp项目获得

1
2
3
4
5
6
7
8
9
10
11
{
"type": "envoy_proxy",
"abiVersions": [
"v0-097b7f2e4cc1fb490cc1943d0d633655ac3c522f"
],
"config": {
"rootIds": [
"hello_world"
]
}
}

打包

1
2
3
4
5
$ wasme build precompiled target/wasm32-unknown-unknown/release/hello_world.wasm --tag webassemblyhub.io/amoyw/hello_world:v0.0.1
$ wasme list

NAME TAG SIZE SHA UPDATED
webassemblyhub.io/amoyw/hello_world v0.0.1 1.9 MB 2e95e556 28 Nov 20 14:27 CST

wasme build三种方式,

1
2
3
4
5
6
$ wasme build -h

Available Commands:
assemblyscript Build a wasm image from an AssemblyScript filter using NPM-in-Docker
cpp Build a wasm image from a CPP filter using Bazel-in-Docker
precompiled Build a wasm image from a Precompiled filter.

本地测试 Envoy

1
2
$ wasme deploy envoy webassemblyhub.io/a mo yw/hello_world:v0.0.1 --envoy-image=istio/proxyv2:1.6.0 --bootstrap=envoy-bootstrap.yml
[2020-05-24 03:03:41.736][12][info][wasm] [external/envoy/source/extensions/common/wasm/context.cc:1103] wasm log hello_world hello_world : Got 16 HTTP headers in #9.

http://localhost:8080/headers

1
2
3
4
5
{
"headers" : {
"X-Hello" : "Hello world from localhost:8080"
}
}

wasme deploy三种方式

1
2
3
4
5
6
$ wasme deploy -h

Available Commands:
envoy Run Envoy locally in Docker and attach a WASM Filter.
gloo Deploy an Envoy WASM Filter to the Gloo Gateway Proxies (Envoy).
istio Deploy an Envoy WASM Filter to Istio Sidecar Proxies (Envoy).

WebAssembly Hub 使用

Create a User on webassemblyhub.io & Log In from the wasme command line

Push 镜像

1
$ wasme push webassemblyhub.io/amoyw/hello_world:v0.0.1

发布到 Istio

httpbin 用例自己部署,本例namespace=ns-httpbin

可以分别通过wasme工具或operator添加扩展

CLI 手动添加

1
2
3
$ wasme deploy istio webassemblyhub.io/amoyw/hello_world:v0.0.1 \
--id=hello-world-filter \
--namespace=ns-httpbin

http://{ingress-host}:{port}/headers

1
2
3
4
5
{
"headers" : {
"X-Hello" : "Hello world from {ingress-host}"
}
}

卸载

1
2
3
$ wasme undeploy istio \
--id=hello-world-filter \
--namespace=ns-httpbin

Operator

CRD

1
2
3
$ kubectl apply -f https://github.com/solo-io/wasme/releases/latest/download/wasme.io_v1_crds.yaml

customresourcedefinition.apiextensions.k8s.io/filterdeployments.wasme.io created

Operator

1
2
3
4
5
6
7
8
9
10
11
12
$ kubectl apply -f https://github.com/solo-io/wasme/releases/latest/download/wasme-default.yaml

namespace/wasme created
configmap/wasme-cache created
serviceaccount/wasme-cache created
serviceaccount/wasme-operator created
clusterrole.rbac.authorization.k8s.io/wasme-operator created
clusterrole.rbac.authorization.k8s.io/wasme-cache created
clusterrolebinding.rbac.authorization.k8s.io/wasme-operator created
clusterrolebinding.rbac.authorization.k8s.io/wasme-cache created
daemonset.apps/wasme-cache created
deployment.apps/wasme-operator created

Filter

1
2
3
4
5
6
7
8
9
10
11
12
13
$ kubectl apply -f - <<EOF
apiVersion: wasme.io/v1
kind: FilterDeployment
metadata:
name: hello-world-filter
namespace: ns-httpbin
spec:
deployment:
istio:
kind: Deployment
filter:
image: webassemblyhub.io/amoyw/hello_world:v0.0.1
EOF

Filter 状态

1
2
3
4
5
6
7
$ kubectl get filterdeployments.wasme.io -n ns-httpbin -o yaml hello-world-filter

status:
observedGeneration: "1"
workloads:
httpbin:
state: Succeeded

注意看下 Pod 是否成功,如果READY 1/2可能是istio-proxy没有启动,测试时这里遇到问题wasme-cache经常失败,可能是网络问题,并且失败后不能续传。可以进入 Pod 看缓存的尺寸是否和hello_world.wasm尺寸一致,缓存路径/var/local/lib/wasme-cache/,不一致就等缓存完成后再测试。

http://{ingress-host}:{port}/headers

1
2
3
4
5
{
"headers" : {
"X-Hello" : "Hello world from {ingress-host}"
}
}

卸载

1
$ kubectl delete FilterDeployment -n ns-httpbin hello-world-filter