日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

基于WASM的无侵入式全链路A/B Test实践

發布時間:2024/9/3 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于WASM的无侵入式全链路A/B Test实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
簡介:我們都知道,服務網格(ServiceMesh)可以為運行其上的微服務提供無侵入式的流量治理能力。通過配置VirtualService和DestinationRule,即可實現流量管理、超時重試、流量復制、限流、熔斷等功能,而無需修改微服務代碼。 本文所述的實踐是根據請求Header實現全鏈路A/B測試。

1 背景介紹

我們都知道,服務網格(ServiceMesh)可以為運行其上的微服務提供無侵入式的流量治理能力。通過配置VirtualService和DestinationRule,即可實現流量管理、超時重試、流量復制、限流、熔斷等功能,而無需修改微服務代碼。

流量管理的前提是一個服務存在多個版本,我們可以按部署多版本的目的進行分類,簡述如下,以方便理解余文。

  • traffic routing:根據請求信息(Header/Cookie/Query Params),將請求流量路由到指定服務(Service)的指定版本(Deployment)的端點上(Pod[])。就是我們所說的A/B測試(A/B Testing)。
  • traffic shifting:通過灰度/金絲雀(Canary)發布,將請求流量無差別地按比例路由到指定服務(Service)的各個版本(Deployment[])的端點上(Pod[])。
  • traffic switching/mirroring:通過藍綠(Blue/Green)發布,根據請求信息按比例進行流量切換,以及進行流量復制。

本文所述的實踐是根據請求Header實現全鏈路A/B測試。

1.1 功能簡述

從Istio社區的文檔,我們很容易找到關于如何根據請求Header將流量路由到一個服務的特定版本的文檔和示例。但是這個示例只能在全鏈路的第一個服務上生效。

舉例來說,一個請求要訪問A-B-C三個服務,這三個服務都有en版本和fr版本。我們期待:

  • header值為user:en的請求,全鏈路路由為A1-B1-C1
  • header值為user:fr的請求,全鏈路路由為A2-B2-C2

相應的VirtualService配置如下所示:

http: - name: A|B|C-routematch:- headers:user:exact: enroute:- destination:host: A|B|C-svcsubset: v1 - route:- destination:host: A|B|C-svcsubset: v2

我們通過實測可以發現,只有A這個服務的路由是符合我們預期的。B和C無法做到根據Header值路由到指定版本。

這是為什么呢?對于服務網格其上的微服務來說,這個header是憑空出現的,也就是微服務代碼無感知。因此,當A服務請求B服務時,不會透傳這個header;也就是說,當A請求B時,這個header已經丟失了。這時,這個匹配header進行路由的VirtualService配置已經毫無意義。

要解決這個問題,從微服務方的業務角度看,只能修改代碼(枚舉業務關注的全部header并透傳)。但這是一種侵入式的修改,而且無法靈活地支持新出現的header。

從服務網格的基礎設施角度看,任何header都是沒有業務意義且要被透傳的kv pair。只有做到這點,服務網格才能實現無差別地透傳用戶自定義的header,從而支持無侵入式全鏈路A/B Test功能。

那么該怎樣實現呢?

1.2 社區現狀

前面已經說明,在header無法透傳的情況下,單純地配置VirtualService的header匹配是無法實現這個功能的。

但是,在VirtualService中是否存在其他配置,可以實現header透傳呢?如果存在,那么單純使用VirtualService,代價是最小的。

經過各種嘗試(包括精心配置header相關的set/add),我發現無法實現。原因是VirtualService對header的干預發生在inbound階段,而透傳是需要在outbound階段干預header的。而微服務workload沒有能力對憑空出現的header值進行透傳,因此在路由到下一個服務時,這個header就會丟失。

因此,我們可以得出一個結論:無法單純使用VirtualService實現無侵入式全鏈路A/B Test,進一步地說,社區提供的現有配置都無法做到直接使用就能支持這個功能。

