用 WebAssembly 为 Istio 扩展插上灵活的翅膀

云原生实验室

共 18068字,需浏览 37分钟

 · 2023-08-17

Sealos 公众号已接入了 GPT-4,完全免费!欢迎前来调戏👇


作者:cuisongliu。Sealos 核心 Maintainer,Kubernetes、Helm、Sealer、Openyurt和 NVIDIA 等项目 commiter。

Istio 引入了 WebAssembly 扩展的概念,允许开发者通过将自定义的 WebAssembly 模块插入 Istio 的 Envoy 代理来扩展 Istio 的功能。这为 Istio 带来了更高的灵活性和可扩展性,开发者可以在不改变 Istio 核心代码的情况下添加自定义功能。

WebAssembly 在 Istio 中的工作原理

WebAssembly 是一种沙盒技术,可以用于扩展 Istio 代理(Envoy)的能力。 Proxy-Wasm 沙盒 API 取代了 Mixer 作为 Istio 主要的扩展机制。

WebAssembly 沙盒的目标:

  • 效率 - 这是一种低延迟,低 CPU 和内存开销的扩展机制。
  • 功能 - 这是一种可以执行策略,收集遥测数据和执行有效荷载变更的扩展机制。
  • 隔离 - 一个插件中程序的错误或是崩溃不会影响其它插件。
  • 配置 - 插件使用与其它 Istio API 一致的 API 进行配置。可以动态的配置扩展。
  • 运维 - 扩展可以以仅日志,故障打开或者故障关闭的方式进行访问和部署。
  • 扩展开发者 - 可以用多种编程语言编写。

高级架构

Istio 扩展(Proxy-Wasm 插件)有几个组成部分:

  • 过滤器服务提供方接口(SPI) 用于为过滤器构建 Proxy-Wasm 插件。
  • 沙盒 在 Envoy 中嵌入 V8 Wasm 运行时。
  • 主机 API 用于处理请求头,尾和元数据。
  • 调出 API 针对 gRPC 和 HTTP 请求。
  • 统计和记录 API 用于度量统计和监控。

应用场景

  • 自定义流量管理:开发者可以使用 WebAssembly 模块实现自定义的流量控制策略,如 AB 测试、灰度发布等。
  • 安全策略:通过 WebAssembly 模块,可以实现自定义的安全策略,例如访问控制、防火墙规则等。
  • 日志和监控:开发者可以使用 WebAssembly 模块来收集特定流量的指标或日志。

ABI 规范定义 (Application Binary Interface)

应用程序二进制接口(ABI)规范定义了 L4/L7 代理与作为 WebAssembly 模块交付的扩展之间使用的约定。这些规范最初为 Envoy 项目中的 WebAssembly 开发而创建,但在代理无关,使用者可以在不同的代理之间使用相同的 Proxy-Wasm 扩展。

SDKs

Istio 提供了多种 WebAssembly SDK,方便开发者使用不同编程语言编写插件:

  • C++ SDK[1]
  • Rust SDK[2]
  • AssemblyScript SDK[3]
  • TinyGo SDK[4]

Istio Wasm Plugin 介绍

Istio Wasm Plugin[5]是一种通过 WebAssembly 过滤器来扩展 Istio 代理功能的机制。通过设置插件的阶段(phase)和优先级(priority),可以在用户提供的 Wasm 插件和 Istio 内部过滤器之间配置复杂的交互。

