HTTP/2와 gRPC 이해하기
HTTP/2 프레임 구조부터 gRPC 동작 방식, Protobuf 인코딩까지 직접 확인하며 이해합니다.
“gRPC는 HTTP/2 기반”이라는 말을 많이 들어봤지만, 실제로 무슨 의미일까요?
이 글에서는 직접 명령어를 실행하며 확인해봅니다.
사전 준비
brew install nghttp2 # HTTP/2 클라이언트brew install grpcurl # gRPC 클라이언트1. HTTP/1.1 vs HTTP/2
HTTP/1.1 요청
curl -v --http1.1 https://nghttp2.org 2>&1 | head -15* ALPN: curl offers http/1.1> GET / HTTP/1.1 # ← 텍스트 형태> Host: nghttp2.org< HTTP/1.1 200 OKHTTP/2 요청
nghttp란? HTTP/2 전용 클라이언트입니다. 프레임 단위로 통신 내용을 볼 수 있습니다.
nghttp -nv https://nghttp2.orgThe 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.1 | HTTP/2 | |
|---|---|---|
| 형식 | 텍스트 | 바이너리 프레임 |
| 동시 요청 | 순차 처리 | 멀티플렉싱 (stream_id) |
| 서버 푸시 | 불가능 | 가능 |
2. gRPC = HTTP/2 + Protobuf
gRPC는 HTTP/2 프레임 안에 Protobuf 데이터를 담아 전송합니다.
┌─────────────────────────────────────┐│ HTTP/2 DATA Frame ││ ┌───────────────────────────────┐ ││ │ gRPC Message │ ││ │ ┌─────────────────────────┐ │ ││ │ │ Protobuf Payload │ │ ││ │ └─────────────────────────┘ │ ││ └───────────────────────────────┘ │└─────────────────────────────────────┘직접 확인하기
간단한 gRPC 서버를 만들어봅니다.
syntax = "proto3";package echo;
service Echo { rpc Say(EchoRequest) returns (EchoResponse);}
message EchoRequest { string message = 1;}
message EchoResponse { string message = 1;}# 서버 실행 후grpcurl -v -plaintext -d '{"message": "Hello"}' \ -import-path . -proto echo.proto \ localhost:50051 echo.Echo/SayResponse 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 bytesProtobuf: 0a0548656c6c6f → 7 bytesProtobuf는 필드명 대신 번호만 저장해서 63% 더 작습니다.
4. 왜 gRPC는 HTTP/2가 필수인가?
gRPC는 HTTP/2의 다음 기능에 의존합니다:
- 바이너리 프레이밍 - gRPC 메시지가 DATA 프레임에 포함
- 멀티플렉싱 - 하나의 연결에서 여러 요청 동시 처리
- 양방향 스트리밍 - 클라이언트/서버 모두 스트림 가능
- 헤더 압축 - 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 | 바이너리 프레임 기반, 멀티플렉싱 지원 |
| gRPC | HTTP/2 위에서 동작하는 RPC 프레임워크 |
| Protobuf | 바이너리 직렬화 포맷, JSON보다 작고 빠름 |
gRPC = HTTP/2 (전송) + Protobuf (인코딩) + RPC (호출 방식)
이 구조를 이해하면 “왜 Nginx에서 gRPC가 안 되지?”같은 문제를 해결할 수 있습니다.
관련 콘텐츠
mTLS로 OTLP 엔드포인트 보호하기 (feat. Claude Code Hook)
Kubernetes에서 OTLP 수집기를 외부에 노출할 때 mTLS로 접근을 제한하는 방법을 다룹니다. 인증서 생성부터 Nginx Ingress 설정, Claude Code Hook 연동까지 실제 구현 과정을 공유합니다.
DevOpsn8n v1 → v2 업그레이드: Kubernetes에서 메이저 버전 넘기
n8n v1.120.4에서 v2.9.4로 2단계 순차 업그레이드한 기록. 연쇄 CVE 대응, Migration Report 활용, community node emptyDir 트레이드오프, Queue 모드 호환성 튜닝.
DevOpsLinkedIn에서 발견한 Tencent WeKnora, GraphRAG PoC하고 PR까지 Merged
LinkedIn에서 발견한 Tencent WeKnora를 홈 Kubernetes 클러스터에서 PoC하고, Helm Chart PR까지 Merge한 여정