那么,就只剩下EnvoyFilter這個更高級的配置了。這是我們一開始很不希望的結論。原因有兩個:

  • EnvoyFilter的配置太過復雜,一般用戶很難在服務網格中快速學習和使用,即便我們提供示例,一旦需求稍有變化,示例對修改EnvoyFilter的參考價值甚微。
  • 就算使用EnvoyFilter,目前Envoy內置的filter也沒有直接支持這個功能的,需要借助Lua或者WebAssembly(WASM)進行開發。
  • 1.3 實現方案

    接下來進入技術選型。我用一句話來概括:

    • Lua的優點是小巧,缺點是性能不理想
    • WASM的優點是性能好,缺點是開發和分發相比Lua要困難。
    • WASM的實現主流是C++和Rust,其他語言的實現尚不成熟或者存在性能問題。本文使用的是Rust。

    我們使用Rust開發一個WASM,在outbound階段,獲取用戶在EnvoyFilter中定義的header并向后傳遞。

    WASM包的分發使用Kubernetes的configmap存儲,Pod通過annotation中的定義獲取WASM配置并加載。(為什么使用這種分發形式,后面會講。)

    2 技術實現

    本節所述的相關代碼:
    https://github.com/AliyunContainerService/rust-wasm-4-envoy/tree/master/propagate-headers-filter

    2.1 使用RUST實現WASM

    1 定義依賴

    WASM工程的核心依賴crates只有一個,就是proxy-wasm,這是使用Rust開發WASM的基礎包。此外,還有用于反序列化的包serde_json和用于打印日志的包log。Cargo.toml定義如下:

    [dependencies] proxy-wasm = "0.1.3" serde_json = "1.0.62" log = "0.4.14"

    2 定義構建

    WASM的最終構建形式是兼容c的動態鏈接庫,Cargo.toml定義如下:

    [lib] name = "propaganda_filter" path = "src/propagate_headers.rs" crate-type = ["cdylib"]

    3 Header透傳功能

    首先定義結構體如下,head_tag_name是用戶自定義的header鍵的名稱,head_tag_value是對應值的名稱。

    struct PropagandaHeaderFilter {config: FilterConfig, }struct FilterConfig {head_tag_name: String,head_tag_value: String, }

    {proxy-wasm}/src/traits.rs中的trait HttpContext定義了on_http_request_headers方法。我們通過實現這個方法來完成Header透傳的功能。

    impl HttpContext for PropagandaHeaderFilter {fn on_http_request_headers(&mut self, _: usize) -> Action {let head_tag_key = self.config.head_tag_name.as_str();info!("::::head_tag_key={}", head_tag_key);if !head_tag_key.is_empty() {self.set_http_request_header(head_tag_key, Some(self.config.head_tag_value.as_str()));self.clear_http_route_cache();}for (name, value) in &self.get_http_request_headers() {info!("::::H[{}] -> {}: {}", self.context_id, name, value);}Action::Continue} }

    第3-6行是獲取配置文件中用戶自定義的header鍵值對,如果存在就調用set_http_request_header方法,將鍵值對寫入當前header。

    第7行是對當前proxy-wasm實現的一個workaround,如果你對此感興趣可以閱讀如下參考:

    • https://github.com/istio/istio/issues/30545#issuecomment-783518257
    • https://github.com/proxy-wasm/spec/issues/16
    • https://www.elvinefendi.com/2020/12/09/dynamic-routing-envoy-wasm.html

    2.2 本地驗證(基于Envoy)

    1 WASM構建

    使用如下命令構建WASM工程。需要強調的是wasm32-unknown-unknown這個target目前只存在于nightly中,因此在構建之前需要臨時切換構建環境。

    rustup override set nightly cargo build --target=wasm32-unknown-unknown --release

    構建完成后,我們在本地使用docker compose啟動Envoy,對WASM功能進行驗證。

    2 Envoy配置

    本例需要為Envoy啟動提供兩個文件,一個是構建好的propaganda_filter.wasm,一個是Envoy配置文件envoy-local-wasm.yaml。示意如下:

    volumes:- ./config/envoy/envoy-local-wasm.yaml:/etc/envoy-local-wasm.yaml- ./target/wasm32-unknown-unknown/release/propaganda_filter.wasm:/etc/propaganda_filter.wasm

    Envoy支持動態配置,本地測試采用靜態配置:

    static_resources:listeners:- address:socket_address:address: 0.0.0.0port_value: 80filter_chains:- filters:- name: envoy.filters.network.http_connection_manager ...http_filters:- name: envoy.filters.http.wasmtyped_config:"@type": type.googleapis.com/udpa.type.v1.TypedStructtype_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasmvalue:config:name: "header_filter"root_id: "propaganda_filter"configuration:"@type": "type.googleapis.com/google.protobuf.StringValue"value: |{"head_tag_name": "custom-version","head_tag_value": "hello1-v1"}vm_config:runtime: "envoy.wasm.runtime.v8"vm_id: "header_filter_vm"code:local:filename: "/etc/propaganda_filter.wasm"allow_precompiled: true ...

    Envoy的配置重點關注如下3點:

    • 15行 我們在http_filters中定義了一個名稱為header_filter的type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
    • 32行 本地文件路徑為/etc/propaganda_filter.wasm
    • 20-26行 相關配置的類型是type.googleapis.com/google.protobuf.StringValue,值的內容是{"head_tag_name": "custom-version","head_tag_value": "hello1-v1"}。這里自定義的Header鍵名為custom-version,值為hello1-v1。

    3 本地驗證

    執行如下命令啟動docker compose:

    docker-compose up --build

    請求本地服務:

    curl -H "version-tag":"v1" "localhost:18000"

    此時Envoy的日志應有如下輸出:

    proxy_1 | [2021-02-25 06:30:09.217][33][info][wasm] [external/envoy/source/extensions/common/wasm/context.cc:1152] wasm log: ::::create_http_context head_tag_name=custom-version,head_tag_value=hello1-v1 proxy_1 | [2021-02-25 06:30:09.217][33][info][wasm] [external/envoy/source/extensions/common/wasm/context.cc:1152] wasm log: ::::head_tag_key=custom-version ... proxy_1 | [2021-02-25 06:30:09.217][33][info][wasm] [external/envoy/source/extensions/common/wasm/context.cc:1152] wasm log: ::::H[2] -> custom-version: hello1-v1

    2.3 WASM的分發方式

    WASM的分發是指將WASM包存儲于一個分布式倉庫中,供指定的Pod拉取的過程。

    1 Configmap + Envoy的Local方式

    雖然這種方式不是WASM分發的終態,但是因為它較為容易理解且適合簡單的場景,本例最終選擇了這個方案作為示例講解。雖然configmap的本職工作不是存WASM的,但是configmap和Envoy的local模式都很成熟,兩者結合恰能滿足當前需求。

    阿里云服務網格ASM產品已經提供了這種類似的方式,具體可以參考 為Envoy編寫WASM Filter并部署到ASM中。

    要把WASM包塞到配置中,首要考慮的是包的尺寸。我們使用wasm-gc進行包裁剪,示意如下:

    ls -hl target/wasm32-unknown-unknown/release/propaganda_filter.wasm wasm-gc ./target/wasm32-unknown-unknown/release/propaganda_filter.wasm ./target/wasm32-unknown-unknown/release/propaganda-header-filter.wasm ls -hl target/wasm32-unknown-unknown/release/propaganda-header-filter.wasm

    執行結果如下,可以看到裁剪前后,包的尺寸對比:

    -rwxr-xr-x 2 han staff 1.7M Feb 25 15:38 target/wasm32-unknown-unknown/release/propaganda_filter.wasm -rw-r--r-- 1 han staff 136K Feb 25 15:38 target/wasm32-unknown-unknown/release/propaganda-header-filter.wasm

    創建configmap:

    wasm_image=target/wasm32-unknown-unknown/release/propaganda-header-filter.wasm kubectl -n $NS create configmap -n $NS propaganda-header --from-file=$wasm_image

    為指定Deployment打Patch:

    patch_annotations=$(cat config/annotations/patch-annotations.yaml) kubectl -n $NS patch deployment "hello$i-deploy-v$j" -p "$patch_annotations"

    patch-annotations.yaml如下:

    spec:template:metadata:annotations:sidecar.istio.io/userVolume: '[{"name":"wasmfilters-dir","configMap": {"name":"propaganda-header"}}]'sidecar.istio.io/userVolumeMount: '[{"mountPath":"/var/local/lib/wasm-filters","name":"wasmfilters-dir"}]'

    2 Envoy的Remote方式

    Envoy同時支持local和remote形式的資源定義。對比如下:

    vm_config:runtime: "envoy.wasm.runtime.v8"vm_id: "header_filter_vm"code:local:filename: "/etc/propaganda_filter.wasm" vm_config:runtime: "envoy.wasm.runtime.v8"code:remote:http_uri:uri: "http://*.*.*.216:8000/propaganda_filter.wasm"cluster: web_servicetimeout:seconds: 60sha256: "da2e22*"

    remote方式是最接近原始Enovy的,因此這種方式本來是本例的首選。但是實測過程中發現在包的hash校驗上存在問題,詳見下方參考。并且,Envoy社區的大牛周禮贊反饋我說remote不是Envoy支持WASM分發的未來方向。因此,本例最終放棄這種方式。

    • https://stackoverflow.com/questions/65871312/how-to-set-the-sha256-hex-in-envoy-wasm-remote-config
    • https://envoyproxy.slack.com/archives/C78M4KW76/p1611496672017500

    3 ORAS + Local方式

    ORAS是OCI Artifacts項目的參考實現,可顯著簡化OCI注冊表中任意內容的存儲。

    使用ORAS客戶端或者API/SDK的方式將具有允許的媒體類型的Wasm模塊推送到注冊庫(一個OCI兼容的注冊庫)中,然后通過控制器將Wasm Filter部署到指定工作負載對應的Pod中,以Local的方式進行掛載。

    阿里云服務網格ASM產品中提供了對WebAssembly(WASM)技術的支持,服務網格使用人員可以把擴展的WASM Filter通過ASM部署到數據面集群中相應的Envoy代理中。通過ASMFilterDeployment Controller組件, 可以支持動態加載插件、簡單易用、以及支持熱更新等能力。具體來說,ASM產品提供了一個新的CRD ASMFilterDeployment以及相關的controller組件。這個controller組件會監聽ASMFilterDeployment資源對象的情況,會做2個方面的事情:

    • 創建出用于控制面的Istio EnvoyFilter Custom Resource,并推送到對應的asm控制面istiod中
    • 從OCI注冊庫中拉取對應的wasm filter鏡像,并掛載到對應的workload pod中

    具體可以參考:基于Wasm和ORAS簡化擴展服務網格功能。

    后續的實踐分享將會使用這種方式進行WASM的分發,敬請期待。

    類似地,業界其他友商也在推進這種方式,特別是Solo.io提供了一整套WASM的開發框架wasme,基于該框架可以開發-構建-分發WASM包(OCI image)并部署到Webassembly Hub。這個方案的優點很明顯,完整地支持了WASM的開發到上線的生命周期。但這個方案的缺點也非常明顯,wasme的自包含導致了很難將其拆分,并擴展到solo體系之外。

    阿里云服務網格ASM團隊正在與包括solo在內的業界相關團隊交流如何共同推進Wasm filter的OCI規范以及相應的生命周期管理,以幫助客戶可以輕松擴展Envoy的功能并將其在服務網格中的應用推向了新的高度。

    2.4 集群驗證(基于Istio)

    1 實驗示例

    WASM分發到Kubernetes的configmap后,我們可以進行集群驗證了。示例(源代碼)包含3個Service:hello1-hello2-hello3,每個服務包含2個版本:v1/en和v2/fr。

    每個Service配置了VirtualService和DestinationRule用來定義匹配Header并路由到指定版本。

    apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata:name: hello2-vs spec:hosts:- hello2-svchttp:- name: hello2-v2-routematch:- headers:route-v:exact: hello2v2route:- destination:host: hello2-svcsubset: hello2v2- route:- destination:host: hello2-svcsubset: hello2v1 ---- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata:name: hello2-dr spec:host: hello2-svcsubsets:- name: hello2v1labels:version: v1- name: hello2v2labels:version: v2

    Envoyfilter示意如下:

    apiVersion: networking.istio.io/v1alpha3 kind: EnvoyFilter metadata:name: hello1v2-propaganda-filter spec:workloadSelector:labels:app: hello1-deploy-v2version: v2configPatches:- applyTo: HTTP_FILTERmatch:context: SIDECAR_OUTBOUNDproxy:proxyVersion: "^1\\.8\\.*"listener:filterChain:filter:name: envoy.filters.network.http_connection_managersubFilter:name: envoy.filters.http.routerpatch:operation: INSERT_BEFOREvalue:name: envoy.filters.http.wasmtyped_config:"@type": type.googleapis.com/udpa.type.v1.TypedStructtype_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasmvalue:config:name: propaganda_filterroot_id: propaganda_filter_rootconfiguration:'@type': type.googleapis.com/google.protobuf.StringValuevalue: |{"head_tag_name": "route-v","head_tag_value": "hello2v2"}vm_config:runtime: envoy.wasm.runtime.v8vm_id: propaganda_filter_vmcode:local:filename: /var/local/lib/wasm-filters/propaganda-header-filter.wasmallow_precompiled: true

    2 驗證方法

    攜帶header的請求curl -H "version:v1" "http://$ingressGatewayIp:8001/hello/xxx"通過istio-ingressgateway進入,全鏈路按header值,進入服務的指定版本。這里,由于header中指定了version為v2,那么全鏈路將
    為hello1 v2-hello2 v2-hello3 v2。效果如下圖所示。

    驗證過程和結果示意如下。

    for i in {1..5}; docurl -s -H "route-v:v2" "http://$ingressGatewayIp:$PORT/hello/eric" >>resultecho >>result done check=$(grep -o "Bonjour eric" result | wc -l) if [[ "$check" -eq "15" ]]; thenecho "pass" elseecho "fail"exit 1 fi

    result:

    Bonjour eric@hello1:172.17.68.205<Bonjour eric@hello2:172.17.68.206<Bonjour eric@hello3:172.17.68.182 Bonjour eric@hello1:172.17.68.205<Bonjour eric@hello2:172.17.68.206<Bonjour eric@hello3:172.17.68.182 Bonjour eric@hello1:172.17.68.205<Bonjour eric@hello2:172.17.68.206<Bonjour eric@hello3:172.17.68.182 Bonjour eric@hello1:172.17.68.205<Bonjour eric@hello2:172.17.68.206<Bonjour eric@hello3:172.17.68.182 Bonjour eric@hello1:172.17.68.205<Bonjour eric@hello2:172.17.68.206<Bonjour eric@hello3:172.17.68.182

    我們看到,輸出信息Bonjour eric來自各個服務的fr版本,說明功能驗證通過。

    3 性能分析

    新增EnvoyFilter+WASM后,功能驗證通過,但這會帶來多少延遲開銷呢?這是服務網格的提供者和使用者都非常關心的問題。本節將對如下兩個關注點進行驗證。

    • 增加EnvoyFilter+WASM后的增量延遲開銷情況
    • WASM版本和Lua版本的開銷對比

    3.1 Lua實現

    Lua的實現可以直接寫到EnvoyFilter中,無需獨立的工程。示例如下:

    patch:operation: INSERT_BEFOREvalue:name: envoy.luatyped_config:"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuainlineCode: |function envoy_on_request(handle)handle:logInfo("[propagate header] route-v:hello3v2")handle:headers():add("route-v", "hello3v2")end

    3.2 壓測方法

    1 部署

    • 分別在3個namespace上部署相同的Deployment/Service/VirtualService/DestinationRule
    • 在hello-abtest-lua中部署基于Lua的EnvoyFilter
    • 在hello-abtest-wasm中部署基于WASM的EnvoyFilter
    hello-abtest 基準環境 hello-abtest-lua 增加EnvoyFilter+LUA的環境 hello-abtest-wasm 增加EnvoyFilter+WASM的環境

    2 工具

    本例使用hey作為壓測工具。hey前身是boom,用來代替ab(Apache Bench)。使用相同的壓測參數分別對三個環境進行壓測。示意如下:

    # 并發work數量 export NUM=2000 # 每秒請求數量 export QPS=2000 # 壓測執行時常 export Duration=10shey -c $NUM -q $QPS -z $Duration -H "route-v:v2" http://$ingressGatewayIp:$PORT/hello/eric > $SIDECAR_WASM_RESULT

    請關注hey壓測結果文件,結果最后不能出現socket: too many open files,否則影響結果。可以使用ulimit -n $MAX_OPENFILE_NUM命令配置,然后再調整壓測參數,以確保結果的準確性。

    3.3 報告

    我們從三份結果報告中選取4個關鍵指標,如下圖所示:

    基準WASMLUA
    1000并發1000QPS持續10秒鐘
    平均延遲0.6317 secs0.6395 secs0.7012 secs
    延遲99%分布0.9167 secs0.9352 secs1.1355 secs
    QPS154115191390
    Total16281161091390
    2000并發2000QPS持續10秒鐘
    平均延遲1.2078 secs1.3290 secs1.4593 secs
    延遲99%分布1.8621 secs1.8354 secs2.2116 secs
    QPS156414211290
    Total176221600914662

    3.4 結論

  • 相對于基準版本,增加EnvoyFilter的兩個版本,平均延遲多出幾十個到幾百個毫秒,增加耗時比為
    • wasm 1.2% (0.6395-0.6317)/0.6317和1% (1.3290-1.2078)/1.2078
    • lua 11%(0.7012-0.6317)/0.6317和20% (1.4593-1.2078)/1.2078
  • WASM版本的性能明顯優于LUA版本
  • 注:相比LUA版本,WASM的實現是一套代碼多份配置。因此WASM的執行過程還比LUA多出一個獲取配置變量的過程。

    4 展望

    4.1 如何使用

    本文從技術實現角度,講述了如何實現并驗證一個透傳用戶自定義Header的WASM,從而支持無侵入式全鏈路A/B Test這個需求。

    但是,作為服務網格的使用者,如果按照本文一步步去實現,是非常繁瑣且容易出錯的。

    阿里云服務網格ASM團隊正在推出一種ASM插件目錄的機制,用戶只需在插件目錄中選擇插件,并為插件提供自定義的Header等極少數量的kv配置,即可自動生成和部署相關的EnvoyFilter+WASM+VirtualService+DestinationRule。

    4.2 如何擴展

    本例只展示了基于Header的匹配路由功能,如果我們希望根據Query Params進行匹配和路由該如何擴展呢?

    這是ASM插件目錄正在密切關注的話題,最終插件目錄將提供最佳實踐。

    以上。

    原文鏈接:https://developer.aliyun.com/article/782181?

    版權聲明:本文內容由阿里云實名注冊用戶自發貢獻,版權歸原作者所有,阿里云開發者社區不擁有其著作權,亦不承擔相應法律責任。具體規則請查看《阿里云開發者社區用戶服務協議》和《阿里云開發者社區知識產權保護指引》。如果您發現本社區中有涉嫌抄襲的內容,填寫侵權投訴表單進行舉報,一經查實,本社區將立刻刪除涉嫌侵權內容。

    總結

    以上是生活随笔為你收集整理的基于WASM的无侵入式全链路A/B Test实践的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。