vlambda博客
学习文章列表

gRPC 无代理服务网格

本文介绍 Istio 对 gRPC 的无代理服务网格功能的支持。


Istio 使用一组发现 API(统称为 xDS API )动态配置其 Envoy sidecar 代理。这些 API 旨在成为通用数据平面 API。gRPC 项目对 xDS API 有重要的支持,这意味着您可以管理 gRPC 工作负载,而无需与它们一起部署 Envoy sidecar。您可以在Megan Yahya 的 KubeCon EU 2021 演讲中了解有关集成的更多信息 。gRPC 支持的最新更新可以在他们的提案中找到以及实施状态。

Istio 1.11 增加了将 gRPC 服务直接添加到网格的实验性支持。我们支持基本的服务发现、一些基于 VirtualService 的流量策略和双向 TLS。

01/ 支持的功能


gRPC是一种服务通信协议,建立在HTTP/2之上。与基于资源的 REST over HTTP/1 不同,gRPC 基于服务定义。您以称为协议缓冲区(“proto”)的格式指定服务定义,可以将其序列化为小型二进制格式以进行传输。


与 Envoy 相比,gRPC 中 xDS API 的当前实现在某些领域受到限制。以下功能应该可以使用,尽管这不是一个详尽的列表,并且其他功能可能具有部分功能:


  • 基本服务发现。您的 gRPC 服务可以访问网格中注册的其他 pod 和虚拟机。
  • DestinationRule
    • 子集:您的 gRPC 服务可以根据标签选择器将流量拆分到不同的实例组。
    • 当前唯一loadBalancer支持的 Istio 是ROUND_ROBIN,consistentHash将在 Istio 的未来版本中添加(它由 gRPC 支持)。
    • tls设置仅限于DISABLE或ISTIO_MUTUAL。其他模式将被视为DISABLE.
  • VirtualService
    • 标头匹配和 URI 格式匹配/ServiceName/RPCName。
    • 覆盖目标主机和子集。
    • 加权流量转移。
  • PeerAuthentication
    • 仅支持DISABLE和STRICT。其他模式将被视为DISABLE.
    • 未来版本中可能会支持自动 mTLS。
未来版本可能支持其他功能,包括故障、重试、超时、镜像和重写规则。其中一些功能正在等待 gRPC 中的实现,而其他功能则需要在 Istio 中进行支持。gRPC 中 xDS 功能的状态可以在此处找到。Istio 的支持状态将在未来的官方文档中存在。

02/ 架构概述


gRPC 服务如何与 istiod 通信的示意图


尽管这不使用代理进行数据平面通信,但它仍然需要代理来初始化和与控制平面通信。首先,代理在启动时生成一个引导文件 ,就像它为 Envoy 生成引导程序一样。这告诉gRPC库如何连接istiod,它可以在哪里找到数据平面通信的证书,以及要发送到控制平面的元数据。接下来,代理充当xDS代理,istiod代表应用程序连接和验证。最后,代理获取并轮换数据平面流量中使用的证书。


03/ 更改应用程序代码


要在 gRPC 中启用 xDS 功能,您的应用程序必须进行一些必要的更改。您的 gRPC 版本至少应为1.39.0.


在客户端


以下副作用导入将在 gRPC 中注册 xDS 解析器和平衡器。它应该添加到您的 main包中或在同一个包中调用grpc.Dial.

import _ "google.golang.org/grpc/xds"
创建 gRPC 连接时,URL 必须使用该xds:///方案。
conn, err := grpc.DialContext(ctx, "xds:///foo.ns.svc.cluster.local:7070")
此外,对于 (m)TLS 支持,必须将一个特殊TransportCredentials选项传递给DialContext. 允许我们在FallbackCredsistiod 不发送安全配置时成功。
import "google.golang.org/grpc/credentials/xds"
...
creds, err := xds.NewClientCredentials(xds.ClientOptions{FallbackCreds: insecure.NewCredentials()})// handle errconn, 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 errserver = xds.NewGRPCServer(grpc.Creds(creds))


04/ 在您的 Kubernetes 部署中


假设你的应用程序代码是兼容的,Pod 只需要注解 inject.istio.io/templates: grpc-agent。这会添加一个运行上述代理的 sidecar 容器,以及 gRPC 用于查找引导文件和启用某些功能的一些环境变量。

对于 gRPC 服务器,您的 Pod 也应该被注释 proxy.istio.io/config: '{"holdApplicationUntilProxyStarts": true}' 以确保在初始化 gRPC 服务器之前代理内 xDS 代理和引导文件已准备好。


例子


在本指南中,您将部署echo一个已经支持服务器端和客户端无代理 gRPC 的应用程序。使用此应用程序,您可以尝试一些支持 mTLS 的流量策略。


先决条件


本指南要求在继续之前安装Istio (1.11+) 控制平面。


