构建api gateway之 openresty 中如何使用 wasm

时间:2023-02-14 17:18:30

WASM 是什么?

WebAssembly是一种运行在现代网络浏览器中的新型代码,并且提供新的性能特性和效果。它设计的目的不是为了手写代码而是为诸如C、C++和Rust等低级源语言提供一个高效的编译目标。

对于网络平台而言,这具有巨大的意义——这为客户端app提供了一种在网络平台以接近本地速度的方式运行多种语言编写的代码的方式;在这之前,客户端app是不可能做到的。

而且,你在不知道如何编写WebAssembly代码的情况下就可以使用它。WebAssembly的模块可以被导入的到一个网络app(或Node.js)中,并且暴露出供JavaScript使用的WebAssembly函数。JavaScript框架不但可以使用WebAssembly获得巨大性能优势和新特性,而且还能使得各种功能保持对网络开发者的易用性。

比如在envoy 中,主要是通过wasm 提供多语言开发扩展envoy功能的目的

openresty如何玩?

那么在openresty 中是否也可以这么集成wasm呢?

当然是可以的,毕竟openresty 本身就是 nginx + lua , 再加一个 wasm vm 进去也是一样可以做到的。

利用nginx模块化系统就可以做到这个事情了

由于模块的代码都是 c, 大家都不太有兴趣,

这里就不细说了, 有兴趣的小伙伴 可以在 api7/wasm-nginx-module 这里了解细节

nginx 模块怎么开发可以看 nginx 模块开发 - Google 搜索

proxy-wasm是什么?

这里重点提 proxy-wasm 这个东西, 它是上述加在nginx 中的wasm 模块中的 api 标准集合

最初出现与 envoy, 现在也是 Istio 1.6 之后扩展选项, 如下图

构建api gateway之 openresty 中如何使用 wasm

目前支持如下

SDKs

Libraries

openresty 中使用wasm 的例子

1. 基于 assemblyscript 的示例代码

内容主要为添加 response header

export * from "@solo-io/proxy-runtime/assembly/proxy"; // this exports the required functions for the proxy to interact with us.
import { RootContext, Context, registerRootContext, FilterHeadersStatusValues, stream_context } from "@solo-io/proxy-runtime/assembly";
 
class AddHeaderRoot extends RootContext {
  createContext(context_id: u32): Context {
    return new AddHeader(context_id, this);
  }
}
 
class AddHeader extends Context {
  constructor(context_id: u32, root_context: AddHeaderRoot) {
    super(context_id, root_context);
  }
  onResponseHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
    const root_context = this.root_context;
    if (root_context.getConfiguration() == "") {
      stream_context.headers.response.add("hello", "world!");
    } else {
      stream_context.headers.response.add("hello", root_context.getConfiguration());  // 添加 response header
    }
    return FilterHeadersStatusValues.Continue;
  }
}
 
registerRootContext((context_id: u32) => { return new AddHeaderRoot(context_id); }, "add_header");

编译可以得到,我们只需要 release.wasm 文件其实

构建api gateway之 openresty 中如何使用 wasm

2. 为 openresty 按照 wasm

重新构建 openresty 并安装 wasm 模块, 如下为对应脚本 build.sh

#!/usr/bin/env bash


# prev_workdir="$PWD"
# repo=$(basename "$prev_workdir")
# workdir=$(mktemp -d)
# cd "$workdir" || exit 1
# echo $workdir

or_ver="$1"

cc_opt=${cc_opt:-}
ld_opt=${ld_opt:-}
luajit_xcflags=${luajit_xcflags:="-DLUAJIT_NUMMODE=2 -DLUAJIT_ENABLE_LUA52COMPAT"}
OR_PREFIX=${OR_PREFIX:="/usr/local/openresty"}
debug_args=${debug_args:-}

wasm_nginx_module_ver="0.6.2"

