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 연동까지 실제 구현 과정을 공유합니다.
DevOpsLinkedIn에서 발견한 Tencent WeKnora, GraphRAG PoC하고 PR까지 Merged
LinkedIn에서 발견한 Tencent WeKnora를 홈 Kubernetes 클러스터에서 PoC하고, Helm Chart PR까지 Merge한 여정
DevOpsEC2 해킹당하고, DevSecOps 파이프라인을 구축하다
사이드 프로젝트 개발 서버가 털린 경험을 계기로 DevSecOps 파이프라인을 구축한 이야기입니다.