gRPC 无代理服务网格
Istio 对 gRPC 无代理服务网格功能的支持介绍。
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 服务可以访问网格中注册的其他 Pod 和虚拟机。
DestinationRule
:- 子集:您的 gRPC 服务可以根据标签选择器将流量拆分到不同的实例组。
- 目前仅支持 Istio 的
loadBalancer
为ROUND_ROBIN
,consistentHash
将在 Istio 的未来版本中添加(gRPC 支持该功能)。 tls
设置限于DISABLE
或ISTIO_MUTUAL
。其他模式将被视为DISABLE
。
VirtualService
:- 以
/ServiceName/RPCName
格式进行标题匹配和 URI 匹配。 - 覆盖目标主机和子集。
- 加权流量切换。
- 以
PeerAuthentication
:- 仅支持
DISABLE
和STRICT
。其他模式将被视为DISABLE
。 - 未来版本中可能支持自动 mTLS。
- 仅支持
包括故障、重试、超时、镜像和重写规则在内的其他功能可能在未来版本中得到支持。其中一些功能正在等待 gRPC 中的实现,而另一些则需要 Istio 进行工作以支持。gRPC 中 xDS 功能的状态可以在 此处 找到。Istio 支持的状态将出现在未来的官方文档中。
架构概述
虽然这没有使用代理进行数据平面通信,但它仍然需要一个代理来进行初始化并与控制平面通信。首先,代理在启动时生成一个 引导文件,与为 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
选项传递给 DialContext
。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 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
限制
初始版本存在一些限制,这些限制可能在未来版本中得到修复
- 不支持自动 mTLS,也不支持宽松模式。相反,我们需要在服务器上使用
STRICT
和在客户端上使用ISTIO_MUTUAL
来显式配置 mTLS。在迁移到STRICT
期间可以使用 Envoy。 - 在写入引导文件或 xDS 代理准备就绪之前调用
grpc.Serve(listener)
或grpc.Dial("xds:///...")
可能会导致失败。holdApplicationUntilProxyStarts
可用于解决此问题,或者应用程序可以更稳健地应对这些故障。 - 如果启用 xDS 的 gRPC 服务器使用 mTLS,则需要确保您的健康检查可以解决此问题。要么使用单独的端口,要么您的健康检查客户端需要一种方法来获取正确的客户端证书。
- gRPC 中 xDS 的实现与 Envoy 不匹配。某些行为可能有所不同,某些功能可能缺失。有关 gRPC 功能状态的更多详细信息,请参阅 此处。确保在您的无代理 gRPC 应用程序上测试任何 Istio 配置是否实际应用。
性能
实验设置
- 使用 Fortio,一个基于 Go 的负载测试应用程序
- 稍微修改了一下,以支持 gRPC 的 XDS 功能(PR)
- 资源
- 具有 3 个
e2-standard-16
节点(每个节点 16 个 CPU + 64 GB 内存)的 GKE 1.20 集群 - Fortio 客户端和服务器应用程序:1.5 个 vCPU,1000 MiB 内存
- Sidecar(istio-agent 和可能的 Envoy 代理):1 个 vCPU,512 MiB 内存
- 具有 3 个
- 测试的工作负载类型
- 基线:没有使用 Envoy 代理或无代理 xDS 的普通 gRPC
- Envoy:标准 istio-agent + Envoy 代理 sidecar
- 无代理:使用 xDS gRPC 服务器实现和
xds:///
解析器的 gRPC - 通过
PeerAuthentication
和DestinationRule
启用/禁用 mTLS
延迟
使用无代理 gRPC 解析器时,延迟会略微增加。与 Envoy 相比,这是一个巨大的改进,它仍然允许使用高级流量管理功能和 mTLS。
istio-proxy 容器资源使用情况
客户端 mCPU | 客户端内存 (MiB ) | 服务器 mCPU | 服务器内存 (MiB ) | |
---|---|---|---|---|
Envoy 明文 | 320.44 | 66.93 | 243.78 | 64.91 |
Envoy mTLS | 340.87 | 66.76 | 309.82 | 64.82 |
无代理明文 | 0.72 | 23.54 | 0.84 | 24.31 |
无代理 mTLS | 0.73 | 25.05 | 0.78 | 25.43 |
即使我们仍然需要一个代理,代理使用的资源也不到一个完整 vCPU 的 0.1%,并且内存仅为 25 MiB,不到运行 Envoy 所需内存的一半。
这些指标不包括应用程序容器中 gRPC 的额外资源使用情况,但可以说明 istio-agent 在此模式下运行时的资源使用影响。