以下是一些使用示例:

  1. 使用本地文件读取 wasm 插件:
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: openid-connect
  namespace: istio-ingress
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  url: file:///opt/filters/openid.wasm
  sha256: 1ef0c9a92b0420cf25f7fe5d481b231464bc88f486ca3b9c83ed5cc21d2f6210
  phase: AUTHN
  pluginConfig:
    openid_server: authn
    openid_realm: ingress
  1. 使用 OCI 镜像读取 wasm 插件并设置拉取的 secret:
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: openid-connect
  namespace: istio-ingress
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  url: oci://private-registry:5000/openid-connect/openid:latest
  imagePullPolicy: IfNotPresent
  imagePullSecret: private-registry-pull-secret
  phase: AUTHN
  pluginConfig:
    openid_server: authn
    openid_realm: ingress
  1. 使用环境变量读取变量:
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: openid-connect
  namespace: istio-ingress
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  url: oci://private-registry:5000/openid-connect/openid:latest
  imagePullPolicy: IfNotPresent
  imagePullSecret: private-registry-pull-secret
  phase: AUTHN
  pluginConfig:
    openid_server: authn
    openid_realm: ingress
  vmConfig:
    env:
    - name: POD_NAME
      valueFrom: HOST
    - name: TRUST_DOMAIN
      value: "cluster.local"
  1. 使用 http 读取 wasm 插件:
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: openid-connect
  namespace: istio-ingress
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  url: https://private-bucket/filters/openid.wasm
  imagePullPolicy: Always
  phase: AUTHN
  pluginConfig:
    openid_server: authn
    openid_realm: ingress
  vmConfig:
    env:
    - name: POD_NAME
      valueFrom: HOST
    - name: TRUST_DOMAIN
      value: "cluster.local"

Wasm Plugin 与 Istio 执行顺序

Wasm 插件的执行顺序由阶段(phase)和优先级(priority)设置决定。在 Istio 代理收到请求时,根据 Wasm 插件的设置,将它们按照阶段进行分组,并在每个阶段内按优先级值进行排序。然后,Istio 代理按照阶段和优先级的顺序依次调用每个 Wasm 插件的逻辑,从而实现个性化处理和功能扩展。

WasmPlugin 支持字段

WasmPlugins 提供了一种通过 WebAssembly 过滤器扩展 Istio 代理功能的机制。以下是 WasmPlugins 支持的字段以及它们的描述:

  • selector: 用于选择将应用该插件配置的特定 pod/ 虚拟机集合的条件。
  • url: Wasm 模块或 OCI 容器的 URL。支持 file://,oci:// 和 http[s]:// 等协议。
  • sha256: SHA256 校验和,用于验证 Wasm 模块或 OCI 容器。
  • imagePullPolicy: 在通过 OCI 镜像或 http/https 获取 Wasm 模块时应用的拉取行为。
  • imagePullSecret: 用于 OCI 镜像拉取的凭据。
  • pluginConfig: 传递给插件的配置信息。
  • pluginName: 在 Envoy 配置中使用的插件名称。
  • phase: 确定将 WasmPlugin 插入过滤器链的位置。
  • priority: 确定在同一阶段中多个 WasmPlugin 的执行顺序。
  • vmConfig: 配置 Wasm 虚拟机(VM)的信息。
  • match: 用于指定哪些流量将传递给 WasmPlugin 的条件。

环境准备

wget https://github.com/labring/sealos/releases/download/v4.3.0/sealos_4.3.0_linux_amd64.tar.gz
tar -zxvf sealos_4.3.0_linux_amd64.tar.gz sealos
chmod a+x sealos 
mv sealos /usr/bin/
sealos run labring/kubernetes-docker:v1.23.0 labring/helm:v3.12.0 labring/calico:v3.24.1

部署 Istio

sealos run labring/istio:1.16.2-min

安装 rust 语言环境

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

如果使用的国内环境,可以使用以下命令操作

export RUSTUP_DIST_SERVER="https://rsproxy.cn"
export RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup"
curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh
cat > ~/.cargo/config <<EOF
[source.crates-io]
replace-with = 'rsproxy-sparse'
[source.rsproxy]
registry = "https://rsproxy.cn/crates.io-index"
[source.rsproxy-sparse]
registry = "sparse+https://rsproxy.cn/index/"
[registries.rsproxy]
index = "https://rsproxy.cn/crates.io-index"
[net]
git-fetch-with-cli = true
EOF

