gRPC 无代理服务网格

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

2021 年 10 月 28 日 | 作者:Steven Landow - Google

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

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

支持的功能

与 Envoy 相比,gRPC 中 xDS API 的当前实现存在一些局限性。以下功能应可正常工作,但这并非详尽清单,其他功能可能具有部分功能

包括故障、重试、超时、镜像和重写规则在内的其他功能可能在未来版本中得到支持。其中一些功能正在等待 gRPC 中的实现,而另一些则需要 Istio 进行工作以支持。gRPC 中 xDS 功能的状态可以在 此处 找到。Istio 支持的状态将出现在未来的官方文档中。

架构概述

Diagram of how gRPC services communicate with the istiod
gRPC 服务如何与 istiod 通信的示意图

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

对应用程序代码的更改

要启用 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 选项传递给 DialContextFallbackCreds 使我们能够在 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 Deployment 中

假设您的应用程序代码兼容,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-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

使用 DestinationRule 创建子集

首先,为每个工作负载版本创建一个子集。

$ 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

$ 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

限制

初始版本存在一些限制,这些限制可能在未来版本中得到修复

性能

实验设置

延迟

p50 latency comparison chart
p50 延迟比较图表
p99 latency comparison chart
p99 延迟比较图表

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

istio-proxy 容器资源使用情况

客户端 mCPU客户端内存 (MiB)服务器 mCPU服务器内存 (MiB)
Envoy 明文320.4466.93243.7864.91
Envoy mTLS340.8766.76309.8264.82
无代理明文0.7223.540.8424.31
无代理 mTLS0.7325.050.7825.43

即使我们仍然需要一个代理,代理使用的资源也不到一个完整 vCPU 的 0.1%,并且内存仅为 25 MiB,不到运行 Envoy 所需内存的一半。

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

分享此帖子