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

2023-02-14 18:00:42

openresty 中如何使用 wasm

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 之後擴充套件選項, 如下圖

目前支援如下

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 檔案其實

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'
            }
        }
    }
}