基于ASM的GRPC服务部署实践

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: 继MicroServices之后,ServiceMesh是又一个推动软件工业的革命性技术。其服务治理的方法论,不仅改变了技术实现的方式和社会分工。 运行于数据平面的用户服务与治理服务的各种规则彻底解耦。运行于控制平面的规则定义组件,将流量控制的具体规则推送给运行于数据平面的proxy,proxy通过对用户服务的ingress和egress的实际控制,最终实现服务治理。 原本需要服务开发

继MicroServices之后,ServiceMesh是又一个推动软件工业的革命性技术。其服务治理的方法论,不仅改变了技术实现的方式和社会分工。

运行于数据平面的用户服务与治理服务的各种规则彻底解耦。运行于控制平面的规则定义组件,将流量控制的具体规则推送给运行于数据平面的proxy,proxy通过对用户服务的ingress和egress的实际控制,最终实现服务治理。

原本需要服务开发者编程实现的服务发现、容错、灰度、流量复制等能力,被ServiceMesh非侵入的方式实现。此外,ServiceMesh还提供了访问控制、认证授权等功能,进一步减轻了用户服务的开发成本。

阿里云提供的服务网格(ASM)是基于容器服务(ACK)之上的托管版ServiceMesh,在提供完整的ServiceMesh能力的同时(ASM还在底层横向拉通了阿里云云原生的各种能力,不在本篇讲述范围),免去了用户搭建和运维ServiceMesh平台istio的繁琐工作。本篇将分享如何将我们自己的GRPC服务,托管到阿里云的服务网格中。

1. grpc服务

grpc协议相比http而言,既具备http跨操作系统和编程语言的好处,又提供了基于流的通信优势。而且,grpc逐渐成为工业界的标准,一旦我们的grpc服务可以mesh化,那么更多的非标准协议就可以通过转为grpc协议的方式,低成本地接入服务网格,实现跨技术栈的服务通信。

grpc服务的示例部分使用最普遍的编程语言Java及最高效的编程框架SpringBoot。示例的拓扑示意如下:

grpc

1.1 springboot

common——proto2java

示例工程包含三个模块,分别是commonproviderconsumer。其中,common负责将定义grpc服务的protobuf转换为java的rpc模板代码;后两者对其依赖,分别实现grpc的服务端和客户端。

示例工程的protobuf定义如下,实现了两个方法SayHelloSayByeSayHello的入参是一个字符串,返回一个字符串;SayBye只有一个字符串类型的出参。

syntax = "proto3";
import "google/protobuf/empty.proto";
package org.feuyeux.grpc;

option java_multiple_files = true;
option java_package = "org.feuyeux.grpc.proto";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  rpc SayBye (google.protobuf.Empty) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string reply = 1;
}

common构建过程使用protobuf-maven-plugin自动生成rpc模板代码。

provider——grpc-spring-boot-starter

provider依赖grpc-spring-boot-starter包以最小化编码,实现grpc服务端逻辑。示例实现了两套grpc方法,以在后文演示不同流量的返回结果不同。

第一套方法示意如下:

@GRpcService
public class GreeterImpl extends GreeterImplBase {

    @Override
    public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
        String message = "Hello " + request.getName() + "!";
        HelloReply helloReply = HelloReply.newBuilder().setReply(message).build();
        responseObserver.onNext(helloReply);
        responseObserver.onCompleted();
    }

    @Override
    public void sayBye(com.google.protobuf.Empty request, StreamObserver<HelloReply> responseObserver) {
        String message = "Bye bye!";
        HelloReply helloReply = HelloReply.newBuilder().setReply(message).build();
        responseObserver.onNext(helloReply);
        responseObserver.onCompleted();
    }
}

第二套方法示意如下:

@GRpcService
public class GreeterImpl2 extends GreeterImplBase {

    @Override
    public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
        String message = "Bonjour " + request.getName() + "!";
        HelloReply helloReply = HelloReply.newBuilder().setReply(message).build();
        responseObserver.onNext(helloReply);
        responseObserver.onCompleted();
    }

    @Override
    public void sayBye(com.google.protobuf.Empty request, StreamObserver<HelloReply> responseObserver) {
        String message = "au revoir!";
        HelloReply helloReply = HelloReply.newBuilder().setReply(message).build();
        responseObserver.onNext(helloReply);
        responseObserver.onCompleted();
    }
}

consumer——RESTful

consumer的作用有两个,一个是对外暴露RESTful服务,一个是作为grpc的客户端调用grpc服务端provider。示意代码如下:

