RPC、gRPC与K8S CRI


一、什么是PRC ?

1.1 定义

RPC(Remote Procedure Call),远程过程调用。是一个计算机通信协议,该协议允许运行于一台计算机的程序调用另一个地址空间(通常为另一台开放网络的计算机)的子程序。而在开发人员的视角看来,该调用过程和调用本地过程一样,无需额外编程。在这个过程中,RPC也可以轻易的进行跨语言调用。

举例来说:存在两台服务器A,B,在A服务器上部署了一个应用程序,该应用程序需要调用在B服务器上应用提供的函数/方法。RPC的作用,就是让A可以通过网络跨内存空间调用B上的函数/方法。

为什么不用Restful接口要提出RPC? Restful接口每次调用时,都需要编写http请求。RPC能做到的,是像本地调用一样去发起远程调用而让使用者对远程调用无感知

1.2 组件

  • Server:RPC服务的提供者,上述例子中为服务器B。

  • Client:RPC服务的调用方,上述例子中的服务器A。

  • Client Stub:存放Server的地址信息,再将服务端的请求打包成网络消息,通过网络远程发送给服务方。

  • Server stub:接收客户端发送过来的消息,将消息解包,并调用本地的方法。

1.3 RPC调用过程

img

(1) 客户端(client)以本地调用方式(即以接口的方式)调用服务;
(2) 客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制);
(3) 客户端通过sockets将消息发送到服务端;
(4) 服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);
(5) 服务端存根( server stub)根据解码结果调用本地的服务;
(6) 本地服务执行并将结果返回给服务端存根( server stub);
(7) 服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);
(8) 服务端(server)通过sockets将消息发送到客户端;
(9) 客户端存根(client stub)接收到结果消息,并进行解码(将结果消息发序列化);
(10) 客户端(client)得到最终结果。

RPC将中间过程封装起来,使用户无感知。

二、什么是GRPC?

gRPC (Google Remote Procedure Calls) 是Google发起的一个开源远程过程调用 (Remote procedure call) 系统。该系统基于 HTTP/2 协议传输,使用Protocol Buffers 作为接口描述语言。

gRPC是RPC系统的一种。 其基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特性。

2.1 Protocol Buffers

Protocol Buffers是一种序列化数据结构的协议。其包含一个接口描述语言用来描述一些方法与数据结构,并可以通过一些工具来将这些描述转化为代码。具体的Protocol语法这里不谈,接下来用一个Python gRPC例子说明gRPC基础概念。

2.2 gRPC Python示例 (参考:daydaygo)

通过代码往往能加深理解!

环境需求:Python3、Proto3、pip包:grpcio、grpc-tools。

  • 编写.proto 文件:定义服务 GreeterAPI SayHello。定义请求和回复message格式。
// helloworld.proto
syntax = "proto3";

service Greeter {
    rpc SayHello(HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}
  • 通过grpc-tools编译protocol文件
python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. helloworld.proto
  • python -m grpc_tools.protoc: python 下的 protoc 编译器通过 python 模块(module) 实现, 所以说这一步非常省心
  • --python_out=. : 编译生成处理 protobuf 相关的代码的路径, 这里生成到当前目录
  • --grpc_python_out=. : 编译生成处理 grpc 相关的代码的路径, 这里生成到当前目录
  • -I. helloworld.proto : proto 文件的路径, 这里的 proto 文件在当前目录

? 编译后生成两个Python模块

  • helloworld_pb2.py: 用来和 protobuf 数据进行交互

  • helloworld_pb2_grpc.py: 用来和 grpc 进行交互

  • gRPC Server实现
from concurrent import futures
import time
import grpc
import helloworld_pb2
import helloworld_pb2_grpc

# 实现 proto 文件中定义的 GreeterServicer
class Greeter(helloworld_pb2_grpc.GreeterServicer):
    # 实现 proto 文件中定义的 rpc 调用
    def SayHello(self, request, context):
        return helloworld_pb2.HelloReply(message = 'hello {msg}'.format(msg = request.name))

def serve():
    # 注册启动 rpc 服务
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    try:
        while True:
            time.sleep(60*60*24) # one day in seconds
    except KeyboardInterrupt:
        server.stop(0)

if __name__ == '__main__':
    serve()

server端提供了 rpc服务的实现并注册开启rpc服务。

  • gRPC Client实现
import grpc
import helloworld_pb2
import helloworld_pb2_grpc

def run():
    # 连接 rpc 服务器
    channel = grpc.insecure_channel('localhost:50051')
    # 调用 rpc 服务
    stub = helloworld_pb2_grpc.GreeterStub(channel)
    response = stub.SayHello(helloworld_pb2.HelloRequest(name='czl'))
    print("Greeter client received: " + response.message)

if __name__ == '__main__':
    run()

三、K8S CRI接口

3.1 CRI是什么?

CRI(Container Runtime Interface)容器运行时接口,定义了容器镜像的服务的接口。因为容器运行时与镜像的生命周期是彼此隔离的,因此需要定义两个服务。该接口使用Protocol Buffer,基于gRPC。使用CRI接口,我们可以通过rpc的方式调用管理k8s集群容器。

3.2 iSula CRI

ISulad使用CRI接口,实现了和kubernetes 的对接。接下来演示python iSula CRI接口的调用。

首先需要通过grpc-io工具编译CRI proto文件生成核心python模块并开启iSulad-CRI服务。

import grpc
import api_pb2_grpc
import api_pb2

if __name__ == '__main__':
	 # connect to rpc service
    channel = grpc.insecure_channel('unix:///var/run/isulad.sock')
    #  rpc server: container and image server
    runtime_stub = api_pb2_grpc.RuntimeServiceStub(channel)
    image_stub = api_pb2_grpc.ImageServiceStub(channel)
    # get iSulad version
    response = runtime_stub.Version(api_pb2.VersionRequest())
    print(response)

其余CRI API调用方式类似,具体参考其proto文件。