初始化 rust wasm 项目

使用 JetBrains 插件

打开 Goland 并安装插件 Rust 后创建项目

  1. 新增配置 istio-wasm-rust 模板 :

填写模板地址 : https://github.com/labring-actions/istio-wasm-template.git

  1. 新建项目选择 istio-wasm-rust 模板

使用命令行

cargo install cargo-generate
cargo generate --git https://github.com/labring-actions/istio-wasm-template.git --name my-project
cd my-project

编译

本地编译:

make build 

容器编译:

REPO=sealos.hub:5000 IMG=wasm/wasm-auth:latest make docker-build

部署

本地编译后部署

sealos login sealos.hub:5000
REPO=sealos.hub:5000 IMG=wasm/wasm-auth:v1 make oci-build
REPO=sealos.hub:5000 IMG=wasm/wasm-auth:latest make sealos-push
sealos run sealos.hub:5000/wasm/wasm-auth:latest

容器编译后部署

REPO=sealos.hub:5000 IMG=wasm/wasm-auth:latest make sealos-push
sealos run sealos.hub:5000/wasm/wasm-auth:latest

验证部署

kubectl get pod -n istio-system
NAME READY STATUS RESTARTS AGE
istio-ingressgateway-556959fc6f-prbbg 1/1 Running 0 4d15h
istiod-5b9c4f9bf9-w6xns 1/1 Running 0 4d15h

kubectl logs -f -n istio-system istio-ingressgateway-556959fc6f-prbbg
...
2023-08-05T08:06:22.020843Z info wasm fetching image wasm/wasm-auth from registry sealos.hub:5000 with tag v1
2023-08-05T08:06:22.049991Z info wasm fetching image with plain text from sealos.hub:5000/wasm/wasm-auth:v1

注意事项:

  • 默认配置是过滤的 istio-ingressgateway 的所有请求。如果需要调整,请修改 wasmplugin 的 selector 即可。
  • 默认是看不到 wasm 相关的日志,需要修改 istio-ingressgateway 的日志级别,添加 proxyComponentLogLevel 配置 wasm:debug 或者 wasm:info
  - proxy
  - router
  - --domain
  - $(POD_NAMESPACE).svc.cluster.local
  - --proxyLogLevel=warning
  - --proxyComponentLogLevel=misc:error,wasm:debug
  - --log_output_level=default:info,wasm:debug

再看日志,可以看到已经打印了默认配置

2023-08-05T08:15:05.751195Z	debug	envoy wasm	wasm log: #on_configure -> {"password":"passw0rd","username":"admin"}
2023-08-05T08:15:05.751208Z debug envoy wasm ~Wasm 12 remaining active
2023-08-05T08:15:05.752986Z debug envoy wasm wasm log: #on_configure -> {"password":"passw0rd","username":"admin"}
2023-08-05T08:15:05.753243Z debug envoy wasm wasm log: #on_configure -> {"password":"passw0rd","username":"admin"}
2023-08-05T08:15:05.753372Z debug envoy wasm wasm log: #on_configure -> {"password":"passw0rd","username":"admin"}

Rust SDK 说明

这里讲解一下 Rust SDK 的使用方法和一些常见问题。

  1. 如何获取 pluginConfig 的配置:
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 }))
    }

    // 读取pluginConfig配置,直接解析json即可
    fn on_configure(&mut self, _plugin_configuration_size: usize) -> bool {
        if let Some(config_bytes) = self.get_plugin_configuration() {
            if let Ok(config_str) = std::str::from_utf8(&config_bytes) {
                debug!("#{} -> {}""on_configure", config_str);
            } else {
                error!("Failed to convert configuration bytes to string");
                return false;
            }
        }
        true
    }
}
  1. 如何获取 HTTP 所有的请求头:
fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action {
   for (name, value) in &self.get_http_response_headers() {
      info!("#{} <- {}: {}"self.context_id, name, value);
   }
   Action::Continue
}
  1. 如何获取 HTTP 的某个请求头:
if let Some(path) = self.get_http_request_header(":path")  {
    // TODO: do something with the path
}
  1. 如何强制修改请求头:
self.set_http_request_header(key, value);
  1. 如何终止请求并发送 401 响应:
fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
    if let Some(path) = self.get_http_request_header(":path") {
        self.send_http_response(
            401,
            sdk::headers(),
            None,
        );
        return Action::Pause;
    }
    return Action::Continue;
}
  1. 如何发送请求给其他服务,并解析请求返回

参考代码 : https://github.com/proxy-wasm/proxy-wasm-rust-sdk/blob/master/examples/http_auth_random/src/lib.rs

这里需要说明一下,它其实是支持的 wasm 的 enovy 的负载均衡的请求,并不支持直接请求 http 服务。所以我们需要先获取当前集群所支持的服务列表,然后再发送请求

找到你要查看 istio 的 istio-ingressgateway pod 名称

istioctl proxy-config clusters istio-ingressgateway-556959fc6f-prbbg.istio-system --fqdn sealos.hub -o yaml

找到其名字规则为 outbound|5000||sealos.hub 既 :  DIRECTION|PORT||SERVICE_ALL_ADDR

fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
    let upstream = format!("outbound|{}||{}"5000"sealos.hub");
        self.dispatch_http_call(
            &upstream,
            vec![
                (":method""GET"), //设置请求方式
                (":path""/bytes/1"), //设置请求路径
                (":authority""sealos.hub:5000"), //设置请求地址
                (":scheme""http"), 
            ],
            None,
            vec![],
            Duration::from_secs(5),
        )
        .unwrap();
        Action::Pause
    }

fn on_http_call_response(&mut self, _: u32, _: usize, body_size: usize, _: usize) {
        if let Some(body) = self.get_http_call_response_body(0, body_size) {
            if !body.is_empty() && body[0] % 2 == 0 {
                info!("Access granted.");
                self.resume_http_request();
                return;
            }
        }
        info!("Access forbidden.");
        self.send_http_response(
            403,
            vec![("Powered-By""proxy-wasm")],
            Some(b"Access forbidden.\n"),
        );
    }
    

总结一下:

  1. 使用 dispatch_http_call 方法向其他服务发送异步请求,在 on_http_call_response 方法中接收返回结果。
  2. on_http_call_response 方法中,可以解析返回的结果,根据需要进行相应的处理。
  3. 如果需要根据请求结果来继续执行之前的数据,可以调用 self.resume_http_request()

以上就是关于 Rust SDK 的使用方法的简要说明。通过这些方法,您可以轻松地扩展和定制 Istio 的功能,并在请求的不同阶段对流量进行个性化处理。

引用链接

[1]

C++ SDK: https://github.com/proxy-wasm/proxy-wasm-cpp-sdk

[2]

Rust SDK: https://github.com/proxy-wasm/proxy-wasm-rust-sdk

[3]

AssemblyScript SDK: https://github.com/solo-io/proxy-runtime

[4]

TinyGo SDK: https://github.com/tetratelabs/proxy-wasm-go-sdk

[5]

Istio Wasm Plugin: https://istio.io/latest/zh/docs/reference/config/proxy_extensions/wasm-plugin/


关于 Sealos

Sealos 是一款以 Kubernetes 为内核的云操作系统发行版。它以云原生的方式,抛弃了传统的云计算架构,转向以 Kubernetes 为云内核的新架构,使企业能够像使用个人电脑一样简单地使用云。

🌟GitHub:https://github.com/labring/sealos

🏠官网:https://sealos.io

💻开发者论坛:https://forum.laf.run

关注 Sealos 公众号与我们一同成长👇👇👇

浏览 980
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报