2024. 9. 18. 15:18ㆍ개발
배경
NGINX가 등장하게 된 배경은 2000년대 초반, 인터넷 사용량과 웹 트래픽이 급증하면서 등장한 웹 서버의 확장성 문제를 해결하려는 시도에서 비롯되었습니다. 당시 가장 많이 사용되던 Apache HTTP Server는 요청당 하나의 스레드를 사용하는 멀티스레드 기반 아키텍처였는데, 이는 다수의 동시 요청을 처리할 때 서버 자원을 과도하게 사용하게 되어 성능 저하와 서버 과부하를 일으키는 문제가 있었습니다.
대표적인 문제로 C10K 문제(한 번에 1만 개의 연결을 처리하는 문제)로 알려져 있으며, 당시 서버는 많은 트래픽을 처리하는 데 어려움을 겪었다고 합니다.
러시아의 소프트웨어 엔지니어 Igor Sysoev는 이러한 문제를 해결하기 위해 2002년부터 새로운 웹 서버 소프트웨어 개발을 시작했고, 2004년에 NGINX를 발표했습니다. NGINX는 기존의 멀티스레드 방식을 탈피해, 이벤트 기반 비동기 처리 방식을 채택했습니다. 이 방식은 수천에서 수만 개의 동시 연결을 효율적으로 처리할 수 있도록 해, 트래픽이 많은 웹사이트에서 더욱 빠르고 안정적인 성능을 발휘할 수 있게 했습니다.
NGINX는 특히 정적 콘텐츠 처리에 탁월하며, 리버스 프록시 및 로드 밸런싱 기능을 제공하여 웹 애플리케이션의 성능을 최적화하는 데 많은 기여를 했습니다. 이로 인해 NGINX는 페이스북, 트위터, 넷플릭스 등 많은 고트래픽 웹사이트에서 빠르게 채택되었고, 경량성과 확장성 덕분에 현재 전 세계 웹 서버 시장의 상당 부분을 차지하고 있습니다
웹 서버란?
웹 서버는 웹사이트나 웹 애플리케이션을 사용자에게 제공하는 소프트웨어입니다. 우리가 인터넷에서 웹사이트를 볼 수 있는 이유는, 웹 서버가 HTML 파일, 이미지, 비디오 같은 데이터를 사용자가 요청할 때마다 전달하기 때문입니다.
웹 서버의 역할
- 호스팅: 웹 서버는 웹사이트 파일들을 보관하고, 사용자가 해당 웹사이트에 접근하면 그 파일들을 전송합니다.
- 요청 처리: 사용자가 링크를 클릭하거나 페이지를 로드할 때마다, 웹 서버는 이를 처리하고 응답을 보냅니다.
- 보안: SSL/TLS와 같은 보안 프로토콜을 지원하여 안전하게 데이터를 주고받을 수 있습니다.
웹 서버 예시
예를 들어, 여러분이 블로그를 운영한다고 생각해 보면 다음과 같습니다.
여러분의 블로그는 여러 글과 이미지로 이루어져 있을 것입니다. 이때, 블로그 데이터를 웹 서버에 저장해 두면, 방문자가 여러분의 블로그에 접속할 때마다 그 데이터가 웹 서버에서 사용자에게 전달됩니다. 이런 방식으로 웹 서버는 웹사이트를 인터넷에 공개하고 접속 가능하게 해 줍니다.
프록시 서버란?
프록시 서버는 클라이언트와 서버 사이에 위치해, 클라이언트의 요청을 대신 서버에 전달하고 그 응답을 다시 클라이언트로 전달하는 중개자입니다. 포워드 프록시와 리버스 프록시 두 가지 종류가 있는데, 각각의 역할이 다릅니다.
포워드 프록시와 리버스 프록시 설명
포워드 프록시 서버란?
포워드 프록시는 클라이언트(예: 컴퓨터나 모바일 기기)와 외부 서버(인터넷 사이)에 위치한 중개 서버입니다. 클라이언트가 웹사이트에 접근할 때, 포워드 프록시는 클라이언트를 대신하여 외부 서버에 요청을 전달하고 그 응답을 다시 클라이언트로 전송합니다.
포워드 프록시는 주로 클라이언트 측에서 사용되며, 익명성, 프라이버시 보호, 콘텐츠 필터링 등의 목적에 유용합니다. 여기 몇 가지 중요한 기능이 있습니다:
- 익명성 및 프라이버시 보호: 포워드 프록시는 클라이언트의 IP 주소를 숨기고 요청을 서버에 전달합니다. 이를 통해 클라이언트의 신원을 보호할 수 있습니다.
- 콘텐츠 필터링 및 접근 제어: 포워드 프록시는 특정 웹사이트나 콘텐츠에 대한 접근을 차단하거나 제한할 수 있어, 조직이나 네트워크에서 보안 정책을 구현하는 데 사용됩니다.
- 캐싱: 자주 요청되는 데이터를 미리 저장하여 동일한 요청에 대해 더 빠르게 응답할 수 있습니다. 이는 대역폭 사용을 줄이고, 응답 시간을 줄이는 데 도움을 줍니다.
리버스 프록시 서버란?
반면에, 리버스 프록시 서버는 클라이언트와 백엔드 서버 사이에서 중개자 역할을 합니다. 클라이언트가 웹사이트에 요청을 보내면, 리버스 프록시 서버가 이를 받아 적절한 내부 서버로 요청을 전달하고, 응답을 클라이언트에게 돌려줍니다.
리버스 프록시는 서버 측에서 사용되며, 특히 로드 밸런싱과 보안 강화에 큰 역할을 합니다. 주요 기능은 다음과 같습니다:
- 요청 라우팅: 리버스 프록시는 들어오는 클라이언트 요청을 적절한 백엔드 서버로 라우팅합니다. 이를 통해 서버 간 작업 분배가 가능합니다.
- 로드 밸런싱: 리버스 프록시는 여러 백엔드 서버에 트래픽을 분산시켜, 시스템의 처리 능력과 안정성을 높입니다. 이는 특히 트래픽이 몰리는 웹사이트에서 중요한 기능입니다.
- 보안 및 익명성 강화: 리버스 프록시는 백엔드 서버의 IP 주소 및 세부 정보를 숨겨, 외부로부터의 공격을 방지할 수 있습니다. 또한, 추가적인 보안 기능(접근 제어, 요청 필터링)을 통해 보안을 강화합니다.
- SSL 종료(SSL Termination): 리버스 프록시는 SSL/TLS 암호화를 처리하여, 백엔드 서버의 부담을 줄이고 성능을 개선합니다. 이를 통해 보안 통신을 더욱 쉽게 관리할 수 있습니다.
- HTTP 가속: HTTP/2 지원, 헤더 압축, 연결 재사용 등을 통해 웹 요청과 응답을 가속화하여 성능을 최적화합니다.
포워드 프록시 vs 리버스 프록시: 차이점 요약
- 포워드 프록시는 클라이언트가 외부 서버에 접근할 때 중개자 역할을 하며, 익명성과 접근 제어에 중점을 둡니다.
- 리버스 프록시는 클라이언트 요청을 여러 백엔드 서버로 분배하여 시스템의 성능과 보안을 최적화합니다.
NGINX와의 관계
NGINX는 단순히 웹 서버의 역할을 하는 것에서 그치지 않고, 리버스 프록시로서도 중요한 역할을 합니다. 이 두 개념을 명확히 이해하는 것이 NGINX의 전체 기능을 이해하는 데 필수적입니다.
- 웹 서버는 클라이언트의 요청에 직접적으로 응답하여 정적 콘텐츠(HTML, CSS, JavaScript 등)를 전달합니다.
- 프록시 서버는 클라이언트와 서버 사이에서 중개 역할을 하며, 리버스 프록시는 특히 백엔드 서버로 요청을 분산시키고, 보안을 강화하는 데 유용합니다.
NGINX는 이러한 두 가지 역할을 모두 수행할 수 있어, 효율적이고 확장 가능한 서버 환경을 만들 수 있습니다. 웹 서버로서의 NGINX는 정적 콘텐츠를 빠르고 효율적으로 처리하며, 프록시 서버로서의 NGINX는 로드 밸런싱과 SSL 처리, 캐싱을 통해 트래픽을 관리하고 보안을 강화합니다.
NGINX 핵심 기능
- 효율적인 웹 서버:
- NGINX는 비동기 이벤트 기반 아키텍처로 설계되어, 적은 리소스로 다수의 동시 연결을 처리합니다. 이는 트래픽이 많은 웹사이트에서 NGINX가 매우 적합한 이유입니다.
- 리버스 프록시:
- 리버스 프록시로서 NGINX는 클라이언트 요청을 백엔드 서버로 분산하여 시스템의 성능을 최적화하고, 부하 분산과 고가용성을 보장합니다.
- SSL/TLS 종료:
- NGINX는 SSL/TLS 암호화를 처리하여 백엔드 서버의 부담을 줄이고, 보안을 유지하면서 성능을 향상시킵니다. 이를 통해 클라이언트와 서버 간의 안전한 통신이 가능해집니다.
- 캐싱 및 콘텐츠 전달:
- NGINX는 자주 요청되는 데이터를 캐싱하여 서버의 부하를 줄이고, 사용자에게 더 빠른 응답을 제공합니다.
- 유연한 구성:
- NGINX는 구성 파일(nginx.conf)을 통해 다양한 기능을 세밀하게 조정할 수 있습니다. URL 리다이렉션, 접근 제어, 오류 처리 등을 쉽게 설정할 수 있습니다.
- 확장성:
- NGINX는 모듈화된 아키텍처로 설계되어 있어, 사용자가 추가 기능을 쉽게 적용할 수 있습니다. 이는 NGINX를 사용하는 기업들이 자신들의 요구에 맞게 시스템을 확장하는 데 용이합니다.
- 커뮤니티와 지원:
- NGINX는 매우 활발한 커뮤니티를 가지고 있어, 문서, 튜토리얼, 서드파티 모듈 등이 풍부하게 제공됩니다. 이를 통해 초보자부터 전문가까지 손쉽게 NGINX를 사용할 수 있습니다.
NGINX의 성능과 확장성: 어떻게 작동하나?
NGINX는 비동기 이벤트 기반 아키텍처 덕분에 높은 성능과 확장성을 자랑합니다. 이는 CPU 코어마다 단일 워커 프로세스가 실행되어 다수의 연결을 처리하는 방식입니다. 논블로킹 방식으로 작동하여, 서버가 많은 요청을 동시에 처리할 수 있으며, 각 요청이 완료될 때까지 기다리지 않고 새로운 작업을 계속 처리합니다. 이 구조는 서버 자원을 효율적으로 사용해 수백만 개의 동시 연결을 처리할 수 있도록 해줍니다.
NGINX의 주요 작동 원리:
- 이벤트 기반 아키텍처: NGINX는 클라이언트의 요청을 이벤트로 처리하여, 하나의 워커 프로세스가 다수의 연결을 동시에 관리할 수 있습니다. 이는 블로킹 없는 작업 처리를 가능하게 하여 성능을 극대화합니다.
- 마스터-워커 프로세스 구조: 마스터 프로세스는 서버의 설정을 관리하고, 워커 프로세스는 실제로 클라이언트의 요청을 처리합니다. 워커 프로세스는 독립적으로 작동하며, 각 워커가 다수의 요청을 병렬로 처리해 고성능을 유지합니다.
- 컨텍스트 스위치 최소화: NGINX는 프로세스 간 전환을 줄여, 운영 체제에서 자주 발생하는 컨텍스트 스위치를 최소화합니다. 이는 CPU 사용량을 줄이고 성능을 더 최적화합니다.
- 논블로킹 I/O: NGINX는 데이터를 처리할 때 논블로킹 I/O를 사용해 하나의 작업이 완료될 때까지 기다리지 않고, 다른 작업을 병행해 처리할 수 있습니다. 이 방식 덕분에 대규모 동시 요청을 처리할 수 있습니다.
- 캐시 및 로드 밸런싱: NGINX는 자주 요청되는 콘텐츠를 캐싱하고, 들어오는 트래픽을 여러 백엔드 서버로 분산시키는 로드 밸런싱 기능을 제공합니다. 이를 통해 서버의 부하를 줄이고 더 빠른 응답을 가능하게 합니다.
NGINX가 AI 서빙할 때 자주 쓰는 이유
그렇다면 내가 궁금했던 부분은 왜 FASTAPI 앞에 이러한 NGINX를 붙이면 어떠한 부분에서 장점이 있는지 궁금했다.
물론 위에서도 저런 핵심 기능 등을 토대로 붙이는 게 일단 좋다는 것을 알지만 더 자세히 알고 싶어서 여러 사이트를 검색해 봤습니다.
왜 FastAPI와 같은 프레임워크에 NGINX 리버스 프록시가 필요한가?
FastAPI는 빠르고 효율적인 API 서버를 제공하는 훌륭한 ASGI(Application Server Gateway Interface) 프레임워크입니다. 하지만, FastAPI가 제공하는 기능만으로는 대규모의 트래픽을 처리하거나 보안, 성능 측면에서 최적의 상태를 유지하는 데 한계가 있습니다. NGINX는 이러한 한계를 극복하기 위한 해결책을 제공합니다. 이제 NGINX가 왜 FastAPI 서버 앞에 리버스 프록시로 배치되어야 하는지 살펴보겠습니다.
1. 보안 강화
FastAPI가 직접 인터넷에 노출될 경우, 다양한 보안 위협에 취약해질 수 있습니다. NGINX는 리버스 프록시로서 FastAPI 서버를 외부로부터 보호합니다. 특히, SSL/TLS 암호화를 처리함으로써 FastAPI 백엔드 서버가 보안 통신에 따른 추가적인 부담을 덜 수 있습니다. 이로 인해 클라이언트와 서버 간의 안전한 연결이 보장되며, FastAPI 서버는 보안이 강화된 상태에서 작동할 수 있습니다
2. 로드 밸런싱
FastAPI는 빠른 응답 속도를 자랑하지만, 다수의 요청을 동시에 처리하는 데 한계가 있을 수 있습니다. NGINX는 로드 밸런서로서 다수의 FastAPI 인스턴스에 요청을 분배하여 트래픽을 고르게 분산시킵니다. 이 방식은 대규모 트래픽을 처리할 때 서버의 성능 저하를 방지하며, 서버의 부하를 효율적으로 관리할 수 있도록 합니다. 또한 NGINX는 여러 인스턴스 간의 트래픽을 관리해 서비스 가용성을 높입니다
3. 캐싱을 통한 성능 최적화
NGINX는 자주 요청되는 데이터를 캐싱할 수 있어, FastAPI 서버가 매번 동일한 작업을 반복하지 않도록 합니다. 이를 통해 클라이언트가 동일한 API를 여러 번 호출할 때에도 FastAPI 서버의 부하를 줄이고 응답 시간을 줄여줍니다. 특히, 반복적인 데이터 요청이 많은 AI 추론 서비스에서는 캐싱 기능이 매우 중요합니다
4. 스케일링과 확장성
NGINX는 FastAPI 서버의 확장성을 크게 향상시킵니다. 여러 개의 FastAPI 인스턴스를 운영해야 하는 경우, NGINX가 그 사이에서 트래픽을 관리하고 여러 인스턴스로 분산시킴으로써 스케일링이 원활하게 이루어질 수 있습니다. 이는 FastAPI 서버가 처리할 수 있는 요청의 양을 대폭 증가시킬 수 있습니다
5. ASGI와 WSGI 호환성
FastAPI는 ASGI 프레임워크로 설계되어 있으며, 이는 비동기 처리를 지원합니다. 하지만 NGINX와 같은 리버스 프록시를 통해 요청을 관리하면 비동기와 동기 요청을 동시에 처리할 수 있으며, WSGI 서버와의 호환성도 확보할 수 있습니다. 특히, gunicorn 같은 WSGI 서버와 uvicorn 같은 ASGI 서버를 조합하여 FastAPI 서버를 최적화할 수 있습니다
FastAPI와 NGINX를 사용하여 LLM(대규모 언어 모델) 서빙 시 부하를 줄이는 예시
LLM(Large Language Model) 모델을 서빙할 때, FastAPI와 NGINX를 함께 사용하면 성능을 최적화하고 서버 부하를 크게 줄일 수 있습니다. 특히, LLM 모델은 많은 리소스를 요구하고, 복잡한 계산을 수행하므로 이러한 구조는 필수적입니다.
예시 1: NGINX 캐싱을 통한 빠른 응답 제공
LLM 모델은 큰 언어 데이터셋을 처리하고 학습했기 때문에, 많은 양의 요청에 대한 처리가 필요합니다. 예를 들어, 사용자가 자주 요청하는 문장 생성이나 텍스트 분석 요청이 있다면, 이러한 반복적인 요청에 대해서는 NGINX의 캐싱 기능을 사용할 수 있습니다.
- 작동 방식:
- 클라이언트가 LLM 모델에 텍스트 생성 요청을 보냅니다.
- NGINX가 요청을 FastAPI로 전달하고, FastAPI는 LLM 모델을 호출하여 결과를 반환합니다.
- NGINX는 첫 번째 응답을 캐싱합니다.
- 동일한 요청이 들어오면 NGINX는 FastAPI를 거치지 않고 캐시에 저장된 결과를 바로 반환하여 응답 속도를 극대화하고 서버 부하를 줄입니다.
이 방식은 반복적인 요청에서 FastAPI와 LLM 모델의 리소스 소비를 줄여줍니다.
예시 2: 로드 밸런싱을 통한 LLM 서버 확장성 향상
LLM 모델의 서빙은 매우 높은 CPU/GPU 리소스를 필요로 하며, 트래픽이 많을 경우 단일 FastAPI 서버만으로는 처리하기 어렵습니다. 이때 NGINX의 로드 밸런싱 기능을 활용하여 여러 FastAPI 서버 인스턴스에 트래픽을 분산할 수 있습니다.
- 작동 방식:
- 클라이언트들이 동시에 수많은 텍스트 생성 요청을 보냅니다.
- NGINX는 여러 FastAPI 서버 인스턴스(각 인스턴스는 독립된 LLM 모델 인스턴스를 구동)에 트래픽을 분배합니다.
- 각 서버는 분산된 요청을 처리하여 서버 과부하를 방지하고, 동시에 더 많은 요청을 처리할 수 있습니다.
- 서버 중 하나가 다운되더라도, NGINX는 나머지 서버로 트래픽을 전환하여 서비스의 가용성을 유지합니다.
LLM 서빙에서 NGINX 사용의 실제 예시
예를 들어, OpenAI의 GPT와 같은 모델을 서빙할 때, 사용자들이 텍스트 생성 요청을 많이 보낼 수 있습니다. NGINX는 이러한 요청들을 다수의 FastAPI 서버로 분산시키고, 반복적인 요청은 캐싱하여 LLM 모델을 최적화합니다. 이를 통해 LLM 모델이 더 많은 트래픽을 효율적으로 처리하면서도 응답 시간을 줄일 수 있습니다.
이와 같은 아키텍처는 대규모 언어 모델을 서빙하는 환경에서 필수적인 성능 최적화 전략으로 사용됩니다.
실제 예시 nginx.conf
이 NGINX 설정 파일의 목적은 하나의 포트(80)에서 여러 AI 모델을 서빙하는 것입니다. 이를 위해 NGINX는 리버스 프록시 역할을 하며, 각각의 모델 요청을 해당 서버로 전달하는 방식으로 동작합니다.
주요 요소 설명:
- upstream ollama_backend:
- upstream 블록은 여러 백엔드 서버를 정의하여 NGINX가 이를 로드 밸런싱하거나 프록시로 요청을 전달할 수 있게 합니다.
- 여기서는 세 개의 AI 모델(ollama_llava, vllm_gpu_google_gemma-2-9b-it, ollama_gemma2_2b)을 같은 포트 11434에서 서비스하도록 설정합니다. 이 세 개의 서버는 모두 11434 포트를 사용하지만, 서로 다른 AI 모델을 서빙합니다.
- server { listen 80; }:
- NGINX는 80번 포트에서 클라이언트 요청을 받습니다. 이 포트는 일반적으로 HTTP 트래픽을 처리하는 기본 포트입니다.
- 클라이언트는 NGINX로 요청을 보내고, NGINX는 이 요청을 해당 모델로 라우팅합니다.
- location / { return 404 "Model not found"; }:
- 루트 경로(/)로 들어오는 요청에 대해 기본 응답으로 404 에러를 반환합니다. 이는 사용자가 경로를 잘못 입력했거나 존재하지 않는 모델을 요청할 경우 발생합니다.
- location /llava/ { proxy_pass http://ollama_llava:11434/; }:
- /llava/ 경로로 들어오는 요청은 ollama_llava 모델에 연결됩니다.
- proxy_pass 지시어는 NGINX가 요청을 **ollama_llava 서버(11434 포트)**로 프록시하여 전달하도록 합니다.
- location /google_gemma-2-9b-it/ { proxy_pass http://vllm_gpu_google_gemma-2-9b-it:11434/; }:
- /google_gemma-2-9b-it/ 경로로 들어오는 요청은 vllm_gpu_google_gemma-2-9b-it 서버로 전달됩니다.
- location /gemma2_2b/ { proxy_pass http://ollama_gemma2_2b:11434/; }:
- /gemma2_2b/ 경로로 들어오는 요청은 ollama_gemma2_2b 서버로 전달됩니다.
헤더 설정:
- proxy_set_header Host $host;: 클라이언트의 호스트 정보를 그대로 전달하여 백엔드 서버가 이를 인식할 수 있게 합니다.
- proxy_set_header X-Real-IP $remote_addr;: 클라이언트의 실제 IP 주소를 백엔드 서버에 전달하여, 서버가 클라이언트의 IP를 알 수 있도록 합니다.
upstream ollama_backend {
server ollama_llava:11434;
server vllm_gpu_google_gemma-2-9b-it:11434;
server ollama_gemma2_2b:11434;
}
server {
listen 80;
server_name localhost;
location / {
return 404 "Model not found";
}
location /llava/ {
proxy_pass http://ollama_llava:11434/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /google_gemma-2-9b-it/ {
proxy_pass http://vllm_gpu_google_gemma-2-9b-it:11434/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /gemma2_2b/ {
proxy_pass http://ollama_gemma2_2b:11434/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
결론
처음 시작은 현재 모델 서빙할 때 여러 개의 포트들을 열어두고, 서빙을 하는 것에 있어서 보완문제나 관리 측면에서 어려움이 있어서 nginx의 리버시 프록시를 사용해서 라우팅하는데 유리하게 하고자 했습니다.
그래서 이러한 설정의 주요 목적은 다음과 같습니다:
- 포트 및 트래픽 관리 간소화: 하나의 포트를 고정하여 모든 AI 모델을 서빙하면, 네트워크 설정을 단순화하고 서버 간 포트 충돌을 방지할 수 있습니다.
- 리버스 프록시를 통한 모델 라우팅: NGINX를 통해 요청 URL에 따라 요청을 특정 AI 모델로 라우팅할 수 있습니다. 예를 들어, /model1로 들어오는 요청은 모델 1로, /model2는 모델 2로 라우팅됩니다.
- 확장성: 여러 모델이 추가될 때마다 별도의 포트 설정 없이 하나의 포트로 다양한 모델을 서빙할 수 있어, 시스템 확장 및 유지보수가 용이해집니다.
- 보안 및 성능 최적화: 하나의 포트를 통해 관리함으로써 SSL 인증서를 단일 포트에 적용할 수 있고, NGINX의 캐싱 및 로드 밸런싱을 통해 모델 서빙 성능을 최적화할 수 있습니다.
그리고 이러한 설정을 했을 때, 부하가 있는지 궁금해서 알아보기 시작했고, 현재 설정과 같이 한다면 내용들을 봤을 때 서빙 시 부하가 있을 것 같습니다.
그래서 이러한 것을 제품이나 실제 서비스에 쓰기 위해서는 nginx worker 프로세스 수를 증가시키고 로드 밸런싱을 사용해서 분산하는 게 필요할 수 있을 것 같습니다. 그리고 또한 캐시 설정도 필요해 보입니다.
참고자료
'개발' 카테고리의 다른 글
React) Nomad 실전형 리액트 훅(Custom) 내용 정리해보기 (2) | 2024.03.16 |
---|---|
Vercel을 이용해서 프론트 배포해보기 (배포 화면) (3) | 2024.02.17 |