REST 로 열고, 관측한다
한 줄 요약
브라우저와 외부 파트너는 gRPC 를 그대로 부르기 어렵습니다. 그래서 .proto 에 HTTP 규칙을 적어 HTTP/JSON 을 함께 열고 (트랜스코딩), 운영은 rpc.* 규약의 추적과 gRPC 가 내는 지표로 봅니다. 이 모듈은 코스를 운영의 바깥쪽에서 마무리합니다.
왜 이게 필요했나
gRPC 는 HTTP/2 트레일러에 상태를 싣는데, 브라우저의 fetch 는 트레일러를 읽을 수 없습니다. 외부 파트너는 REST 와 JSON 을 기대하고, 사내 운영 도구는 curl 한 줄로 부를 수 있기를 바랍니다. 그렇다고 REST 래퍼를 손으로 따로 짜면 같은 API 를 두 벌 유지하게 되고, 둘은 반드시 어긋납니다.
관측도 비슷합니다. 서비스가 여럿으로 나뉘면 한 요청이 어느 홉에서 늦었는지를 추적 없이 찾기 어렵고, 상태 코드별 지표가 없으면 3모듈의 DEADLINE_EXCEEDED 가 어디서 생겼는지 알 수 없습니다.
어떻게 동작하나
트랜스코딩의 출발점은 .proto 의 google.api.http 옵션입니다. rpc 마다 HTTP 메서드와 경로를 적고, 경로 안의 변수를 요청 메시지의 필드에 잇습니다.
rpc GetBook(GetBookRequest) returns (Book) {
option (google.api.http) = { get: "/v1/books/{isbn}" };
}
GET /v1/books/9780000000105 → GetBookRequest{isbn: "9780000000105"} → Book 을 JSON 으로
이 규칙을 읽는 쪽이 두 갈래입니다. gRPC-Gateway 는 protoc 플러그인이 Go 로 된 역방향 프록시를 생성하고, 같은 .proto 에서 OpenAPI 문서도 만듭니다. Envoy 의 grpc_json_transcoder 필터는 코드 생성 없이 디스크립터 세트 파일을 읽어 같은 변환을 합니다. 브라우저에서 gRPC 메시지를 그대로 쓰고 싶다면 gRPC-Web 을 씁니다. 상태를 트레일러 대신 본문 끝에 싣는 변형이고, Envoy 의 grpc_web 필터가 보통의 gRPC 로 바꿔 줍니다.
HTTP 로 바꿀 때는 상태 코드도 바뀝니다. gRPC-Gateway 의 기본 표에서 INVALID_ARGUMENT·FAILED_PRECONDITION·OUT_OF_RANGE 는 400, UNAUTHENTICATED 는 401, PERMISSION_DENIED 는 403, NOT_FOUND 는 404, ALREADY_EXISTS·ABORTED 는 409, RESOURCE_EXHAUSTED 는 429, UNIMPLEMENTED 는 501, UNAVAILABLE 은 503, DEADLINE_EXCEEDED 는 504 입니다. 400 하나에 세 코드가 겹치므로, HTTP 쪽 클라이언트가 2모듈처럼 이유로 분기하려면 본문에 실린 오류 상세를 읽어야 합니다.
관측은 세 층입니다. 추적은 인터셉터가 W3C traceparent 를 메타데이터에 싣고 꺼내며 호출마다 구간을 만듭니다. OpenTelemetry 의 RPC 규약은 그 구간에 rpc.system(grpc)·rpc.service·rpc.method·rpc.grpc.status_code 같은 속성을 붙입니다. 규약은 판에 따라 이름이 바뀌는 중이라 쓰는 SDK 의 판을 확인합니다. 지표는 gRPC 라이브러리의 OpenTelemetry 플러그인이 grpc.client.attempt.duration·grpc.server.call.duration 처럼 시도와 호출을 나눠 냅니다(gRFC A66). 시도와 호출을 나누는 까닭은 재시도 때문입니다. 호출 하나에 시도가 셋이면 지연과 실패율이 두 층에서 다르게 보입니다. 마지막으로 channelz 는 실행 중인 채널과 하위 채널, 소켓의 상태를 그 자리에서 보여 줍니다. 6모듈에서 본 「연결이 한 파드에 묶였다」 같은 상태를 지표가 아니라 그 순간의 연결 목록으로 확인할 수 있어, 쏠림이나 끊긴 하위 채널을 찾을 때 씁니다.
현장에서 만나는 모습
트랜스코딩 경로에서 「400 이 늘었다」 는 경보가 울리면 그것이 잘못된 요청(INVALID_ARGUMENT)인지 상태 문제 (FAILED_PRECONDITION)인지 HTTP 상태만으로는 알 수 없습니다. gRPC 쪽 지표를 상태 코드로 나눠 함께 봅니다. 오류율 경보는 NOT_FOUND 처럼 정상적인 응답을 빼고 계산합니다. 그리고 REST 경로(/v1/...)는 외부와의 약속이라 .proto 의 필드 번호처럼 함부로 바꾸지 않습니다.
지표 이름과 라벨이 너무 잘게 나뉘면 시계열 수가 폭발합니다. 메서드 이름은 라벨로 괜찮지만 요청 번호나 사용자 ID 를 라벨로 붙이면 안 됩니다. 블로그 글 디버깅과 관측: grpcurl, 리플렉션, channelz, 그리고 메트릭의 함정 에 작은 클러스터에서 실제로 밟은 메트릭 함정이 있습니다.
다음 퀴즈 전에
이 코스에서 한 일을 한 줄씩 되짚어 봅니다. 계약서에서 서비스를 세웠고, 실패에 이름과 이유를 붙였고, 기한을 예산으로 나눴고, 인터셉터로 둘레를 지켰고, 인증서로 상대를 확인했고, 쿠버네티스에서 헬스와 부하 분산을 이었습니다. 다음 퀴즈는 그 바깥 경계, HTTP 로 여는 일과 관측하는 일을 묻습니다.