記一次 Istio 衝刺調優

MarkZhu發表於2021-09-11

為何要調優

如果說,引入一個技術需要興趣和衝勁,那麼,讓這個技術上線需要的是堅持和執著。 Cloud Native 如是, Istio 如是。
在上線前的效能測試中,Istio 的使用提供了可觀察性、運維上的便利,同時也引入了痛苦:增加了服務響應延時。如何讓痛苦減到最低,成了當下之急。

表現:之前 9ms 的 SERVICE-A,現在要 14 ms 了。SERVICE-A 依賴於 SERVICE-B。

分析之路

腳下有兩條路:

  1. 直接調整一些認為可疑的配置,禁用一些功能。再壓測看結果。
  2. 對 sidecar 做 cpu profile,定位可疑的地方。進行相對有根據的調優

我選擇了 2。

Sidecar CPU Profile(照肺)

Istio 作為一個比較成熟的開源產品,有其官方的 benchmark 專案:

https://github.com/istio/tool...

我參考了:https://github.com/istio/tool...

安裝 perf

在 container 中執行 linux 的 perf 工具來對 sidecar 作 profile。其中有一些難點,如 Istio-proxy container 預設全檔案系統只讀,我修改了為可寫。需要以 root 身份進入 container。如果覺得麻煩,自行基於原 image 製作定製 Image 也可。具體方法不是本文重點,不說了。之後可以用包工具(如 apt)來安裝 perf了。

這是一個 istio-proxy container 配置的例子:

spec:
  containers:
  - name: istio-proxy
    image: xyz
    securityContext:
      allowPrivilegeEscalation: true
      capabilities:
        add:
        - ALL
      privileged: true
      readOnlyRootFilesystem: false
      runAsGroup: 1337
      runAsNonRoot: false
      runAsUser: 1337

執行 profile 、生成 Flame Graph

以 root 身份進入 istio-proxy container(是的,root可以省點事)

perf record -g  -F 19 -p `pgrep envoy` -o perf.data -- sleep 120
perf script --header -i perf.data > perf.stacks

perf.stacks 複製到開發機後,生成 Flame Graph。是的,需要用到一個 perl 指令碼:https://github.com/brendangre... (由我的偶像 Brendan Gregg 榮譽出品)

export FlameGraph=/xyz/FlameGraph
$FlameGraph/stackcollapse-perf.pl < perf.stacks | $FlameGraph/flamegraph.pl --hash > perf.svg

最終生成了 perf.svg

上圖只是一個 envoy worker執行緒,還有一個執行緒與之類似。所以上面的 proxy_wasm::ContextBase::onLog 使用了全程式的 14% CPU。從上圖看出,這大概是一個 Envoy 擴充套件 Filter。問題來了,這是什麼 Filter,為何有部分 stack 資訊會獲取不到(上圖中的 perf-18.map)。

Envoy Filter - wasm 的烏托邦

我知道的是,wasm 是一個 vm 引擎(類比 jvm 吧)。Envoy 支援 Native 方式實現擴充套件,也支援 wasm 方式實現擴充套件。當然了,vm 擎和 Native 相比一定有效能損耗了。

還好,某哥搜尋帶我找到這文件:

https://istio.io/v1.8/docs/op...

其中一個圖,與一段話給予了我提示:

  • baseline Client pod directly calls the server pod, no sidecars are present.
  • none_both Istio proxy with no Istio specific filters configured.
  • v2-stats-wasm_both Client and server sidecars are present with telemetry v2 v8 configured.
  • v2-stats-nullvm_both Client and server sidecars are present with telemetry v2 nullvm configured by default.
  • v2-sd-full-nullvm_both Export Stackdriver metrics, access logs and edges with telemetry v2 nullvm configured.
  • v2-sd-nologging-nullvm_both Same as above, but does not export access logs.

好地地(現在流行粵語)一個效能測試,整那麼多條線幹什麼?翻譯成接地氣的是:

  • baseline 不使用 sidecars
  • none_both 不使用 Istio 的 Filter
  • v2-stats-wasm_both 使用 wasm 實現的 filter
  • v2-stats-nullvm_both 使用 Native 實現的 Filter

這幾句話想說什麼?老外有時還是比較含蓄的,接地氣地說,就是我們想推廣使用 wasm 技術,所以預設就使用這個了。如果你介意那 1ms 的延時,和那麼一點點CPU 。還請用回 Native 技術吧。好吧,我承認,我介意。

注:後來我發現,官方標準版本的 Istio 1.8 使用的是 Native 的 Filter。而我們的環境中是個內部定製版本,預設使用了 wasm Filter(或者也是基於安全、隔離性、可移植性大於效能的烏托邦)。所以,可能對於你來說,Native Filter 已經是預設配置。

