나의 잡다한 노트 및 메모
NGINX의 리버스 프록시 동작 원리 본문
리버스 프록시란 무엇인가?
리버스 프록시(Reverse Proxy)는 클라이언트와 서버 사이에 위치하여 클라이언트의 요청을 받아 실제 서버로 전달하고, 서버의 응답을 다시 클라이언트에게 중계하는 중간자 서버입니다
일반 프록시(포워드 프록시)가 내부 클라이언트가 외부로 나가는 요청을 대리하는 것이라면, 리버스 프록시는 외부 클라이언트의 요청을 받아 내부 네트워크의 서버로 전달한다는 점이 다릅니다
클라이언트 입장에서는 응답을 프록시로부터 받기 때문에 내부 서버의 존재나 주소를 알 수 없으며, 마치 프록시 서버가 웹 서버인 것처럼 동작합니다
리버스 프록시 개념도: 다수의 외부 클라이언트 요청이 인터넷을 거쳐 리버스 프록시 서버로 전달되고, 프록시는 이를 내부의 실제 서버들로 포워딩합니다
클라이언트는 프록시와만 통신하기 때문에 실제 내부 서버의 존재나 IP를 알지 못하며, 이러한 구조로 내부 서버를 외부로부터 숨겨 보안성을 높일 수 있습니다
또한 하나의 프록시가 다수의 서버로 요청을 분배할 수 있어 부하 분산에도 유용합니다.
리버스 프록시를 사용하면 다음과 같은 이점이 있습니다
- 보안 강화: 외부 클라이언트는 내부망의 실제 서버를 직접 접속하지 않으므로, 서버의 존재와 정보를 숨길 수 있습니다 (예: 내부 서버들의 IP 노출 방지)
- 부하 분산: 프록시가 여러 서버로 요청을 분배하여 서버 부하를 균형 있게 할당할 수 있습니다 (예: 다중 WAS 서버에 로드밸런싱)
- 캐싱 및 성능: 프록시 서버에 캐시를 두어 자주 요청되는 데이터를 저장하면 백엔드 부하 감소 및 응답 속도 향상이 가능합니다.
Nginx의 아키텍처와 내부 메커니즘
Nginx는 이러한 리버스 프록시 역할을 효율적으로 수행하도록 설계된 웹 서버/프록시 서버입니다. 이벤트 기반(event-driven) 아키텍처와 마스터-워커 프로세스 모델을 사용하여 고성능을 발휘합니다
- 마스터/워커 프로세스 구조: Nginx는 하나의 마스터 프로세스와 여러 워커(worker) 프로세스로 구성됩니다
마스터 프로세스는 설정을 읽고 워커 프로세스를 생성/관리하며, 각 워커 프로세스가 실제 클라이언트 요청을 처리합니다워커 프로세스의 수는 설정(worker_processes)으로 지정하며 CPU 코어 수에 맞게 조정할 수 있습니다
- 비동기 이벤트 처리: 각 워커 프로세스는 싱글 스레드로 동작하면서 비동기 I/O (예: epoll, kqueue) 기반의 이벤트 루프를 통해 다수의 연결을 동시에 처리합니다
Nginx 리버스 프록시의 요청 처리 과정
Nginx가 리버스 프록시로 동작할 때, 클라이언트의 요청이 접수되어 백엔드 서버로 전달되고 다시 응답이 돌아오는 일련의 단계를 거칩니다:
- 클라이언트 요청 수신: 클라이언트가 웹 브라우저 등으로 Nginx 서버의 주소(예: http://example.com)에 요청을 보내면, 해당 포트에서 대기 중이던 Nginx 워커 프로세스가 TCP 연결을 수락하고 HTTP 요청 헤더와 본문을 읽기 시작합니다. Nginx는 가상 호스트 설정에 따라 Host 헤더와 URI를 보고 어느 server 블록으로 요청을 보낼지 결정합니다.
- 위치(Location) 매칭 및 프록시 결정: 선택된 가상 서버 블록 내에서 요청 URI에 가장 잘 맞는 location을 찾습니다. 이 위치에 proxy_pass 지시어가 있다면, Nginx는 이 요청을 지정된 업스트림(upstream) 서버로 프록시해야 함을 알게 됩니다. 예를 들어 location /app/ { proxy_pass http://backend; }와 같은 설정이 있으면 /app 이하 요청은 미리 정의된 backend 서버(혹은 주소)로 전달됩니다. 이 과정에서 필요한 경우 Nginx는 프록시할 대상 서버 주소를 DNS 조회하거나, 미리 정의된 업스트림 풀에서 선택합니다.
- 백엔드 서버로 요청 전달: Nginx는 클라이언트의 요청 내용을 백엔드 서버로 전달합니다. proxy_pass에 지정된 대상(예: http://127.0.0.1:3000 또는 업스트림 풀 이름)에 대해 새로운 연결을 열거나 기존 연결(Keep-Alive 연결)이 있다면 재사용합니다. 이때 원본 요청의 주요 헤더를 백엔드에 넘겨주도록 기본 설정되어 있으며, 예를 들어 Host 헤더나 X-Forwarded-For 헤더를 설정하여 원본 클라이언트 정보를 전달합니다. Nginx는 클라이언트로부터 받은 요청 라인을 기반으로 백엔드 서버에 새로운 요청을 구성하며, 필요 시 경로를 조정하거나 프로토콜을 변환합니다. 이 과정을 Nginx는 비동기적으로 처리하여 백엔드로부터 응답을 기다리는 동안에도 다른 클라이언트의 작업을 처리할 수 있습니다. Nginx는 백엔드에 요청을 전송한 후 응답을 기다리면서 (이벤트 루프로) 대기하게 됩니다
- 백엔드 응답 수신: 백엔드(실제 애플리케이션 서버)가 요청을 처리한 후 응답을 보내면, Nginx 워커 프로세스는 그 응답 데이터를 비동기적으로 받아옵니다. 일반적으로 Nginx는 성능 향상과 백엔드 부하 감소를 위해 백엔드 응답을 메모리 버퍼에 저장하면서 받습니다. (예를 들어, proxy_buffer_size 및 proxy_buffers 지시어로 제어되는 영역에 일단 응답을 모두 수신한 뒤 클라이언트에게 전송할 수도 있습니다.) 응답 헤더를 받으면 Nginx는 필요에 따라 일부 헤더를 수정하기도 합니다 (예: Location 헤더 리라이트 등). 백엔드로부터 콘텐츠를 일부씩 읽어 들이며, 곧바로 클라이언트로 전송할 준비를 합니다.
- 클라이언트로 응답 전송: Nginx는 수신된 백엔드 응답을 클라이언트에게 보내기 시작합니다. 만약 프록시 버퍼링이 켜져 있다면(기본값), Nginx는 백엔드 응답 전체를 모두 수신 완료한 후 클라이언트에게 보내거나, 버퍼 단위로 클라이언트 전송을 수행합니다. 버퍼링이 꺼져 있는 경우 백엔드에서 오는 대로 곧바로 클라이언트로 스트리밍합니다. Nginx는 **쓰기 이벤트 처리기(write event handler)**를 통해 응답 데이터를 일정 크기로 잘라 비동기적으로 전송하며, 한 번에 모두 보내지 못하면 나머지 데이터를 버퍼에 저장한 뒤 나중에 전송을 이어갑니다. 이를 통해 대용량 응답도 메모리 효율적으로 보내고, 클라이언트 네트워크 상황에 맞춰 조절할 수 있습니다. 클라이언트는 결국 Nginx로부터 온 응답을 받게 되며, 결과적으로 백엔드 서버의 응답이 프록시를 통해 전달된 것입니다
- 연결 정리: 응답 전송이 완료되면 Nginx는 클라이언트와의 연결을 종료하거나(Connection: close) HTTP keep-alive가 활성화된 경우 해당 연결을 일정 시간 유지하여 다음 요청을 대기합니다. 동시에 백엔드와의 연결도 정리합니다. 백엔드 서버와의 연결도 keep-alive 설정이 되어 있다면 열린 채로 유지하여 이후 들어오는 프록시 요청에 재사용하고, 그렇지 않으면 닫습니다. Nginx는 요청 처리를 마무리하면서 사용한 자원(버퍼 등)을 해제하고, 워커 프로세스는 다른 요청 처리에 계속 활용됩니다. 이러한 흐름이 매 요청마다 반복되며, 마스터 프로세스는 다수의 워커들을 지속적으로 관리하여 장애 시 워커 재시작이나 설정 리로드 등을 수행합니다.
- 나의 질문 -> nginx는 server 블록을 분기로 어떤 upstream 서버로 보낼지 결정하는것같은데 그 기준은 포트가 되는 건가?
Nginx가 어떤 server 블록(즉, 어떤 설정을 적용할지)을 선택하는 기준은 주로 요청이 도착한 IP와 포트, 그리고 Host 헤더의 값(server_name) 입니다. 이 과정을 좀 더 자세히 설명하면:
- 리스닝 포트 및 IP 매칭:
Nginx는 각 server 블록에서 listen 지시어로 지정한 포트와 IP 주소를 기준으로 들어오는 요청을 분류합니다. 예를 들어, 어떤 server 블록은 포트 80을, 다른 블록은 포트 443을 리스닝할 수 있습니다. 따라서 클라이언트가 어떤 포트로 요청을 보내느냐에 따라 먼저 해당 server 블록들이 후보에 오게 됩니다. - 도메인 이름(server_name) 매칭:
같은 포트에서 여러 server 블록이 있다면, Nginx는 요청 헤더에 있는 Host 값을 확인해서, server_name 지시어와 일치하는 server 블록을 선택합니다. 만약 여러 server 블록이 일치한다면, 가장 구체적인(정확한 이름 매칭) 블록을 선택합니다. - Location 블록에 따른 처리:
선택된 server 블록 내부에서는 요청 URI에 따라 어떤 location 블록이 매칭되는지 확인합니다. 그리고 그 location 블록 안에서 정의한 proxy_pass 지시어나 다른 처리 지시어에 따라, 실제로 어떤 upstream 서버로 요청을 전달할지 결정됩니다.
즉, 포트는 server 블록 매칭의 한 요소일 뿐, upstream 서버 선택 자체는 server 블록 내부의 설정(예: proxy_pass가 어느 upstream 그룹을 가리키는지)에 의해 결정됩니다.
예를 들어, 다음과 같은 설정이 있다고 해봅시다:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend1;
}
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://backend2;
}
}
- 클라이언트가 http://example.com으로 접속하면, Nginx는 포트 80과 Host 헤더를 기준으로 첫 번째 server 블록을 선택한 후, /에 해당하는 location 블록의 proxy_pass http://backend1; 지시어에 따라 요청을 backend1으로 전달합니다.
- 반면, 클라이언트가 http://api.example.com으로 접속하면, 두 번째 server 블록이 선택되고, 요청은 backend2로 전달됩니다.
따라서 Nginx는 리스닝 포트와 Host 헤더를 기반으로 어떤 server 블록을 사용할지 결정하고, 그 server 블록 내부의 location 블록 설정에 따라 특정 upstream 서버로 요청을 프록시하게 됩니다. 포트는 중요한 매칭 기준 중 하나이지만, 유일한 기준은 아니며, server_name과 location 규칙 등도 함께 작용합니다.
'DevOps > Reverse_proxy' 카테고리의 다른 글
nginx의 프록시 관련 내장 변수, client가 요청한 포트를 나타내려면? (0) | 2025.03.20 |
---|---|
nginx의 request rewrite, response rewrite (0) | 2025.03.19 |
nginx 설정 중 proxy 관련 설정 (0) | 2025.03.19 |