在gRPC服务中使用Proxyless服务网格功能

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模式兼容或支持:

注意事项

当前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。

目标规则

  • 支持根据标签选择器将流量分割到不同的实例组。

  • 支持ROUND_ROBIN负载均衡策略。

  • 支持DISABLE和ISTIO_MUTUAL的TLS模式。

虚拟服务

  • 支持Header匹配和URI匹配,匹配格式为/ServiceName/RPCName

  • 支持配置目标主机和子集。

  • 支持配置流量权重比例,实现流量路由。

对等身份认证

支持DISABLE和STRICT的mTLS模式。

使用无代理服务网格功能

本文以已支持Proxyless模式的gRPC应用Echo为例,在不使用Sidecar代理情况下,介绍Echo应用如何使用流量路由和mTLS认证功能。

部署示例应用

  1. 在echo-grpc应用的Pod中添加inject.istio.io/templates:grpc-agent注解。

    如果您的应用代码支持Proxyless模式,您需要在应用Pod中添加inject.istio.io/templates:grpc-agent注解,用于开启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
  2. 获取集群KubeConfig并通过kubectl工具连接集群

  3. 执行以下命令,创建echo-grpc命名空间。

    kubectl create namespace echo-grpc
  4. 执行以下命令,为echo-grpc命名空间添加标签,从而注入Sidecar。

    Proxyless模式不会使用到Sidecar,但是需要使用Agent与控制面进行通信,因此仍然需要为命名空间注入Sidecar。

    kubectl label namespace echo-grpc istio-injection=enabled
  5. 执行以下命令,部署echo-grpc应用。

    kubectl -n echo-grpc apply -f https://alibabacloudservicemesh.oss-cn-beijing.aliyuncs.com/asm-grpc-proxyless/grpc-echo.yaml
  6. 验证应用是否部署成功。

    1. 执行以下命令,暴露17171端口。

      kubectl -n echo-grpc port-forward $(kubectl -n echo-grpc get pods -l version=v1 -ojsonpath='{.items[0].metadata.name}') 17171 &
    2. 执行以下命令,访问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

场景一:使用流量路由功能

  1. 创建目标规则。

    使用目标规则为每个版本的工作负载创建一个子集。

    1. 登录ASM控制台,在左侧导航栏,选择服务网格 > 网格管理

    2. 网格管理页面,单击目标实例名称,然后在左侧导航栏,选择流量管理中心 > 目标规则,然后单击创建

    3. 创建页面,设置命名空间echo-grpc,输入名称,设置服务名称echo.echo-grpc.svc.cluster.local

    4. 单击服务版本(子集),单击添加服务版本(子集),在版本1区域,设置版本名称v1,单击添加标签,设置标签名version标签值v1

    5. 单击添加服务版本(子集),在版本2区域,设置版本名称v2,单击添加标签,设置标签名version标签值v2

    6. 单击创建

  2. 创建虚拟服务。

    使用虚拟服务将80%的流量路由到v2版本,20%流量路由到v1版本。

    1. 登录ASM控制台,在左侧导航栏,选择服务网格 > 网格管理

    2. 网格管理页面,单击目标实例名称,然后在左侧导航栏,选择流量管理中心 > 虚拟服务,然后单击创建

    3. 创建页面,设置命名空间echo-grpc,输入名称

    4. 单击HTTP路由,单击添加路由,输入路由名称,单击添加路由目的地,设置服务名称echo.echo-grpc.svc.cluster.local版本v1权重20

    5. 单击添加路由目的地,设置服务名称echo.echo-grpc.svc.cluster.local版本v2权重80

    6. 单击创建

  3. 执行以下命令,向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加密。

  1. 创建目标规则。

    使用目标规则启用客户端的mTLS。

    1. 登录ASM控制台

    2. 在左侧导航栏,选择服务网格 > 网格管理

    3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理

    4. 在网格详情页面左侧导航栏选择流量管理中心 > 目标规则,然后单击创建

    5. 创建页面,设置命名空间echo-grpc,输入名称,设置服务名称echo.echo-grpc.svc.cluster.local

    6. 单击流量策略,单击添加策略,打开客户端TLS开关,设置TLS模式Istio双向

    7. 单击创建

  2. 执行以下命令,访问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应用失败。

  3. 创建对等身份认证。

    使用对等身份认证启用服务端的mTLS。

    1. 登录ASM控制台,在左侧导航栏,选择服务网格 > 网格管理

    2. 网格管理页面,单击目标实例名称,然后在左侧导航栏,选择网格安全中心 > 对等身份认证,然后单击创建双向mTLS模式

    3. 创建双向mTLS模式页面,设置命名空间echo-grpc,输入名称,设置mTLS模式(命名空间级)STRICT - 严格遵循双向TLS认证,然后单击创建

  4. 执行以下命令,访问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客户端

  1. 按照以下内容修改gRPC程序代码,使得应用程序可以在gRPC中注册xDS解析器和均衡器。

    以下内容需要被添加到主包或调用grpc.Dia的同一个包中。

    import _ "google.golang.org/grpc/xds"
  2. 按照以下内容修改gRPC程序代码,使得当创建一个gRPC连接时,URL必须使用xds:/// scheme

    conn, err := grpc.DialContext(ctx, "xds:///foo.ns.svc.cluster.local:7070")
  3. 按照以下内容修改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服务端

  1. 按照以下内容修改gRPC程序代码,使用一个特殊的构造函数来创建GRPC Server。

    import "google.golang.org/grpc/xds"
    
    ...
    
    server = xds.NewGRPCServer()
    RegisterFooServer(server, &fooServerImpl)
  2. 如果您的protoc生成的Go代码已经过期,你需要重新生成Go代码,以便与xDS服务器兼容。

    生成的RegisterFooServer函数应如下所示:

    func RegisterFooServer(s grpc.ServiceRegistrar, srv FooServer) {
    s.RegisterService(&FooServer_ServiceDesc, srv)
    }
  3. 按照以下内容修改gRPC程序代码,启用安全支持。

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