@RestController
public class GreeterController {
    private static String GRPC_PROVIDER_HOST;

    static {
        GRPC_PROVIDER_HOST = System.getenv("GRPC_PROVIDER_HOST");
        if (GRPC_PROVIDER_HOST == null || GRPC_PROVIDER_HOST.isEmpty()) {
            GRPC_PROVIDER_HOST = "provider";
        }
        LOGGER.info("GRPC_PROVIDER_HOST={}", GRPC_PROVIDER_HOST);
    }

    @GetMapping(path = "/hello/{msg}")
    public String sayHello(@PathVariable String msg) {
        final ManagedChannel channel = ManagedChannelBuilder.forAddress(GRPC_PROVIDER_HOST, 6565)
                .usePlaintext()
                .build();
        final GreeterGrpc.GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel);
        ListenableFuture<HelloReply> future = stub.sayHello(HelloRequest.newBuilder().setName(msg).build());
        try {
            return future.get().getReply();
        } catch (InterruptedException | ExecutionException e) {
            LOGGER.error("", e);
            return "ERROR";
        }
    }

    @GetMapping("bye")
    public String sayBye() {
        final ManagedChannel channel = ManagedChannelBuilder.forAddress(GRPC_PROVIDER_HOST, 6565)
                .usePlaintext()
                .build();
        final GreeterGrpc.GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel);
        ListenableFuture<HelloReply> future = stub.sayBye(Empty.newBuilder().build());
        try {
            return future.get().getReply();
        } catch (InterruptedException | ExecutionException e) {
            LOGGER.error("", e);
            return "ERROR";
        }
    }
}

这里需要注意的是GRPC_PROVIDER_HOST变量,我们在ManagedChannelBuilder.forAddress(GRPC_PROVIDER_HOST, 6565)中使用到这个变量,以获得provider服务的地址。相信你已经发现,服务开发过程中,我们没有进行任何服务发现能力的开发,而是从系统环境变量里获取这个值。而且,在该值为空时,我们使用了一个hardcode值provider。没错,这个值将是后文配置在isito中的provider服务的约定值。

1.2 curl&grpcurl

本节将讲述示例工程的本地启动和验证。首先我们通过如下脚本构建和启动provider和consumer服务:

# terminal 1
mvn clean install -DskipTests -U
java -jar provider/target/provider-1.0.0.jar

# terminal 2
export GRPC_PROVIDER_HOST=localhost
java -jar consumer/target/consumer-1.0.0.jar

我们使用curl以http的方式请求consumer:

# terminal 3
$ curl localhost:9001/hello/feuyeux

Hello feuyeux!

$ curl localhost:9001/bye

Bye bye!

最后我们使用grpcurl直接测试provider:

$ grpcurl -plaintext -d @ localhost:6565 org.feuyeux.grpc.Greeter/SayHello <<EOM   
{
  "name":"feuyeux"
}
EOM

{
  "reply": "Hello feuyeux!"
}

$ grpcurl -plaintext localhost:6565 org.feuyeux.grpc.Greeter/SayBye                                                                                                 
{
  "reply": "Bye bye!"
}

1.2 docker

服务验证通过后,我们制作三个docker镜像,以作为deployment部署到kubernetes上。这里以provider的dockerfile为例:

FROM openjdk:8-jdk-alpine
ARG JAR_FILE=provider-1.0.0.jar
COPY ${JAR_FILE} provider.jar
COPY grpcurl /usr/bin/grpcurl
ENTRYPOINT ["java","-jar","/provider.jar"]

构建镜像和推送到远端仓库的脚本示意如下:

docker build -f grpc.provider.dockerfile -t feuyeux/grpc_provider_v1:1.0.0 .
docker build -f grpc.provider.dockerfile -t feuyeux/grpc_provider_v2:1.0.0 .
docker build -f grpc.consumer.dockerfile -t feuyeux/grpc_consumer:1.0.0 .

docker push feuyeux/grpc_provider_v1:1.0.0
docker push feuyeux/grpc_provider_v2:1.0.0
docker push feuyeux/grpc_consumer:1.0.0

本地启动服务验证,示意如下:

# terminal 1
docker run --name provider2 -p 6565:6565 feuyeux/grpc_provider_v2:1.0.0

# terminal 2
docker exec -it provider2 sh
grpcurl -v -plaintext localhost:6565 org.feuyeux.grpc.Greeter/SayBye
exit
# terminal 3
export LOCAL=$(ipconfig getifaddr en0)
docker run --name consumer -e GRPC_PROVIDER_HOST=${LOCAL} -p 9001:9001 feuyeux/grpc_consumer
# terminal 4
curl -i localhost:9001/bye

