非容器应用与K8s工作负载的服务网格化实践-4 基于ASM的POD和VM互访实践-GRPC协议篇

为了实现高可用,非容器应用通常有一套服务注册和发现的机制。相对而言,kubernetes容器服务为POD提供了统一的基于dns的注册和发现机制。对于http协议的非容器应用的迁移,因为都是基于dns机制,切换成本交底。而对于使用非http协议的非容器应用,服务注册和发现这个技术点为迁移带来了额外的困难。

如何从非容器的注册和发现大脑最终迁移到kubernetes的注册和发现大脑,目前尚没有广泛认可的方案。目前常见的讨论是双脑方案,就是让非容器应用在启动时同时向两套大脑注册,并从两套中发现依赖服务,然后逐步实现向kubernetes大脑过度。这套方案的优点是可以通过随时摆动来保证可用性。

我个人比较倾向于彻底的kubernetes一脑模式。相比多脑,一脑的复杂度会极大下降。遗留大脑集群保持现状,kubernetes大脑集群使用新的方案。这样可以将服务发现和网格化解耦为两个独立的问题。借助服务网格的蓝绿部署/流量转移,可以将验证完毕依赖关系的集群逐步放行并最终替换遗留大脑集群。这个方案最有挑战的地方是如何在不改变非容器应用代码的前提下,将其底层协议替换。如果可以替换为grpc,那么就可以按照本篇的实践实现网格化。目前这个想法还很不成熟,期待交流。

1 POD和VM互访

路由规则

服务网格的流量转移是一脑方案的核心。首先需要明确的是,网格化的服务链路都会经过sidecar,sidecar内部(envoy)实现了对grpc的负载均衡,grpc协议的上游服务通过长链接和下游服务各个节点保持连接,在不配置流量转移的情况下,上游请求会均匀地路由到下游各个节点。

与http协议通过path配置流量转移类似,grpc的配置是通过service/method或者service.method来定义匹配规则的。grpc的路由配置示意如下。

spec:
  hosts:
    - hello2-svc
  http:
    - name: grpc-hello-route
      match:
        - uri:
            prefix: /org.feuyeux.grpc.Greeter/SayHello
      route:
        - destination:
            host: hello2-svc
            subset: v1
          weight: 30
        - destination:
            host: hello2-svc
            subset: v2
          weight: 60
        - destination:
            host: hello2-svc
            subset: v3
          weight: 10

搭建实验环境

本篇示例实验与前一篇相仿,不再冗述重复部分。我们希望路由到hello2 en/fr/es的流量比例为:30%:60%:10%。

非容器应用与K8s工作负载的服务网格化实践-4 基于ASM的POD和VM互访实践-GRPC协议篇

示例(grpc_reciprocal_demo)包含如下元素:

  • hello1 deployment(镜像grpc_springboot_v1)
  • hello3 deployment(镜像grpc_springboot_v1)
  • hello2 docker containers(镜像grpc_springboot_v1/grpc_springboot_v2/grpc_springboot_v3)
  • 入口网关:istio-ingressgateway
  • 入口流量配置:gateway/virtualservice
  • hello1流量配置:hello1 service
  • hello2流量配置:hello2 service/hello2 virtualservice/hello2 destinationrule
  • hello3流量配置:hello3 service
  • hello2 serviceentry/hello2 workloadentry

与前一篇http所用示例类似,grpc示例使用的镜像为grpc_springboot-{version},是一个基于springboot开发的grpc服务(源代码在这里)。每个实例可以通过环境变量GRPC_HELLO_BACKEND定义下游服务。

启动hello2应用

通过如下命令分别在3个ecs节点上启动hello2的 docker container

#ssh1.sh
docker run \
--rm \
--network host \
--name http_v1 \
-e GRPC_HELLO_BACKEND=hello3-svc.grpc-reciprocal-hello.svc.cluster.local \
registry.cn-beijing.aliyuncs.com/asm_repo/grpc_springboot_v1:1.0.0
#ssh2.sh
docker run \
--rm \
--network host \
--name http_v2 \
-e GRPC_HELLO_BACKEND=hello3-svc.grpc-reciprocal-hello.svc.cluster.local \
registry.cn-beijing.aliyuncs.com/asm_repo/grpc_springboot_v2:1.0.0
#ssh3.sh
docker run \
--rm \
--network host \
--name http_v3 \
-e GRPC_HELLO_BACKEND=hello3-svc.grpc-reciprocal-hello.svc.cluster.local \
registry.cn-beijing.aliyuncs.com/asm_repo/grpc_springboot_v3:1.0.0

其他部分的搭建与前篇类似,可以参考前篇和本篇示例脚本,余文不在冗述。

grpcurl

相对于http的验证工具curl,我们使用grpcurl对grpc服务进行验证。

当grpc服务提供反射protocolbuf能力时,可以使用如下命令进行请求验证:

k exec "$hello1_pod" -c hello-v1-deploy -n grpc-reciprocal-hello \
  -- grpcurl -plaintext -d '{"name":"eric"}' \
  hello3-svc.grpc-reciprocal-hello.svc.cluster.local:7001 org.feuyeux.grpc.Greeter/SayHello | jq -r '.reply'
  
Hello eric(172.18.1.10)

如果上述命令返回错误,说明grpc服务无法反射protocolbuf,需要在执行grpccurl时,通过-import-path-proto参数提供proto文件。示意如下。

