빈 폴더에서 서비스 하나를 세운다
한 줄 요약
gRPC 서비스는 .proto 계약서 한 장에서 시작합니다. 코드 생성기가 그 계약서로 서버 뼈대와 클라이언트 스텁을 만들어 양쪽에 나눠 주고, 서버 리플렉션은 실행 중인 서버가 그 계약서를 직접 내주게 하는 표준 서비스입니다.
왜 이게 필요했나
JSON 위의 REST API 에서 요청의 모양은 문서에만 있습니다. 필드 이름이 바뀌면 사람이 문서와 서버와 클라이언트를 함께 고쳐야 하고, 하나를 빠뜨려도 컴파일러는 아무 말도 하지 않습니다. 오류는 운영에서 「필드가 비어 있다」 는 모습으로 나타납니다.
gRPC 는 순서를 뒤집었습니다. 계약서(.proto)를 먼저 쓰고, 서버와 클라이언트의 코드를 그 계약서에서 만듭니다. 계약이 바뀌면 다시 만든 코드가 컴파일 단계에서 어긋남을 드러냅니다. 와이어에는 필드 이름 대신 번호가 실리니 메시지도 작고 빠릅니다.
그 대가가 있습니다. 바이트에 이름이 없으므로 .proto 없이는 응답을 읽을 수 없고, curl 처럼 손으로 두드려 보는 도구가 그대로는 통하지 않습니다. 장애 현장에서 「이 서버가 정말 그 메서드를 가지고 있나」 를 확인하려면 계약서가 필요한데, 운영 중인 서버 앞에 그 파일이 늘 있지는 않습니다. 서버 리플렉션은 이 빈자리를 메우려고 생겼습니다.
어떻게 동작하나
.proto 의 package 와 service 와 rpc 이름이 합쳐져 와이어의 메서드 이름이 됩니다. package lib.v1 의 서비스 Catalog 에 있는 GetBook 은 HTTP/2 요청의 :path 머리에 /lib.v1.Catalog/GetBook 으로 실립니다. 응답을 여러 번 흘려보내는 호출은 returns 쪽에 stream 을 붙이고, 그것이 서버 스트리밍입니다.
proto/lib/v1/catalog.proto --protoc--> gen/lib/v1/catalog_pb2.py 메시지와 디스크립터
gen/lib/v1/catalog_pb2_grpc.py CatalogServicer · CatalogStub
파이썬에서는 grpcio-tools 패키지가 protoc 을 품고 있어 python -m grpc_tools.protoc 하나로 두 파일이 생깁니다. 이때 -I 로 준 폴더가 import 경로의 뿌리입니다. -I proto 로 주면 파일 이름이 lib/v1/catalog.proto 로 기억되고 코드는 lib/v1/ 아래에 놓입니다. 같은 파일을 다른 뿌리로 생성하면 디스크립터의 파일 이름이 달라지고, 그 이름은 리플렉션과 다른 .proto 의 import 가 찾는 열쇠라서 나중에 엉뚱한 곳에서 실패합니다.
서버는 grpc.server 에 서비스 구현을 붙이고 포트를 열어 기다립니다. 처리기는 (요청, context) 를 받아 응답을 돌려주고, 실패는 context.abort(상태 코드, 설명) 으로 알립니다. 처리기를 제너레이터로 만들면 응답이 하나씩 나갑니다. 클라이언트 쪽의 채널은 연결을 관리하는 객체이고 스텁은 메서드 이름을 함수처럼 부르게 해 주는 얇은 껍데기입니다. 호출마다 timeout 을 거는 습관은 3모듈에서 다시 다룹니다.
서버 리플렉션은 grpc.reflection.v1.ServerReflection 이라는 양방향 스트리밍 서비스입니다. 클라이언트가 「서비스 목록」 이나 「이 이름이 든 파일」 을 물으면 서버는 자기 프로세스의 디스크립터 풀에서 FileDescriptorProto 를 꺼내 바이트로 보냅니다. 그 파일이 import 하는 파일까지 함께 보냅니다. grpcurl 같은 도구는 받은 파일로 디스크립터 풀을 다시 세우고, 사람이 쓴 JSON 을 요청 바이트로, 응답 바이트를 JSON 으로 바꿉니다. 리플렉션이 목록에 올리는 서비스는 서버를 띄울 때 넘긴 이름뿐입니다.
현장에서 만나는 모습
가장 흔한 사고는 .proto 를 고치고 코드를 다시 만들지 않는 것입니다. 저장소에 생성 코드를 넣어 두는 팀이라면 CI 에서 다시 생성해 차이가 없는지 보는 단계를 둡니다. 생성 코드를 빌드 때 만드는 팀이라면 생성기 판을 고정합니다. 생성 코드가 실행 판의 protobuf 를 검사하기 때문입니다.
장애 대응에서는 grpcurl list 와 describe 가 첫 명령입니다. 배포된 서버가 정말 새 메서드를 가지고 있는지, 필드 번호가 기대와 같은지를 .proto 저장소를 뒤지지 않고 확인합니다. 반대로 인터넷에 열린 포트에서 리플렉션을 켜 두면 내부 API 목록이 그대로 드러납니다. 그래서 리플렉션은 내부 포트에만 열거나 인증 뒤에 둡니다. 이 코스의 4모듈이 그 인증을 다룹니다.
배경 설명은 블로그 글 gRPC 가 푸는 문제와 전체 그림 과 디버깅과 관측: grpcurl, 리플렉션, channelz 에 있습니다. 와이어 바이트와 필드 번호 규칙은 grpc-contract 코스가 자세히 다룹니다.
다음 실습에서 할 것
빈 폴더에서 도서 목록 서비스 lib.v1.Catalog 를 세웁니다. .proto 를 쓰고, 코드를 만들고, 단항 호출과 서버 스트리밍 호출을 받는 서버와 그 서버를 부르는 클라이언트를 짭니다. 그다음 리플렉션을 켜고, 이 실습 이미지에 있는 grpcurl 의 파이썬 판(rpcurl)으로 .proto 없이 서비스를 둘러보고 불러 봅니다.