상태: 초안 (2026-10-17), 일부 실측. 기준 판 grpc/grpc v1.84.0(문서), grpc/grpc-go v1.83.2(커밋 030ee8becb20). 실측은 2026-10-17 03시대(KST) 작은 클러스터에서 Prometheus 를 읽기만 해서 얻었습니다(질의문은 5절). 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 확인하지 못한 것은 「미확인」으로 표시합니다.
목차
- 1. 직관: 전화 대신 서식
- 2. 그림
- 3. 왜 JSON over HTTP/1.1 이 아닌가
- 4. 코드 생성이 만들어 주는 것
- 5. 실측: 작은 클러스터에서 gRPC 로 말하는 것들
- 6. 채널의 수명: 연결은 오래, 호출은 짧게
- 7. 한계
- 8. 작은 클러스터와의 연결
- 참고
1. 직관: 전화 대신 서식
옆 팀에 일을 부탁할 때 전화로 말하면 빠르지만 오해가 생깁니다. 그래서 회사는 서식을 만듭니다. 칸의 이름과 순서, 어느 칸이 숫자인지가 정해져 있으면 받는 쪽은 서식만 보고 처리할 수 있고, 서식이 조금 바뀌어도(칸 하나 추가) 예전 서식을 든 사람과 계속 일할 수 있습니다.
gRPC 는 이 서식을 .proto 파일로 적고, 서식에서 양쪽 언어의 코드를 자동으로 뽑아냅니다. 부르는 쪽은 client.Hit(ctx, req) 처럼
평범한 함수를 부르고, 그 아래에서 라이브러리가 요청을 바이트로 바꿔 HTTP/2 연결로 보내고 답을 다시 객체로 돌려줍니다.
구글이 사내에서 10년 넘게 쓰던 RPC 기반(Stubby)의 다음 판을 2015년 3월에 오픈소스로 만들기로 한 것이 시작이고, CNCF 에는
2017-02-16 에 인큐베이팅 단계로 들어왔습니다(grpc.io/about, CNCF 프로젝트 페이지).
| 부품 | 책임 | 이 시리즈에서 |
|---|---|---|
| IDL(.proto) | 서비스·메서드·메시지의 계약. 필드 번호가 하위 호환의 열쇠 | 2편 |
| 코드 생성(protoc + 플러그인) | 언어별 메시지 타입, 클라이언트 스텁, 서버 인터페이스 | 1편 4절 |
| 채널(ClientConn) | 이름 풀이, 부하 분산, 연결 유지, 재시도, deadline | 4·5·7편 |
| HTTP/2 전송 | 호출 하나 = 스트림 하나, 5바이트 메시지 접두, 트레일러의 상태 코드 | 3편 |
2. 그림
- 윗줄은 빌드할 때 일입니다.
.proto를protoc와 언어별 플러그인에 넣으면 메시지 타입과 스텁·서버 인터페이스가 나옵니다. - 아랫줄은 실행할 때 일입니다. 클라이언트 스텁은 채널에 「이 메서드 경로로, 이 바이트를 보내 달라」고만 말합니다. 채널 안에서 이름 풀이(resolver)가 주소 목록을 만들고, 부하 분산 정책이 서브채널(주소 하나에 대한 연결)을 고르고, 그 서브채널의 HTTP/2 연결에 새 스트림이 열립니다.
- 굵은 선 하나가 TCP 연결 하나입니다. 그 위로 호출 여러 개가 동시에 흐릅니다. 이것이 5편의 「부하가 한 파드에 쏠리는」 문제의 뿌리입니다.
3. 왜 JSON over HTTP/1.1 이 아닌가
REST 와 JSON 으로도 원격 호출은 됩니다. gRPC 가 따로 있는 이유를 계약·바이트·연결·실패 네 가지로 나눠 보면 이렇습니다.
| 축 | 흔한 REST/JSON | gRPC | 대가 |
|---|---|---|---|
| 계약 | OpenAPI 는 선택, 코드와 어긋나기 쉬움 | .proto 가 곧 코드의 원천 | 빌드에 코드 생성 단계가 들어감 |
| 바이트 | 필드 이름을 매번 문자로 보냄 | 필드 번호 + 이진 값. 2편 예제 21바이트 대 JSON 72바이트 | 사람이 바로 못 읽음(8편의 도구 필요) |
| 연결 | HTTP/1.1 은 연결당 요청 하나씩 | HTTP/2 스트림으로 한 연결에 많은 호출, 양방향 스트리밍 | L4 부하 분산이 연결 단위라 쏠림(5편) |
| 실패 | HTTP 상태와 본문 형식이 서비스마다 다름 | 17개 상태 코드, deadline 전파, 재시도 정책이 라이브러리에 | 상태 코드를 제대로 고르는 책임은 앱에 남음(3편) |
브라우저의 자바스크립트 API 로는 HTTP/2 프레임과 트레일러를 직접 다룰 수 없어 gRPC 를 그대로 쓰기 어렵습니다. 그래서 gRPC-Web 이나 HTTP/JSON 변환 게이트웨이 같은 다리가 따로 있습니다(이 시리즈에서는 다루지 않음).
4. 코드 생성이 만들어 주는 것
예제 계약서입니다. 시리즈 내내 이 서비스를 씁니다(탁구 랠리 한 번을 기록하는 가상의 서비스).
syntax = "proto3";
package tt.v1;
message Rally { int32 rally_id = 1; string server = 2; repeated int32 hits = 3; bool won = 4; sint32 delta = 5; }
message Score { int32 home = 1; int32 away = 2; }
service Scoreboard {
rpc Hit(Rally) returns (Score); // 단항
rpc Watch(Rally) returns (stream Score); // 서버 스트리밍
}
이 계약에서 Hit 의 메서드 경로는 /tt.v1.Scoreboard/Hit 입니다. 3편의 프레임 그림도 이 경로를 씁니다.
생성된 클라이언트 스텁이 하는 일은 놀랄 만큼 적습니다. 직접 protoc 를 돌리지 않고, grpc-go 저장소에 들어 있는 실제 생성 파일 (헬스 체크 서비스)을 보면 메서드 하나가 이렇습니다.
const Health_Check_FullMethodName = "/grpc.health.v1.Health/Check"
func (c *healthClient) Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(HealthCheckResponse)
err := c.cc.Invoke(ctx, Health_Check_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
출처: grpc/grpc-go v1.83.2 health/grpc_health_v1/health_grpc.pb.go(protoc-gen-go-grpc v1.6.2 가 생성), Apache-2.0
- 스텁은 메서드 경로 문자열과 요청·응답 객체를 채널의
Invoke에 넘길 뿐입니다. 경로는/+ 패키지.서비스 +/+ 메서드이고, 이 문자열이 그대로 HTTP/2 의:path가 됩니다(명세 PROTOCOL-HTTP2.md 부록 A). - 서버 쪽 생성 코드는
ServiceDesc라는 표입니다. 서비스 이름 아래에 단항 메서드 목록(Methods)과 스트림 목록(Streams, 서버·클라이언트 스트리밍 여부 표시)이 있고, 각 항목이 핸들러 함수를 가리킵니다. 서버는 들어온:path로 이 표를 찾아 핸들러를 부릅니다(7편). - 생성 코드는 언어마다 다르지만 와이어는 같습니다. Go 클라이언트가 C++ 서버를 불러도 되는 이유입니다.
5. 실측: 작은 클러스터에서 gRPC 로 말하는 것들
2026-10-17 03:00~03:04(KST), Prometheus 에 다음 질의를 보냈습니다(쿠버네티스 API 서버의 서비스 프록시로 GET, 읽기만).
count by (__name__, job) ({__name__=~"grpc_.*|.*_grpc_.*|rpc_.*"})
sum by (grpc_service, grpc_method, grpc_type) (grpc_client_started_total{job="apiserver"})
sum by (operation_type) (kubelet_runtime_operations_total)
지표에 남은 gRPC 서비스 이름을 모으면 다음과 같습니다(값은 6편에서 자세히).
| 누가 → 누구에게 | gRPC 서비스(지표 라벨) | 근거 |
|---|---|---|
| kube-apiserver → etcd | etcdserverpb.KV·Watch·Lease·Maintenance |
apiserver 의 grpc_client_* 지표 |
| kubelet → containerd | runtime.v1.RuntimeService·ImageService |
kubelet_runtime_operations_total 의 operation_type 21종 |
| 앱 → OTel Collector → Tempo | OTLP TraceService/Export |
Collector 의 transport="grpc", 내보내기 대상 :4317 |
| Tempo 내부 | /tempopb.Pusher/PushBytesV2, /grpc.health.v1.Health/Check |
tempo_request_duration_seconds 의 route 라벨 |
| Argo CD UI·CLI → argocd-server | application.ApplicationService 등 114개 메서드 |
grpc_server_started_total |
| 사이드카 → istiod | istio.v1.auth.IstioCertificateService/CreateCertificate |
istiod 의 grpc_server_* |
| hubble-relay → 노드마다 Hubble | observer.Observer·peer.Peer |
릴레이 로그, gRPC 프로브(5·6편) |
이름이 etcdserverpb.KV 처럼 proto 패키지.서비스 꼴인 것은 4절의 경로 규칙이 그대로 지표 라벨로 내려왔기 때문입니다.
반대로 kubelet 은 gRPC 공용 지표를 쓰지 않고 list_containers 처럼 자기 이름으로 셉니다. 같은 gRPC 라도 관측 방식은 구현마다 다릅니다.
6. 채널의 수명: 연결은 오래, 호출은 짧게
- 채널은 만들 때 연결하지 않습니다. grpc-go 의
NewClient는 유휴(IDLE) 상태로 시작하고 첫 호출에서 이름을 풀고 연결합니다. 호출이 없으면 30분 뒤 다시 유휴로 돌아갑니다(기본값dialoptions.go:729). - 채널의 상태는 IDLE·CONNECTING·READY·TRANSIENT_FAILURE·SHUTDOWN 다섯입니다(connectivity-semantics-and-api.md).
- 그래서 채널은 재사용이 원칙입니다. 공식 성능 안내도 채널·스텁을 다시 쓰라고 권합니다(Performance Best Practices). 호출마다 채널을 새로 만들면 TCP·TLS·HTTP/2 설정 교환을 매번 치릅니다. 5편에서 보듯 kubelet 의 gRPC 프로브가 바로 그렇게 동작합니다.
- 기본 이름 풀이도 판에 따라 다릅니다.
NewClient는dns, 옛Dial·DialContext는passthrough입니다 (dialoptions.go:730,clientconn.go의 주석). 쿠버네티스 소스가Dial에서NewClient로 옮겨 갈 때passthrough:///를 붙이는 이유가 이것입니다(5편 6절).
7. 한계
- 다른 언어 구현(C++ 코어, Java)의 내부 구조는 열지 않았습니다. 이 시리즈의 코드 읽기는 grpc-go 하나입니다.
- gRPC-Web, JSON 변환 게이트웨이, ALTS 같은 인증 방식은 다루지 않습니다.
- 5절의 목록은 Prometheus 가 긁는 대상만 셉니다. 지표를 내지 않거나 긁지 않는 gRPC(예: CSI 드라이버의 유닉스 소켓, kubelet 플러그인 등록)는 빠져 있습니다(미실측).
8. 작은 클러스터와의 연결
클러스터를 매일 만지면서도 gRPC 를 직접 짠 적이 없다면, 사실은 이미 하루에 수억 번 쓰고 있다는 점부터 확인하면 좋습니다. 제어 노드 A 의 etcd 는 기동 이후 Range 를 약 8,333만 번 처리했고, kubelet 8대는 CRI 목록 조회만 합쳐 약 6,900만 번을 불렀습니다(6편). 다음 편은 그 호출 하나하나가 어떤 바이트로 나가는지부터 봅니다.
티티 한마디. 서식만 잘 만들어 두면 상대가 Go 든 C++ 든 상관없어요. 칸 번호만 바꾸지 마세요. 번호가 곧 약속이거든요!
참고
모든 자료는 2026-10-17 에 열람했습니다.
- grpc.io About, Performance Best Practices
- CNCF 프로젝트 페이지: gRPC
- grpc/grpc v1.84.0:
doc/PROTOCOL-HTTP2.md,doc/connectivity-semantics-and-api.md - grpc/grpc-go v1.83.2:
clientconn.go,dialoptions.go,health/grpc_health_v1/health_grpc.pb.go - 인용 코드: grpc-go v1.83.2, Apache License 2.0.