理解使用 Istio 在 Kubernetes 中的 gRPC 负载均衡

点击查看目录

本文译自:https://medium.com/@shrishs/understanding-grpc-load-balancing-in-kubernetes-with-istio-c6e71634724c

摘要:本文介绍了在 Kubernetes 和 Istio 中使用 gRPC 负载均衡的行为。首先,通过创建命名空间、部署资源和配置文件来准备环境。然后,介绍了没有 Istio 的情况下,gRPC 服务的负载均衡行为。接下来,介绍了如何使用 Istio 创建虚拟服务和目标规则来实现负载均衡。还讨论了 ConnectionPoolSetting 对负载均衡行为的影响。最后,介绍了如何通过入口网关访问 gRPC 服务,并提供了验证步骤。

gRPC(gRPC 远程过程调用)是一个跨平台的开源高性能远程过程调用(RPC)框架,使用 HTTP/2 进行传输。gRPC 相对于传统的 HTTP 机制具有诸多优势,例如在一个连接上多路复用多个请求(HTTP/2)。本文将解释在 Kubernetes(RedHat Openshift)和(Redhat Openshift Service Mesh)中结合使用时的行为。

环境准备

  • 创建两个新的命名空间
oc new-project grpc
oc new-project test
  • 在 grpc 命名空间部署所有与服务器相关的资源。本示例基于 Quarkus 的gRPC 快速入门
  • 创建一个 ConfigMap。
kind: ConfigMap
apiVersion: v1
metadata:
  name: grpcconfig
  namespace: grpc
data:
  application.properties: |
    numrequest: 25
    quarkus.grpc.clients.hello.host: grpc.grpc.svc.cluster.local
    teststring: prom    
  • 基于以下内容创建一个新的部署。
kind: Deployment
apiVersion: apps/v1
metadata:
  name: grpc-v1
  namespace: grpc
  labels:
    app: grpc
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grpc
  template:
    metadata:
      labels:
        app: grpc
        app.kubernetes.io/version: l1
      annotations:
        sidecar.istio.io/inject: 'true'
        sidecar.istio.io/logLevel: debug
    spec:
      volumes:
        - name: quarkus-config-volume
          configMap:
            name: grpcconfig
            defaultMode: 420
      containers:
        - name: custom-grpc-hello
          image: 'quay.io/shrishs/grpc-plain-text-quickstart-jvm'
          ports:
            - name: grpc
              containerPort: 9000
              protocol: TCP
          volumeMounts:
            - name: quarkus-config-volume
              mountPath: /deployments/config
      restartPolicy: Always
  • 使用相同的配置创建另外两个部署 grpc-v2 和 grpc-v3,只需提供不同的版本。
app.kubernetes.io/version: l2
&
app.kubernetes.io/version: l3
  • 基于上述部署创建一个 Kubernetes 服务。
kind: Service
apiVersion: v1
metadata:
  name: grpc
  namespace: grpc
  labels:
    app: grpc
