經過 Wasm 擴展 Envoy 1.17+

2021年1月12日,Envoy 1.17.0 正式 released!本次更新值得你們關注的功能:html

  1. wasm filter
  2. xds 資源 ttl
  3. skywalking tracer

本文主要小試Wasm filter。git

Wasm擴展概述

Envoy Wasm擴展是一種Filter,可經過Wasm ABI 將Envoy內部 C++ API 」翻譯「 到 Wasm 運行時。 目前Envoy 支持如下4種Wasm 運行時:github

Name Description
envoy.wasm.runtime.v8 V8-based runtime
envoy.wasm.runtime.wasmtime Wasmtime runtime
envoy.wasm.runtime.wavm WAVM runtime
envoy.wasm.runtime.null Compiled modules linked into Envoy

默認狀況下,Envoy發行鏡像中不包含Wasmtime和WAVM運行時。web

ABI 定義了Wasm擴展兩側的函數約定:主機公開的函數和Wasm模塊實現的函數。主機公開的函數被「import」到Wasm模塊中。docker

固然咱們在真正編寫一個Wasm Filter的時候,咱們並非直接操做ABI。咱們通常使用的是SDK,SDK是ABI的一種特定於語言的實現,它使使用該語言編寫Wasm Filter變得更加容易。api

理論上,咱們可使用各類支持Wasm的語言編寫Filter。不過目前官方實現了C++Rust 兩種語言的SDK。安全

Envoy 支持 Wasm Network Filter 和 HTTP Filter。dom

Wasm是沙箱技術。從安全的角度來看,這是很是理想的,但它對內存模型有影響。 Envoy和Wasm VM之間的任何交互都將從Envoy內存複製到Wasm內存,而後再返回。curl

擴展現例

在本示例中,咱們基於 proxy-wasm-rust-sdk 實現一個Wasm 擴展 ,該擴展主要實現功能:當請求的Header中path屬性值爲「/hello」的時候,Envoy代理直接返回「Hello, World」。socket

1:建立庫並設置

Wasm過濾器是從Rust庫項目編譯而成的,所以首先咱們須要建立該項目:

cargo new --lib my-http-wasm-filter

生產的庫是要被Envoy C++ 代碼加載的,所以無需包含任何Rust特定的信息。故咱們編輯Cargo.toml文件,將庫的類型設置爲"cdylib"。

同時咱們須要引用proxy-wasm-rust SDK,故須要配置一下proxy-wasm-rust SDK依賴。

以下:

[package]
name = "my-http-wasm-filter"
version = "0.1.0"
authors = ["iyacontrol <gaohj2015@yeah.net>"]
edition = "2018"

[lib] 
crate-type = ["cdylib"]

[dependencies]
log = "0.4.8"
proxy-wasm = "0.1.3" # The Rust SDK for proxy-wasm

2:編寫代碼

src/lib.rs 代碼以下:

use log::trace;
use proxy_wasm::traits::*;
use proxy_wasm::types::*;

#[no_mangle]
pub fn _start() {
    proxy_wasm::set_log_level(LogLevel::Trace);
    proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> { Box::new(HttpHeadersRoot) });
}

struct HttpHeadersRoot;

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) -> Action {
        for (name, value) in &self.get_http_request_headers() {
            trace!("#{} -> {}: {}", self.context_id, name, value);
        }

        match self.get_http_request_header(":path") {
            Some(path) if path == "/hello" => {
                self.send_http_response(
                    200,
                    vec![("Hello", "World"), ("Powered-By", "proxy-wasm")],
                    Some(b"Hello, World!n"),
                );
                Action::Pause
            }
            _ => Action::Continue,
        }
    }

    fn on_http_response_headers(&mut self, _: usize) -> Action {
        for (name, value) in &self.get_http_response_headers() {
            trace!("#{} <- {}: {}", self.context_id, name, value);
        }
        Action::Continue
    }

    fn on_log(&mut self) {
        trace!("#{} completed.", self.context_id);
    }
}

3:編譯

執行以下語句:

cargo build --target wasm32-unknown-unknown --release

生成的結果以下:

主要是my_http_wasm_filter.wasm文件。後續咱們會使用到該文件。

4: 運行

配置envoy啓動文件,以下:

static_resources:
  listeners:
  - address:
      socket_address:
        address: 0.0.0.0
        port_value: 8000
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          codec_type: auto
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains:
              - "*"
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: web_service
          http_filters:
          - name: envoy.filters.http.wasm
            typed_config:
              "@type": type.googleapis.com/udpa.type.v1.TypedStruct
              type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
              value:
                config:
                  name: "my_plugin"
                  root_id: "my_root_id"
                  configuration:
                    "@type": "type.googleapis.com/google.protobuf.StringValue"
                    value: |
                      {}
                  vm_config:
                    runtime: "envoy.wasm.runtime.v8"
                    vm_id: "my_vm_id"
                    code:
                      local:
                        filename: "/lib/my_http_wasm_filter.wasm"
                    configuration: {}
          - name: envoy.filters.http.router
            typed_config: {}
  clusters:
  - name: web_service
    connect_timeout: 0.5s
    type: LOGICAL_DNS
    lb_policy: round_robin
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: httpbin
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: httpbin.org
                port_value: 80
                ipv4_compat: true

經過envoy.filters.http.wasm配置項,將咱們編寫的my_http_wasm_filter.wasm加入到envoy filter中。

爲了簡單,咱們基於Envoy 1.17 官方鏡像打一個新的鏡像,Dockerfile以下:

FROM envoyproxy/envoy:v1.17.0
ADD ./target/wasm32-unknown-unknown/release/my_http_wasm_filter.wasm /lib/my_http_wasm_filter.wasm
ADD ./envoy.yaml /etc/envoy.yaml
ENTRYPOINT /usr/local/bin/envoy -c /etc/envoy.yaml -l debug --service-cluster proxy

使用如下命令構建咱們新的鏡像:

docker build -t iyacontrol/envoy:1.17.0 .

運行構建好的鏡像:

docker run iyacontrol/envoy:1.17.0

正常狀況下,咱們的Envoy正常運行。

5:驗證

exec 到Envoy容器中,安裝curl來測試。若是響應內容以下,則咱們的Wasm擴展預期工做。

# curl http://127.0.0.1:8000/hello
Hello, World!


# curl http://127.0.0.1:8000
<!DOCTYPE html>
<html lang="en">
...

總結

相信之後官方會支持愈來愈多的語言編寫Envoy的Wasm擴展。咱們能夠輕鬆選擇本身熟悉的語言實現諸如度量,可觀察性,轉換,數據丟失預防,合規性驗證或其餘功能。

相關文章
相關標籤/搜索