1년 정도gRPC 기반의 프레임워크로 gRPC 통신을 통해 Microservices 중 몇가지를 개발했다. 프로토버퍼를 빌드하고, 배포도 해봤지만 그동안 뭔지도 잘 모르고 배포해왔다는 생각이 들어.. Back to the basic의 첫 시리즈로 gRPC를 공부해봤다.
사실 Back to the basic이 아니라 나같은 주니어한텐 Start from the basic이 더 맞을지도.
특징
(1) Protocol Buffer
Server client 간 메시지를 정의하는 역할. 원래 REST API에서는 string / int.. 같은 자료형을 docs로 정의해서 노션이나 엑셀로 공유하고.. 했었다면 이 data 자체를 Code화 시킴.
실제 통신 시에는 byte stream으로 data를 encoding하여 통신.
통신 채널과 구현부가 분리되어서 서로 다른 언어로 구현 가능하다.
XML보다 작고, 경량화된 형태임.
(2) HTTP/2
속도 향상을 위해 Data를 나눠서 동시에 여러개를 보내고(Binary Framing) 요청 없이도 보내고 header도 줄이고..
(3) 양방향 스트리밍
Server / Client가 동시에 데이터를 스트리밍으로 주고받을 수 있음
출처: woori SASOO님 JHSong의 파이콘 발표영상
클라이언트가 바로 method 형식으로, 마치 로컬의 함수를 갖다 쓰듯이, 미리 정의된 프로토 버퍼 메시지의 response 대로 맞춰서 return을 받는다.
Proto buffer를 작성하기 위한 언어(Google에서 정의한 IDL)를 배워 Proto buffer를 작성해 메시지를 정의한다.
각 언어에 맞게 proto buffer를 빌드하면 언어에 맞게 클래스가 떨어짐.
Protobuf를 python에서 사용 가능하게 변환 (build). 예를 들어 user라는 API를 작성한다고 하면,
user_pb2.py
와 : Message 정의에 사용될 classuser_pb2_grpc.py
가 결과물로 빌드됨 : Service 정의에 사용될 Class (UserServicer)protobuf를 통해 생성된 user_pb2_grpc의 Servicer
를 상속 받아서 subclass로 구현 (ex. init
, verify
, parse
.. )
Server implementation
server를 띄움 (서버측 grpc 서버)
구현하고 있는 open source platform인 SpaceONE에서는 core의 server.py가 해주는건데.. server = grpc.server() server.start() 와 같은 grpc의 내장함수를 활용한다.
https://github.com/spaceone-dev/python-core/blob/master/src/spaceone/core/pygrpc/server.py#L120
Client implementation
protobuf를 통해 생성된 user_pb2_grpc에 자동 생성된 Stub 클래스를 인스턴스로 생성해서 사용한다.
class CollectorStub(object):
"""Missing associated documentation comment in .proto file."""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.init = channel.unary_unary(
'/spaceone.api.inventory.plugin.Collector/init',
request_serializer=spaceone_dot_api_dot_inventory_dot_plugin_dot_collector__pb2.InitRequest.SerializeToString,
response_deserializer=spaceone_dot_api_dot_inventory_dot_plugin_dot_collector__pb2.PluginInfo.FromString,
)
self.verify = channel.unary_unary(
'/spaceone.api.inventory.plugin.Collector/verify',
request_serializer=spaceone_dot_api_dot_inventory_dot_plugin_dot_collector__pb2.VerifyRequest.SerializeToString,
response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString,
)
self.collect = channel.unary_stream(
'/spaceone.api.inventory.plugin.Collector/collect',
request_serializer=spaceone_dot_api_dot_inventory_dot_plugin_dot_collector__pb2.CollectRequest.SerializeToString,
response_deserializer=spaceone_dot_api_dot_inventory_dot_plugin_dot_collector__pb2.ResourceInfo.FromString,
)
class Collector(BaseAPI, collector_pb2_grpc.CollectorServicer):
pb2 = collector_pb2
pb2_grpc = collector_pb2_grpc
def init(self, request, context):
params, metadata = self.parse_request(request, context)
def verify(self, request, context):
xxx... 이하생략
python `server.py / client.py`
gRPC 를 사용하는 서비스에서 개발자가 할 일 한줄 정리
API source git clone받음
protobuf 작성
make python
결과는 dist 디렉토리에 생김
import해서 사용
Protobuf는 Server / Client를 모두 갖고 있어야하고
잘게 쪼개진 Service의 경우 갖고 있는 Server/client... 모두 각자 관리해야 하며 이에 따른 복잡도가 좀 높아진다.
Protobuffer 버전 관리, 배포를 하는 경우 더욱 관리에 대한 복잡성이 있다.
Protobuf build가 쉽지는 않다. (build package간 dependancy check... ) 개발자 별 다른 환경..
단점을 극복하기 위해 만들어진 gRPC focused Framework인 SpaceONE →
API / Core / Service로 나뉜 gRPC focused framework : SpaceONE
Note) SpaceONE의 grpc server implement 과정에서 다양한 config 파일들이 합쳐지는 과정
conf를 띄울 때는 spinnaker의 helm values (from.gitlab) + core의 conf + plugin의 global_conf(https://github.com/spaceone-dev/plugin-azure-cloud-service-inven-collector/blob/master/src/spaceone/inventory/conf/global_conf.py)가 합쳐져서 돌아감 이 모든건 grpc server를 띄우기 위한 configuration 작업임.
실제로 inventory 서비스가 배포된 pod에 들어가서 보면 (kubectl exec -it ~
) conf 보면 디폴트만 있고 config값들은 opt/spaceone directory 아래 모아져 있음
Note) SpaceONE Framework 구조 및 역할
pkg
: protobuf build를 위해 필요한 패키지 List package 소스 파일 proto
build
dist
template