1 寫在前面
微服務和容器化是工程領域過去5年多的革命性發展。從此,合格的程式員是Dev-Sec-Ops三合一的工種、靈活不再是熱門話題,因為逐漸成為日常、服務的可恢複性(resiliency)因為基礎設施的使能,變得更容易實作……更重要的是,期間由此誕生了
CNCF,雲原生成為主旋律。
着眼當下,微服務數量激增、釋出流程和中間件在雲原生的進化中,我們需要在雲作業系統(kubernetes)之上,有一層可以對釋出、服務治理、中間件接入統一治理的基礎設施。這是服務網格(Service Mesh)出現的意義——入坑姿勢很重要,sidecar是表,服務治理是裡。
kubernetes帶給我們的是應用編排能力。k8s關注的是資料中心作業系統要解決的編排問題及相關的計算、存儲、網絡、安全問題;再往上看,不再是k8s要解決的核心問題。k8s提供了諸如CRD(以及阿裡巴巴貢獻給開源社群的OAM)、operator等方式,來保證其上的可擴充性和标準化,至于上層具體如何實作,不是k8s要關心的。
往上這一層是
istio的地盤,istio帶給我們的是更進階、抽象的服務治理能力。服務治理到底包含哪些内容,我沒找到很權威的圖。我從這些年做對話服務的角度,總結了一張圖,如下圖所示。