1.3 istio

验证完镜像后,我们进入重点。本节将完整讲述如下拓扑的服务治理配置:
mesh-arch

Deployment

consumer的deployment声明示意如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: consumer
    version: v1
...
      containers:
        - name: consumer
          image: feuyeux/grpc_consumer:1.0.0
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 9001

provider1的deployment声明示意如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: provider-v1
  labels:
    app: provider
    version: v1
...
      containers:
        - name: provider
          image: feuyeux/grpc_provider_v1:1.0.0
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 6565

provider2的deployment声明示意如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: provider-v2
  labels:
    app: provider
    version: v2
...
      containers:
        - name: provider
          image: feuyeux/grpc_provider_v2:1.0.0
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 6565

Deployment中使用到了前文构建的三个镜像。在容器服务中不存在时(IfNotPresent)即会拉取。

这里需要注意的是,provider1和provider2定义的labels.app都是provider,这个标签是provider的唯一标识,只有相同才能被Service的Selector找到并认为是一个服务的两个版本。

服务发现

provider的Service声明示意如下:

apiVersion: v1
kind: Service
metadata:
  name: provider
  labels:
    app: provider
    service: provider
spec:
  ports:
    - port: 6565
      name: grpc
      protocol: TCP
  selector:
    app: provider

前文已经讲到,服务开发者并不实现服务注册和服务发现的功能,也就是说示例工程不需要诸如zookeeper/etcd/Consul等组件的客户端调用实现。Service的域名将作为服务注册的名称,服务发现时通过这个名称就能找到相应的实例。因此,前文我们直接使用了hardcode的provider

grpc路由

