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调用过程
(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
文件:定义服务Greeter
和 APISayHello
。定义请求和回复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文件。