git clone --depth=1 -b $wasm_nginx_module_ver \
        https://github.com/api7/wasm-nginx-module.git \
        wasm-nginx-module-${wasm_nginx_module_ver}

cd wasm-nginx-module-${wasm_nginx_module_ver} || exit 1
./install-wasmtime.sh
cd ..


cd openresty-${or_ver} || exit 1
./configure --prefix="$OR_PREFIX" \
    --with-cc-opt="$cc_opt" \
    --with-ld-opt="-Wl,-rpath,$OR_PREFIX/wasmtime-c-api/lib $ld_opt" \
    $debug_args \
    --add-module=../wasm-nginx-module-${wasm_nginx_module_ver} \
    --with-poll_module \
    --with-pcre-jit \
    --without-http_rds_json_module \
    --without-http_rds_csv_module \
    --without-lua_rds_parser \
    --with-stream \
    --with-stream_ssl_module \
    --with-stream_ssl_preread_module \
    --with-http_v2_module \
    --without-mail_pop3_module \
    --without-mail_imap_module \
    --without-mail_smtp_module \
    --with-http_stub_status_module \
    --with-http_addition_module \
    --with-http_auth_request_module \
    --with-http_secure_link_module \
    --with-http_random_index_module \
    --with-http_gzip_static_module \
    --with-http_sub_module \
    --with-http_dav_module \
    --with-http_flv_module \
    --with-http_mp4_module \
    --with-http_gunzip_module \
    --with-threads \
    --with-compat \
    --with-luajit-xcflags="$luajit_xcflags" \
    -j`nproc`

make -j`nproc`
make install DESTDIR="$PWD"
OPENRESTY_PREFIX="$PWD$OR_PREFIX"
cd ..

cd wasm-nginx-module-${wasm_nginx_module_ver} || exit 1
OPENRESTY_PREFIX="$OPENRESTY_PREFIX" make install
cd ..

运行

#!/usr/bin/env bash

or_ver="1.21.4.1"

tempdir=$(mktemp -d)
echo "do at ${tempdir}"
cp -R ./ ${tempdir}
cd ${tempdir}
wget --no-check-certificate https://openresty.org/download/openresty-${or_ver}.tar.gz
tar -zxvpf openresty-${or_ver}.tar.gz > /dev/null
sh build.sh $or_ver

3. 测试

这里就只列举 demo 文件,感兴趣的自己测试

worker_processes 1;
worker_cpu_affinity auto;
worker_rlimit_nofile 65535;
 
events {
    use epoll;
    worker_connections 65535;
    accept_mutex off;
    multi_accept on;
}
 
http {
 
    lua_package_path  "${prefix}deps/share/lua/5.1/?.lua;${prefix}deps/share/lua/5.1/?/init.lua;${prefix}?.lua;${prefix}?/init.lua;;./?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/openresty/luajit/share/lua/5.1/?.lua;/usr/local/openresty/luajit/share/lua/5.1/?/init.lua;";
    lua_package_cpath "${prefix}deps/lib64/lua/5.1/?.so;${prefix}deps/lib/lua/5.1/?.so;;./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/openresty/luajit/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so;";
    lua_socket_log_errors off;
    wasm_vm wasmtime;  # wasm 运行时设置
     
    init_by_lua_block {   # 初始化加载可以复用wasm。避免运行时加载的损耗
        wasm = require("resty.proxy-wasm")
        plugin = wasm.load("add_header", "/app/wasm/assemblyscript/build/release.wasm")
    }
 
    server {
        listen 80;
        server_tokens off;
         
        location /t {
            return 200;
            header_filter_by_lua_block {  # 这里设置调用 wasm
                local ctx = wasm.on_configure(plugin, 'add_header')
                wasm.on_http_response_headers(ctx)
            }
        }
 
         location /d {
            return 200;
            header_filter_by_lua_block {
                ngx.header['test'] = 'add_header'
            }
        }
    }
}