部署应用程序


创建一个启用注入的命名空间echo-grpc。接下来部署应用程序和服务的两个实例echo。

kubectl create namespace echo-grpckubectl label namespace echo-grpc istio-injection=enabledkubectl -n echo-grpc apply -f samples/grpc-echo/grpc-echo.yaml
确保两个 pod 正在运行:
kubectl -n echo-grpc get pods

测试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

您还可以对短名称使用类似 Kubernetes 的名称解析:

grpcurl -plaintext -d '{"url": "xds:///echo:7070"}' :17171 proto.EchoTestService/ForwardEcho | jq -r '.output | join

使用目标规则创建子集

首先,为每个版本的工作负载创建一个子集。
cat <<EOF | kubectl apply -f -apiVersion: networking.istio.io/v1alpha3kind: DestinationRulemetadata: name: echo-versions namespace: echo-grpcspec: host: echo.echo-grpc.svc.cluster.local subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2EOF

流量转移

使用上面定义的子集,您可以将 80% 的流量发送到特定版本:

cat <<EOF | kubectl apply -f -apiVersion: networking.istio.io/v1beta1kind: VirtualServicemetadata: name: echo-weights namespace: echo-grpcspec: 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: 80EOF

现在,发送一组 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,请应用DestinationRulewithtls设置:
cat <<EOF | kubectl apply -f -apiVersion: networking.istio.io/v1alpha3kind: DestinationRulemetadata: name: echo-mtls namespace: echo-grpcspec: host: echo.echo-grpc.svc.cluster.local trafficPolicy: tls: mode: ISTIO_MUTUALEOF

现在尝试调用尚未为 mTLS 配置的服务器将失败。

grpcurl -plaintext -d '{"url": "xds:///echo.echo-grpc.svc.cluster.local:7070"}' :17171 proto.EchoTestService/ForwardEcho | jq -r '.output | join("")'Handling connection for 17171ERROR:Code: UnknownMessage: 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/v1beta1kind: PeerAuthenticationmetadata: name: echo-mtls namespace: echo-grpcspec: mtls: mode: STRICTEOF

应用策略后,请求将开始成功。

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

限制


初始版本有几个限制,可能会在未来的版本中修复:
  • 不支持 Auto-mTLS,也不支持许可模式。STRICT相反,我们需要在服务器和客户端上进行显式 mTLS 配置 ISTIO_MUTUAL。在迁移到STRICT.
  • grpc.Serve(listener)或grpc.Dial("xds:///...")在编写引导程序或 xDS 代理准备好之前调用可能会导致失败。holdApplicationUntilProxyStarts可以用来解决这个问题,或者应用程序可以更健壮地应对这些故障。
  • 如果启用 xDS 的 gRPC 服务器使用 mTLS,那么您需要确保您的运行状况检查可以解决此问题。应该使用单独的端口,或者您的健康检查客户端需要一种方法来获取正确的客户端证书。
  • gRPC 中 xDS 的实现与 Envoy 不匹配。某些行为可能会有所不同,并且某些功能可能会丢失。gRPC的功能状态提供了更多详细信息。确保测试任何 Istio 配置实际上适用于您的无代理 gRPC 应用程序。

05/ 性能表现

实验设置

  • 使用基于 Go 的负载测试应用程序 Fortio
    • 稍作修改,以支持 gRPC 的 XDS 功能 (PR)
  • 资源:
    • 具有 3 个节点的 GKE 1.20 集群e2-standard-16(每个节点 16 个 CPU + 64 GB 内存)
    • Fortio 客户端和服务器应用程序:1.5 个 vCPU,1000 MiB 内存
    • Sidecar(istio-agent 和可能的 Envoy 代理):1 个 vCPU,512 MiB 内存
  • 测试的工作负载类型:
    • 基线:没有使用 Envoy 代理或无代理 xDS 的常规 gRPC
    • Envoy:标准 istio-agent + Envoy 代理 sidecar
    • 无代理:gRPCxds:///在客户端使用 xDS gRPC 服务器实现和解析器
    • PeerAuthentication通过和启用/禁用 mTLSDestinationRule

延迟表现

gRPC 无代理服务网格


gRPC 无代理服务网格


使用无代理 gRPC 解析器时延迟略有增加。与 Envoy 相比,这是一个巨大的改进,仍然允许高级流量管理功能和 mTLS。


istio-proxy 容器资源使用情况

尽管我们仍然需要一个代理,但该代理只使用了不到 0.1% 的完整 vCPU,并且只有 25 MiB 的内存,这还不到运行 Envoy 所需内存的一半。

这些指标不包括 gRPC 在应用程序容器中的额外资源使用情况,但用于展示在此模式下运行时 istio-agent 对资源使用的影响。

参考链接:
[1] https://istio.io/latest/blog/2021/proxyless-grpc/