k exec "$hello1_pod" -c hello-v1-deploy -n grpc-reciprocal-hello -- \
grpcurl -plaintext -d '{"name":"eric"}' \
-import-path /opt -proto hello.proto \
hello3-svc.grpc-reciprocal-hello.svc.cluster.local:7001 org.feuyeux.grpc.Greeter/SayHello | jq -r '.reply'

Hello eric(172.18.1.10)

KUBE验证-POD和VM互访

初步掌握grpccurl命令后,我们验证基于grpc协议的POD和VM互访。请求从hello1 POD发向hello2 service,然后路由到各VM中的hello2 app,再由hello2 app请求到hello3 POD。

for i in {1..6}; do
  k exec "$hello1_pod" -c hello-v1-deploy -n grpc-reciprocal-hello \
    -- grpcurl -plaintext -d '{"name":"eric"}' localhost:7001 org.feuyeux.grpc.Greeter/SayHello | jq -r '.reply'
done
Hello eric(172.18.1.11)<-Hola eric(192.168.0.172)<-Hello eric(172.18.1.10)
Hello eric(172.18.1.11)<-Bonjour eric(192.168.0.171)<-Hello eric(172.18.1.10)
Hello eric(172.18.1.11)<-Hello eric(192.168.0.170)<-Hello eric(172.18.1.10)
...

MESH验证-全链路流量转移

最后部署gateway/virtualservice/destinationrule进行端到端验证。

IP=$(k -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

for i in {1..100}; do
  resp=$(grpcurl -plaintext -d '{"name":"eric"}' "$IP":7004 org.feuyeux.grpc.Greeter/SayHello | jq -r '.reply')
  echo "$resp" >>test_traffic_shift_result
done

echo "expected 30%(Hello eric)-60%(Bonjour eric)-10%(Hola eric):"
sort test_traffic_shift_result | grep -v "^[[:space:]]*$"| uniq -c | sort -nrk1
  57 Hello eric(172.18.1.11)<-Bonjour eric(192.168.0.171)<-Hello eric(172.18.1.10)
  32 Hello eric(172.18.1.11)<-Hello eric(192.168.0.170)<-Hello eric(172.18.1.10)
  11 Hello eric(172.18.1.11)<-Hola eric(192.168.0.172)<-Hello eric(172.18.1.10)

2 grpc基准测试

ghz

ghz是grpc协议的基准测试工具,功能与http协议下的apache benchfortio类似。我们可以通过ghz对服务网格内的grpc服务进行测试和调参。ghz的基本用法示意如下。

hello1_pod=$(k get pod -l app=hello1-deploy -n grpc-reciprocal-hello -o jsonpath={.items..metadata.name})

k exec "$hello1_pod" -c hello-v1-deploy -n grpc-reciprocal-hello -- \
  ghz --insecure -d '{"name":"eric"}' \
  --proto /opt/hello.proto \
  --call org.feuyeux.grpc.Greeter/SayHello \
  -n 2000 \
  -c 20 \
  -q 500 \
  localhost:7001
  • -n参数用来指定请求数量
  • -c参数用来指定并发数量
  • -q参数用来指定QPS

测试结果示意如下。

Summary:
  Count:        2000
  Total:        5.60 s
  Slowest:      851.86 ms
  Fastest:      4.88 ms
  Average:      54.51 ms
  Requests/sec: 357.21

Response time histogram:
  4.883 [1]     |
  89.580 [1919] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  174.278 [43]  |∎
  258.976 [3]   |
  343.673 [5]   |
  428.371 [2]   |
  513.068 [4]   |
  597.766 [3]   |
  682.464 [0]   |
  767.161 [7]   |
  851.859 [13]  |

Latency distribution:
  10 % in 24.37 ms 
  25 % in 31.71 ms 
  50 % in 41.98 ms 
  75 % in 53.87 ms 
  90 % in 70.67 ms 
  95 % in 83.83 ms 
  99 % in 683.16 ms 

Status code distribution:
  [OK]   2000 responses   

上述结果表示RT/latency基本(1919/2000)落在90ms以内,平均54.51 ms,95%的请求延迟在83.83 ms以内,99 %段的延迟在683.16 ms,存在长尾,需要进一步优化服务网格的配置以及服务源代码。

trafficPolicy

通过修改hello2的DestinationRule可以随时调整连接池配置。我们可以结合基准测试,不断调整配置找到最佳性能点。示意如下。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: hello2-dr
  namespace: grpc-reciprocal-hello
spec:
  host: hello2-svc
  subsets:
    - labels:
        version: v1
      name: v1
    - labels:
        version: v2
      name: v2
    - labels:
        version: v3
      name: v3
  trafficPolicy:
    connectionPool:
      http:
        http1MaxPendingRequests: 20
        http2MaxRequests: 100
        maxRequestsPerConnection: 10
        maxRetries: 1
      tcp:
        connectTimeout: 10s
        maxConnections: 10

到此,POD和VM互访的grpc协议流量管理讲述完毕。

本篇示例完整演示了非容器应用网格化过程中的一个比较经典的场景。覆盖到从istio-ingressgateway到deployment、workloadentry等各种CRD的配置,较完整地展示了POD和VM互访中遇到的各技术点的配置。

接下来,我们在此基础上,验证下POD和VM作为同一service的混合流量,如何实现流量转移。

上一篇:服务网格GRPC协议多种编程语言实践-2.GRPC协议示例的实现


下一篇:非容器应用与K8s工作负载的服务网格化实践-7 基于ASM的POD和VM可观测性实践