Observability gRPC Kubernetes

HTTP/2와 gRPC 이해하기

HTTP/2 프레임 구조부터 gRPC 동작 방식, Protobuf 인코딩까지 직접 확인하며 이해합니다.

“gRPC는 HTTP/2 기반”이라는 말을 많이 들어봤지만, 실제로 무슨 의미일까요?

이 글에서는 직접 명령어를 실행하며 확인해봅니다.

사전 준비

Terminal window
brew install nghttp2 # HTTP/2 클라이언트
brew install grpcurl # gRPC 클라이언트

1. HTTP/1.1 vs HTTP/2

HTTP/1.1 요청

Terminal window
curl -v --http1.1 https://nghttp2.org 2>&1 | head -15
Terminal window
* ALPN: curl offers http/1.1
> GET / HTTP/1.1 # ← 텍스트 형태
> Host: nghttp2.org
< HTTP/1.1 200 OK

HTTP/2 요청

nghttp란? HTTP/2 전용 클라이언트입니다. 프레임 단위로 통신 내용을 볼 수 있습니다.

Terminal window
nghttp -nv https://nghttp2.org
Terminal window
The negotiated protocol: h2
# 연결 설정
[ 0.095] send SETTINGS frame # ← 프레임!
[SETTINGS_MAX_CONCURRENT_STREAMS:100]
# 요청
[ 0.095] send HEADERS frame # ← GET 요청도 프레임
:method: GET
:path: /
# 응답
[ 0.140] recv HEADERS frame
[ 0.140] recv DATA frame <length=6324> # ← 본문도 프레임
# 서버 푸시 (요청 안 했는데 서버가 먼저 보냄)
[ 0.136] recv PUSH_PROMISE frame
(promised_stream_id=2)
[ 0.168] recv DATA frame # ← CSS 파일

핵심 차이

HTTP/1.1HTTP/2
형식텍스트바이너리 프레임
동시 요청순차 처리멀티플렉싱 (stream_id)
서버 푸시불가능가능

2. gRPC = HTTP/2 + Protobuf

gRPC는 HTTP/2 프레임 안에 Protobuf 데이터를 담아 전송합니다.

┌─────────────────────────────────────┐
│ HTTP/2 DATA Frame │
│ ┌───────────────────────────────┐ │
│ │ gRPC Message │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ Protobuf Payload │ │ │
│ │ └─────────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘

직접 확인하기

간단한 gRPC 서버를 만들어봅니다.

echo.proto
syntax = "proto3";
package echo;
service Echo {
rpc Say(EchoRequest) returns (EchoResponse);
}
message EchoRequest {
string message = 1;
}
message EchoResponse {
string message = 1;
}
Terminal window
# 서버 실행 후
grpcurl -v -plaintext -d '{"message": "Hello"}' \
-import-path . -proto echo.proto \
localhost:50051 echo.Echo/Say
Terminal window
Response headers received:
content-type: application/grpc # ← gRPC 전용 Content-Type
Response contents:
{
"message": "Echo: Hello"
}

핵심: content-type: application/grpc로 gRPC임을 표시하지만, 내부는 HTTP/2 프레임입니다.


3. Protobuf 인코딩

gRPC 메시지 안의 Protobuf가 어떻게 생겼는지 확인해봅니다.

import echo_pb2
req = echo_pb2.EchoRequest(message='Hello')
data = req.SerializeToString()
print(f'Protobuf: {data.hex()}')
Protobuf: 0a0548656c6c6f (7 bytes)

바이트 분석

0a 05 48 65 6c 6c 6f
│ │ └──────────────┘
│ │ "Hello"
│ └── 길이: 5
└── 필드 태그 (field=1, type=string)

JSON과 비교

JSON: {"message":"Hello"} → 19 bytes
Protobuf: 0a0548656c6c6f → 7 bytes

Protobuf는 필드명 대신 번호만 저장해서 63% 더 작습니다.


4. 왜 gRPC는 HTTP/2가 필수인가?

gRPC는 HTTP/2의 다음 기능에 의존합니다:

  1. 바이너리 프레이밍 - gRPC 메시지가 DATA 프레임에 포함
  2. 멀티플렉싱 - 하나의 연결에서 여러 요청 동시 처리
  3. 양방향 스트리밍 - 클라이언트/서버 모두 스트림 가능
  4. 헤더 압축 - HPACK으로 메타데이터 효율적 전송

HTTP/1.1은 이 기능들을 지원하지 않아서 gRPC가 동작할 수 없습니다.


5. 실무 문제: Nginx에서 gRPC

문제

Client ──HTTP/2──> Nginx ──HTTP/1.1──> Backend
(gRPC) (깨짐!)

Nginx는 기본적으로 백엔드와 HTTP/1.1로 통신합니다. HTTP/2 프레임이 변환되면서 gRPC가 깨집니다.

해결

annotations:
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
Client ──HTTP/2──> Nginx ──HTTP/2──> Backend
(gRPC) (유지!)

정리

개념설명
HTTP/2바이너리 프레임 기반, 멀티플렉싱 지원
gRPCHTTP/2 위에서 동작하는 RPC 프레임워크
Protobuf바이너리 직렬화 포맷, JSON보다 작고 빠름

gRPC = HTTP/2 (전송) + Protobuf (인코딩) + RPC (호출 방식)

이 구조를 이해하면 “왜 Nginx에서 gRPC가 안 되지?”같은 문제를 해결할 수 있습니다.

관련 콘텐츠

댓글