我們在過去2-3年裡,通過二方庫(即阿裡内部的lib,開源的lib叫三方庫)的方式實作了這張圖的大部分能力,但還是無法做完整。二方庫實作相比業務代碼實作,提供了足夠的抽象和可複用性,但這兩種方式的共同問題是代碼侵入性和對底層中間件的強依賴。将這些能力從應用中剝離、交給istio去做才是未來的發展方向(,但這需要依賴于釋出和中間件首先雲原生化)。
2 WebAssembly閃亮登場
從服務治理的能力圖中,我們很容易聯想到istio是通過熱插拔的方式來實作其可擴充性的。當
envoy宣布支援
wasm後,wasm作為istio未來可插拔的傳遞物,就是個時間表的事情。終于,
istio1.5.0宣布(March 5, 2020)支援wasm,wasm從此走上雲原生的舞台,即将開始她的表演。
2.1 istio的擴充性
istio官網的最新
文檔中展示了wasm作為filter extension的架構圖。未來,我們可以用任何自己擅長的程式設計語言,編寫小巧的wasm代碼,來實作各種擴充能力以結合自身業務增強服務治理。這還是非常讓人興奮的事情。
那麼,我們該怎樣從開發代碼到熱部署一個wasm形式的插件到istio呢?接下來,我們一起看下wasm module的流水線。
2.2 wasm module流水線
- 我們首先要基于proxy-wasm SDK開始自己的wasm module代碼。因為現在處于很早期,目前 wasme 隻支援初始化c++和 AssemblyScript 的wasm module工程。但很快就會支援Rust的! proxy-wasm ABI spec 下,第一批sdk包含了 C++ 、 Rust 。
- wasm module的工程使用 Google bazel 作為建構工具,代碼開發完畢後,通過bazel建構出wasm包,然後通過wasme建構出wasm的oci image。當然,建構wasm包和oci image也可以使用wasme一步搞定,詳見下文。
- 接下來使用wasme指令行登入 WebAssembly Hub 并推送鏡像到WebAssembly Hub。
- 然後通過wasme指令行以
的形式,将wasm部署到istio-proxy容器中。EnvoyFilter
3 實戰
Talk is cheap. Show me the code.
來吧,我們進入實戰。
3.1 Installing
首先是安裝wasme這個cli,
官方給出的是方法1,因為有牆,我建議使用方法2。安裝完畢和執行
wasme --version
驗證。
方法1(沒有加速 有可能失敗)
▶ curl -sL https://run.solo.io/wasme/install | sh
export PATH=$HOME/.wasme/bin:$PATH
Attempting to download Wasme CLI version v0.0.19
Downloading wasme-darwin-amd64...
方法2
# 直接下載下傳 https://github.com/solo-io/wasme/releases/download/v0.0.19/wasme-darwin-amd64
# 然後執行如下指令
▶ chmod +x wasme-darwin-amd64
▶ mv wasme-darwin-amd64 /usr/local/bin/wasme
驗證
▶ wasme --version
wasme version 0.0.19
3.2 Initialize a new filter project
我們使用wasme提供的初始化工程的指令,建立一個wasm module工程。如下指令會讓你做出2個選擇,第一個是語言,目前是c++和assembly script二選一;第二個是元件,
gloo和istio二選一,即api gateway和service mesh,這裡不做展開。本文展示的是c++/istio。
▶ wasme init cpp-filter
▶ cd cpp-filter
▶ tree
.
├── BUILD
├── README.md
├── WORKSPACE
├── bazel
│ └── external
│ ├── BUILD
│ ├── emscripten-toolchain.BUILD
│ └── envoy-wasm-api.BUILD
├── filter.cc
├── filter.proto
├── runtime-config.json
└── toolchain
├── BUILD
├── cc_toolchain_config.bzl
├── common.sh
├── emar.sh
└── emcc.sh
▶ code .
使用你喜歡的IDE載入工程,這裡以
Visual Stiudio Code為例。修改
filter.cc
中的
onResponseHeaders
方法,示意如下。
FilterHeadersStatus AddHeaderContext::onResponseHeaders(uint32_t) {
addResponseHeader(root_->header_name_, root_->header_value_);
addResponseHeader("你好", "六翁");
return FilterHeadersStatus::Continue;
}
代碼功能很簡單,在response header中,增加一行動态配置的config的kv資訊,再增加一行寫死資訊:
你好: 六翁
3.3 Building WASM
接下來是建構。wasme提供了兩種方式,第一種是先使用bazel建構出wasm,然後使用wasme建構oci image;第二種是一鍵傻瓜式搞定全部。我推薦方法1,因為GFW的存在,方法2會失敗。
方法1 基于 預編譯 建構
使用bazel建構wasm包。我對bazel的第一印象是太重了,因為我的第一次體驗和其官方講的fast相反(也許依然是牆的因素吧),為了這個示例我也是拼了。
▶ bazel build :filter.wasm
...
INFO: Elapsed time: 444.459s, Critical Path: 165.26s
INFO: 291 processes: 291 darwin-sandbox.
INFO: Build completed successfully, 294 total actions
這步結束後,我們有了wasm包,路徑是
bazel-bin/filter.wasm
。接下來,我們使用wasme來包這個wasm包。最終得到的是名稱為header-filter的鏡像。
▶ WASM_HUB_USER=feuyeux
▶ wasme build precompiled bazel-bin/filter.wasm -t webassemblyhub.io/$WASM_HUB_USER/header-filter:v0.2
方法2 基于 cpp
如下一行指令可以直接從c++工程進行建構出oci image。
▶ WASM_HUB_USER=feuyeux
▶ wasme build cpp -t webassemblyhub.io/$WASM_HUB_USER/header-filter:v0.2 .
wasme首先會下載下傳一個名稱為
ee-builder
的鏡像,這個鏡像用于bazel建構,尺寸
2.12GB
,有點猛。
Unable to find image 'quay.io/solo-io/ee-builder:0.0.19' locally
0.0.19: Pulling from solo-io/ee-builder
...
Status: Downloaded newer image for quay.io/solo-io/ee-builder:0.0.19
▶ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
quay.io/solo-io/ee-builder 0.0.19 05c33d54ed1c 11 days ago 2.12GB
然後和方法1類似,使用
bazel build :filter.wasm
指令開始建構wasm。很不幸,我沒有看到這步成功後是什麼樣。🤷♂️
Building with bazel...running bazel build :filter.wasm
Extracting Bazel installation...
Starting local Bazel server and connecting to it...
Loading:
...
Analyzing: target //:filter.wasm (15 packages loaded, 23 targets configured)
3.4 Pushing WASM
oci image包建構完畢後,我們将其釋出到webassemblyhub.io,以便于envoy加載。請先新增賬號。
▶ WASM_HUB_USER=feuyeux
▶ wasme login -u $WASM_HUB_USER
Enter password: *********
INFO[0006] Successfully logged in as feuyeux (Lu Han)
INFO[0006] stored credentials in /Users/han/.wasme/credentials.json
▶ wasme push webassemblyhub.io/$WASM_HUB_USER/header-filter:v0.2
▶ wasme list --search $WASM_HUB_USER
NAME TAG SIZE SHA UPDATED
webassemblyhub.io/feuyeux/header-filter v0.1 1.0 MB 16bb8169 31 Mar 20 07:00 UTC
webassemblyhub.io/feuyeux/header-filter v0.2 1.0 MB cf0602e9 31 Mar 20 10:54 UTC
推送完畢後,浏覽器打開
https://webassemblyhub.io/repositories/73/header-filter檢驗下是否成功釋出。
3.5 Testing Bookinfo App
接下來,我們在
istio的bookinfo示例上驗證我們的wasm module。
示例環境準備
- 啟動minikube環境,如果需要搭建,推薦看這篇: Minikube - Kubernetes本地實驗環境 ,省去牆之痛。
- 進入istio目錄(這裡下載下傳: istio 1.5.1 并解壓),執行安裝和自動注入指令。
- 釋出bookinfo示例的各種資源。
- 檢查資源就緒情況,一定要确認
READY= 2/2
STATUS=Running
# 啟動minikube環境
▶ minikube start --registry-mirror https://fwu2jk9e.mirror.aliyuncs.com --memory 4096
# 使用demo profile安裝istio
▶ cd shop/istio-1.5.1
▶ istioctl manifest apply --set profile=demo
# 在default namespace啟用sidecar自動注入
▶ kubectl label namespace default istio-injection=enabled --overwrite
# 釋出bookinfo
▶ kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml
# re run until all pods running and 2/2
▶ kubectl get po
NAME READY STATUS RESTARTS AGE
details-v1-78d78fbddf-xjqwn 2/2 Running 0 58s
productpage-v1-85b9bf9cd7-sskwc 2/2 Running 0 57s
ratings-v1-6c9dbf6b45-7c2w4 2/2 Running 0 57s
reviews-v1-564b97f875-7nfg6 2/2 Running 0 57s
reviews-v2-568c7c9d8f-4n4ch 2/2 Running 0 57s
reviews-v3-67b4988599-zpc9j 2/2 Running 0 57s
測試
productpage
—>
details
為了比對,在熱部署我們的wasm module之前,先看看
productpage
details
這條鍊路上,從
productpage
的sidecar請求
details
時,收到的header資訊:
▶ kubectl exec -ti deploy/productpage-v1 -c istio-proxy -- curl -v http://details:9080/details/123
...
< HTTP/1.1 200 OK
< content-type: application/json
< server: istio-envoy
< date: Tue, 31 Mar 2020 10:10:07 GMT
< content-length: 180
< x-envoy-upstream-service-time: 31
< x-envoy-peer-metadata: ..
< x-envoy-peer-metadata-id: sidecar~172.17.0.15~details-v1-78d78fbddf-xjqwn.default~default.svc.cluster.local
< x-envoy-decorator-operation: details.default.svc.cluster.local:9080/*
<
* Connection #0 to host details left intact
{"id":123,"author":"William Shakespeare","year":1595,"type":"paperback","pages":200,"publisher":"PublisherA","language":"English","ISBN-10":"1234567890","ISBN-13":"123-1234567890"}
3.6 Deploying the filter
現在可以釋出我們的wasm module了。如下指令動态配置了一個config,用于驗證代碼行
addResponseHeader(root_->header_name_, root_->header_value_)
是否生效。
▶ wasme deploy istio webassemblyhub.io/feuyeux/header-filter:v0.2 \
--id=header-filter \
--config '{"name":"hello","value":"world"}'
再次測試
productpage
details
,我們可以看到預期的兩行header資訊。大功告成。
▶ kubectl exec -ti deploy/productpage-v1 -c istio-proxy -- curl -v http://details:9080/details/123
...
< hello: world
< 你好: 六翁
< x-envoy-peer-metadata: ...
...
{"id":123,"author":"William Shakespeare","year":1595,"type":"paperback","pages":200,"publisher":"PublisherA","language":"English","ISBN-10":"1234567890","ISBN-13":"123-1234567890"}
如果遇到如下問題 請稍後嘗試 因為此時envoy proxy 未就緒
Unable to connect to the server: http2: server sent GOAWAY and closed the connection; LastStreamID=81, ErrCode=NO_ERROR, debug=""
3.7 cleanup
▶ wasme undeploy istio --id header-filter --namespace default
▶ kubectl delete -n default -f samples/bookinfo/platform/kube/bookinfo.yaml
▶ kubectl get po -A
到此,示例實操結束。期待rust版的release,期待wasm更廣泛的使用場景。