Published on

Python Web Application Server 용어정리하기 with Gunicorn and Nginx and Load Balance

Authors

1. Python 으로 만드는 웹 애플리케이션 서버

Python으로 웹 애플리케이션을 개발할 때는 Flask, Django, FastAPI와 같은 프레임워크를 사용합니다.

이렇게 개발된 애플리케이션을 흔히 Python 웹 애플리케이션이라고 부르고, 이러한 애플리케이션(Application)을 실제로 운영 환경에서 실행하고 HTTP 요청을 처리하는 역할을 하는 것은 웹 서버 또는 애플리케이션 서버입니다.

저는 위의 3가지 프레임워크 중에서도 Django(Django Rest Framework)로 개발을 하는데, 이 Django 를 세상에 내놓으려면 Docker, Gunicorn, Nginx, AWS자원 등등 수많은 “서버”가 필요합니다.

그렇다면 이중에서도 정말 “서버”라고 불릴만한 것이 무엇일까요? 또 뭐가 어떤 역할을 할까요??

2. Gunicorn, WSGI(Web Server Gateway Interface)/WAS(Web Application Server)

Gunicorn은 WSGI(Web Server Gateway Interface) 서버입니다.

WSGI는 Python 웹 애플리케이션과 웹 서버 간의 표준 인터페이스를 정의합니다.

HTTP 요청을 직접 처리할 수 있는 기능을 가지고 있어 HTTP 서버로도 통하고, Python 웹 애플리케이션(예: Django, Flask)을 실행하고 관리하는 역할을 합니다.

Gunicorn은 여러 워커 프로세스를 생성하고 관리하는 프로세스 관리자 역할도 수행합니다.

Django 를 이용해 풀스택으로 개발을 하게 된다면 프로덕션 환경에서는 주로 Nginx와 같은 웹 서버를 프론트엔드로 사용하고, Gunicorn을 백엔드 서버로 사용합니다.

(이 구조에서 Nginx는 정적 파일 서빙, SSL 종료, 로드 밸런싱 등을 담당하고, Gunicorn은 Python 애플리케이션 실행에 집중합니다.)

2.1 Gunicorn 사용방법

CLI) 실행방법은 아래 명령어를 터미널에 입력합니다.