spec:
  ports:
    - name: grpc
      protocol: TCP
      port: 9000
      targetPort: 9000
  selector:
    app: grpc
  • 在 test 命名空间中创建另一个 ConfigMap(如上所述)和名为 grpc-client 的部署(使用上述相同的定义)。它将用于调用名为 testString 的 gRPC 服务和 numRequest 在 ConfigMap 中定义。
    @GET
    @Path("/grpc")
    public String testGRPC() throws InterruptedException
    {
            MyClient client = new MyClient(host, 9000);
            try {
                client.makeMultipleRequests(testString, numRequest);
            } finally {
                client.shutdown();
            }
            return "Hello";
        }
    public class MyClient {
        private final ManagedChannel channel;
        private final GreeterGrpc.GreeterBlockingStub blockingStub;

    public MyClient(String host, int port) {
        this(ManagedChannelBuilder.forAddress(host, port).usePlaintext().build());
    }

    private MyClient(ManagedChannel channel) {
        this.channel = channel;
        this.blockingStub = GreeterGrpc.newBlockingStub(channel);
    }

    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    public void makeMultipleRequests(String name, int numRequests) {
        for (int i = 0; i < numRequests; i++) {
            HelloRequest request = HelloRequest.newBuilder().setName(name + i).build();
            HelloReply response = blockingStub.sayHello(request);
            System.out.println(response.getMessage());
        }
    }

没有 Istio 的 Kubernetes gRPC 服务

  • 验证应用程序是否在 grpc 命名空间中运行。
bash-3.2$ oc -n grpc get pods -l app=grpc
NAME                       READY   STATUS    RESTARTS   AGE
grpc-v1-7cc77c5cfc-tfbhf   1/1     Running   0          9m15s
grpc-v2-977b698f6-rb9z6    1/1     Running   0          9m14s
grpc-v3-8679b7f6ff-hxx9c   1/1     Running   0          9m14s
  • 从测试命名空间测试请求。
oc -n test exec deploy/grpc-client -- curl http://localhost:8080/hello/grpc
  • 检查所有 Pod 的日志,可以观察到只有一个 Pod 收到了所有的请求。

上述行为的原因是gRPC构建在HTTP/2之上,而HTTP/2设计为具有单个持久的TCP连接,在该连接上所有请求都被多路复用,这意味着多个请求可以同时在同一个连接上进行。通常情况下,这是很好的,因为它减少了连接管理的开销。然而,这也意味着(可以想象)连接级别的负载均衡并不是非常有用。一旦连接建立完成,就没有更多的负载均衡可做了。所有请求将被固定到单个目标 Pod。

带有 Istio 的 Kubernetes gRPC 服务

  • 创建虚拟服务。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: grpc-vs
  namespace: grpc
spec:
  hosts:
    - grpc.grpc.svc.cluster.local
  http:
    - route:
        - destination:
            host: grpc
            port:
              number: 9000
  • 创建目标规则。
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: grpc-dr
  namespace: grpc
spec:
  host: grpc
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
    connectionPool:
      http:
        http1MaxPendingRequests: 1
        http2MaxRequests: 1
        maxRequestsPerConnection: 1
      tcp:
        maxConnections: 1
  subsets:
  - name: v1
    labels:
      app.kubernetes.io/version: l1
  - name: v2
    labels:
      app.kubernetes.io/version: l2
  - name: v3
    labels:
      app.kubernetes.io/version: l3
  • 在 ServiceMeshMemberRoll 中添加 grpc 和 test 命名空间。
apiVersion: maistra.io/v1
kind: ServiceMeshMemberRoll
metadata:
  name: default
  namespace: istio-system
spec:
  members:
    - grpc
    - test
  • 重启 grpc 和 test 命名空间中的 Pod。现在 Envoy 代理作为 sidecar 附加。
bash-3.2$ oc -n grpc get pods -l app=grpc
NAME                       READY   STATUS    RESTARTS   AGE
grpc-v1-7cc77c5cfc-fm9m4   2/2     Running   0          33s
grpc-v2-977b698f6-hrmhq    2/2     Running   0          33s
grpc-v3-8679b7f6ff-lw9fp   2/2     Running   0          32s
bash-3.2$ oc -n test get pods -l app=grpc
NAME                           READY   STATUS    RESTARTS   AGE
grpc-client-65bb4d7c64-4ftpz   2/2     Running   0          37s
  • 再次测试服务。
oc -n test exec deploy/grpc-client -- curl http://localhost:8080/hello/grpc
  • 在 Kiali 中验证图表。现在可以观察到负载均衡在所有 3 个 Pod 之间进行。

上述行为的原因是,Istio(Envoy 代理)会对每个目标(所有 3 个 Pod)打开一个 HTTP/2 连接,并将请求分流到这些连接上。

  • 在 Jaeger 中进行验证。

ConnectionPoolSetting

可以使用 DestinationRule ConnectionPool 设置来改变负载均衡的行为。

  • 从上述跟踪中获取请求 ID,并在 Istio 代理日志中查找。

观察上面的行(创建新连接)。这是因为在 DestinationRule ConnectionPool 设置中 (http2MaxRequestsmaxRequestPerConnection被定义为 1)。每个请求都会创建一个新的连接,并在请求完成后关闭连接。

  • 更改 DestinationRule ConnectionPool 设置 (http2MaxRequests: 50, maxRequestsPerConnection: 100)
spec:
  host: grpc
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
    connectionPool:
      http:
        http1MaxPendingRequests: 1
        http2MaxRequests: 50
        maxRequestsPerConnection: 100
      tcp:
        maxConnections: 1
  • 再次测试服务
oc -n test exec deploy/grpc-client -- curl http://localhost:8080/hello/grpc
  • 从 Jaeger 中获取请求 ID,并查看 Envoy 代理日志。现在,我们每个连接的请求数大于 1,它将使用现有连接来处理请求。

通过入口网关访问 gRPC 服务

Openshift 使用 HAProxy 作为入口控制器,默认情况下未启用 HTTP2。可以按如下方式启用

oc annotate ingresses.config/cluster ingress.operator.openshift.io/default-enable-http2=true
  • 启用HAProxy的HTTP/2后,使用互相 TLS创建一个网关定义。
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: grpc-gateway
  namespace: grpc
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    - grpc-istio-system.apps.ocp-XXX.com
    port:
      number: 443
      protocol: https
      name: HTTPS
    tls:
      mode: MUTUAL
      credentialName: istio-ingressgateway-certs
  • Openshift Service Mesh 会自动创建一个经过路由定义。
kind: Route
apiVersion: route.openshift.io/v1
metadata:
  name: grpc-grpc-gateway
  namespace: istio-system
  labels:
    maistra.io/gateway-name: grpc-gateway
    maistra.io/gateway-namespace: grpc
    maistra.io/generated-by: ior
  annotations:
    maistra.io/original-host: grpc-istio-system.apps.ocp-XXX.com
spec:
  host: grpc-istio-system.apps.ocp-XXX.com
  to:
    kind: Service
    name: istio-ingressgateway
    weight: 100
  port:
    targetPort: https
  tls:
    termination: passthrough
  wildcardPolicy: None
  • 验证 gRPC 服务是否正常工作。
$ grpcurl --proto helloworld.proto -v  --insecure --cert istio/files/cert/client.example.com.crt --key istio/files/cert/client.example.com.key -d '{"name": "test"}' grpc-istio-system.apps.ocp-XXX.com:443 helloworld.Greeter/SayHello

Resolved method descriptor:
// Sends a greeting
rpc SayHello ( .helloworld.HelloRequest ) returns ( .helloworld.HelloReply );

Request metadata to send:
(empty)

Response headers received:
content-type: application/grpc
date: Fri, 19 May 2023 15:40:14 GMT
grpc-accept-encoding: gzip
server: istio-envoy
x-envoy-upstream-service-time: 12

Response contents:
{
  "message": "Hello test"
}

Response trailers received:
(empty)
Sent 1 request and received 1 response

注意:有时由于定义未在 envoy 代理中同步,可能无法正常工作。在这种情况下,请重新启动入口网关。

oc -n istio-system delete pod -l app=istio-ingressgateway
  • 在 Kiali 中进行验证。

编辑本页