OSI 계층으로 읽는 웹 서버 흐름
OSI 모델은 네트워크 문제를 계층별로 좁혀 진단하는 기준이고, Web Server와 Nginx는 요청이 내부 애플리케이션까지 흘러가는 과정을 이해하게 해준다. 특히 502와 504, TLS 인증서, Health Check, Security Group 같은 실무 장애를 계층 관점으로 구분하는 데 초점을 둔다.
Script Companion
오디오와 함께 스크립트 보기
- 01
OSI 모델은 네트워크를 외우기 위한 표라기보다, 문제가 생겼을 때 어느 계층을 먼저 의심할지 정하는 기준입니다. 브라우저 요청이 80번 포트로 들어왔는데 3000번 포트의 Node.js 서버에 닿는지, HTTPS 인증서 문제인지, 포트가 막힌 문제인지, 애플리케이션 응답 문제인지를 나누는 데 쓰입니다. 실무에서는 모든 계층을 똑같이 보지 않고, 주로 7계층 HTTP 오류, 4계층 포트와 방화벽, 3계층 IP와 VPC, 6계층 TLS 인증서를 중심으로 좁혀 갑니다.
- 02
OSI 7계층은 국제 택배처럼 생각할 수 있습니다. 편지 내용이 HTTP 요청이라면, 암호화 봉투는 TLS, 주소 라벨은 IP, 택배 차량에 싣는 과정은 TCP, 실제 도로 이동은 물리 계층에 가깝습니다. 중요한 점은 각 단계가 독립적이라는 것입니다. HTTP 코드를 작성할 때 TCP 3-way handshake를 직접 다루지 않아도 되는 이유가 여기에 있습니다. 실제 통신은 TCP/IP 4계층으로 더 단순하게 설명되는 경우가 많지만, 장애 진단에서는 OSI 7계층이 원인을 분리하는 언어로 유용합니다.
- 03
이 독립성은 Layered Abstraction Principle, 즉 계층화된 추상화 원리로 볼 수 있습니다. 상위 계층은 하위 계층의 구현 세부를 몰라도 안정된 인터페이스를 통해 기능을 사용합니다. Nginx가 TLS 핸드셰이크를 처리한 뒤 내부 Nest.js 서버에는 평문 HTTP로 넘기는 것도 이 분리의 예입니다. 같은 원리는 OS 커널과 유저 공간의 read() 시스템 콜, React 컴포넌트 트리의 props, DB 드라이버와 MySQL 바이너리 프로토콜 사이에서도 반복됩니다.
- 04
프론트엔드에서 fetch()를 호출하면 개발자는 JavaScript 레벨의 요청과 await response.json() 정도만 보게 됩니다. 하지만 브라우저 내부에서는 TCP 3-way handshake, TLS handshake, HTTP 요청 전송, TCP 수신 확인, TLS 복호화가 이어집니다. 이때 TCP는 연결을 확인하고 순서를 보장하는 신뢰성 높은 방식이라 HTTP, DB 연결, SSH에 쓰입니다. UDP는 연결 없이 빠르게 보내지만 손실 가능성이 있어 DNS 쿼리 초기 과정, 동영상 스트리밍, WebRTC 같은 곳에서 쓰입니다.
- 05
Web Server로서 Nginx를 볼 때 핵심은 리버스 프록시와 이벤트 기반 비동기 아키텍처입니다. Nginx는 클라이언트 요청을 먼저 받고, SSL 처리나 로드밸런싱을 맡은 뒤 내부 서버로 전달합니다. 내부 원리는 I/O Multiplexing입니다. epoll, kqueue, IOCP 같은 시스템 콜을 통해 하나의 스레드가 많은 소켓의 준비 이벤트를 감시하고, 준비된 것만 처리합니다. 요청마다 프로세스나 스레드를 만드는 Apache prefork 방식과 달리, 연결 수가 늘어도 컨텍스트 스위치 비용과 메모리 사용량이 선형으로 커지지 않는다는 점이 중요합니다.
- 06
이 비동기 I/O 모델은 Nginx에만 있는 특징이 아닙니다. Node.js의 libuv도 단일 이벤트 루프가 epoll이나 kqueue로 많은 fd를 감시하고 콜백을 디스패치합니다. Redis 단일 스레드, HAProxy, Envoy의 데이터 플레인에서도 유사한 원리가 보입니다. 다만 이 모델은 I/O bound라는 가정 위에서 강합니다. 이미지 인코딩이나 무거운 JSON 직렬화 같은 CPU 바운드 작업이 이벤트 루프를 오래 점유하면 흐름이 깨지고, 그때는 워커 프로세스를 늘리거나 별도 워커 풀로 오프로드해야 합니다.
- 07
HTTP/2는 클라이언트와 Nginx 사이의 성능 흐름을 이해할 때 중요합니다. HTTP/1.1에서는 요청마다 새 TCP 연결을 열거나 순차 처리 때문에 헤드-오브-라인 블로킹이 생길 수 있습니다. HTTP/2는 하나의 TCP 연결로 여러 요청을 동시에 처리하는 멀티플렉싱을 제공해 지연 시간을 줄입니다. ALB에서 ECS Fargate로 가는 구성에서는 ALB가 이미 HTTP/2를 지원하므로 별도 Nginx 설정 없이 적용될 수 있고, Target Group 프로토콜 버전을 HTTP2로 설정하면 ALB에서 컨테이너 구간도 HTTP/2로 동작합니다.
- 08
장애를 보면 계층 구분이 바로 실무 도구가 됩니다. 502 Bad Gateway는 Nginx가 업스트림 서버에 연결하지 못하는 경우로, Node.js 프로세스가 죽었거나 proxy_pass 포트가 틀렸거나 서버가 아직 기동 중일 수 있습니다. 504 Gateway Timeout은 업스트림 응답이 proxy_read_timeout 기본 60초보다 늦을 때 나타나며, 근본 해결은 단순히 시간을 늘리는 것이 아니라 무거운 작업을 Queue로 분리하고 즉시 응답하는 쪽입니다. 대용량 쿠키나 긴 Authorization 헤더 때문에 502가 나는 경우에는 실제 헤더 크기를 먼저 측정한 뒤 proxy_buffer_size를 필요한 만큼만 조정해야 합니다.
- 09
ECS Fargate와 ALB 환경에서는 Health Check와 Security Group도 함께 봐야 합니다. ALB Health Check 경로인 /health가 Nest.js에 없거나, 체크 포트와 프로토콜이 실제 앱과 다르거나, ALB에서 컨테이너 포트로 가는 Security Group이 막혀 있으면 태스크가 Unhealthy로 반복 재시작될 수 있습니다. HTTPS 인증서 오류는 6계층 문제로, 인증서 도메인과 실제 접속 도메인의 불일치나 HTTP 포트로 HTTPS 요청을 보낸 경우를 확인합니다. 같은 증상처럼 보여도 7계층 응답 문제인지, 4계층 포트 문제인지, 6계층 TLS 문제인지가 해결 방향을 가릅니다.
- 10
마지막으로 조심해야 할 장애 패턴은 Cascading Failure입니다. DB가 느려졌는데 /health가 DB를 조회하지 않으면 ALB는 여전히 Target을 Healthy로 판단하고 새 요청을 계속 보냅니다. L4 TCP 연결은 살아 있지만 L7 HTTP 응답은 늦어지고, 클라이언트 재시도가 부하를 더 키울 수 있습니다. 또 Nginx의 max_fails, fail_timeout, slow_start는 upstream에 server가 하나뿐이면 무시되고 해당 서버는 unavailable로 간주되지 않습니다. 정리하면 OSI 7계층은 7, 4, 3, 6계층 순서로 원인을 좁히는 진단 기준이고, Nginx와 ALB는 요청 전달, SSL 처리, Health Check, 포트 접근 제어를 이해하는 핵심 연결점입니다.
같은 레이어