基於 gRPC 和 Istio 的無 Sidecar 代理的服務網格

原文:https://istio.io/latest/blog/2021/proxyless-grpc/,作者 Steven Landow(Google),譯者宋淨超。

譯者注:本文譯自 Istio 官方博客,博客原標題 gRPC Proxyless Service Mesh[1],其實是 Istio 1.11 版本中支持的實驗特性,可以直接將 gRPC 服務添加到 Istio 中,而不需要再向 Pod 中注入 Envoy 代理。本文中還給出了一個 Demo 性能測試數據,這種做法可以極大的提升應用性能,降低網絡延遲。

Istio 使用一組發現 API(統稱爲 xDS API[2] 來動態配置其 Envoy sidecar 代理。這些 API 的目標是成爲一個 通用的數據平面 API[3]。gRPC 項目對 xDS API 有很好的支持,也就是說你可以管理 gRPC 工作負載,而不需要同時部署 Envoy sidecar。你可以在 Megan Yahya 的 KubeCon EU 2021 演講 [4] 中瞭解更多關於該集成的信息。關於 gRPC 支持的最新情況,可以在他們的 提案 [5] 中找到,還有實現狀態。

Istio 1.11 增加了實驗性支持,可以直接將 gRPC 服務添加到網格中。我們支持基本的服務發現,一些基於 VirtualService 的流量策略,以及雙向 TLS。

支持的功能

與 Envoy 相比,目前 gRPC 內的 xDS API 的實現在某些方面是有限的。以下功能應該可以使用,儘管這不是一個詳盡的列表,其他功能可能部分可用。

• 基本的服務發現。你的 gRPC 服務可以接觸到在網格中註冊的其他 pod 和虛擬機。•DestinationRule•Sutset:你的 gRPC 服務可以根據標籤選擇器將流量分割到不同的實例組。• 目前唯一支持的 Istio loadBalancer 是 ROUND_ROBINconsistentHash 將在未來的 Istio 版本中加入(支持 gRPC)。•tls 設置被限制爲 DISABLE 或 ISTIO_MUTUAL。其他模式將被視爲 DISABLE。•VirtualService•Header 匹配和 URI 匹配的格式爲 /ServiceName/RPCName。• 覆蓋目標主機和子集。• 加權的流量轉移。•PeerAuthentication• 只支持 DISABLE 和 STRICT。其他模式將被視爲 DISABLE。• 在未來的版本中可能會有對 auto-mTLS 的支持。

其他功能包括故障、重試、超時、鏡像和重寫規則,可能會在未來的版本中支持。其中一些功能正等待在 gRPC 中實現,而其他功能則需要在 Istio 中支持。gRPC 中 xDS 功能的狀態可以在這裏 [6] 找到。Istio 的支持狀況將存在於未來的官方文檔中。

這個功能是實驗性的 [7]。標準的 Istio 功能將隨着時間的推移和整體設計的改進而得到支持。

架構概述

gRPC 服務如何與 istiod 通信的示意圖

gRPC 服務如何與 istiod 通信的示意圖

雖然不使用 proxy 進行數據面通信,但它仍然需要一個 agent 來進行初始化和與控制面的通信。首先,agent 在啓動時生成一個引導文件 [8],與爲 Envoy 生成引導文件的方式相同。這告訴 gRPC 庫如何連接到 istiod,在哪裏可以找到數據面通信的證書,以及向控制面發送什麼元數據。接下來,agent 作爲一個 xDS proxy,代表應用程序與 istiod 進行連接和認證。最後,agent 獲取並輪換數據平面通信中使用的證書。

對應用程序代碼的修改

本節介紹了 gRPC 在 Go 中的 XDS 支持。其他語言也有類似的 API。

爲了啓用 gRPC 中的 xDS 功能,你的應用程序必須做一些必要的修改。你的 gRPC 版本應該至少是 1.39.0。

客戶端

下面的導入將在 gRPC 中註冊 xDS 解析器和平衡器。它應該被添加到你的主包或調用 grpc.Dial 的同一個包中。

import _ "google.golang.org/grpc/xds"

當創建一個 gRPC 連接時,URL 必須使用 xds:/// scheme。

conn, err := grpc.DialContext(ctx, "xds:///foo.ns.svc.cluster.local:7070")

此外,爲了支持(m)TLS,必須向 DialContext 傳遞一個特殊的 TransportCredentials 選項。FallbackCreds 允許我們在 istiod 不發送安全配置時成功。

import "google.golang.org/grpc/credentials/xds"
...
creds, err := xds.NewClientCredentials(xds.ClientOptions{
FallbackCreds: insecure.NewCredentials()
})
// handle err
conn, err := grpc.DialContext(
ctx,
"xds:///foo.ns.svc.cluster.local:7070",
grpc.WithTransportCredentials(creds),
)

服務端

爲了支持服務器端的配置,如 mTLS,必須做一些修改。

首先,我們使用一個特殊的構造函數來創建 GRPCServer

import "google.golang.org/grpc/xds"
...
server = xds.NewGRPCServer()
RegisterFooServer(server, &fooServerImpl)

如果你的 protoc 生成的 Go 代碼已經過期,你可能需要重新生成,以便與 xDS 服務器兼容。你生成的 RegisterFooServer 函數應該像下面這樣。

func RegisterFooServer(s grpc.ServiceRegistrar, srv FooServer) {
s.RegisterService(&FooServer_ServiceDesc, srv)
}

最後,與客戶端的變化一樣,我們必須啓用安全支持。

creds, err := xds.NewServerCredentials(xdscreds.ServerOptions{FallbackCreds: insecure.NewCredentials()})
// handle err
server = xds.NewGRPCServer(grpc.Creds(creds))

在你的 Kubernetes 部署中

假設你的應用代碼是兼容的,Pod 只需要註釋 inject.istio.io/templates:grpc-agent。這增加了一個運行上述代理的 sidecar 容器,以及一些環境變量,gRPC 使用這些變量來尋找引導文件並啓用某些功能。

對於 gRPC 服務端,你的 Pod 也應該用 proxy.istio.io/config: '{"holdApplicationUntilProxyStarts": true}' 來註釋,以確保在你的 gRPC 服務端初始化之前,代理中的 xDS 代理和引導文件已經準備就緒。

例子

在本指南中,你將部署 echo,一個已經支持服務器端和客戶端無代理的 gRPC 的應用。通過這個應用程序,你可以嘗試一些支持的流量策略,啓用 mTLS。

先決條件

本指南要求在進行之前安裝 [9] Istio(1.11+)控制平面。

部署應用程序

創建一個支持注入的命名空間 echo-grpc。接下來部署兩個 echo 應用程序的實例以及服務。

$ kubectl create namespace echo-grpc
$ kubectl label namespace echo-grpc istio-injection=enabled
$ kubectl -n echo-grpc apply -f samples/grpc-echo/grpc-echo.yaml

確保兩個 Pod 正在運行。

$ kubectl -n echo-grpc get pods
NAME                       READY   STATUS    RESTARTS   AGE
echo-v1-69d6d96cb7-gpcpd   2/2     Running   0          58s
echo-v2-5c6cbf6dc7-dfhcb   2/2     Running   0          58s

測試 gRPC 解析器

首先,將 17171 端口轉發到其中一個 Pod 上。這個端口是一個非 xDS 支持的 gRPC 服務端,允許從端口轉發的 Pod 發出請求。

$ kubectl -n echo-grpc port-forward $(kubectl -n echo-grpc get pods -l version=v1 -ojsonpath='{.items[0].metadata.name}') 17171 &

接下來,我們可以發送一批 5 個請求。

$ grpcurl -plaintext -d '{"url": "xds:///echo.echo-grpc.svc.cluster.local:7070", "count": 5}' :17171 proto.EchoTestService/ForwardEcho | jq -r '.output | join("")'  | grep Hostname
Handling connection for 17171
[0 body] Hostname=echo-v1-7cf5b76586-bgn6t
[1 body] Hostname=echo-v2-cf97bd94d-qf628
[2 body] Hostname=echo-v1-7cf5b76586-bgn6t
[3 body] Hostname=echo-v2-cf97bd94d-qf628
[4 body] Hostname=echo-v1-7cf5b76586-bgn6t

你也可以使用類似 Kubernetes 名稱解析的短名稱。

$ grpcurl -plaintext -d '{"url": "xds:///echo:7070"}' :17171 proto.EchoTestService/ForwardEcho | jq -r '.output | join
("")'  | grep Hostname
[0 body] Hostname=echo-v1-7cf5b76586-ltr8q
$ grpcurl -plaintext -d '{"url": "xds:///echo.echo-grpc:7070"}' :17171 proto.EchoTestService/ForwardEcho | jq -r
'.output | join("")'  | grep Hostname
[0 body] Hostname=echo-v1-7cf5b76586-ltr8q
$ grpcurl -plaintext -d '{"url": "xds:///echo.echo-grpc.svc:7070"}' :17171 proto.EchoTestService/ForwardEcho | jq -r
'.output | join("")'  | grep Hostname
[0 body] Hostname=echo-v2-cf97bd94d-jt5mf

用目的地規則創建子集

首先,爲每個版本的工作負載創建一個子集。

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: echo-versions
  namespace: echo-grpc
spec:
  host: echo.echo-grpc.svc.cluster.local
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
EOF

流量轉移

使用上面定義的子集,你可以把 80% 的流量發送到一個特定的版本。

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: echo-weights
  namespace: echo-grpc
spec:
  hosts:
  - echo.echo-grpc.svc.cluster.local
  http:
  - route:
    - destination:
        host: echo.echo-grpc.svc.cluster.local
        subset: v1
      weight: 20
    - destination:
        host: echo.echo-grpc.svc.cluster.local
        subset: v2
      weight: 80
EOF

現在,發送一組 10 個請求。

grpcurl -plaintext -d '{"url": "xds:///echo.echo-grpc.svc.cluster.local:7070", "count": 10}' :17171 proto.EchoTestService/ForwardEcho | jq -r '.output | join("")'  | grep ServiceVersion

響應應主要包含 v2 響應。

[0 body] ServiceVersion=v2
[1 body] ServiceVersion=v2
[2 body] ServiceVersion=v1
[3 body] ServiceVersion=v2
[4 body] ServiceVersion=v1
[5 body] ServiceVersion=v2
[6 body] ServiceVersion=v2
[7 body] ServiceVersion=v2
[8 body] ServiceVersion=v2
[9 body] ServiceVersion=v2

啓用 mTLS

由於在 gRPC 中啓用安全所需的應用程序本身的變化,Istio 的自動檢測 mTLS 支持的傳統方法是不可靠的。出於這個原因,初始版本需要在客戶端和服務端上明確啓用 mTLS。

要啓用客戶端的 mTLS,請應用帶有 tls 設置的 DestinationRule

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: echo-mtls
  namespace: echo-grpc
spec:
  host: echo.echo-grpc.svc.cluster.local
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
EOF

現在,試圖調用尚未配置 mTLS 的服務器將會失敗。

$ grpcurl -plaintext -d '{"url": "xds:///echo.echo-grpc.svc.cluster.local:7070"}' :17171 proto.EchoTestService/ForwardEcho | jq -r '.output | join("")'
Handling connection for 17171
ERROR:
Code: Unknown
Message: 1/1 requests had errors; first error: rpc error: code = Unavailable desc = all SubConns are in TransientFailure

爲了啓用服務器端的 mTLS,應用一個 PeerAuthentication

以下策略對整個命名空間強制採用 STRICT mTLS。

$ cat <<EOF | kubectl apply -f -
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: echo-mtls
  namespace: echo-grpc
spec:
  mtls:
    mode: STRICT
EOF

應用該政策後,請求將開始成功。

$ grpcurl -plaintext -d '{"url": "xds:///echo.echo-grpc.svc.cluster.local:7070"}' :17171 proto.EchoTestService/ForwardEcho | jq -r '.output | join("")'
Handling connection for 17171
[0] grpcecho.Echo(&{xds:///echo.echo-grpc.svc.cluster.local:7070 map[] 0  5s false })
[0 body] x-request-id=0
[0 body] Host=echo.echo-grpc.svc.cluster.local:7070
[0 body] content-type=application/grpc
[0 body] user-agent=grpc-go/1.39.1
[0 body] StatusCode=200
[0 body] ServiceVersion=v1
[0 body] ServicePort=17070
[0 body] Cluster=
[0 body] IP=10.68.1.18
[0 body] IstioVersion=
[0 body] Echo=
[0 body] Hostname=echo-v1-7cf5b76586-z5p8l

限制條件

最初的版本有幾個限制,可能會在未來的版本中修復。

• 不支持自動 mTLS,也不支持許可模式。相反,我們需要在服務器上使用 STRICT,在客戶端使用 ISTIO_MUTUAL 的明確 mTLS 配置。在遷移到 STRICT 的過程中,可以使用 Envoy。•grpc.Serve(listener) 或 grpc.Dial("xds://...") 在 bootstrap 被寫入或 xDS 代理準備好之前被調用會導致失敗。 holdApplicationUntilProxyStarts 可以用來解決這個問題,或者應用程序可以對這些失敗更加穩健。• 如果支持 xDS 的 gRPC 服務器使用 mTLS,那麼你將需要確保你的健康檢查可以繞過這個問題。要麼使用一個單獨的端口,要麼你的健康檢查客戶端需要一種方法來獲得適當的客戶端證書。•gRPC 中 xDS 的實現與 Envoy 不一致。某些行爲可能不同,某些功能可能缺失。gRPC 的功能狀態 [10] 提供了更多細節。請確保測試任何 Istio 配置是否真正適用於你的無代理的 gRPC 應用程序。

性能

實驗設置

• 使用 Fortio,一個基於 Go 的負載測試應用程序 • 稍作修改,以支持 gRPC 的 XDS 功能 (PR)• 資源:•GKE 1.20 集羣有 3 個 e2-standard-16 節點(每個節點有 16 個 CPU+64GB 內存)•Fortio 客戶端和服務器應用程序:1.5 vCPU,1000 MiB 內存 •Sidecar(istio-agent 和可能的 Envoy 代理):1 vCPU,512 MiB 內存 • 測試的工作負載類型:• 基線:常規的 gRPC,沒有使用 Envoy 代理或 Proxyless xDS•Envoy:標準的 istio-agent + Envoy proxy sidecar• 無代理:使用 xDS gRPC 服務器實現的 gRPC 和客戶端的 xds:/// 解析器。• 通過 PeerAuthentication 和 DestinationRule 啓用 / 停用 mTLS

延遲

P50 延遲對比圖

P99 延遲對比圖

在使用無代理的 gRPC 解析器時,延遲會有微小的增加。與 Envoy 相比,這是一個巨大的改進,仍然可以實現先進的流量管理功能和 mTLS。

istio-proxy 容器的資源使用情況

儘管我們仍然需要一個代理,但代理使用的內存不到完整 vCPU 的 0.1%,而且只有 25 MiB,這還不到運行 Envoy 所需內存的一半。

這些指標不包括應用容器中 gRPC 的額外資源使用量,但有助於展示 itio-agent 在此模式下運行時的資源使用影響。

引用鏈接

[1] gRPC Proxyless Service Mesh: https://istio.io/latest/blog/2021/proxyless-grpc/
[2] xDS API: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration
[3] 通用的數據平面 API: https://blog.envoyproxy.io/the-universal-data-plane-api-d15cec7a?gi=64aa2eea0283
[4] Megan Yahya 的 KubeCon EU 2021 演講: https://www.youtube.com/watch?v=cGJXkZ7jiDk
[5] 提案: https://github.com/grpc/proposal/search?q=xds
[6] 在這裏: https://github.com/grpc/grpc/blob/master/doc/grpc_xds_features.md
[7] 實驗性的: https://istio.io/latest/docs/releases/feature-stages/
[8] 引導文件: https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md#xdsclient-and-bootstrap-file
[9] 安裝: https://istio.io/latest/docs/setup/install/
[10] gRPC 的功能狀態: https://github.com/grpc/grpc/blob/master/doc/grpc_xds_features.md

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/aYwo2criOotqNp8lD39QAA