[Network] WAS·웹서버·프록시·로드밸런싱(요청이 도달하기까지)
개요
브라우저가 도메인을 입력하고 엔터를 누른 순간부터 백엔드의 비즈니스 코드가 실행되기까지, 요청은 여러 계층을 거친다. 클라이언트 입장에서는 단순히 한 번의 HTTP 요청이지만, 실제로는 DNS가 IP 를 풀어주고, TCP가 연결을 맺고, TLS가 암호를 풀고, 로드 밸런서가 트래픽을 나누고, 리버스 프록시가 정적 자원을 가로채고, 그제서야 WAS(Web Application Server)가 비즈니스 로직을 실행한다. 이 흐름의 각 단계는 자기 책임을 명확히 가지고 있다.
외부 사용자는 웹 서버(Web Server, WS), 웹 애플리케이션 서버(WAS), 리버스 프록시·포워드 프록시, 그리고 로드 밸런서 등을 통해 요청을 한다. 추가적으로 공용 인터넷을 타고 들어오는 외부 사용자 말고도, 재택, 출장 중인 원격 사용자는 사내 네트워크에 직접 닿을 수 없어 VPN(Virtual Private Network) 터널을 이용하여 요청할 수 있다. VPN은 원격 사용자를 같은 로드 밸런서·프록시·WAS 체인에 합류시키는 또 인터페이스이다.
클라이언트와 WAS 사이에는 책임 분리를 위해 여러 계층이 존재하고, 각 계층은 자기 일에만 충실해야 한다. 웹 서버는 정적 자원과 TLS 종료를, WAS는 비즈니스 로직과 동적 응답을, 프록시는 보안과 캐싱과 라우팅을, 로드 밸런서는 분산을 담당한다. VPN은 그 체인 앞단에서 원격 사용자를 사내 네트워크 경계 안으로 들여보내는 역할만 맡는다. 이 분리 덕분에 시스템은 수평 확장 가능해지고, 보안은 한 곳에 집중되며, 장애를 계층 단위로 분리할 수 있다.
| 계층 | 대표 구현 | 주요 책임 |
|---|---|---|
| 웹 서버(WS) | Nginx, Apache HTTP Server | 정적 자원, TLS 종료, 리버스 프록시 |
| WAS | Tomcat, Jetty, WebLogic | 비즈니스 로직, 동적 응답, 트랜잭션 |
| 프록시 | Squid(포워드), Nginx(리버스) | 보안·캐싱·라우팅 |
| 로드 밸런서 | HAProxy, AWS ELB(NLB/ALB) | 트래픽 분산 |
웹 서버와 WAS
웹 서버(WS)
웹 서버는 HTTP 요청을 받아 정적 자원(HTML, CSS, JavaScript, 이미지, 동영상)을 응답한다. 대표적인 구현으로 Apache HTTP Server, Nginx, Microsoft IIS, Cloudflare 등이 있다. 정적 자원만 다루므로 동적 처리(데이터베이스 조회, 사용자별 페이지 생성)는 불가능하고, 이런 동적 처리 필요성에서 CGI(Common Gateway Interface)가 생겼다.
Apache HTTP Server는 프로세스, 스레드 기반으로 동작한다. 마스터 프로세스 하나가 여러 워커 스레드(또는 워커 프로세스)를 거느리고, 요청 하나당 스레드 하나를 할당해 블로킹 I/O로 처리한다. 단순하지만 동시 접속이 많아지면 스레드 수가 폭증해 메모리와 컨텍스트 스위칭 비용이 커진다.
Nginx는 이벤트 기반(event-driven)으로 동작한다. 마스터 프로세스 하나가 여러 워커 프로세스를 거느리고, 각 워커는 이벤트 루프 안에서 비동기 I/O 로 수많은 커넥션을 동시에 처리한다. 워커 수는 보통 CPU 코어 수와 동일하게 두고, 블로킹 호출은 별도 스레드 풀로 분리한다. 정적 자원 서빙, 역방향 프록시, SSL 종료에 강점이 있다.
두 모델의 차이를 그림으로 보면 명확하다. Apache는 요청 하나마다 워커 스레드 하나가 붙어 블로킹 I/O 로 그 요청을 끝까지 점유하고, Nginx는 소수의 워커가 이벤트 루프로 다수 커넥션을 번갈아 처리한다.
worker_processes auto;
events {
worker_connections 8192;
use epoll;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/ssl/example.crt;
ssl_certificate_key /etc/ssl/example.key;
location /static/ {
root /var/www;
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable";
}
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 30s;
}
}
upstream backend {
least_conn;
server 10.0.0.11:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.13:8080 max_fails=3 fail_timeout=30s;
}
}CGI와 그 한계
CGI는 웹 서버와 외부 프로그램이 정보를 주고받는 규약이다. 요청이 들어오면 웹 서버는 fork로 새 프로세스를 띄워 외부 프로그램(예: 펄·파이썬 스크립트)을 실행하고, 정보를 주고 받은 뒤 응답을 받은 뒤 프로세스를 종료시킨다. 요청 단위로 프로세스를 생성, 소멸시키므로 자원 사용이 비효율적이었다. 이 한계를 해결하려는 시도가 FastCGI, mod_php, 서블릿 컨테이너로 이어졌고, 그 결과가 WAS이다.
WAS(Web Application Server)
WAS는 HTTP 요청을 받아 비즈니스 로직을 수행하고 동적 응답(JSON, HTML 렌더링 결과)을 돌려준다. 데이터베이스 조회, 트랜잭션 관리, 인증과 인가, 외부 API 호출 등이 WAS의 책임이다. 대표 구현으로 Apache Tomcat, Jetty, Oracle WebLogic, IBM WebSphere가 있다. Spring Boot의 내장 서버도 Tomcat, Jetty, Undertow 중 하나이다.
WAS는 스레드 풀 기반으로 요청을 처리한다. 요청이 들어오면 풀에서 스레드 하나를 할당하고, 그 스레드 안에서 비즈니스 로직과 JDBC 호출이 진행된다. 한 요청이 끝나면 스레드는 풀로 돌아간다. 이 모델 덕분에 매 요청마다 스레드를 생성, 소멸시키는 비용이 사라지고, 동시 요청 수만큼의 스레드만 유지하면 된다.
Web Server와 WAS를 분리하는 이유
첫째, 확장성. 웹 서버를 앞에 두고 뒷단의 WAS만 수평 확장(스케일 아웃)하면 트래픽이 늘어날 때 대응이 쉬워진다. 정적 자원은 웹 서버가 캐시, CDN 으로 처리하고, 동적 요청만 WAS로 흘려보낸다.
둘째, 보안. HTTPS 종료(TLS termination)를 웹 서버가 담당하면 WAS 는 평문 HTTP만 다루면 된다. 인증서 갱신, 암호 스위트 관리, 보안 헤더 부여 같은 운영 작업이 웹 서버 한 곳에 집중된다.
셋째, 부하 분산. 로드 밸런서 또는 리버스 프록시 기능을 가진 웹 서버가 뒷단 WAS 인스턴스들에게 트래픽을 골고루 보낸다. 한 인스턴스에 요청이 몰리는 핫스팟을 막을 수 있다.
| 항목 | Web Server | WAS |
|---|---|---|
| 주 응답 자원 | 정적 자원 | 동적 응답 |
| 비즈니스 로직 | 처리하지 않음 | 처리함 |
| 동시성 모델 | 이벤트 루프(Nginx) 또는 프로세스/스레드(Apache) | 스레드 풀 |
| 트랜잭션 | 없음 | 데이터베이스 트랜잭션 관리 |
| 대표 구현 | Nginx, Apache HTTP Server | Tomcat, Jetty, WebLogic |
| 대표 책임 | TLS 종료, 정적 자원, 캐시, 리버스 프록시 | 비즈니스 로직, 인증, 동적 응답 |
포워드 프록시와 리버스 프록시
프록시(Proxy)는 클라이언트와 서버 사이에 위치해 어느 한쪽을 대신해 요청을 전달하는 중개자이다. 누구를 대신하느냐에 따라 두 가지로 나뉜다.
포워드 프록시
포워드 프록시는 클라이언트 측에 위치해 클라이언트를 대신해 외부 서버에 요청한다. 사내망에서 외부 인터넷으로 나가는 트래픽이 이 프록시를 통과한다. 외부 서버는 클라이언트의 실제 IP를 보지 못하고 프록시의 IP만 본다.
주요 용도는 다음과 같다.
보안 측면. 프록시가 요청 정책(허용 도메인, 차단 도메인)을 검사하고, 통과한 요청만 외부로 내보낸다. 외부 서버는 실제 클라이언트의 IP를 알 수 없으므로 익명성이 보장된다.
캐싱 측면. 같은 사이트의 응답을 프록시 단에서 캐시해 두면 사내 트래픽이 외부로 매번 나가지 않아 비용, 지연이 줄어든다.
콘텐츠 필터링 측면. 특정 카테고리 사이트를 차단해 사내 정책을 강제할 수 있다.
리버스 프록시
리버스 프록시는 서버 측에 위치해 서버를 대신해 클라이언트의 요청을 받는다. 클라이언트는 자기가 진짜 서버에 직접 붙는다고 생각하지만, 실제로는 프록시가 받아 뒷단의 적절한 서버로 전달한다.
주요 용도는 다음과 같다.
로드 밸런싱 측면. 뒷단의 여러 WAS 인스턴스에 요청을 골고루 분배한다.
보안 측면. 클라이언트는 뒷단 서버의 IP, 구조를 모른다. WAS는 내부망에 두고 프록시만 공개망에 노출시켜 공격 표면을 줄인다.
캐싱 측면. 정적 자원 또는 자주 조회되는 동적 응답을 프록시 단에서 캐시한다.
암호화 측면. HTTPS 의 SSL/TLS 종료를 프록시에서 처리해 WAS는 평문 HTTP만 다루게 한다.
| 구분 | 포워드 프록시 | 리버스 프록시 |
|---|---|---|
| 위치 | 클라이언트 측 | 서버 측 |
| 대신하는 주체 | 클라이언트 | 서버 |
| 클라이언트의 인지 | 프록시 사용을 알고 설정함 | 모름(직접 서버와 통신한다고 생각) |
| 서버의 인지 | 실제 클라이언트 IP 를 모름 | 자기가 진짜 서버라고 생각 |
| 대표 용도 | 사내망 외부 접근 제어, 익명성 | 로드 밸런싱, TLS 종료, 보안 |
| 대표 구현 | Squid, 사내 SSO 게이트웨이 | Nginx, HAProxy, AWS ALB |
리버스 프록시 설정 예시이다. Nginx 가 api.example.com 으로 들어온 요청을 뒷단 WAS 풀로 보내고, 응답을 캐시한다.
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:64m max_size=2g inactive=10m use_temp_path=off;
server {
listen 443 ssl http2;
server_name api.example.com;
location /api/v1/products {
proxy_pass http://backend;
proxy_cache api_cache;
proxy_cache_valid 200 5m;
proxy_cache_key "$scheme$request_method$host$request_uri";
add_header X-Cache-Status $upstream_cache_status;
}
location /api/ {
proxy_pass http://backend;
}
}로드 밸런서
로드 밸런서는 여러 클라이언트의 요청을 여러 백엔드 인스턴스에 균등하게 분산한다. 단일 인스턴스의 성능 한계를 넘기 위한 수평 확장의 핵심 컴포넌트이고, 동시에 한 인스턴스 장애가 전체 서비스 장애로 번지지 않게 막아주는 가용성 장치이다.
분배 알고리즘
| 알고리즘 | 동작 | 사용처 |
|---|---|---|
| 라운드 로빈(Round Robin) | 순서대로 돌아가며 분배 | 인스턴스 성능이 동일할 때 |
| 가중 라운드 로빈(Weighted RR) | 성능에 따라 가중치 배분 | 인스턴스 사양이 섞여 있을 때 |
| 최소 연결(Least Connections) | 현재 연결 수가 가장 적은 인스턴스 | 요청 처리 시간이 들쭉날쭉할 때 |
| IP 해시(IP Hash) | 클라이언트 IP 기반 고정 할당 | 세션 고정(sticky session)이 필요할 때 |
| 응답 시간 기반 | 평균 응답 시간이 짧은 인스턴스 | 인스턴스 상태 편차가 클 때 |
L4 로드 밸런서
L4 로드 밸런서는 OSI 4계층(전송 계층)에서 동작한다. 클라이언트가 보낸 패킷의 IP와 포트만 보고 어느 백엔드로 보낼지 결정한다. 응용 계층의 정보(URL, 헤더, 쿠키)는 보지 않는다.
기본 동작은 다음과 같다. 클라이언트는 LB의 가상 IP(VIP)로 연결을 맺는다. LB는 이 연결을 백엔드 중 하나에게 매핑하고, 양쪽 사이의 패킷을 중계한다. 클라이언트 입장에서는 LB와 TCP 연결을 맺은 것이지 실제 백엔드와 직접 연결한 것은 아니다.
| 단계 | source | destination |
|---|---|---|
| 클라이언트가 LB 로 | client IP | VIP |
| LB 가 백엔드로 | LB IP 또는 client IP | 백엔드 실제 IP |
L4 LB의 장점은 단순함과 속도이다. 패킷의 IP, 포트만 보고 분배하므로 처리 비용이 낮고, 초당 수십만~수백만 패킷을 다룰 수 있다. AWS NLB(Network Load Balancer), HAProxy의 TCP 모드가 대표 예이다.
단점은 응용 계층 정보를 활용할 수 없다는 점이다. URL 경로별 분배, 쿠키 기반 세션 고정, A/B 테스트용 헤더 라우팅 같은 작업은 불가능하다.
L7 로드 밸런서
L7 로드 밸런서는 OSI 7계층(응용 계층)에서 동작한다. HTTP 메시지를 파싱해 URL 경로, 헤더, 쿠키 같은 정보를 보고 분배 대상을 결정할 수 있다.
L7 LB는 클라이언트와 LB 사이의 연결과 LB 와 백엔드 사이의 연결을 별도로 관리한다. 즉, 클라이언트 측 TCP 연결과 백엔드 측 TCP 연결이 각각 따로 존재하고, LB는 HTTP 메시지 단위로 라우팅 결정을 내린다. 이 덕분에 keep-alive, HTTP/2 멀티플렉싱, 연결 풀링을 양쪽에서 별도로 최적화할 수 있다.
L7 LB 의 장점은 라우팅 유연성이다. URL 경로 /api/payments 는 결제 서비스로, /api/users 는 사용자 서비스로 보낼 수 있다. 헤더 X-Beta: true 가 있으면 베타 인스턴스로 보내는 카나리 배포도 가능하다. 응답 캐싱, TLS 종료, HTTP 압축 같은 부가 기능도 함께 처리한다.
단점은 비용이다. HTTP 파싱과 별도 연결 관리가 필요하므로 L4 보다 자원 소모가 크다. 그리고 응용 계층 정보를 다루므로 잘못 구성하면 보안 사고(헤더 인젝션, 라우팅 우회)의 표면이 된다.
| 항목 | L4 LB | L7 LB |
|---|---|---|
| 동작 계층 | 전송 계층(IP, Port) | 응용 계층(HTTP) |
| 라우팅 정보 | IP, Port | URL, 헤더, 쿠키, 메서드 |
| 처리 비용 | 낮음 | 높음 |
| 연결 모델 | 클라이언트 ↔ 백엔드 패킷 중계 | 클라이언트-LB / LB-백엔드 분리 |
| 대표 기능 | 단순 TCP 분산 | URL 라우팅, 캐시, TLS 종료, 카나리 |
| 대표 구현 | AWS NLB, HAProxy(TCP 모드) | Nginx, AWS ALB, Spring Cloud Gateway |
앞단에 L4 로드 밸런서를 두어 대량 트래픽을 낮은 비용으로 받아 넘기고, 그 뒤에 L7 로드 밸런서를 두어 TLS 종료와 URL, 헤더 기반 라우팅을 맡긴다. L4는 IP, Port만 보고 뒷단으로 패킷을 넘기고, L7이 HTTP를 파싱해 서비스별로 분기한다.
클라이언트 요청이 도달하는 전체 흐름
지금까지 정리한 계층이 실제로 한 요청 안에서 어떻게 줄지어 동작하는지 시퀀스로 정리한다. 시간 축이 본질적이므로 시퀀스로 그린다.
sequenceDiagram autonumber participant C as Client(Browser) participant LB as L4 LB (NLB) participant WS as Reverse Proxy (Nginx, L7) participant W1 as WAS Instance 1 participant DB as Database C->>LB: TLS handshake, HTTPS GET /api/orders/1 LB->>WS: TCP 패킷 중계 (TLS 미종료) Note over WS: TLS 종료, 평문 HTTP 로 디캡슐화 WS->>WS: 캐시 조회 (MISS) WS->>W1: GET /api/orders/1 (X-Forwarded-For 부여) W1->>DB: SELECT ... WHERE id=1 DB-->>W1: row W1-->>WS: 200 OK, JSON WS->>WS: 응답 캐시 (Cache-Control 에 따라) WS-->>LB: 200 OK LB-->>C: 200 OK (암호화 응답)
LB는 트래픽 분산만, 웹 서버는 TLS 종료와 캐싱과 정적 자원 응답을, WAS는 비즈니스 로직과 데이터베이스 접근을 담당한다.
VPN과 원격 사용자의 진입
일반적으로는 공용 인터넷을 타고 들어오는 외부 사용자를 전제로 한다. 그러나 원격 사용자가 사내 네트워크의 자원(내부 관리 도구, 사내 전용 WAS)에 다른 방식으로 접근해야 한다. 사내 네트워크는 외부에서 라우팅이 불가능한 사설 IP 대역을 쓰므로 원격 사용자의 요청은 로드 밸런서에 닿기 전에 먼저 사내 네트워크 경계 안으로 들어와야 한다. 그 입구가 VPN(Virtual Private Network)이다.
VPN은 물리적으로 떨어진 클라이언트가 사내 네트워크에 접속하기 위한 소프트웨어 터널이다. 개요의 컴포넌트 다이어그램에서 원격 사용자가 VPN 게이트웨이를 거쳐 로드 밸런서에 합류하던 그 경로가 바로 이것이다. 즉 VPN은 외부 사용자가 타는 로드 밸런서, 프록시, WAS 체인 앞에 원격 사용자만을 위한 진입 구간을 하나 더 개설하는 것이다.
동작 원리
사내 네트워크가 쓰는 사설 IP 대역은 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 이다. 외부 클라이언트가 이 사내 IP로 직접 연결할 방법은 없으므로, VPN은 사내 IP 패킷을 외부 인터넷용 패킷으로 한 번 더 감싸 전송한다. 이를 캡슐화(Encapsulation) 또는 터널링이라 한다.
- 공유기 IP: 9.9.9.10
- Secure Gateway IP: 3.3.3.1
- VPN 클라이언트가 받은 사내 IP: 3.3.3.200
- 사내 서버 IP: 3.3.3.20
흐름은 다음과 같다.
첫째, 클라이언트의 애플리케이션은 사내 서버(3.3.3.20)로 보내는 IP 패킷을 만든다. 이 inner 패킷의 source는 클라이언트의 사내 IP(3.3.3.200), destination은 사내 서버 IP(3.3.3.20)이다.
둘째, VPN 클라이언트는 이 inner 패킷을 그대로 본문으로 두고, 그 앞에 outer IP 헤더를 붙인다. outer의 source는 공유기 IP(9.9.9.10), destination은 Secure Gateway IP(3.3.3.1)이다.
셋째, outer 헤더는 인터넷을 타고 Secure Gateway에 도달한다. Gateway는 outer를 떼고 inner 를 복호화한 뒤, inner의 destination을 보고 사내 네트워크로 라우팅한다.
응답은 이 흐름을 역으로 거친다. 클라이언트와 Gateway 사이의 본문은 보통 IPSec 또는 TLS로 암호화되므로, 인터넷 구간에서 패킷이 탈취되어도 inner 패킷의 내용은 드러나지 않는다.
| 유형 | 설명 | 사용 사례 |
|---|---|---|
| Remote Access VPN | 개인 클라이언트가 사내 네트워크에 접속 | 재택근무, 외부 출장 |
| Site-to-Site VPN | 두 네트워크(본사·지사)를 항상 연결 | 본사-지사 연결 |
| SSL VPN | 웹 브라우저로 접속(별도 클라이언트 불필요) | 간편한 원격 접속 |
| 프로토콜 | 암호화 | 속도 | 특징 |
|---|---|---|---|
| IPSec | 강함(AES) | 중간 | Site-to-Site 주로 사용 |
| OpenVPN | 강함(TLS) | 중간 | 오픈소스, TCP/UDP 모두 지원 |
| WireGuard | 강함(ChaCha20) | 빠름 | 최신 프로토콜, 코드 간결, 낮은 지연 |
| L2TP/IPSec | IPSec 의존 | 느림 | L2 터널링 + IPSec 암호화 |
풀 터널링과 스플릿 터널링
VPN 연결 이후 클라이언트의 모든 트래픽을 VPN으로 보낼지(Full Tunneling), 사내 대상 트래픽만 VPN으로 보내고 일반 인터넷은 직접 보낼지(Split Tunneling)를 선택할 수 있다. 풀 터널링은 보안이 강하지만 사내 게이트웨이에 트래픽이 몰리고, 스플릿 터널링은 속도가 유지되지만 클라이언트의 일반 트래픽이 보호되지 않는다.