累壞的 Worker Thread 與 袖手旁觀的 core

下面是 enovy 程式的執行緒級 top 監控。是的,pthread說了,執行緒命名,不是 Java 世界的專利。COMMAND 一列是執行緒名字。

top -p `pgrep envoy` -H -b

top - 01:13:52 up 42 days, 14:01,  0 users,  load average: 17.79, 14.09, 10.73
Threads:  28 total,   2 running,  26 sleeping,   0 stopped,   0 zombie
%Cpu(s): 42.0 us,  7.3 sy,  0.0 ni, 46.9 id,  0.0 wa,  0.0 hi,  3.7 si,  0.1 st
MiB Mem : 94629.32+total, 67159.44+free, 13834.21+used, 13635.66+buff/cache
MiB Swap:    0.000 total,    0.000 free,    0.000 used. 80094.03+avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    42 istio-p+  20   0  0.274t 221108  43012 R 60.47 0.228 174:48.28 wrk:worker_1
    41 istio-p+  20   0  0.274t 221108  43012 R 55.81 0.228 149:33.37 wrk:worker_0
    18 istio-p+  20   0  0.274t 221108  43012 S 0.332 0.228   2:22.48 envoy

同時發現, client 的併發壓力提高,並不能明顯提高這個 2 worker thread 的 envoy 的間執行緒 CPU 使用到 100%。民間一直流傳的 超執行緒 CPU core 不能達到 core * 2 效能的情況來了。怎麼辦?加 worker 試試啦。

一個字:調

Istio 透過 EnvoyFilter 可以定製 Filter,所以我這樣玩了:

kubectl apply -f - <<"EOF"
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  ...
  name: stats-filter-1.8
spec:
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_OUTBOUND
      listener:
        filterChain:
          filter:
            name: envoy.http_connection_manager
            subFilter:
              name: envoy.router
      proxy:
        proxyVersion: ^1\.8.*
    patch:
      operation: INSERT_BEFORE
      value:
        name: istio.stats
        typed_config:
          '@type': type.googleapis.com/udpa.type.v1.TypedStruct
          type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
          value:
            config:
              configuration:
                '@type': type.googleapis.com/google.protobuf.StringValue
                value: |
                  {
                  }
              root_id: stats_outbound
              vm_config:
                allow_precompiled: true
                code:
                  local:
                    inline_string: envoy.wasm.stats
                runtime: envoy.wasm.runtime.null
                vm_id: stats_outbound
...
EOF
kubectl apply -f - <<"EOF"
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: metadata-exchange-1.8
spec:
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        filterChain:
          filter:
            name: envoy.http_connection_manager
      proxy:
        proxyVersion: ^1\.8.*
    patch:
      operation: INSERT_BEFORE
      value:
        name: istio.metadata_exchange
        typed_config:
          '@type': type.googleapis.com/udpa.type.v1.TypedStruct
          type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
          value:
            config:
              configuration:
                '@type': type.googleapis.com/google.protobuf.StringValue
                value: |
                  {}
              vm_config:
                allow_precompiled: true
                code:
                  local:
                    inline_string: envoy.wasm.metadata_exchange
                runtime: envoy.wasm.runtime.null
...
EOF
注:後來我發現,官方標準版本的 Istio 1.8 使用的是 Native 的 Filter,即 envoy.wasm.runtime.null。而我們的環境中是個內部定製版本,預設使用了 wasm Filter(或者也是基於安全、隔離性、可移植性大於效能的烏托邦)。所以,上面的的最佳化,可能對於你來說,是預設配置已經完成了。即,你可以忽略……

下面是修改 envoy 的執行緒數:

kubectl edit deployments.apps my-service-deployment

spec:
  template:
    metadata:
      annotations:
        proxy.istio.io/config: 'concurrency: 4'

Sidecar CPU Profile(再照肺)

由於用 native envoy filter 代替了 wasm filter。上圖可見,stack 丟失情況沒了。實測 CPU 使用率下降約 8%,延遲減少了 1ms。

總結

與其譴責坑爹的定製版本的預設 wasm envoy filter 配置、執行緒配置,不如想想自己為何付出數天的代價,才定位到這個問題。當我們很興奮地坐上某新技術船上時,除了記得帶上救生圈,還不能忘記:你是船長,除了會駕駛,你更應該瞭解船的工作原理和維修技術,才可以應對突發,不負所托。

原文:https://blog.mygraphql.com/zh/posts/cloud/istio/istio-tunning/istio-filter-tunning-thread/