gunicorn --workers {워커수} --bind 0.0.0.0:8000 {root_path's_app}.wsgi:application

이렇게 명령어를 입력하면 내부적으로 WSGIHandler 를 통해 실행됩니다. 워커 수는 보통 사용 가능한 CPU 코어 수에 따라 결정됩니다. 일반적으로 (2 * CPU 코어 수 + 1)를 권장한다고 공식문서에 기재되어 있습니다. 시스템 리소스와 애플리케이션 특성에 따라 자유롭게 조정이 가능합니다.

현재 적용된 설정을 확인하려면 다음 명령을 사용할 수 있습니다.

gunicorn --check-config {root_path's_app}.wsgi:application

만약 경로, 구조적인 어려움이 있다면 아래명령어 처럼 경로를 설정할 수 있습니다.


gunicorn -c /some_path/gunicorn.config.py your_app.wsgi:application

이 옵션은 Gunicorn 설정 파일의 위치를 지정하는 데 사용됩니다. 이에 대해 좀 더 자세히 설명드리겠습니다.

  1. 기본 설정 파일 위치 [./gunicorn.conf.py] 를 통해서, Gunicorn을 (현재 디렉토리에서) gunicorn.conf.py라는 이름의 설정 파일을 통해 적어놓은(?) 설정으로 사용할 수 있습니다.
  2. c 또는 -config 옵션 사용 이 옵션을 사용하면 기본 위치가 아닌 다른 위치의 설정 파일을 지정할 수 있습니다. (따라서 명령줄에서 지정한 옵션은 설정 파일의 동일한 옵션을 덮어씁니다.)

Gunicorn의 설정 파일 사용은 특히 복잡한 설정이나 여러 환경(개발, 테스트, 프로덕션 등)에서 다른 설정을 사용해야 할 때 유용합니다. 설정 파일을 사용하면 이러한 설정을 쉽게 관리하고 버전 관리 시스템에 포함시킬 수 있습니다.

2.2 Gunicorn 의 로드밸런싱

Gunicorn은 자체적으로 워커 프로세스 간의 로드 밸런싱을 수행합니다. 이렇게 하는 이유는 단일 Python 프로세스의 한계를 극복하고, 동시에 여러 요청을 처리할 수 있게 하기 위함입니다. (뒤에서 ALB에 대해 나와서 덧붙히면, 당연히 이는 인프라 클라우드 단의 ALB의 로드 밸런싱과는 다른 레벨에서 작동합니다. ALB 는 여러 ECS 태스크(컨테이너) 등의 로드 밸런싱을 담당합니다. 즉, 여러 서버 인스턴스 간의 트래픽 분배를 처리합니다.)

Gunicorn의 로드 밸런싱은 (ECS환경등의 컨테이너 환경이라면 단일 컨테이너 내에서) 여러 워커 프로세스 간에 요청을 분배 합니다.

gunicorn --workers 4

설정에서 workers = 4로 지정되어 있다면, Gunicorn은 4개의 워커 프로세스를 생성하고 이들 간에 요청을 분배합니다.

2.3 동시성에 대한 의문(심)

4개의 워커가 있다고 해서 하나의 API 요청에 대해 4번 코드가 실행되는 것이 아닙니다. 즉 4개의 다른 클라이언트가 동시에 요청을 보내면, 각 요청은 서로 다른 워커에 의해 병렬로 처리될 수 있습니다.(동시와는 무관)

각 워커는 독립적으로 대기*(like 유휴상태)*하고 있다가, 요청이 들어오면 그 중 하나의 워커만 해당 요청을 처리합니다.

  • 요청 처리 과정

    • Gunicorn의 마스터 프로세스가 들어오는 요청을 받습니다.
    • 마스터 프로세스는 현재 유휴 상태인 워커 중 하나를 선택합니다.
    • 선택된 워커만 해당 요청을 처리합니다. 다른 워커들은 이 요청에 관여하지 않습니다.
  • (단일)요청 처리를 예로들어

    • API 요청에 대해서 선택된 하나의 워커만 프레임워크 단의 코드를 한 번만 실행하며, 결과를 반환합니다.

3. Nginx, Reverse Proxy Server / Web Server

Nginx를 사용하는 이유는 정적 파일 서빙, 고급 캐싱, 정교한 라우팅, 부하 분산, 유연한 로깅 및 모니터링 기능을 제공하기 때문이고 정리하면 아래와 같습니다.

  1. Reverse Proxy
    • 리버스 프록시는 클라이언트-서버 사이에 위치하여, 클라이언트의 요청을 받아 적절한 백엔드 서버로 전달하고, 그 응답을 다시 클라이언트에게 전달하는 중간 서버입니다. 클라이언트는 직접 백엔드 서버와 통신하지 않고, 리버스 프록시를 통해 통신합니다.
    • Nginx는 애플리케이션 서버 앞에서 리버스 프록시 역할을 하며, 요청을 애플리케이션 서버로 전달합니다. (이로 인해 아래 처럼 로드 밸런싱 기능을 수행할 수 있습니다.)
  2. 로드 밸런싱
    • 여러 애플리케이션 서버가 있을 때, Nginx가 로드 밸런서 역할을 하여 요청을 서버들 사이에 분산시킬 수 있습니다.
  3. 보안
    • Nginx를 통해 SSL/TLS 암호화를 처리하고, 보안 헤더를 추가하여 웹 애플리케이션의 보안을 강화할 수 있습니다.
  4. 캐싱
    • Nginx는 HTTP 캐시 기능을 제공하여, 데이터베이스나 애플리케이션 서버에 대한 요청 수를 줄일 수 있습니다. 정적 파일의 캐싱 외에도 동적 콘텐츠 캐싱을 지원합니다.
  5. 압축 및 최적화
    • Gzip 압축과 같은 기능을 통해 응답 데이터의 크기를 줄이고, 네트워크 대역폭을 절약할 수 있습니다.
  6. 다양한 리퀘스트 처리
    • Nginx는 다양한 요청을 처리할 수 있으며, 정적 파일 서빙 외에도 다양한 요청 처리 방식을 지원합니다.

3.1 Nginx 설정으로 알아보기

nginx 설정 예시

http {
    upstream backend {
        server backend1.hongreat.co.kr weight=3; # Tip: 가중치예시
        server backend2.hongreat.co.kr;
    }

    server {
        listen 80;
        server_name hongreat.co.kr;

        location / {
            proxy_pass http://backend;
        }
    }
}

  1. upstream backend { ... }:
    • backend1.hongreat.co.krbackend2.hongreat.co.kr 두 서버를 백엔드 서버 그룹으로 정의 정의합니다.
    • Nginx는 이 서버들 간에 요청을 분배합니다. (기본적으로 라운드 로빈 방식이며 라운드 로빈 방식은 알고리즘의 일종으로 순차적으로 요청을 분배합니다. 위의 예시의 경우 backend1→backend2 이렇게 분배하는 것이며, 예시에 적은 weight 옵션을 설정하면 가중치 라운드 로빈 방식으로 backend1 서버에 더 많은 가중치를 두게 됩니다.
  2. server_name hongreat.co.kr;
    • 이 가상 호스트가 처리할 도메인 이름을 지정합니다.
    • ssl 이나 tls 설정도 server 단에서 설정하면 되고 이번 글에서는 생략하도록 하겠습니다.
  3. location / { ... }:
    • 모든 URL 경로('/')에 대한 처리 규칙을 정의합니다.
  4. proxy_pass http://backend;:
    • 들어오는 요청을 backend라는 이름의 upstream 그룹으로 전달합니다.

이것이 실제로 리버스 프록시 기능을 수행하는 부분입니다.

3.2. Nginx 동작 순서

즉 정리하면 아래와 같은 순서로 요청 프로세스가 일어납니다.

  1. 클라이언트가 http://hongreat.co.kr으로 요청을 보냅니다.
  2. Nginx가 이 요청을 받습니다.
  3. Nginx는 proxy_pass 지시어에 따라 이 요청을 backend upstream 그룹의 서버 중 하나(Gunicorn worker)로 전달합니다.
  4. 백엔드 서버가 요청을 처리하고 응답을 Nginx에 보냅니다.
  5. Nginx는 이 응답을 다시 클라이언트에게 전달합니다.

3.3 Nginx가 필요 없는 경우

실제로 제가 일하는 곳에서는 AWS인프라로 ALB가 로드 밸런싱, SSL/TLS 종료를 처리하고 ECS가 서비스 디스커버리와 자동 확장을 해주기 때문에 Nginx는 사용하지 않습니다.

4. 도움이 되는 문서 링크

hongreat 블로그의 글을 봐주셔서 감사합니다!