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 연동까지 실제 구현 과정을 공유합니다.
DevOps에어갭 환경에서 Helm Chart와 컨테이너 이미지 무결성 검증하기
공격자 관점으로 이해하는 GPG 서명, Cosign 검증, SHA256 해시 비교의 필요성과 실무 적용법
DevOps거짓 CI 알림을 따라 내려가니 CNI 데드락 — 위아래 두 곳을 근본 수정한 기록
정적 분석이 실패해도 배포는 되는데 알림은 배포 실패처럼 울렸다. 그 거짓말을 따라가 CNI 부트스트랩 데드락과 알림 배선 결함을 양쪽 끝에서 각각 root fix한 기록.