Istio为无代理的gRPC 应用提供了一种额外的部署模型,在该模型中,gRPC 应用可以不附加Sidecar代理。Istio控制面组件使用开源的xDS API直接配置gRPC应用, 也就是说,您无需使用Sidecar,就可以使用流量路由、mTLS认证等功能管理工作负载。本文介绍如何在gRPC服务中使用无代理服务网格功能。
前提条件
已创建ASM实例,且ASM实例为v1.12.4.2-gc5962641-aliyun或以上版本。具体操作,请参见创建ASM实例。
已添加集群到ASM实例。具体操作,请参见添加集群到ASM实例。
背景信息
无代理服务网格(Proxyless)功能不使用Proxy进行数据面通信,但仍然需要一个Agent来进行初始化和与控制面的通信。Agent具有以下功能:
Agent会获取并轮换数据平面通信中使用的证书。
Agent作为一个xDS Proxy,会代表应用程序与Istiod进行连接和认证。
Agent在启动时生成一个引导文件,与为Envoy生成引导文件的方式相同,用于告诉gRPC库如何连接到Istiod,在哪里可以找到数据面通信的证书,以及向控制面发送什么数据。
以下是一些业界框架和平台,它们与Istio的proxyless模式兼容或支持:
Apache Dubbo: Dubbo是一个高性能的Java RPC框架,已经开展了与Istio的集成,支持在无代理模式下进行服务调用。具体参考:https://cn.dubbo.apache.org/en/overview/tasks/mesh/proxyless/
gRPC框架:gRPC项目对 xDS API 提供了大量支持。 具体可以参考社区文档: https://istio.io/latest/blog/2021/proxyless-grpc/
Kitex Proxyless:Kitex 服务能够不借助 envoy sidecar 直接与服务网格控制面组件进行交互,基于 xDS 协议动态获取控制面下发的服务治理规则,并转换为 Kitex 对应规则来实现一些服务治理功能。具体参考:https://www.cloudwego.io/blog/2022/12/20/kitex-proxyless-practicetraffic-lane-implementation-with-istio-and-opentelemetry/
注意事项
当前Proxyless模式存在以下限制:
不支持PERMISSIVE(明文或双向TLS认证)。如果您需要在服务端上使用STRICT,您需要在客户端使用ISTIO_MUTUAL的明确mTLS配置。
在bootstrap或xDS配置生效之前,通过
grpc.Serve(listener)
或grpc.Dial("xds://...")
的请求可能会调用失败。您可以使用holdApplicationUntilProxyStarts来避免这个问题。gRPC中xDS API的实现可能与Envoy不兼容,存在功能缺失或配置不生效的情况。请检查Istio配置是真正适用于您的无代理的gRPC应用程序。更多信息,请参见xDS Features in gRPC。
Proxyless模式支持的功能
与Envoy相比,目前gRPC内的xDS API仅支持部分功能。以下为支持的功能:
功能归类 | 支持的功能 |
服务发现 | gRPC服务可以发现在网格中注册的其他Pod。 |
目标规则 |
|
虚拟服务 |
|
对等身份认证 | 支持DISABLE和STRICT的mTLS模式。 |
使用无代理服务网格功能
本文以已支持Proxyless模式的gRPC应用Echo为例,在不使用Sidecar代理情况下,介绍Echo应用如何使用流量路由和mTLS认证功能。
部署示例应用
在echo-grpc应用的Pod中添加
inject.istio.io/templates:grpc-agent
注解。如果您的应用代码支持Proxyless模式,您需要在应用Pod中添加
inject.istio.io/templates:grpc-agen
t注解,用于开启Proxyless模式。同时需要为gRPC服务端添加proxy.istio.io/config: '{"holdApplicationUntilProxyStarts": true}'
注解(ASM已默认为您开启),以确保在gRPC服务端初始化之前,代理中的xDS代理和引导文件已经准备就绪。以下为echo-grpc应用添加注解的部分内容,关于echo-grpc应用的详细YAML内容,请参见grpc-echo。
template: metadata: annotations: inject.istio.io/templates: grpc-agent proxy.istio.io/config: '{"holdApplicationUntilProxyStarts": true}' labels: app: echo version: v1
执行以下命令,创建echo-grpc命名空间。
kubectl create namespace echo-grpc
执行以下命令,为echo-grpc命名空间添加标签,从而注入Sidecar。
Proxyless模式不会使用到Sidecar,但是需要使用Agent与控制面进行通信,因此仍然需要为命名空间注入Sidecar。
kubectl label namespace echo-grpc istio-injection=enabled
执行以下命令,部署echo-grpc应用。
kubectl -n echo-grpc apply -f https://alibabacloudservicemesh.oss-cn-beijing.aliyuncs.com/asm-grpc-proxyless/grpc-echo.yaml
验证应用是否部署成功。
执行以下命令,暴露17171端口。
kubectl -n echo-grpc port-forward $(kubectl -n echo-grpc get pods -l version=v1 -ojsonpath='{.items[0].metadata.name}') 17171 &
执行以下命令,访问echo-grpc应用。
grpcurl -plaintext -d '{"url": "xds:///echo.echo-grpc.svc.cluster.local:7070", "count": 5}' :17171 proto.EchoTestService/ForwardEcho | jq -r '.output | join("")' | grep Hostname
预期输出:
[0 body] Hostname=echo-v1-f76996c45-h7plh [1 body] Hostname=echo-v1-f76996c45-h7plh [2 body] Hostname=echo-v2-7d76b7969-ltlkp [3 body] Hostname=echo-v2-7d76b7969-ltlkp [4 body] Hostname=echo-v1-f76996c45-h7plh
场景一:使用流量路由功能
创建目标规则。
使用目标规则为每个版本的工作负载创建一个子集。
登录ASM控制台,在左侧导航栏,选择 。
在网格管理页面,单击目标实例名称,然后在左侧导航栏,选择 ,然后单击创建。
在创建页面,设置命名空间为echo-grpc,输入名称,设置服务名称为echo.echo-grpc.svc.cluster.local。
单击服务版本(子集),单击添加服务版本(子集),在版本1区域,设置版本名称为v1,单击添加标签,设置标签名为version,标签值为v1。
单击添加服务版本(子集),在版本2区域,设置版本名称为v2,单击添加标签,设置标签名为version,标签值为v2。
单击创建。
创建虚拟服务。
使用虚拟服务将80%的流量路由到v2版本,20%流量路由到v1版本。
登录ASM控制台,在左侧导航栏,选择 。
在网格管理页面,单击目标实例名称,然后在左侧导航栏,选择 ,然后单击创建。
在创建页面,设置命名空间为echo-grpc,输入名称。
单击HTTP路由,单击添加路由,输入路由名称,单击添加路由目的地,设置服务名称为echo.echo-grpc.svc.cluster.local,版本为v1,权重为20。
单击添加路由目的地,设置服务名称为echo.echo-grpc.svc.cluster.local,版本为v2,权重为80。
单击创建。
执行以下命令,向echo-grpc应用发送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
预期输出:
[0 body] ServiveVersion=v2 [0 body] ServiveVersion=v1 [0 body] ServiveVersion=v2 [0 body] ServiveVersion=v2 [0 body] ServiveVersion=v2 [0 body] ServiveVersion=v2 [0 body] ServiveVersion=v1 [0 body] ServiveVersion=v2 [0 body] ServiveVersion=v2 [0 body] ServiveVersion=v2
可以看到,向echo-grpc应用发送10个请求,8个请求路由到v2版本,2个请求路由到v1版本。说明流量路由配置成功。
场景二:使用mTLS认证
使用mTLS认证,对服务之间的通信进行双向TLS加密。
创建目标规则。
使用目标规则启用客户端的mTLS。
登录ASM控制台。
在左侧导航栏,选择 。
在网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理。
在网格详情页面左侧导航栏选择 ,然后单击创建。
在创建页面,设置命名空间为echo-grpc,输入名称,设置服务名称为echo.echo-grpc.svc.cluster.local。
单击流量策略,单击添加策略,打开客户端TLS开关,设置TLS模式为Istio双向。
单击创建。
执行以下命令,访问echo-grpc应用。
grpcurl -plaintext -d '{"url": "xds:///echo.echo-grpc.svc.cluster.local:7070"}' :17171 proto.EchoTestService/ForwardEcho | jq -r '.output | join("")'
预期输出:
ERROR: Code: Unknown Message: 1/1 requests had errors; first error: rpc error: code = Unavaliable desc = all SubConna are in TransientFailure
由于服务端未启用mTLS认证,访问echo-grpc应用失败。
创建对等身份认证。
使用对等身份认证启用服务端的mTLS。
登录ASM控制台,在左侧导航栏,选择 。
在网格管理页面,单击目标实例名称,然后在左侧导航栏,选择 ,然后单击创建双向mTLS模式。
在创建双向mTLS模式页面,设置命名空间为echo-grpc,输入名称,设置mTLS模式(命名空间级)为STRICT - 严格遵循双向TLS认证,然后单击创建。
执行以下命令,访问echo-grpc应用。
grpcurl -plaintext -d '{"url": "xds:///echo.echo-grpc.svc.cluster.local:7070"}' :17171 proto.EchoTestService/ForwardEcho | jq -r '.output | join("")'
预期输出:
[0] grpcecho.Echo(${xds:///echo.echo-grpc.svc.cluster.local:7070 map [] 0 <nil> 5s false }) [0 body] RequestHeader=x-request-id:0 [0 body] Host=echo.echo-grpc.svc.cluster.local [0 body] RequestHeader=:authorit:echo.echo-grpc.svc.cluster.local:7070 ........
可以看到,访问echo-grpc应用成功。
修改gRPC应用程序代码
如果您的gRPC服务不支持Proxyless模式,您需要修改gRPC的客户端和服务端序启用gRPC服务的xDS API功能,然后您才能在Proxyless模式下使用流量策略等功能。
仅1.39.0及以上版本的gRPC应用支持通过修改应用程序的方式启用xDS API功能。
修改gRPC客户端
按照以下内容修改gRPC程序代码,使得应用程序可以在gRPC中注册xDS解析器和均衡器。
以下内容需要被添加到主包或调用grpc.Dia的同一个包中。
import _ "google.golang.org/grpc/xds"
按照以下内容修改gRPC程序代码,使得当创建一个gRPC连接时,URL必须使用
xds:/// scheme
。conn, err := grpc.DialContext(ctx, "xds:///foo.ns.svc.cluster.local:7070")
按照以下内容修改gRPC程序代码,使得使用(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), )
修改gRPC服务端
按照以下内容修改gRPC程序代码,使用一个特殊的构造函数来创建GRPC Server。
import "google.golang.org/grpc/xds" ... server = xds.NewGRPCServer() RegisterFooServer(server, &fooServerImpl)
如果您的protoc生成的Go代码已经过期,你需要重新生成Go代码,以便与xDS服务器兼容。
生成的RegisterFooServer函数应如下所示:
func RegisterFooServer(s grpc.ServiceRegistrar, srv FooServer) { s.RegisterService(&FooServer_ServiceDesc, srv) }
按照以下内容修改gRPC程序代码,启用安全支持。
creds, err := xds.NewServerCredentials(xdscreds.ServerOptions{FallbackCreds: insecure.NewCredentials()}) // handle err server = xds.NewGRPCServer(grpc.Creds(creds))