服务治理的经典场景是对http协议的服务,通过匹配方法路径前缀来路由不同的RESTful方法。grpc的路由方式与此类似,它是通过http2实现的。grpc的service接口及方法名与 http2的对应形式是`Path : /Service-Name/{method name}。因此,我们可以为Gateway的VirtualService定义如下的匹配规则:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: grpc-gw-vs
spec:
  hosts:
    - "*"
  gateways:
    - grpc-gateway
  http:
...
    - match:
        - uri:
            prefix: /org.feuyeux.grpc.Greeter/SayBye
        - uri:
            prefix: /org.feuyeux.grpc.Greeter/SayHello

AB流量

掌握了grpc通过路径的方式路由,定义AB流量便水到渠成:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: provider
spec:
  gateways:
    - grpc-gateway
  hosts:
    - provider
  http:
    - match:
        - uri:
            prefix: /org.feuyeux.grpc.Greeter/SayHello
      name: hello-routes
      route:
        - destination:
            host: provider
            subset: v1
          weight: 50
        - destination:
            host: provider
            subset: v2
          weight: 50
    - match:
        - uri:
            prefix: /org.feuyeux.grpc.Greeter/SayBye
      name: bye-route
...

到此,示例工程的核心能力简单扼要地讲述完毕。详细代码请clone本示例工程。接下来,我将介绍如何将我们的grpc服务实例部署到阿里云服务网格。

2. 服务网格实践

2.1 托管集群

首先使用阿里云账号登录,进入容器服务控制台(https://cs.console.aliyun.com),创建Kubernetes集群-标准托管集群。详情见帮助文档:快速创建Kubernetes托管版集群

2.2 服务网格

进入服务网格控制台(https://servicemesh.console.aliyun.com/),创建服务网格实例。详情见帮助文档:服务网格 ASM > 快速入门 > 使用流程

服务网格实例创建成功后,确保数据平面已经添加容器服务集群。然后开始数据平面的配置。

2.3 数据平面

kubeconfig

在执行数据平面的部署前,我们先确认下即将用到的两个kubeconfig。

  • 进入容器实例界面,获取kubconfig,并保存到本地~/shop/bj_config
  • 进入服务网格实例界面,点击连接配置,获取kubconfig,并保存到本地~/shop/bj_asm_config

请注意,在数据平面部署过程中,我们使用~/shop/bj_config这个kubeconfig;在控制平面的部署中,我们使用~/shop/bj_asm_config这个kubeconfig。

设置自动注入

kubectl \
--kubeconfig ~/shop/bj_config \
label namespace default istio-injection=enabled

可以通过访问容器服务的命名空间界面进行验证。

部署deployment和service

export DEMO_HOME=

kubectl \
--kubeconfig ~/shop/bj_config \
apply -f $DEMO_HOME/istio/kube/consumer.yaml

kubectl \
--kubeconfig ~/shop/bj_config \
apply -f $DEMO_HOME/istio/kube/provider1.yaml

kubectl \
--kubeconfig ~/shop/bj_config \
apply -f $DEMO_HOME/istio/kube/provider2.yaml

可以通过访问容器服务的如下界面进行验证:

通过如下命令,确认pod的状态是否符合预期:

$ kubectl \
--kubeconfig ~/shop/bj_config \
get pod

NAME                           READY   STATUS    RESTARTS   AGE
consumer-v1-5c565d57f-vb8qb    2/2     Running   0          7h24m
provider-v1-54dbbb65d8-lzfnj   2/2     Running   0          7h24m
provider-v2-9fdf7bd6b-58d4v    2/2     Running   0          7h24m

入口网关服务

最后,我们通过ASM管控台配置入口网关服务,以对外公开http协议的9001端口和grpc协议的6565端口。

ingressgateway

创建完成后,我们就有了公网的IP。余文测试验证环节将使用到这里配置的入口网关IP 39.102.37.176

2.4 控制平面

部署Gateway

kubectl \
--kubeconfig ~/shop/bj_asm_config \
apply -f $DEMO_HOME/istio/networking/gateway.yaml

部署完毕后,在ASM控制台的控制平面-服务网关界面下,可以看到这个Gateway实例。也可以直接使用该界面创建和删除服务网格的Gateway实例。

部署VirtualService

kubectl \
--kubeconfig ~/shop/bj_asm_config \
apply -f $DEMO_HOME/istio/networking/gateway-virtual-service.yaml

kubectl \
--kubeconfig ~/shop/bj_asm_config \
apply -f $DEMO_HOME/istio/networking/provider-virtual-service.yaml


kubectl \
--kubeconfig ~/shop/bj_asm_config \
apply -f $DEMO_HOME/istio/networking/consumer-virtual-service.yaml

部署完毕后,在ASM控制台的控制平面-虚拟服务界面下,可以看到VirtualService实例列表。也可以直接使用界面创建和删除服务网格的VirtualService实例。

部署DestinationRule

kubectl \
--kubeconfig ~/shop/bj_asm_config \
apply -f $DEMO_HOME/istio/networking/provider-destination-rule.yaml

kubectl \
--kubeconfig ~/shop/bj_asm_config \
apply -f $DEMO_HOME/istio/networking/consumer-destination-rule.yaml

部署完毕后,在ASM控制台的控制平面-目标规则界面下,可以看到DestinationRule实例列表。也可以直接使用界面创建和删除服务网格的DestinationRule实例。

2.5 流量验证

完成grpc服务在ASM的部署后,我们首先验证如下链路的流量:

HOST=39.102.37.176
for ((i=1;i<=10;i++)) ;  
do   
curl ${HOST}:9001/hello/feuyeux
echo
done

最后再来验证我如下链路的流量:

# terminal 1
export GRPC_PROVIDER_HOST=39.102.37.176
java -jar consumer/target/consumer-1.0.0.jar

# terminal 2
for ((i=1;i<=10;i++)) ;  
do   
curl localhost:9001/bye
echo
done
目录
相关文章
|
3月前
|
运维 Kubernetes 安全
利用服务网格实现全链路mTLS(一):在入口网关上提供mTLS服务
阿里云服务网格(Service Mesh,简称ASM)提供了一个全托管式的服务网格平台,兼容Istio开源服务网格,用于简化服务治理,包括流量管理和拆分、安全认证及网格可观测性,有效减轻开发运维负担。ASM支持通过mTLS提供服务,要求客户端提供证书以增强安全性。本文介绍如何在ASM入口网关上配置mTLS服务并通过授权策略实现特定用户的访问限制。首先需部署ASM实例和ACK集群,并开启sidecar自动注入。接着,在集群中部署入口网关和httpbin应用,并生成mTLS通信所需的根证书、服务器证书及客户端证书。最后,配置网关上的mTLS监听并设置授权策略,以限制特定客户端对特定路径的访问。
131 2
|
3月前
|
运维 负载均衡 监控
探索微服务架构下的服务网格(Service Mesh)实践之路
【8月更文挑战第30天】 在当今日益复杂的分布式系统中,微服务架构已成为众多企业解决系统扩展与维护难题的利器。然而,随着服务的不断增多和网络交互的复杂性提升,传统的微服务管理方式开始显得力不从心。服务网格(Service Mesh)作为一种新兴的解决方案,旨在通过提供应用层的网络基础设施来简化服务间通讯,并增强系统的可观察性和安全性。本文将分享我在采用服务网格技术过程中的经验与思考,探讨如何在现代云原生环境中有效地实施服务网格,以及它给开发和运维带来的变革。
|
3月前
|
Prometheus Kubernetes 监控
打造无缝灾备新境界:运用服务网格ASM,将集群外服务无缝融入集群内服务,铸就高可用性坚盾!
【8月更文挑战第2天】随着微服务架构的应用,服务的高可用性变得至关重要。服务网格如阿里巴巴的ASM提供流量管理、服务发现等功能,支撑高可靠服务系统。本文介绍如何利用ASM实现集群外服务作为集群内服务的灾备方案,确保服务连续性。先决条件包括已部署ASM的Kubernetes集群环境及内外部的关键服务副本。通过定义服务条目、配置虚拟服务和目的地规则,可实现自动或手动故障转移。借助ASM的流量管理能力,确保服务高可用性和业务连续性。
52 10
|
3月前
|
Kubernetes 安全 数据安全/隐私保护
利用服务网格实现全链路mTLS(二):通过出口网关访问外部mTLS服务
阿里云服务网格(Service Mesh,简称ASM)提供了一个全托管式的服务网格平台,兼容Istio开源服务网格,简化服务治理,包括流量管理、服务间通信安全及网格可观测性。ASM出口网关统一管理网格内的出口流量,实现全链路加密通信与精细访问控制。本文介绍如何配置ASM出口网关以管理出口流量并发起mTLS通信,涉及配置ServiceEntry、创建出口网关、设置虚拟服务及目标规则等步骤,最终实现安全可控的mTLS服务访问。
150 3
|
3月前
|
Perl
如何利用服务网格ASM使用集群外服务做集群内服务的灾备
本文档指导您如何配置阿里云服务网格(ASM)以实现在多集群环境下,服务间的优先访问及故障转移策略。
115 2
|
5月前
|
负载均衡 测试技术 网络安全
阿里云服务网格ASM多集群实践(一)多集群管理概述
服务网格多集群管理网络打通和部署模式的多种最佳实践
|
4月前
|
Cloud Native 测试技术 开发者
阿里云服务网格ASM多集群实践(二):高效按需的应用多环境部署与全链路灰度发布
介绍服务网格ASM提出的一种多集群部署下的多环境部署与全链路灰度发布解决方案。
|
6月前
|
监控 负载均衡 数据安全/隐私保护
探索微服务架构下的服务网格(Service Mesh)实践
【5月更文挑战第6天】 在现代软件工程的复杂多变的开发环境中,微服务架构已成为构建、部署和扩展应用的一种流行方式。随着微服务架构的普及,服务网格(Service Mesh)作为一种新兴技术范式,旨在提供一种透明且高效的方式来管理微服务间的通讯。本文将深入探讨服务网格的核心概念、它在微服务架构中的作用以及如何在实际项目中落地实施服务网格。通过剖析服务网格的关键组件及其与现有系统的协同工作方式,我们揭示了服务网格提高系统可观察性、安全性和可操作性的内在机制。此外,文章还将分享一些实践中的挑战和应对策略,为开发者和企业决策者提供实用的参考。
|
6月前
|
运维 监控 负载均衡
探索微服务架构下的服务网格(Service Mesh)实践之路
【4月更文挑战第30天】 在现代云计算的大背景下,微服务架构以其灵活性和可扩展性成为众多企业转型的首选。然而,随着服务的激增和网络交互的复杂化,传统的服务通信模式已无法满足需求,服务网格(Service Mesh)应运而生。本文通过分析服务网格的核心组件、运作机制以及在企业中的实际应用案例,探讨了服务网格在微服务架构中的关键作用及其带来的变革,同时提出了实施过程中面临的挑战和解决策略。
|
6月前
|
运维 监控 Cloud Native
云原生架构下的服务网格演进与实践
【5月更文挑战第23天】 随着云计算技术的不断成熟,云原生架构已成为推动企业数字化转型的关键动力。本文将深入探讨服务网格在云原生环境中的重要性,分析其在微服务管理、流量控制和安全性方面的创新应用。通过对服务网格的技术和实践案例的剖析,揭示其如何优化云原生应用的部署、运行和管理,为企业构建更加动态、可靠和高效的分布式系统提供策略指导。